Skip to main content
POST
/
v1
/
assets
Create an asset
curl --request POST \
  --url https://roughy-api-staging.fly.dev/v1/assets \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "project_id": "019de7f5-7e21-7402-92a8-1c0e1fa84411",
  "size_bytes": 14600000000,
  "language": "de"
}
'
{
  "id": "019de7f7-3a52-74d1-9eeb-323e0bfb7bb9",
  "project_id": "019de7f5-7e21-7402-92a8-1c0e1fa84411",
  "type": null,
  "state": "pending_upload",
  "extension": null,
  "size_bytes": null,
  "content_type": null,
  "duration_seconds": null,
  "language": null,
  "error_code": null,
  "error_message": null,
  "created_at": "2026-05-02T08:54:52.423000Z",
  "upload_url": "https://api.roughy.ai/v1/uploads/019df1a4-2385-7091-9f9f-4c350b988497"
}

Authorizations

Authorization
string
header
required

Pass your API key as Authorization: Bearer sk_…. Mint a key from your dashboard's API-keys page.

Body

application/json

Asset to create — opens a TUS-resumable source upload.

project_id
string<uuid>
required

Project that will own the new asset.

size_bytes
integer
required

Total file size in bytes.

language
string | null

Optional ISO 639-1 hint for the spoken language of the media (e.g. de, en). Normalised to lowercase; must be a 2-letter code. Omit / null to auto-detect. Set once at create and immutable thereafter.

Response

Asset created; the upload transfer URL is in the Location header.

Asset metadata.

id
string<uuid>
required

Stable unique identifier for the asset.

project_id
string<uuid>
required

Identifier of the project that owns this asset.

type
enum<string> | null
required

What kind of media this asset holds: audio or video, derived from the uploaded bytes once the first chunk is received. null before then.

Available options:
audio,
video
state
enum<string>
required

Lifecycle state: pending_upload while bytes are uploading, processing while Roughy prepares the asset (the per-minute charge settles here, once preparation finishes), pending_payment if your balance didn't cover the charge (bytes kept; auto-activates on your next top-up), ready once prepared and paid for (cuttable and renderable), and failed if preparation permanently failed (see error_code / error_message; nothing is charged on a failure).

Available options:
pending_upload,
processing,
pending_payment,
ready,
failed
extension
string | null
required

File extension without the leading dot (e.g. mp3, mp4), derived from the uploaded bytes once the first chunk is received. null before then.

size_bytes
integer | null
required

Total stored size in bytes. null while in pending_upload.

content_type
string | null
required

MIME type of the stored bytes (e.g. audio/mpeg, video/mp4), derived from the uploaded bytes once the first chunk is received. null before then.

duration_seconds
string | null
required

Duration in seconds, measured while the asset is processing. null while pending_upload or processing (before it is measured) and on a failed asset; always present once ready or pending_payment.

Pattern: ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
language
string | null
required

Optional ISO 639-1 hint for the spoken language of the media, set at create and immutable. null means auto-detect.

created_at
string<date-time>
required

When the asset row was created.

error_code
string | null

Machine-readable failure reason. Set only when state is failed; null otherwise.

error_message
string | null

Human-readable failure detail. Set only when state is failed.

upload_url
string | null

TUS transfer URL for resuming this asset's source upload. Present only while state is pending_upload; null once the upload finalises. Hand it to a TUS client (tus-js-client / tus-py-client) as the upload URL — the client issues a HEAD against it to read the authoritative Upload-Offset, then resumes. While pending_upload, this is the same transfer URL the create response carries in its Location header.