Errors
Use this guide to handle failed API responses consistently. Every /v1/*
error follows the same envelope, and every doc_url in that envelope
links into the reference table on this page.
Error envelope
{
"error": {
"type": "invalid_request_error",
"code": "missing_fields",
"message": "template_id and scene_revision are required.",
"doc_url": "https://stackfill.com/docs/errors/missing_fields",
"request_id": "req_123"
}
}
Fields:
type — high-level family: authentication_error, permission_error,
invalid_request_error, not_found_error, plan_limit_error,
rate_limit_error, api_error, service_unavailable.
code — stable machine-readable identifier. Stable across API
versions; safe to switch on.
message — human-readable summary. May change wording without notice.
doc_url — deep-links to the matching row in the reference
on this page.
request_id — repeat back to support to look up the offending request.
Common status codes
| Status |
Meaning |
| 400 |
Invalid JSON, missing fields, or invalid parameters. |
| 401 |
Missing or invalid API key. |
| 402 |
Plan or entitlement limit. |
| 403 |
Origin, scope, or permission failure. |
| 404 |
Resource not found for this team. |
| 409 |
Conflict, usually duplicate mapping or wrong state. |
| 410 |
Gone — session/demo/fill has expired. |
| 413 |
Upload exceeds the per-asset size cap. |
| 415 |
Unsupported media type. |
| 422 |
Validation or idempotency mismatch. |
| 429 |
Rate limit exceeded. |
| 500 |
Unexpected API error. Safe to retry once after a short delay. |
| 502 |
Renderer container unreachable or failed. |
| 503 |
Subsystem (D1/KV/R2/Stripe/webhook secrets) not configured. |
Common handling
- Show user-recoverable copy for 400, 402, 403, 404, 409, 410, 413, 415, 422.
- Retry only 429 and transient 5xx, and respect
Retry-After when present.
- Treat 5xx as transient unless the same
code repeats — *_not_configured
503s mean an operator needs to set a secret.
request_id is your friend. Log it on every failed call.
Looking up a specific code
doc_url URLs follow the shape https://stackfill.com/docs/errors/<code>.
The platform redirects those to this page with the code as the URL
fragment, so the row scrolls into view automatically. You can also use
the in-page search (Cmd/Ctrl + F) to find a code.
Reference
Authentication (401)
| Code |
Type |
What it means |
authentication_required |
authentication_error |
No Authorization: Bearer <key> header. Send your API key. |
invalid_api_key |
authentication_error |
Header present but malformed, unknown, or revoked. |
api_key_expired |
authentication_error |
Key passed its expires_at. Rotate or create a new one. |
api_key_rotated |
authentication_error |
Key was rotated more than 24 hours ago. Swap to the new key in Link: rel="successor-version". |
invalid_publishable_key |
authentication_error |
Public widget routes only accept publishable (pk_) keys. |
invalid_embed_session_secret |
authentication_error |
client_secret from the URL fragment doesn't match the session row. |
embed_session_secret_required |
authentication_error |
Embed runtime called without a client_secret. |
invalid_access_token |
authentication_error |
One-time hosted-fill access token is malformed or revoked. |
session_required |
authentication_error |
sf_session cookie missing or expired on a /app or /admin route. |
demo_expired |
authentication_error |
24-hour demo workspace has expired. Sign up for a real plan. |
sweep_unauthorized |
authentication_error |
/api/admin/webhooks/sweep needs platform admin or the WEBHOOK_SWEEP_SECRET bearer. |
Permission / scope / origin (403)
| Code |
Type |
What it means |
insufficient_scope |
authentication_error |
Key authenticated but lacks the scope this endpoint requires. |
origin_not_allowed |
authentication_error |
Browser request from an Origin not in the publishable key's allowlist. |
ip_not_allowed |
authentication_error |
Secret key's IP allowlist blocked this caller. |
publishable_key_required |
authentication_error |
Endpoint is browser-only — use a publishable (pk_) key. |
secret_key_required |
authentication_error |
Endpoint is server-only — use a secret (sk_) key, not publishable. |
access_token_mismatch |
permission_error |
Hosted-fill token doesn't authorize the requested fill. |
platform_admin_required |
permission_error |
/admin + /api/admin/* are restricted to PLATFORM_ADMINS. |
Plan / billing (402)
| Code |
Type |
What it means |
api_plan_required |
plan_limit_error |
Public API needs Pro+. Upgrade in the dashboard. |
embed_plan_required |
plan_limit_error |
Storefront embed needs Pro+. Upgrade in the dashboard. |
template_limit_reached |
plan_limit_error |
Plan's template cap is full. Archive one or upgrade. |
subscription_inactive |
plan_limit_error |
Subscription past due. Update payment in the customer portal. |
subscription_canceled |
plan_limit_error |
Subscription was canceled; period_end has passed. Resubscribe. |
Not found (404)
These all mean "no record matches that ID for the calling team".
404 instead of 403 to avoid leaking existence to unrelated tenants.
| Code |
Type |
What it means |
not_found |
not_found_error |
Generic — path not handled by the API. |
wrong_host |
not_found_error |
/v1/* is served at api.stackfill.com. The apex returns this hint. |
template_not_found |
not_found_error |
Template ID missing or archived. |
template_not_live |
not_found_error |
Template exists but isn't published to live. Call POST /v1/templates/:id/publish. |
fill_not_found |
not_found_error |
Fill ID missing or expired. |
render_not_found |
not_found_error |
Render ID missing for this team. |
render_blob_missing |
not_found_error |
Render row exists but the R2 blob is gone (rare, contact support). |
embed_application_not_found |
not_found_error |
Publishable key isn't linked to any embed app. |
embed_session_not_found |
not_found_error |
Session ID missing or expired. |
product_mapping_not_found |
not_found_error |
No mapping for this SKU/product_id. |
webhook_endpoint_not_found |
not_found_error |
Endpoint ID missing or owned by another team. |
webhook_delivery_not_found |
not_found_error |
Delivery row missing under that endpoint. |
api_key_not_found |
not_found_error |
API key ID missing or revoked. |
publishable_key_not_found |
not_found_error |
Publishable key not in the catalog. |
font_not_found |
not_found_error |
Font asset missing for this team. |
font_blob_missing |
not_found_error |
Font row exists but the R2 blob is gone. |
image_asset_not_found |
not_found_error |
Image asset missing. |
image_file_not_found |
not_found_error |
Image row exists but the R2 blob is gone. |
scene_not_found |
not_found_error |
Template has no scene rows; ingest may have failed. |
selection_set_not_found |
not_found_error |
Selection set ID missing. |
brand_logo_not_found |
not_found_error |
Brand logo asset missing. |
company_not_found |
not_found_error |
Company (CRM) ID missing. |
client_user_not_found |
not_found_error |
Client (CRM) user missing. |
project_not_found |
not_found_error |
Project ID missing. |
team_not_found |
not_found_error |
Team ID missing. |
user_not_found |
not_found_error |
User ID missing. |
plan_not_found |
not_found_error |
Plan ID missing in the catalog. |
billing_team_not_found |
not_found_error |
Stripe customer references a team we no longer have. |
setting_not_found |
not_found_error |
Operator setting key not found. |
access_token_expired |
not_found_error |
Hosted-fill one-time token already used or expired. |
Bad request (400)
Almost always "you sent JSON we can't accept". The message field
spells out which field. Read it once during integration, then drop the
verbose check.
| Code |
Type |
What it means |
invalid_json |
invalid_request_error |
Request body didn't parse as JSON. |
invalid_multipart |
invalid_request_error |
Multipart upload couldn't be parsed. |
missing_fields |
invalid_request_error |
Required JSON fields missing. |
missing_required_field |
invalid_request_error |
A single named field is missing. |
missing_asset_fields |
invalid_request_error |
Asset upload missing kind/scope/file. |
missing_asset_name |
invalid_request_error |
Asset name field missing. |
missing_allowed_origins |
invalid_request_error |
Publishable key needs at least one allowed origin. |
invalid_mode |
invalid_request_error |
mode must be live or test. |
invalid_kind |
invalid_request_error |
Asset kind outside the allowed enum. |
invalid_scene |
invalid_request_error |
Scene JSON failed validation. |
invalid_plan |
invalid_request_error |
Plan ID not in the catalog or not active. |
invalid_billing_interval |
invalid_request_error |
interval must be monthly or annual. |
invalid_pricing_mode |
invalid_request_error |
Pricing mode unrecognized. |
invalid_features_json |
invalid_request_error |
Plan feature JSON failed validation. |
invalid_limits_source |
invalid_request_error |
Limits-source field unrecognized. |
invalid_setting_key |
invalid_request_error |
Operator setting key outside the allowlist. |
invalid_setting_value |
invalid_request_error |
Operator setting value rejected by validator. |
invalid_setting_scope |
invalid_request_error |
Operator setting scope rejected. |
invalid_application_status |
invalid_request_error |
Embed app status outside active/paused/archived. |
invalid_asset_scope |
invalid_request_error |
Asset scope not team or template. |
invalid_client_email |
invalid_request_error |
Client email field failed RFC 5322 validation. |
invalid_client_user_status |
invalid_request_error |
Client-user status outside the allowlist. |
invalid_company_status |
invalid_request_error |
Company status outside the allowlist. |
invalid_key_mode |
invalid_request_error |
API key mode must be live or test. |
invalid_key_status |
invalid_request_error |
API key status outside active/revoked. |
invalid_key_type |
invalid_request_error |
API key type outside secret/publishable. |
invalid_last_team |
invalid_request_error |
Cannot remove yourself from your only team. |
invalid_mapping_status |
invalid_request_error |
Product mapping status outside the allowlist. |
invalid_project_status |
invalid_request_error |
Project status outside the allowlist. |
invalid_session_status |
invalid_request_error |
Embed session status outside the allowlist. |
invalid_template_status |
invalid_request_error |
Template status outside the allowlist. |
invalid_webhook_status |
invalid_request_error |
Webhook endpoint status outside active/paused/disabled. |
invalid_webhook_url |
invalid_request_error |
Webhook URL must be https and absolute. |
invalid_stripe_event |
invalid_request_error |
Stripe payload failed signature or shape check. |
invalid_accent_color |
invalid_request_error |
Color value not a valid hex. |
invalid_primary_color |
invalid_request_error |
Color value not a valid hex. |
cannot_delete_self |
invalid_request_error |
Admins can't delete their own user from /admin. |
Conflicts (409)
You're trying to write a row that conflicts with state or another row.
| Code |
Type |
What it means |
client_email_taken |
invalid_request_error |
Another client (CRM) record uses that email. |
company_name_taken |
invalid_request_error |
Another company (CRM) uses that display name. |
team_email_taken |
invalid_request_error |
Another team uses that contact email. |
user_email_taken |
invalid_request_error |
Another user uses that email. |
project_name_taken |
invalid_request_error |
Project name collides within the team. |
plan_exists |
invalid_request_error |
Plan ID already in the catalog. |
product_mapping_exists |
invalid_request_error |
Mapping for that external_product_id already exists. |
setting_exists |
invalid_request_error |
Operator setting already present. |
template_archived |
invalid_request_error |
Cannot mutate an archived template. |
no_scene |
invalid_request_error |
Template has no scene; publish it after ingest finishes. |
render_not_ready |
invalid_request_error |
Render is still pending; wait for succeeded. |
delivery_already_succeeded |
invalid_request_error |
Webhook delivery already succeeded — no retry needed. |
endpoint_disabled |
invalid_request_error |
Webhook endpoint disabled after 20 consecutive failures. Re-enable in the dashboard. |
fill_expired |
invalid_request_error |
Fill's expires_at has passed. |
fill_not_open |
invalid_request_error |
Fill is finalized or archived; reopen by creating a new one. |
key_already_rotated |
invalid_request_error |
API key is already mid-rotation; wait for the grace window to end. |
key_not_active |
invalid_request_error |
Cannot rotate a revoked or expired key. |
Gone (410)
| Code |
Type |
What it means |
embed_session_closed |
invalid_request_error |
Embed session closed (saved/abandoned); start a new one. |
embed_session_expired |
invalid_request_error |
Embed session past its TTL; start a new one. |
Size limits (413)
| Code |
Type |
What it means |
font_too_large |
invalid_request_error |
Font file exceeds the per-asset cap. |
image_too_large |
invalid_request_error |
Image file exceeds the per-asset cap. |
pdf_too_large |
invalid_request_error |
PDF upload exceeds the per-template cap. |
Media type (415)
| Code |
Type |
What it means |
unsupported_media_type |
invalid_request_error |
Request body's Content-Type isn't accepted here. |
unsupported_font_binary |
invalid_request_error |
Font file isn't OTF/TTF/WOFF/WOFF2. |
unsupported_image_type |
invalid_request_error |
Image file isn't PNG/JPG/SVG. |
Validation / idempotency (422)
| Code |
Type |
What it means |
delivery_exhausted |
invalid_request_error |
Webhook delivery already hit its 10-attempt cap. |
delivery_skipped |
invalid_request_error |
Delivery was skipped (endpoint paused at the time); not retryable. |
publishable_no_rotate |
invalid_request_error |
Publishable keys don't support rotation; revoke and create a new one. |
idempotency_key_mismatch |
idempotency_error |
Same Idempotency-Key reused with a different request body. |
Idempotency conflicts (409)
| Code |
Type |
What it means |
idempotency_request_in_progress |
idempotency_error |
Another request with this Idempotency-Key is still being processed. Retry after a short delay to pick up the cached outcome. |
Rate limit (429)
| Code |
Type |
What it means |
rate_limit_exceeded |
rate_limit_error |
Sliding-window rate limit tripped. Respect Retry-After. |
Server errors (500 / 502 / 503)
Transient unless the same code repeats. *_not_configured 503s mean an
operator needs to set a secret — see STRIPE-PRODUCTION-CHECKLIST.md
or docs/operations/.
| Code |
Type |
What it means |
internal_error |
api_error |
Handler crashed before sending a normal response. |
render_failed |
api_error |
Render pipeline crashed mid-job. |
fill_render_failed |
api_error |
Finalize step couldn't produce output. |
ingest_failed |
api_error |
PDF ingest crashed; check the templates log in /admin. |
rotation_failed |
api_error |
API key rotation crashed mid-write. |
scene_fetch_failed |
api_error |
Couldn't load the scene row for a template. |
stripe_event_failed |
api_error |
Stripe webhook handler crashed; Stripe will retry. |
access_token_corrupt |
api_error |
Hosted-fill token couldn't be decoded; create a new fill. |
fill_context_missing |
api_error |
Fill row exists but linked template/scene is gone. |
renderer_failed |
api_error |
Ghostscript renderer container returned an error. |
renderer_unreachable |
api_error |
RENDERER_SERVICE_URL returned a network error. |
fill_team_mismatch |
invalid_request_error |
Internal — fill's team doesn't match the calling key. |
fill_template_mismatch |
invalid_request_error |
Internal — fill's template_id doesn't match the URL. |
billing_not_configured |
invalid_request_error |
Plan catalog row missing both Stripe price IDs. |
billing_plan_not_found |
invalid_request_error |
Plan row referenced by team is gone. |
db_unavailable |
service_unavailable |
D1 binding missing. |
kv_not_configured |
service_unavailable |
Required KV binding missing. |
r2_not_configured |
service_unavailable |
Required R2 binding missing. |
stripe_not_configured |
service_unavailable |
STRIPE_SECRET_KEY unset. |
stripe_webhook_secret_missing |
service_unavailable |
STRIPE_WEBHOOK_SECRET unset. |
webhook_secrets_not_configured |
service_unavailable |
Outbound webhook signing secrets unset. |
idempotency_store_unavailable |
service_unavailable |
Internal idempotency-claim store unreachable; retry shortly. |
See API reference for endpoint-level docs.