Error catalogue

One envelope, one voice. Every rejection names a stable code, speaks to a person in message, and when it concerns a field gives the JSON path, what was expected and what arrived. Codes are added here before the API returns them.

On this page

The envelope

The envelope
{
  "error": {
    "type": "validation_error",
    "code": "missing_field",
    "message": "You didn't send $.visit.clinician. I can't file it without that.",
    "field": "$.visit.clinician",
    "expected": "a value",
    "received": "nothing",
    "docs_url": "https://pressfox.io/docs/errors#missing_field",
    "request_id": "req_01j9xk8d2"
  }
}

type groups codes into validation_error, invalid_request, authentication_error, permission_error, not_found, conflict, gone, rate_limited and limit_exceeded. field, expected and received are present only when they have a value; request_id is present on every response the API served. Branch on code, not on message: the wording is for people.

missing_field

HTTP 422 validation_error
“You didn't send $.visit.clinician. I can't file it without that.”

Add the field named by the JSON path. The contract lists every required field; fetch it from GET /api/v1/templates/{key}/contract. Also returned for a missing $.template or $.payload, and for $.access.password or $.access.expires_at when the access kind needs them.

Extra fields: field, expected: "a value", received: "nothing"

wrong_type

HTTP 422 validation_error
“$.results[0].value should be a number. You sent a string. I'll be here.”

Send the JSON type the contract asks for. Numbers as numbers, dates as strings in ISO 8601, lists as arrays. $.options and $.access must be objects.

Extra fields: field, expected, received

empty_array

HTTP 422 validation_error
“$.results is empty. What exactly am I filing?”

A required array that arrives empty is refused even when the contract does not say minItems. Lists inside repeated items (an area with no findings) may be empty. If a top-level section can legitimately be empty, make the path optional in the template.

Extra fields: field, expected: "at least one item", received: "an empty list"

invalid_value

HTTP 422 validation_error
“$.visit.date is not a valid date. Check it and send it again.”

The value is the right type but fails a rule: a format, an enum, a range or a length. The message names the rule. Also used for a bad Idempotency-Key (over 128 characters), an unknown status filter, a cursor this API did not issue, an access kind outside the five, a password under 8 characters and an expires_at that is not a future time. For template versions: a malformed @N suffix on $.template or $.template_id (key@, key@0, key@x, key@1@2; N is a positive integer), a $.template_version that differs from the @N on $.template (send one, or the same number twice), and on GET /api/v1/templates/{key}/contract a ?version= that differs from the @N in the path (field: "version").

Extra fields: field

unknown_option

HTTP 422 validation_error
“$.options.watermark is not an option this template declares. It declares: show_totals, compact.”

Send only the switches the published version declares, by name. The message lists them; an empty list reads "none".

Extra fields: field

unknown_format

HTTP 422 validation_error
“$.formats[0] is "docx"; formats are html, pdf and markdown.”

formats adds outputs to a report: send ["markdown"] for a Markdown version, or leave it out. HTML and PDF are always made.

Extra fields: field: "$.formats[i]", expected, received

too_many_images

HTTP 422 validation_error
“The payload links 612 images; a report fetches at most 500. Send fewer.”

Count distinct image URLs in the payload. Split the report, or upload images used in every report once as assets and reference them as asset:<id>.

Extra fields: field: "$"

template_not_published

HTTP 422 validation_error
“This template has no published version. Publish it first; drafts don't ship.”

Reports render from published versions only. Open the template in the app and publish; drafts are never used by the API. GET /api/v1/templates shows current_version: null for such templates.

Extra fields: field: "$.template" (or "$.template_id")

language_not_available

HTTP 422 validation_error
“This template has no German translation. It can render in: en, es. Add the translation in the designer and publish, or send one of those.”

A report renders in the published version's source language (the default) or in a language the version has translations for. GET /api/v1/templates lists them per template as languages. Translate the template in the designer's Languages panel and publish, or send one of the listed codes. A code outside en, es, pt, de, fr receives invalid_value instead.

