Skip to content

Errors

The SteadyLink error body, every status code and error code, rate limits and their headers, request IDs, and when it is safe to retry.

On this page

Every SteadyLink API error has the same JSON shape, a standard HTTP status, and a request ID. Use this page to decide whether a failed request should be fixed, retried, or reported, and to find the exact meaning of an error code your integration received.

The S3-compatible API uses S3's XML errors instead; see S3 errors.

Error body#

403 ForbiddenResponse
{
  "code": 403,
  "message": {
    "code": "insufficient_scope",
    "required": "assets:write"
  },
  "requestId": "7d3c1f2e-9a4b-4c6d-8e1f-0a2b3c4d5e6f"
}

Fields

codeinteger
The HTTP status code, repeated in the body.
messagestring | object
A sentence such as "Upload session expired", or an object with a machine-readable code and extra fields. Branch on message.code when message is an object, and fall back to the HTTP status when it is a string.
requestIdstring
The request ID, also returned in the X-Request-Id header.

Structured messages always have a code, and often a human-readable message inside them:

429 Too Many RequestsResponse
{
  "code": 429,
  "message": {
    "code": "api_write_limit_exceeded",
    "message": "Rate limit exceeded",
    "retryAfter": 12
  },
  "requestId": "0b9e4d2a-6f1c-4a3e-8d7b-2c5f9e1a4b60"
}

When the request body or query string does not match the route's schema (a missing field, a wrong type, a value out of range), the response is a 400 with the plain message "Validation error" and no field details. Check the request against the route's reference page.

Status codes#

StatusMeaningWhat to do
400The request is malformed: schema validation failed, both Authorization and X-API-Key were sent, or the operation is not allowed in this state (for example deleting the current revision without force=true, or a malware-blocked replacement).Fix the request. Do not retry it unchanged.
401No credential, or the credential is invalid, expired, or revoked (Unauthorized, Invalid API key, API key expired or revoked).Check the key or refresh the session.
403The caller is authenticated but not allowed: missing scope, a workspace role without the permission, an inactive API entitlement, a plan feature that is not included, or delivery of a file blocked as infected.Check the code in message. Retrying will not help.
404The resource does not exist in the active workspace, or the caller cannot see it.Check the ID and the workspace.
409The resource is in a conflicting state: it already exists, is already processing, an idempotency key does not match, or a plan count limit was reached (feature_limit_reached, seat_limit_reached).Read the resource again, then decide.
410The thing you referenced expired: an upload session, a conversion, an invitation, a replacement request, a widget submission, or a collection link.Create a new one.
413Too large: the file exceeds the plan's upload size, or storing it would exceed the workspace storage allowance.Upload a smaller file, free space, or upgrade.
415Unsupported media: the declared type contradicts the file's bytes (MIME mismatch), the type is not accepted, or a transform was requested for a non-raster file.Fix the content type or the request.
422The request is well-formed but a value is not acceptable, such as an invalid webhook URL, delivery domain, or country code.Fix the value named in message.
423Delivery only: the requested revision has not finished its malware scan.Retry after a few seconds.
429A rate limit, usage limit, or concurrency limit was reached.Wait for Retry-After, then retry.
451Delivery only: the workspace's country policy blocks the requester's region.Nothing to retry.
500Unexpected server error. The body is "Internal Server Error".Retry reads and idempotent writes with backoff; report it with the request ID if it persists.
502An upstream service failed: storage could not save the bytes, an SSO provider could not be reached, or a webhook test endpoint did not answer with a 2xx.Retry with backoff.
503A feature is not configured on the server, or the service is temporarily unavailable.Retry with backoff.
504A gateway timed out before the API answered.Retry reads and idempotent writes with backoff.

Error codes#

The code inside a structured message, grouped by what caused it.

Access and entitlement#

CodeStatusMeaning
insufficient_scope403The API key lacks the scope this route needs. required names it, for example assets:write.
insufficient_workspace_permission403The signed-in user's role does not allow the action. Includes permission and role.
subscription_inactive403The workspace's paid plan is not active or its billing period has ended. Existing keys are kept and can still be listed and revoked in the dashboard, but they cannot authenticate until billing is active again.
api_access_disabled403API access was turned off for this workspace by an administrative override.
paid_plan_required403The workspace's plan does not include API access.
business_plan_required403The feature, such as SSO, needs a Business or Enterprise plan.
workspace_required400A user who belongs to several workspaces did not send X-Workspace-Id.

Entitlement errors also carry message and plan. While a key is rejected for its entitlement, repeated attempts are themselves rate limited (see Rate limits).

Limits and quotas#

These share one shape:

413 Payload Too LargeResponse
{
  "code": 413,
  "message": {
    "code": "upload_size_limit",
    "resource": "upload_bytes",
    "usage": 262144000,
    "reserved": 0,
    "requested": 262144000,
    "limit": 104857600,
    "resetAt": null,
    "behavior": "hard_stop",
    "upgrade": { "recommendedPlan": "personal", "url": "/dashboard/settings?section=plans" }
  },
  "requestId": "3a8f1c6e-2d4b-4e9a-b7c0-5f1e8d2a6c93"
}
CodeStatusMeaning
upload_size_limit413The file is larger than the plan allows for one upload.
storage_quota_exceeded413Storing the file would exceed the workspace storage allowance. Includes usedBytes, requestedBytes, and limitBytes.
temporary_intake_storage_limit413Pending request and widget uploads would exceed the remaining storage.
usage_limit_exceeded413 or 429A metered allowance for the billing period is used up (413 for storage, 429 for others). resetAt is when it renews.
usage_throttled429Usage over the allowance is being slowed rather than stopped.
delivery_quota_exceeded429The monthly delivery bandwidth allowance is used up. Sent with Retry-After: 3600.
feature_limit_reached409A count limit for the plan, such as webhook endpoints, collections, or webhook deliveries. resource names it.
seat_limit_reached409Inviting another member would exceed the plan's seats.
api_key_limit_reached409The workspace has as many active API keys as its plan allows.
s3_key_limit_reached409The user already has 20 active S3 app keys in this workspace.

