Skip to content
Docs menu · Errors and rate limits
Reference / Errors and rate limits

Errors and rate limits

Every error has the same shape, a stable code to branch on, and a plain-English message that is safe to show to people.

Error shape

Errors return a non-2xx status and {"error": {…}} with code and message. Validation errors add fields, mapping each bad field to what's wrong. Rate limits add retry_after_seconds.

Validation error
HTTP/1.1 400 Bad Request

{
  "error": {
    "code": "invalid",
    "message": "Must be between 1 and 50.",
    "fields": { "copies": "Must be between 1 and 50." }
  }
}

The SDKs turn these into typed errors — ValidationError, PermissionError, OutOfPrintsError, RateLimitError and so on (exceptions in PHP, Error variants in Rust) — carrying the same status, code, message, fields and retry delay. See SDKs.

Error codes

StatusCodeMeaning
400invalidA field is wrong, has the wrong type, or isn't a field we know. See fields.
400invalid_jsonThe body isn't a single valid JSON object.
401unauthenticatedNo credentials, or the session has ended.
401invalid_api_keyThe key is wrong, revoked or expired.
402out_of_printsThe organization has used its prints. Upgrade or top up. A job needs prints left for all of its copies (each copy is a print).
403forbiddenYour role or the key's scopes don't allow this — for example cancelling someone else's job without the Operator role, or sending "live": false below the Developer role.
403ip_not_allowedThe key is limited to other IP addresses.
403org_suspendedThe organization is suspended. Contact support.
403two_factor_requiredThis action needs a two-factor sign-in.
404not_foundIt doesn't exist, or you can't see it. Organizations you can't access also answer 404.
409printer_disabledThe printer, station or location is turned off and set to refuse jobs.
410content_deletedThe job's document was deleted after the retention period (30 days after the job finished, by default). Preview, content and reprint answer this; the job record stays. Send the document again as a new job. See Retention.
413content_too_largeJob content is over 50 MB.
422no_printerNo printer matches the role or location. Add one or set printer_id.
422conversion_failedAn Office document couldn't be converted. Send a PDF.
422conversion_unavailableDocument conversion is temporarily unavailable. Send a PDF or retry later.
429rate_limitedToo many requests. Wait for Retry-After.
429test_limitTest mode allows 200 prints a day per organization; each copy counts as a print.
500internalSomething went wrong on our side. It's safe to retry with the same idempotency key.

Rate limits

Limits are counted per IP address with a token bucket, so short bursts are fine:

WhatLimit
All API requests1,200 a minute, with bursts of up to 240
Public endpoints (/v1/public/…), sign-in and device pairing30 a minute, with bursts of up to 10
Test-mode jobs200 a day per organization
Contact form messages3 an hour per email address or IP

Over a limit you get 429 with a Retry-After header (seconds) and retry_after_seconds in the body.

Rate limited
HTTP/1.1 429 Too Many Requests
Retry-After: 3

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Slow down.",
    "retry_after_seconds": 3
  }
}

Retrying safely

The SDKs do this for you: network errors, 429 and 502/503/504 are retried with back-off, waiting as long as Retry-After asks. Every print carries an idempotency key — yours, or one the SDK generates — so a retry after a timeout can never print twice. Pass your own (an order number) to make your own retries safe too. Other 4xx errors (including 410 content_deleted) are not retried — fix the request instead.

import { CatzPrintError, RateLimitError, ValidationError } from "@catzprint/sdk";

try {
  await cz.printReceipt(commands, { role: "receipt", idempotencyKey: `order-${order.id}` });
} catch (err) {
  if (err instanceof ValidationError) console.error(err.fields); // fix the request
  else if (err instanceof RateLimitError) console.error(`still limited; retry in ${err.retryAfter}s`);
  else if (err instanceof CatzPrintError) console.error(err.status, err.code, err.message);
  else throw err;
}
TipThe message is written for people. Show it in your UI rather than inventing your own text for each code.