Extra fields: field: "$.language", expected, received

nothing_to_translate

HTTP 422 validation_error
“This template marks no fields for translation. Mark free-text fields with Translate in its field dictionary and publish it again, or send the report without translate_from.”

translate_from translates only the fields the template's author marked Translate (the contract carries "x-pressfox-translate": true on them). Mark them in the designer and publish, or leave translate_from out.

Extra fields: field: "$.translate_from"

template_contract_invalid

HTTP 422 validation_error
“This template's contract cannot be compiled. Publish the template again.”

The stored contract could not be compiled into a validator. Publishing the template again regenerates it. Rare; write to support with the request_id if it repeats.

Extra fields: field: "$.template_id", received

unsupported_asset_type

HTTP 422 validation_error
“image/bmp is not an image type I store. Send png, jpeg, webp, gif or svg.”

Accepted: image/png, image/jpeg, image/webp, image/gif, image/svg+xml. The bytes must start the way the declared type must, and an SVG may contain no <script> and no on*= handler; otherwise received reads "bytes that are not <type>".

Extra fields: field: "content_type", expected, received

asset_too_large

HTTP 422 validation_error
“That file is 12582912 bytes. The ceiling is 10485760 bytes.”

Keep each asset at or under 10 MiB. A request whose Content-Length already shows it is over the ceiling is refused before the body is read.

Extra fields: field: "file", expected, received

not_json

HTTP 400 invalid_request
“That isn't JSON. I don't know what that is.”

The body of a JSON endpoint did not parse: a trailing comma, a cut-off body, or form data where JSON was expected. Send valid JSON with Content-Type: application/json. POST /api/v1/assets answers it with 422 when its JSON shape does not parse; send a JSON object there, or use multipart/form-data with a file part.

Extra fields: none

invalid_request

HTTP 400 invalid_request
“This request could not be read. Check it and send it again.”

The request is malformed in a way no other code covers, for example a Host the API does not serve. The status follows the cause; usually 400. A body that is not JSON is not_json.

Extra fields: none

method_not_allowed

HTTP 405 invalid_request
“This route does not take DELETE. It takes GET, PATCH.”

The path exists, but not for this method. Use one of the methods the Allow header and the message name.

Extra fields: none; the Allow header lists the methods the route takes

webhook_rejected

HTTP 400 invalid_request
“This webhook was not accepted. Nothing was stored.”

Returned to the payment provider by POST /webhooks/<provider> when the signature or the body does not check out. It says nothing about why on purpose. An integrator never sees it.

Extra fields: none

payload_too_large

HTTP 413 invalid_request
“The payload is 9437184 bytes; the limit is 8388608. Send less.”

Payloads are measured as canonical JSON and capped at 8 MiB. Move large images to assets and reference them as asset:<id>; split very long tables across reports.

Extra fields: field: "$.payload"

invalid_email

HTTP 422 validation_error
“These are not email addresses: not-an-address, b@@x. Fix them and send it again.”

Sharing a report by email checks every address before anyone is mailed; fix or remove the ones the message names. Items may be pasted lists separated by commas or new lines, and "Name <address>" is read as the address.

Extra fields: field: "$.emails", expected, received

too_many_recipients

HTTP 422 validation_error
“A share goes to at most 50 people; this one has 64. Split it in two.”

Counted after duplicates are removed. Send two shares.

Extra fields: field: "$.emails", expected, received

report_not_ready

HTTP 422 validation_error
“Only a ready report can be shared; this one is rendering.”

Wait for the report to be ready (poll it or wait for the report.ready webhook), then share it. GET /api/v1/reports/{id}/markdown answers the same code with 409 while the report renders or after it failed, and so does a correction (corrects) that names a report that is not ready: only an issued report can be corrected.

Extra fields: field: "$.report_id"

brand_not_found

HTTP 404 not_found
“No brand 'tolland' in this workspace. Brands are listed by GET /api/v1/brands, with their keys.”

