Errors & rate limits
The canonical error envelope, the full error code catalogue, billing-block codes, and per-key rate limits.
Type: Reference · Audience: Anyone integrating against
/api/v1
Every error response uses the same envelope:
{ "error": "Human-readable message", "code": "MACHINE_CODE" }
error is always present and safe to surface to end users. code is present on
actionable failures — branch on it, never on the error string (which may be
reworded without notice). Some responses add context fields (e.g.
available_cents, required_cents, maxAllowed, action_url).
Error code catalogue
code |
HTTP | Meaning | What to do |
|---|---|---|---|
UNAUTHORIZED |
401 | Missing/invalid API key or session | Check the Authorization: Bearer kc_live_… header |
INSUFFICIENT_SCOPE |
403 | Key authenticated but lacks the required scope | Create a key with the needed scope (jobs:write, etc.) |
TEST_KEY_LIVE_BILLING |
403 | A kc_test_ key was used on POST /v1/jobs/submit |
Use a kc_live_ key — test keys can't place billable jobs |
NO_PAYMENT_METHOD |
402 | No active subscription and no credits | Add credits (POST /v1/credits/topup) or subscribe |
INSUFFICIENT_CREDITS |
402 | Credit balance is below the job's estimated cost | Top up; see available_cents / required_cents |
COST_LIMIT_EXCEEDED |
422 | Estimated cost exceeds the per-job ceiling | Reduce job size; see maxAllowed. Contact support for higher limits |
SUBMISSION_IN_PROGRESS |
409 | A submit with this Idempotency-Key is still in flight |
Retry after Retry-After seconds |
UNSUPPORTED_SIMULATOR |
400 | simulator is not on the allowlist |
Use one of the listed simulator ids |
UNKNOWN_TIER |
400 | tierId is not a valid top-up tier |
Use a documented tier id |
INVALID_CHECKSUM |
400 | sha256 is not a 64-char hex digest |
Send a valid SHA-256, or omit it |
PAYLOAD_TOO_LARGE |
413 | Declared/assembled size exceeds the upload ceiling | Use resumable multipart (POST /v1/uploads); see Large-file uploads |
UNSUPPORTED_MEDIA_TYPE |
415 | contentType is denied (active content) or not in the allowlist |
Upload a supported asset type |
SIZE_MISMATCH |
422 | Assembled object size ≠ declared size at completion | Re-upload missing parts; the upload is incomplete/corrupt |
FORBIDDEN_ASSET_KEY |
403 | sceneFile references an uploads/ key owned by another user |
Submit only keys returned by your own upload |
RATE_LIMITED |
429 | Too many requests in the window | Back off; honour Retry-After |
SERVICE_UNAVAILABLE |
503 | A dependency (database/provider) is down | Retry with backoff |
INTERNAL_ERROR |
500 | Unexpected server error | Retry; if persistent, contact support |
Billing-block codes
When access is blocked by the billing layer, code carries the specific
reason so you can route the user correctly. Marketplace reasons return 403
(the user must resolve in the Azure Portal — action_url points there);
Stripe/generic reasons return 402 (the user can add a payment method):
code |
HTTP | Meaning |
|---|---|---|
marketplace_pending |
403 | Azure Marketplace subscription still activating |
marketplace_suspended |
403 | Azure Marketplace subscription suspended (billing issue) |
marketplace_unsubscribed |
403 | Azure Marketplace subscription cancelled |
stripe_past_due |
402 | Subscription payment past due |
no_payment_method |
402 | No subscription or payment method on file |
Rate limits
Limits are per API key (key-authenticated calls) or per user/IP (session and public calls), using a sliding 60-second window.
| Class | Limit | Applies to |
|---|---|---|
| Write | 10 / min | POST /v1/jobs/submit, POST /v1/credits/topup |
| Public | 60 / min | POST /v1/public/estimate (per IP) |
Every rate-limited response includes:
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
Requests allowed in the window |
X-RateLimit-Remaining |
Requests remaining |
Retry-After |
Seconds to wait, on a 429 only |
Limits may be raised per plan over time. Treat the headers as authoritative rather than hard-coding the numbers above, and implement exponential backoff on
429.
Upload size limits
There is no 25 MB-class request-body limit on uploads: file bytes never go through the API, only through presigned URLs to object storage.
| Method | Ceiling | Notes |
|---|---|---|
POST /v1/jobs/upload-url (single PUT) |
5 GiB (MAX_UPLOAD_BYTES) |
One-shot, not resumable; capped at the provider single-object limit |
POST /v1/uploads (multipart) |
200 GiB default (MAX_MULTIPART_UPLOAD_BYTES) |
Resumable; raise per plan |
Exceeding the ceiling returns 413 PAYLOAD_TOO_LARGE. For anything large or on
an unreliable connection, use the resumable flow — see
Large-file uploads.