{"openapi":"3.0.3","info":{"title":"Cover API","version":"1.0.0","description":"Create music covers from links, uploaded audio or existing clips. Source preparation is automatic. Jobs are asynchronous; retain the returned jobId."},"servers":[{"url":"https://covers.waily.io/api/v1"}],"security":[{"ApiKey":[]}],"paths":{"/health":{"get":{"operationId":"health","summary":"Service readiness","security":[],"responses":{"200":{"description":"Service status.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"ready":{"type":"boolean"}}}}}}}}},"/cover":{"post":{"operationId":"createCover","summary":"Create a cover","description":"Uses account credits. At most three jobs may be running or queued; processing is serial. Use Idempotency-Key when retrying network requests. All source preparation is automatic.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":128},"description":"Unique request key, retained for 24 hours after the job ends. Reusing it with the same body returns the same job; a different body returns 409."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoverRequest"},"example":{"url":"https://example.com/my-song.mp3","persona":"flavia","title":"My cover","lyrics":"[Verse]\nYour lyrics here"}}}},"responses":{"202":{"description":"Accepted, or an existing job for this Idempotency-Key.","content":{"application/json":{"schema":{"type":"object","required":["jobId","statusUrl"],"properties":{"jobId":{"type":"string","format":"uuid"},"statusUrl":{"type":"string"}}}}}},"400":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/uploads":{"post":{"operationId":"uploadAudio","summary":"Upload raw audio bytes","description":"Send file bytes, not JSON or multipart. Maximum 50 MiB. The returned uploadId expires after one hour and can be used by one cover job. No generation starts until POST /cover.","requestBody":{"required":true,"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}},"audio/mpeg":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"Uploaded.","content":{"application/json":{"schema":{"type":"object","properties":{"uploadId":{"type":"string","format":"uuid"},"bytes":{"type":"integer"},"expiresAt":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/personas":{"get":{"operationId":"listPersonas","summary":"Available voice presets","responses":{"200":{"description":"Use the key field as persona in a cover request.","content":{"application/json":{"schema":{"type":"object","properties":{"personas":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"voiceAvailable":{"type":"boolean"},"gender":{"type":"string","nullable":true}}}}}}}}},"400":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/jobs/{jobId}":{"get":{"operationId":"getCoverJob","summary":"Poll a cover job","description":"Poll about every five seconds. Done/failed results remain available for 24 hours. Interrupted jobs fail after a service restart; they are never automatically generated twice. Result media URLs may expire: download finished audio promptly.","parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Job status and finished audio URLs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}}},"400":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Request failed; see error.code and error.message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/openapi.json":{"get":{"operationId":"getOpenApi","summary":"OpenAPI contract","security":[],"responses":{"200":{"description":"This OpenAPI document.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","description":"A private API key supplied by the administrator. Keep it on your server."}},"schemas":{"CoverRequest":{"type":"object","additionalProperties":false,"properties":{"url":{"type":"string","description":"Public audio-file URL, or a YouTube, SoundCloud, Google Drive or Dropbox link. Choose exactly one source field.","maxLength":4096,"format":"uri","minLength":1},"clipId":{"type":"string","format":"uuid","description":"An existing source clip ID. Choose exactly one source field."},"uploadId":{"type":"string","format":"uuid","description":"One-use ID from POST /uploads. Choose exactly one source field."},"title":{"type":"string","description":"Name for the resulting cover. Default: linked filename/title, or Cover.","maxLength":160},"lyrics":{"type":"string","description":"Lyrics to sing. Omit to let the audio guide the cover; supply lyrics when exact wording matters.","maxLength":12000},"persona":{"type":"string","description":"Preset key from GET /personas.","maxLength":80},"style":{"type":"string","description":"Style prompt. Replaces the selected persona style while keeping its voice selection.","maxLength":2000},"extraTags":{"type":"string","description":"Additional style instructions appended to the style prompt.","maxLength":2000},"voiceClone":{"type":"boolean","description":"Use the selected preset voice when available.","default":true},"quality":{"type":"string","enum":["normal","8khz","8khz_mono","opus_low","4khz","2khz","1khz"],"default":"normal","description":"Source audio preparation quality; applies to URLs and uploaded files."},"model":{"type":"string","enum":["v6","v6-wild","v6-mini"],"default":"v6","description":"Generation model."},"negativePrompts":{"type":"boolean","description":"Enable exclusion instructions from negativeTags and leading minus style tags.","default":false},"negativeTags":{"type":"string","description":"Styles to exclude. Used only when negativePrompts is true.","maxLength":2000},"styleWeight":{"type":"number","description":"How strongly to follow the style, 0–100. Omitted uses account defaults.","minimum":0,"maximum":100},"audioWeight":{"type":"number","description":"How strongly to follow the source, 0–100. Omitted uses account defaults.","minimum":0,"maximum":100},"weirdness":{"type":"number","description":"Experimentalness, 0–100. Omitted uses account defaults.","minimum":0,"maximum":100},"variety":{"type":"number","description":"Variation level, 0–1.","minimum":0,"maximum":1},"transpose":{"type":"integer","minimum":-24,"maximum":12,"description":"Source pitch shift in semitones; URLs/uploads only. Defaults to the preset voice register, otherwise 0."},"vocalGender":{"type":"string","enum":["f","m",null],"nullable":true,"description":"f or m overrides the preset; null omits the gender hint."},"vocalsOnly":{"type":"boolean","description":"Isolate source vocals before preparation; URLs/uploads only.","default":false},"instrumental":{"type":"boolean","description":"Generate without vocals.","default":false},"maxMode":{"type":"boolean","description":"Enable the model’s max mode.","default":false}},"oneOf":[{"required":["url"],"not":{"anyOf":[{"required":["clipId"]},{"required":["uploadId"]}]}},{"required":["clipId"],"not":{"anyOf":[{"required":["url"]},{"required":["uploadId"]}]}},{"required":["uploadId"],"not":{"anyOf":[{"required":["url"]},{"required":["clipId"]}]}}]},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}},"Clip":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"audioUrl":{"type":"string","nullable":true},"imageUrl":{"type":"string","nullable":true},"duration":{"type":"number","nullable":true}}},"Job":{"type":"object","properties":{"id":{"type":"string"},"state":{"type":"string","enum":["queued","running","done","failed"]},"stage":{"type":"string"},"startedAt":{"type":"string","format":"date-time"},"endedAt":{"type":"string","format":"date-time"},"result":{"type":"object","properties":{"clips":{"type":"array","items":{"$ref":"#/components/schemas/Clip"}}}},"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}}