The brand key or id is not in this workspace: it was deleted, its key was changed in the app, or it belongs to another workspace. List the keys with GET /api/v1/brands, or leave brand out to render with the template's own theme.

Extra fields: field: "$.brand" (or "$.brand_id")

markdown_not_requested

HTTP 404 not_found
“This report has no Markdown version. Ask for one with "formats": ["markdown"] when you create it.”

Returned by GET /api/v1/reports/{id}/markdown. A Markdown version is made only when the request sends formats: ["markdown"] or the workspace setting Always include Markdown is on; it cannot be made later. Create the report again with it.

Extra fields: none

report_expired

HTTP 422 validation_error
“This report's link has expired, so it cannot be shared.”

A report whose own expiry has passed cannot be shared. Create the report again with a later expires_at.

Extra fields: field: "$.report_id"

rate_limited

HTTP 429 rate_limited
“This API key sent 600 requests this minute, the limit. Try again in 23 seconds.”

The API counts requests in one-minute windows that start on the minute: 600 a minute per API key, of which 120 may be report creations (POST /api/v1/reports), and 1,200 a minute (240 report creations) across all of a workspace's keys, test and live together. The message says which window is full: "This API key" or "This workspace's API keys", "requests" or "reports". Wait the Retry-After seconds and send the request again (with the same Idempotency-Key for a report); the refused request was not counted and created nothing. Every counted response carries RateLimit-Remaining, so a batch job can slow down before it is refused. Write to us if your volume needs more.

Extra fields: expected: "at most 600 requests a minute"; the headers Retry-After, RateLimit-Limit, RateLimit-Remaining (0) and RateLimit-Reset

share_limit

HTTP 429 rate_limited
“This workspace can share with 25 people a day and has 5 left. Try fewer addresses, or again tomorrow.”

Sharing is capped per workspace (25 people a day on Free, 100 on Starter, 500 on Standard and above) and per member (200 a day), over a rolling 24 hours. In the app, sending one email again is limited to 5 times per person, once a minute.

Extra fields: none; a Retry-After header

playground_ai_daily_limit

HTTP 429 rate_limited
“This workspace used its 10 Playground runs with AI today. They reset at midnight (CEST), in about 5 hours. Switch off AI to simulate it, or send the request through the API.”

Returned by the app, not the API. A workspace makes at most 10 Playground runs a day with AI switched on (real translation or summaries), counted per calendar day in the workspace's time zone. Switch the AI toggle off to simulate AI for free, wait for the reset, or send the request through the API with a key, where only the plan's AI limits apply.

Extra fields: field: "$.ai", expected, received, resets_at; a Retry-After header

playground_rate_limited

HTTP 429 rate_limited
“You sent 60 Playground requests in the last hour. Try again in 4 minutes, or send it through the API.”

Returned by the app, not the API. Each member can send 60 Playground requests an hour; checking a request (the live validation) does not count. Wait the minutes the message names, or send the request through the API with a test key.

Extra fields: none; a Retry-After header

ai_off

HTTP 403 permission_error
“AI features are off for this workspace. Its industry is health, and no model provider here has signed for health data yet.”

A workspace whose industry is health cannot use translate_from (or any AI feature) until a business associate agreement covers the model provider. Send the report without translate_from. AI summaries in such a workspace are left empty and the widget prints nothing.

Extra fields: none

unauthorized

HTTP 401 authentication_error
“No valid API key. Nothing leaves the building without one.”

Send Authorization: Bearer pf_live_… or pf_test_…. The key may be revoked; create a new one in the app under API keys.

Extra fields: none

forbidden

HTTP 403 permission_error
“your role (viewer) cannot reports.delete. Ask an admin of this workspace.”

Returned by the dashboard API when a signed-in member's role does not allow the action. API keys act for the whole workspace and do not receive it today.

Extra fields: none

csrf_failed

HTTP 403 permission_error
“This page's security token is stale. Reload the page and try again.”

