Responses & errors
One envelope for everything, so a client parses success and failure the same way.
The envelope
success
{
"success": true,
"message": "Video added.",
"data": { "asset": { "public_id": "oI1j0SfJK3v" } },
"meta": { "timestamp": "2026-08-17T20:36:23.813Z" }
}failure
{
"success": false,
"message": "Invalid or expired API key.",
"error": { "code": "INVALID_API_KEY" },
"meta": { "timestamp": "2026-08-17T20:36:23.813Z" }
}Branch on success. Use error.code for logic — message is written for humans and may be reworded without notice.
Error codes
By status
| Key | Type | Sample | Description |
|---|---|---|---|
| INVALID_API_KEYoptional | 401 | Invalid or expired API key. | Missing, malformed, unknown, revoked or expired — all the same response, on purpose. |
| PERMISSION_DENIEDoptional | 403 | error.details = ["add_video"] | The key’s organization lacks a permission. details lists the missing codenames. |
| API_KEY_EXISTSoptional | 409 | “Production server” is already active. | One key is live per organization. Revoke the current one before creating another. |
| NOT_FOUNDoptional | 404 | Video not found. | Does not exist, or is not yours. Deliberately indistinguishable. |
| VALIDATION_ERRORoptional | 422 | error.details = { "title": "Enter a title" } | The body failed validation. details is per field. |
| RATE_LIMITEDoptional | 429 | Retry-After: 37 | Too many requests. The header says how long to wait. |
| TRANSCODE_NOT_CONFIGUREDoptional | 409 | This organization has no transcoding settings yet. | Your encoding settings have not been set up. Nothing is created — talk to the StreamOS team and it is a one-minute fix. Retrying will not help. |
| DRM_NOT_AVAILABLEoptional | 409 | Your plan does not include DRM. | Sent drm_enabled: true on a video or stream, but your plan does not include DRM. Retrying with the same body will not help — either omit drm_enabled or ask the StreamOS team to enable DRM for your organization. |
| TRANSCODE_QUEUE_FAILEDoptional | 502 | Transcoding service timed out. | We reached the pipeline and it refused or timed out. Safe to retry. |
| NO_SLOTS_AVAILABLEoptional | 503 | No stream slots available. | Live stream capacity is exhausted. Retry shortly. |
A 404 never confirms that something exists somewhere else. An id belonging to another organization returns exactly what a typo returns — otherwise the API would be a way to test whether an id is real.
Rate limits
Limits are per key, so running your integration across several servers does not buy more allowance, and another customer cannot consume yours.
Limits
| Key | Type | Sample | Description |
|---|---|---|---|
| POST /livestreams/optional | per organization | 10 / minute | Stream slots are a finite shared pool. |
| All endpointsoptional | per key | 120 / minute | The general ceiling for a single API key, counted per key rather than per IP. |
| POST /videos/optional | per organization | 20 / minute | Each success queues a billed transcode, so this is capped for the org across every key and the dashboard together. |
| POST /videos/upload-url/optional | per organization | 60 / minute | Presigning is cheap, but it is the door to the bucket. |
On 429, read Retry-After and wait that many seconds. Retrying immediately just consumes the next window.
What is safe to retry
5xx and 429 are safe — nothing was created. A 4xx other than 429 will fail identically until you change the request.
POST /videos/ is idempotent on source_url: registering the same URL twice returns the existing asset rather than transcoding it again.