Errors
Fromenance is a communication provenance platform whose API returns one error shape everywhere: an RFC 9457 problem details document with content type application/problem+json. This page lists every problem type and what to do about it.
By the end you will be able to parse any error, find the offending field, quote a request id to support, and back off correctly when rate limited.
The problem document
Section titled “The problem document”{ "type": "https://docs.fromenance.com/api/errors#validation", "title": "Validation failed", "status": 400, "detail": "The request body did not match the schema.", "instance": "/v1/communications", "errors": [ { "path": "recipient_hash", "message": "Invalid input: must match pattern ^hmac-sha256:[0-9a-f]{64}$" } ], "request_id": "a1b2c3d4e5f6"}| Field | Notes |
|---|---|
type |
A URL on this page. The fragment is the stable problem code; match on it, not on title |
title |
Short human readable summary, stable per type |
status |
The HTTP status, repeated |
detail |
What went wrong in this instance. May change wording between releases |
instance |
The request path |
errors[] |
Only on validation: one entry per failing field with a JSON path (items.3.sent_at for a batch item) and a message |
request_id |
Also sent as the x-request-id response header on every response. Quote it when contacting support |
The TypeScript SDK throws FromenanceError with status, problem (the document), and requestId.
Problem types
Section titled “Problem types”| Type | Status | When | What to do |
|---|---|---|---|
#bad-request |
400 | The request is malformed in a way a schema does not describe: no input on the verify page, a screenshot that is not an image, an unparseable webhook body | Fix the request |
#validation |
400 | The body, query, or path did not match the schema | Read errors[]; each path names the field |
#unauthorized |
401 | Missing, revoked, or malformed credential; a site key used from an origin not on its allow list; a provider webhook with a bad signature | Check the bearer token, the X-Site-Key and Origin, or the provider’s signing secret |
#forbidden |
403 | The credential is valid but lacks the scope or role, or a tenant context is required | Use a key with the right scope or a user with the right role |
#not-found |
404 | No such resource in your tenant. Ids are tenant scoped, so another tenant’s id is also a 404 | Check the id and its prefix |
#conflict |
409 | The action does not fit the current state: a forwarding test on an unverified domain, completing an expired reservation | Resolve the state first |
#payload-too-large |
413 | A screenshot over 6 MB | Send a smaller image or the message text |
#idempotency-mismatch |
422 | The Idempotency-Key was already used with a different body within 24 hours |
Use a new key for a new request |
#turnstile-required |
428 | The site key is over its normal rate or has Turnstile required | Render Turnstile and resend with turnstile_token |
#rate-limited |
429 | Over a rate limit | Wait for Retry-After seconds, then retry |
#internal |
500 | Something failed on the Fromenance side. Logged with the request id | Retry with backoff; contact support with the request id if it persists |
#upstream |
502 | A dependency failed: the raw message could not be fetched from the inbound provider, an adapter is temporarily disabled, a screenshot could not be read | Retry later, or use a different input |
Rate limits
Section titled “Rate limits”| Limit | Scope | Retry-After |
|---|---|---|
| 600 requests per minute | Per API key on POST /v1/communications and /reserve |
10 |
| 6,000 items per minute | Per API key on POST /v1/communications/batch, counted per 100 items |
10 |
| 60 requests per minute | Per IP on POST /v1/public/submit |
60 |
| 600 requests per minute | Per site key on POST /v1/public/submit |
10 |
A 429 always carries Retry-After in seconds. Back off for at least that long; do not retry validation errors at all.
Idempotent replays
Section titled “Idempotent replays”A successful replay of a creating POST with the same Idempotency-Key and body returns the stored response with its original status and the header idempotent-replayed: true. It is not an error, and the SDK treats it as a normal response.