Returned by the app and its sign-in routes when the CSRF token is missing or stale: an old tab, or a sign-in in another tab. Nothing was changed and no role is at fault; reload the page. API keys never need a CSRF token.

Extra fields: none

terms_not_accepted

HTTP 403 permission_error
“Accept the current terms (2026-10-10) to continue.”

Returned by the app when the signed-in person has not accepted the current terms yet. Accept them on the screen the app shows, then try again. API keys are not affected.

Extra fields: expected: the terms version in force

not_found

HTTP 404 not_found
“There is no template with that id here. Check the id.”

The id does not exist in this workspace, is archived, or belongs to the other mode (a test key cannot see live reports, nor the reverse). Variants: "report with that id", "asset". Ids are never guessable; copy them from a response or the app.

Extra fields: none

version_not_found

HTTP 404 not_found
“Template inspection has no version 7. It has 3, 2, 1. Send one of those, or leave the version out to use the current one (3).”

The template exists but has no published version with that number. The message lists the numbers it has, newest first (at most ten); GET /api/v1/templates lists them as versions. A template that was never published answers this too when a version is pinned, and template_not_published when none is. Pin one of the listed numbers, or leave the version out to render the current one.

Extra fields: field: "$.template" (or "$.template_id", "$.template_version", "version")

version_retired

HTTP 410 gone
“Version 1 of inspection is retired. Move to inspection@3, the current version, or ask an admin to unretire it.”

Someone in the workspace retired the version you pinned, so new reports may no longer use it; reports already made from it keep their links and PDFs. Fetch the current version's contract, adjust your payload if it changed, and move your pin to the version the message names. If the retirement was a mistake, an owner, admin or editor can unretire it in the app's version history. In the app, making a retired version current is refused with the same code and HTTP 409: unretire it first.

Extra fields: field: "$.template" (or "$.template_id", "$.template_version", "version")

conflict

HTTP 409 conflict
“That Idempotency-Key was already used with a different request. Send the same body or pick a new key.”

Reuse an idempotency key only with the identical body from a key of the same mode. The other wording, "A request with that Idempotency-Key is still in flight. Try again.", means the first request has not finished; wait for it rather than retrying at once.

Extra fields: none

workspace_changed

HTTP 409 conflict
“You switched workspace in another tab. Reload to see the workspace you are in, then try again.”

Returned by the app when this tab still shows a workspace that another tab switched away from. The write was refused, so nothing landed in the wrong workspace. Reload and try again.

Extra fields: none

already_replaced

HTTP 409 conflict
“This report was already replaced by rpt_01ja7c2kd. Correct that one instead.”

A correction names the report it corrects with corrects. That report was already corrected, so correct the newest version, which the message names; GET /api/v1/reports/{id} shows it as replaced_by, and replaced=false on the list leaves replaced reports out.

Extra fields: field: "$.corrects" (or "$.replaces")

correction_in_progress

HTTP 409 conflict
“rpt_01ja7c2kd is already correcting this report. Wait for it, then correct that one.”

Another correction of the same report is still queued or rendering, and the message names it. A report has one line of versions, so wait for that correction to be ready (poll it or wait for the report.ready webhook), then correct it instead; if it fails, correct the original again.

Extra fields: field: "$.corrects" (or "$.replaces")

zero_retention_unsupported

HTTP 422 validation_error
“A zero-retention report keeps no labels: they are stored in the clear. Leave $.labels out.”

A zero-retention report keeps nothing past its delivery window, so it refuses what would outlive it or be stored in the clear: labels, partial forms, AI translation and summaries, retain_days, share emails and batch retries. The message names the field; leave it out, or send the report without zero retention.

Extra fields: field

version_is_current

HTTP 409 conflict
“Version 3 is the current version of inspection and cannot be retired. Make another version current first.”

Returned by the app, not the API. Requests without a version render the current version, so it cannot be retired. Make another version current (the rollback), then retire this one. The app answers version_retired with 409 when you try to make a retired version current: unretire it first.

Extra fields: none