Plan limits are listed in Usage and limits.

Rate-limit codes#

CodeMeaning
api_burst_limit_exceededToo many API-key requests in 10 seconds, across the workspace.
api_read_limit_exceededToo many API-key reads (GET, HEAD, OPTIONS) in one minute.
api_write_limit_exceededToo many API-key writes in one minute.
api_monthly_quota_exceededThe workspace used its monthly API request allowance.
api_expensive_operation_rate_exceededToo many expensive writes in one minute.
api_expensive_operation_limit_exceededToo many expensive writes today.
invalid_api_key_attempt_limit_exceededMore than 30 requests with an invalid or revoked key from one IP address in a minute.
disabled_api_key_attempt_limit_exceeded, disabled_api_key_workspace_limit_exceededToo many requests with a key whose workspace has no API entitlement (60 per minute per IP, 300 per minute per workspace).
rate_limit_exceededA per-user limit for signed-in sessions (see below).
conversion_archive_rate_limit_exceededMore than 6 conversion archive requests in a minute.

Every rate-limit body includes retryAfter in seconds, matching the Retry-After header.

Rate limits#

API-key limits are pooled per workspace, not per key: creating more keys does not raise them. A request counts against the burst window, then the per-minute read or write window, then the monthly allowance, and, for expensive writes, the per-minute and per-day expensive limits.

PlanReads per minuteWrites per minuteBurst per 10 sExpensive per minuteExpensive per dayRequests per month
Free12030301025050,000
Personal3007575252,500250,000
Pro1,20030030012025,0002,000,000
Business6,0001,5001,250600250,00025,000,000
Enterprise60,00015,00010,0006,0005,000,0002,000,000,000

A write is expensive when its path contains /upload, /commit, /replace, /purge, /signed-url, or /retry, or when it is any write under /api/conversions. These are the operations that start scans, workers, or storage operations.

Signed-in sessions (the dashboard, or an accessToken) are not subject to the plan table. Their writes are limited per credential to 60 per minute, and delivery and download routes such as /a/{asset_id} allow 600 requests per minute per client when no API key is sent. Sign-in and other authentication routes allow 10 requests per minute per IP address. The S3-compatible API has its own per-key limits.

Response headers#

HeaderWhenValue
X-Request-IdEvery responseYour own X-Request-Id if you sent one that is ASCII and at most 128 characters, otherwise a generated UUID.
RateLimitAPI-key requestslimit=300, remaining=297, reset=41: the per-minute read or write window, with reset in seconds.
RateLimit-PolicyAPI-key requests"steadylink";q=300;w=60: the window's quota and length in seconds.
X-RateLimit-LimitAPI-key requests, and every 429Requests allowed in the window.
X-RateLimit-RemainingAPI-key requests, and every 429Requests left in the window. 0 on a 429.
X-RateLimit-ResetAPI-key requests, and every 429Unix time, in seconds, when the window resets. Note this is a timestamp, while reset in RateLimit is a number of seconds.
Retry-After429 responses, and some quota errorsSeconds to wait before retrying.

On a successful request the rate-limit headers describe the per-minute read or write window. On a 429 they describe whichever window was exceeded. Browsers can read all of these headers from cross-origin requests.

Send your own X-Request-Id (for example your trace ID) to find a request in both your logs and SteadyLink's. When you report a problem, include the request ID, the route, the status, and the time. Never include the API key or a signed token.

Retry policy#

SituationRetry?
Network error or timeout, safe method (GET, HEAD, OPTIONS)Yes, with exponential backoff and jitter.
Network error or timeout on a writeOnly if the write is idempotent (it carries an Idempotency-Key), or after reading the resource to check whether it happened.
429Yes, after Retry-After seconds. Monthly and daily quota errors will keep failing until the window resets.
423 on deliveryYes, after a short wait for the scan to finish.
500, 502, 503, 504Yes for reads and idempotent writes, with backoff. Stop after a few attempts and report persistent 500s.
409Read the resource first; retry only if the state now allows it.
Other 4xxNo. Fix the request.

Start backoff around 250 ms and double it on each attempt, with random jitter so many clients do not retry at once. Both SDKs do this for you on 429, 502, 503, and 504 (see TypeScript SDK retries and Python SDK retries).

Idempotency keys#

Upload completion (POST /api/upload-sessions/{id}/complete) has an explicit idempotency contract. Send an Idempotency-Key header and reuse it on every retry of the same completion:

  • The first completion stores the key. A retry with the same key, or a completion of a session that is already finished, returns the session without doing the work twice.
  • A completion with a different key while the session already has one returns 409 Idempotency key mismatch.

For any API-key request, an Idempotency-Key also identifies retries for metering, so a retried request with the same key counts once against the monthly management-operation allowance. It still counts against the per-minute rate limits. Other writes do not deduplicate on the key, so check the resource before retrying them.

Next steps#