Skip to main content
Files are never sent through the API directly. You request a presigned URL, then PUT the file straight to storage. This keeps large uploads fast and off the API’s request path.

Flow

  1. POST /v1/upload with { filename, size } (size in bytes).
  2. Receive { id, uploadUrl, contentType, uploadUrlExpiresIn, expiresAt }.
  3. PUT the raw file bytes to uploadUrl with exactly the returned Content-Type; the presigned signature is bound to it.
  4. Use id as audioId (audio jobs), imageId (image jobs), or fileId (text jobs).

Constraints

Content types are assigned by extension: audio/mpeg, audio/wav, application/pdf, text/plain, image/jpeg, image/png, image/webp.

Validation

The declared size is checked against your plan limit when the URL is created. When the upload is first consumed by a job, the API additionally verifies that:
  • the object actually landed in storage (UPLOAD_NOT_COMPLETED, 409, if the PUT never happened),
  • the real size is within your plan limit (FILE_TOO_LARGE, 413),
  • the file’s magic bytes match a valid MP3/WAV/PDF/JPEG/PNG/WebP signature (INVALID_FORMAT, 422). Plain-text files skip this check.
Which uploads a job accepts:
  • POST /v1/audio: .mp3 and .wav only.
  • POST /v1/image: .jpg, .jpeg, .png, .webp only.
  • POST /v1/text (fileId): .pdf and .txt only.

Errors