plan_not_self_serve

HTTP 422 validation_error
“The Enterprise plan is arranged with us, not bought online. Write to us from the pricing page.”

Returned by the app when a checkout or plan change names Enterprise. Enterprise is a contract: write to us from the pricing page.

Extra fields: field: "$.plan"

too_many_pending_invitations

HTTP 422 validation_error
“This workspace has 200 invitations waiting, the most it can have. Revoke some, or wait until they are accepted or expire.”

Returned by the app when inviting a member. Viewers count too. Revoke invitations nobody will answer, or wait until some are accepted or expire.

Extra fields: field: "$.email", expected: "at most 200 pending invitations"

file_not_ready

HTTP 409 conflict
“This file is still being prepared. Try again when it is ready.”

Returned by the app when downloading a file (an export, a batch ZIP) that is still being prepared. Its status in /api/files says when it is ready.

Extra fields: none

file_expired

HTTP 410 gone
“This file has expired and was deleted. Request a new one.”

A prepared file is kept for a limited time, then deleted. Request it again.

Extra fields: none

files_in_progress

HTTP 429 rate_limited
“This workspace already has 3 files being prepared. Try again when one has finished.”

A workspace prepares at most 3 files at once. Wait for one to finish, then ask again.

Extra fields: expected: "at most 3 files at a time"; the header Retry-After

moved

HTTP 410 gone
“The in-request translation was retired. Call POST /api/ai/translations instead.”

Returned by the app for a route that now runs as a background job. The message names the route to call instead.

Extra fields: none

import_not_found

HTTP 404 not_found
“No import with this id in this workspace.”

Returned by the app for a template import id that does not exist in this workspace. Copy the id from the import list.

Extra fields: none

pdf_too_large

HTTP 422 validation_error
“This PDF is larger than 20 MB.”

Returned by the app when a PDF uploaded to start a template import is over 20 MB. Upload a smaller file.

Extra fields: field: "$.file"

template_import_rate_limited

HTTP 429 rate_limited
“You started 10 imports in the last hour. Try again in 12 minutes.”

A workspace starts at most 10 template imports an hour. Wait the Retry-After seconds.

Extra fields: the header Retry-After

import_file_gone

HTTP 410 gone
“The PDF is no longer kept: it is deleted 24 hours after the upload.”

The PDF uploaded for a template import is deleted 24 hours after the upload. Upload it again to see it.

Extra fields: none

billing_provider_error

HTTP 502 service_unavailable
“The payment provider did not complete this request, so nothing changed. Try again in a few minutes; if it keeps failing, contact support.”

Returned by the app's billing routes when the payment provider refuses or fails the request. Nothing changed on your plan. Try again later; if it keeps failing, write to support.

Extra fields: none

storage_unavailable

HTTP 503 service_unavailable
“This workspace's storage is not available right now. Try again in a few minutes.”

The workspace's storage did not answer, or its customer-managed key is unavailable. While a workspace's files move to another storage cell the wording is "This workspace's files are moving to their new home, and writes are paused while they do. Try again in a few minutes." Send the request again later.

Extra fields: none

internal_error

HTTP 500 api_error
“Something broke on our side. Nothing was changed; try again.”

An unexpected failure on our side. Nothing was changed. Send the request again; if it keeps failing, write to support with the request_id.

Extra fields: none

Plan limits

Every plan limit answers HTTP 402 with type: "limit_exceeded" and a code that names the limit: <limit>_limit for a count or quota, <feature>_not_included for a feature the plan lacks. field is the limit's name, or the request field that tripped it. upgrade_plan is the code of the lowest plan whose limits lift the refusal ("starter", "standard", "pro" or "enterprise"), and the message names the same plan; it is left out when no plan would lift it. A limit never deletes anything: after a downgrade, what you already have stays, and only new items over the limit are refused. The limits themselves are listed on the pricing page.

live_units_limit

HTTP 402 limit_exceeded
“This workspace used its 50 free report units this month. The Starter plan includes 1,000.”

