Skip to content

Errors

The Slicer API error format, HTTP statuses and stable error codes, which errors are safe to retry and how to handle them in code.

On this page

Every API error comes with an HTTP status and a body of the same shape. Make decisions in code by the code field: codes are stable within v1, while message is written for people — in the account language — and may be reworded.

Error response
{
  "ok": false,
  "error": {
    "code": "insufficient_scope",
    "message": "The token lacks access: Run montage — spends tokens (montage:run)"
  }
}

Error

Field Type Description
okrequiredfalse
errorrequiredobject
error.coderequiredstringStable snake_case error code
error.messagerequiredstringHuman-readable message in the account language
error.fieldsRecord<string, string>Field errors
error.retryAfterSecintegerSeconds to wait before retrying
  • fields — per-field errors when the request body fails validation: { "uploadIds": "…" }.
  • retryAfterSec — how many seconds to wait before retrying (when a rate limit is exceeded).

Statuses

Status When What to do
400the body or parameters failed validation, or the file is not acceptablefix the request; details are in fields and message
401no token, or the token is wrong, revoked or expiredcheck the token; for token_expired and token_revoked, issue a new one
402not enough tokens on the balance, or the token's spend limit is used uptop up in the app or raise the token's limit
403the token lacks the required scopeissue a token with the right scopes
404the resource doesn't exist or belongs to another accountcheck the id; other people's resources don't exist for the API
409a state conflict: the price changed, the document is stale, the project is busyre-read the state and retry
410the upload link has expired or was already usedcreate a new upload
429rate limit exceededwait Retry-After seconds
5xxa temporary server or AI failureretry later with the same Idempotency-Key

Common codes

Code Status Meaning
unauthorized401no Authorization: Bearer header
invalid_token401token not found or mistyped
token_revoked401the token was revoked, the app disconnected or the account deleted
token_expired401the token has expired
insufficient_scope403the token lacks a scope; the missing ones are listed in message
not_found404project, upload or webhook not found
insufficient_tokens402the balance is lower than the operation's price
spend_limit_exceeded402the operation doesn't fit into the token's monthly spend limit
cost_changed409confirmCost doesn't match the current price — request the price again
stale_document409the document revision is outdated — re-read rev and repeat the edit
busy409the project is already being worked on (montage, render) — wait for it to finish
not_ready409the video isn't ready for this operation yet
too_many_drafts409too many drafts of projects without the AI director — export or delete some
daily_export_limit429today's free exports of projects without the AI director are used up — retry after retryAfterSec
manual_disabled503projects without the AI director are temporarily unavailable
request_in_progress409a request with this Idempotency-Key is still running
bad_idempotency_key409Idempotency-Key has the wrong format: 8–128 characters A–Z a–z 0–9 _ -
bad_cursor400the page cursor is damaged — start from the first page
platform_requires_app400publishing to this platform is only possible from the Slicer app
rate_limit_exceeded429too many requests per minute for the token
ai_unavailable503AI is temporarily unavailable — retry later, no tokens were charged
api_disabled503the public API is temporarily turned off
internal_server_error500an unexpected error — retry later

Upload errors (unsupported_format, file_too_large, resolution_too_low, link_expired and others) are described in Uploading files.

All codes

Code HTTP When
unauthorized401No token, or the token is not valid
token_revoked401The token was revoked or the app was disconnected
token_expired401The token has expired
insufficient_scope403The token lacks a required scope (see WWW-Authenticate)
forbidden403The action is not allowed for this account
not_found404No such object, or it belongs to another account
bad_request400Invalid request; field errors are in fields
conflict409The object is in a state that does not allow the action
stale_document409The document has changed: fetch the latest rev and retry
busy409The project is busy with a running job
not_ready409The video or upload is not ready yet
request_in_progress409A request with this Idempotency-Key is still running
cost_changed409The price has changed: confirm the new cost
insufficient_tokens402Not enough tokens on the balance
spend_limit_exceeded402The token's monthly spend limit is reached
rate_limit_exceeded429Too many requests: wait Retry-After seconds
file_too_large400The file is larger than allowed for its type
unsupported_format400The file format is not supported
empty_file400The file is empty
parts_missing400Not all parts were uploaded before complete
upload_not_active409The upload is already completed or cancelled
link_expired410The one-time upload link has expired
link_used410The one-time upload link was already used
too_many_imports429Too many imports by URL are running at once
platform_requires_app400Publishing to this platform is only available in the Slicer app
ai_unavailable503The AI service is temporarily unavailable: retry later

What to retry

Retrying with the same Idempotency-Key is safe: if the first request reached the server, you get its response instead of a second operation (Idempotency).

  • Network failure, 500, 502, 503, 504 — retry with exponential backoff: 1, 2, 4, 8 seconds and so on, 5–6 attempts at most.
  • 429 — wait as many seconds as Retry-After (or retryAfterSec) says, then retry.
  • 409 request_in_progress — the first request is still running; wait a couple of seconds and retry with the same key.
  • 409 cost_changed, stale_document — don't retry blindly: re-read the price or the document, show it to the user if needed, and send the request again.
  • 400, 401, 402, 403, 404 — retrying won't help; fix the request, the token or the balance.
JavaScript
Python
slicer.mjs
import { randomUUID } from 'node:crypto';

export class SlicerError extends Error {
  constructor(status, error) {
    super(`${status} ${error.code}: ${error.message}`);
    this.status = status;
    this.code = error.code;
    this.fields = error.fields;
    this.retryAfterSec = error.retryAfterSec;
  }
}

const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

/** API request with retries: one Idempotency-Key for all attempts. */
export async function slicer(method, path, body, { attempts = 5 } = {}) {
  const key = method === 'POST' ? randomUUID() : undefined;
  for (let attempt = 1; ; attempt += 1) {
    let response;
    try {
      response = await fetch(`https://api.slicer.media/v1${path}`, {
        method,
        headers: {
          Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
          ...(body ? { 'Content-Type': 'application/json' } : {}),
          ...(key ? { 'Idempotency-Key': key } : {}),
        },
        body: body ? JSON.stringify(body) : undefined,
      });
    } catch (networkError) {
      if (attempt >= attempts) throw networkError;
      await sleep(2 ** attempt * 500);
      continue;
    }
    const json = await response.json();
    if (json.ok) return json.data;
    const retry = RETRYABLE.has(response.status) || json.error.code === 'request_in_progress';
    if (!retry || attempt >= attempts) throw new SlicerError(response.status, json.error);
    const wait = Number(response.headers.get('Retry-After')) || 2 ** attempt * 0.5;
    await sleep(wait * 1000);
  }
}

What's next