Free stops at its included units. Starter stops there too unless an owner turns off "cap at included" in billing settings (then each 1,000 units past it cost $30); Standard stops there only when the cap is turned on. On a capped paid plan the wording is "This workspace reached its starter plan's 1000 included report units this month and is set to stop there. Turn off \"cap at included\" in billing settings to allow overage." Test keys are never metered.

Extra fields: field: "live_units_per_month", upgrade_plan

report_storage_limit

HTTP 402 limit_exceeded
“This workspace's reports use all of its starter plan's 10 GB of report storage. Delete reports you no longer need, add storage, or move up: the Standard plan lifts it.”

Live reports are counted by the bytes stored for them (page, PDF, Markdown and report images). Report storage is 1 GB on Free, 10 GB on Starter, 50 GB on Standard, 500 GB on Pro and 2 TB on Enterprise, plus any storage add-ons. Checked when a live report is created; a report already queued is never refused late, test reports never count, and nothing is deleted to make room. Delete old reports in the app, add 50 GB for $5 a month on a paid plan, or change plan.

Extra fields: field: "report_storage_bytes", expected, received, upgrade_plan

templates_limit

HTTP 402 limit_exceeded
“This workspace has reached its free plan limit of 3 templates. The Starter plan lifts it.”

Returned by the app when creating a template over the plan's count: 3 on Free, 5 on Starter, 15 on Standard, unlimited on Pro. Archive one or change plan. Nothing is archived for you after a downgrade; only new templates are refused.

Extra fields: field: "templates", expected, received, upgrade_plan

brands_limit

HTTP 402 limit_exceeded
“This workspace has reached its starter plan limit of 1 brand. The Standard plan lifts it.”

Returned by the app when creating a brand profile over the plan's count: 1 on Free and Starter, 3 on Standard, unlimited on Pro.

Extra fields: field: "brands", expected, received, upgrade_plan

asset_bytes_limit

HTTP 402 limit_exceeded
“This workspace has reached its free plan asset storage of 104857600 bytes. The Starter plan lifts it.”

Stored asset bytes are capped per plan: 100 MB on Free, 1 GB on Starter, 5 GB on Standard, 50 GB on Pro, 500 GB on Enterprise. Delete assets in the app or change plan. Identical bytes are stored once, so re-uploading costs nothing.

Extra fields: field: "asset_bytes", expected, received, upgrade_plan

webhooks_not_included

HTTP 402 limit_exceeded
“Webhooks are not included in the free plan. The Starter plan lifts it.”

Returned by the app when creating a webhook endpoint on Free. Starter includes 1 endpoint, Standard up to 10, Pro and Enterprise as many as you need.

Extra fields: field: "webhooks", upgrade_plan

signed_urls_not_included

HTTP 402 limit_exceeded
“Signed and private links are not included in the Standard plan. The Pro plan lifts it.”

access.kind "signed" and "private" need Pro. Use "link", "expiry" or, from Standard, "password".

Extra fields: field: "$.access.kind", upgrade_plan

access_policy_not_included

HTTP 402 limit_exceeded
“Password links are not included in the Starter plan. The Standard plan lifts it.”

On Free only access.kind "link" is available; Starter adds "expiry"; Standard adds "password".

Extra fields: field: "$.access.kind", upgrade_plan

addons_not_included

HTTP 402 limit_exceeded
“Add-ons are not included in the free plan. The Starter plan lifts it.”

Returned by the app. Report storage and AI translation add-ons are for paid plans; a workspace on Free or on its trial subscribes first.

Extra fields: field: "kind", upgrade_plan

translated_units_per_month_not_included

HTTP 402 limit_exceeded
“Report text translation is not included in the free plan. The Starter plan lifts it.”

translate_from needs a plan with translated report units: every paid plan, from Starter. Send the report without it, or move to a paid plan.

Extra fields: field: "translated_units_per_month", upgrade_plan

translated_units_limit

HTTP 402 limit_exceeded
“This workspace translated 4,998 of its pro plan's 5,000 report units this month. Send the report without translate_from, or move to a higher plan.”

Translated reports count their estimated units against the month's translated units. Wait for the next month, send without translate_from, buy 1,000 more units for $10 in the app (paid plans), or move up a plan. Checked at create and again when the render starts; refused at render, the report fails with this message.

Extra fields: field: "translated_units_per_month", expected, received, upgrade_plan

custom_domain_not_included

HTTP 402 limit_exceeded
“A custom domain is not included in the Starter plan. The Standard plan lifts it.”

Returned by the app. Standard includes 1 custom domain, Pro 25, Enterprise as agreed.

Extra fields: field: "custom_domain", upgrade_plan

keep_report_data_not_included

HTTP 402 limit_exceeded
“Keeping report data for corrections is not included in the Standard plan. The Pro plan lifts it.”

Returned by the app when Settings, Reports, Keep report data for corrections is set to 7 or 30 days. Included on Pro and Enterprise. Without it, correct a report from the API with the data your system sent.

Extra fields: field: "keep_report_data", upgrade_plan

pdf_password_not_included

HTTP 402 limit_exceeded
“PDF passwords are not included in the Starter plan.”

Send the report without pdf_password (or publish the template without a PDF password), or move to Standard or higher. A password on the hosted link is a separate setting: access.kind "password", also Standard and above.

Extra fields: field: "$.pdf_password", upgrade_plan

share_ack_not_included

HTTP 402 limit_exceeded
“Asking recipients to confirm, and reminding them, is not included in the Starter plan. The Standard plan lifts it.”

Share without ask_ack and remind_after_days, or move to Standard or higher.

Extra fields: field: "share_ack", upgrade_plan

reminder_not_needed

HTTP 409 conflict
“ana@client.com already confirmed they received it.”

Returned by the app's Remind now when the person already confirmed (or, when confirmation was not asked, opened the report). Nothing to do.

Extra fields:

share_not_sent

HTTP 409 conflict
“The first email has not gone out yet; send it again instead.”

Returned by the app's Remind now before the share's first email was sent. Use Send again on that person, or wait for it to go out.

Extra fields:

tenant_subdomain_not_included

HTTP 402 limit_exceeded
“A tenant subdomain is not included in the Starter plan. The Standard plan lifts it.”

Returned by the app. Tenant subdomains start on Standard.

Extra fields: field: "tenant_subdomain", upgrade_plan

webhook_endpoints_limit

HTTP 402 limit_exceeded
“This workspace has reached its Starter plan limit of 1 webhook endpoint. The Standard plan lifts it.”

Returned by the app when adding a webhook endpoint past the plan's count: Starter 1, Standard 10, Pro and Enterprise as many as you need. Send several event types to one endpoint, delete one you no longer use, or move up.

Extra fields: field: "webhook_endpoints", expected, received, upgrade_plan

custom_domains_limit

HTTP 402 limit_exceeded
“This workspace has reached its Standard plan limit of 1 custom domain. The Pro plan lifts it.”

Returned by the app when adding a custom domain past the plan's count: Standard 1, Pro 25, Enterprise as agreed. Every included domain is part of the plan; none is billed extra.

Extra fields: field: "custom_domains", expected, received, upgrade_plan

ai_assistant_turns_limit

HTTP 402 limit_exceeded
“This workspace used its 300 designer assistant turns this month on the Starter plan. They start again next month. The Standard plan lifts it.”

Returned by the designer assistant once the month's turns are used: Starter 300, Standard 1,000 plus 100 per extra member, Pro 3,000, Enterprise by contract. The count starts again on the first of the month in the workspace's time zone.

Extra fields: field: "ai_assistant_turns_per_month", expected, received, upgrade_plan

addon_not_on_plan

HTTP 402 limit_exceeded
“Extra members are sold on the Standard plan, not on Starter. The Standard plan lifts it.”

Returned by the app. Extra members and extra brands are sold on Standard only. Pro and Enterprise already include unlimited members and brands, so there the refusal names no plan.

Extra fields: field: "$.kind", upgrade_plan (below Standard only)

Why a report failed

The codes above refuse a request before anything is created. A report that was accepted (202) can still fail while it renders: its status becomes "failed", a report.failed webhook is sent, no units are used, and the reason is in the report's error. Most reports carry a sentence there, sometimes starting with a code (too-many-pages: document lays out to about 7 pages; the cap is 3). A zero-retention report carries only the code and, when there is one, the path (image_fetch_failed (at $.photos[2].url)), because a sentence might quote your data. Its data is deleted at once, so it cannot be retried: send it again.

CodeWhat happenedWhat to do
render_timeoutThe render did not finish in time and was stopped.Send the report again. If it keeps happening, the document is likely too heavy: fewer or smaller photos, fewer pages.
render_failedThe render failed after its retries, for a reason that is not one of the codes below (a zero-retention report records only this code).Send the report again. If it fails again with the same data, contact support with the report id.
pdf_finish_failedThe PDF was rendered but its last step (its properties and fast-web-view layout) failed. Nothing was issued and no units were used.Send the report again. If it fails again, contact support with the report id.
pdf_protection_failedThe PDF could not be protected with the pdf_password the request sent. A report that asked for a password is never issued without it.Send the report again. If it fails again, contact support with the report id; to issue it now, send it without pdf_password and protect the link instead (access.kind "password").
too-many-pagesThe document lays out to more pages than the plan allows for a live report (covers and the photo appendix are not counted).Send less data, or move to a plan with more pages. A test report renders at full length, so you can see how long it is.
body-too-largeThe report's data and photos together are larger than a render accepts. Its neighbours json-size, json-depth and json-forbidden-key mean data too large, nested too deeply, or holding a key such as __proto__.Send fewer or smaller photos (uploaded assets are sized for print before the render), or flatter, smaller data.
pdf-too-largeThe finished PDF would be larger than the limit.Fewer or smaller photos, or split the report in two.
image_fetch_failedAn image the payload links by URL could not be fetched. The error names its path.Check the URL is public and answers with an image, or upload the image as an asset and send its id.
images_too_slowFetching the payload's linked images took too long.Send fewer or smaller images, or upload them as assets first.
ai_failedThe text could not be translated reliably (translate_from). Nothing is printed half-translated.Send it again, or send it without translate_from to keep the original text.
live_units_limitThe month's included report units ran out while the report rendered.As for the request refusal (see live_units_limit under Plan limits): wait for next month, raise the plan, or turn off the cap on a paid plan.
cancelledThe report's batch was cancelled before it rendered.Nothing to fix. Send the report again if you still need it.

Template faults

These name a fault in the published template version rather than in your data: a block the kit no longer knows, a property out of range, a cover out of place, an expression that does not parse or runs too long, too many repeated blocks. The error names the block or path. Open the template in the designer, which shows the same fault, fix it and publish a new version; then send the report again (pin the new version with @N if you pin). Codes: unknown-block, invalid-prop, invalid-context, unknown-party, unknown-option, invalid-template, chrome-block, cover-block, cover-placement, fixed-block, expression-parse, expression-runtime, expression-timeout, repeat-depth, repeat-limit, block-limit.

Not an API error: the password gate

A password-protected report page answers 429 as plain text, with a Retry-After header, after five wrong passwords in fifteen minutes: “Too many wrong passwords. Wait 30 seconds and try again.” The wait doubles with each further wrong attempt, up to fifteen minutes. The API's own 429s are rate_limited and share_limit; the app's Playground has its own two (playground_rate_limited, playground_ai_daily_limit).

Success has a voice too

A ready report's message reads “Filed. Don't lose the link.”; a queued one “Rendering. Poll this report or wait for the webhook.”; a failed one “The render failed. Fix what the error names and send it again.”; a deleted one “This report was deleted.” They are there for the human reading the log, not for your code.