Skip to content
In this section

Errors

The error body, status codes, request ids and rate limiting.

Errors are JSON with a single field:

{"error": "invoice not found"}

Authentication failures add a code: missing_api_key or invalid_api_key on a 401, account_not_active on a 403.

Every response, including 401, 429, and server errors, carries X-Request-Id: req_<uuid>. Include this value in a support request; it is the safe correlation handle for a request, not your API key or customer data.

Status Meaning
400 The request is malformed: an unparseable id, an unknown filter value, an invalid parameter.
401 Missing or invalid API key.
403 The key is valid but the seller account is not active.
404 The object does not exist — or belongs to another seller.
422 The object exists but the action is not applicable to it (for example, sending an invoice that is collected by bank transfer).
429 Rate limit exceeded. Honour Retry-After and the X-RateLimit-* headers.
501 The capability is not enabled for this deployment (for example, credits before stored value is switched on).
500 Server fault. Safe to retry idempotent (GET) requests with backoff.

Rate limiting

Requests are rate-limited per seller, on a budget dedicated to this API — billing traffic and payments-api traffic do not throttle each other. The default budget is 1000 requests per hour per seller, with a burst of 100 so a single client cannot spend the whole hour in one instant. The number is set per environment, so read X-RateLimit-Limit rather than hard-coding it.

The metering routes under /billing-api/v1/usage are not counted against this per-seller budget and return no X-RateLimit-* headers: bound your own concurrency and send events in batches rather than one call per event — see Usage ingestion.

Responses normally carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (whole seconds until the budget refills), plus the current standards-track structured fields RateLimit-Policy ("seller";q=1000;qu="requests";w=3600) and RateLimit ("seller";r=940;t=2100). Both spellings carry the same numbers. Note these are not the RateLimit-Limit/RateLimit-Remaining/RateLimit-Reset triple from an earlier revision of that draft, which is not sent. Read them rather than assuming a number: the budget is set per environment, and X-RateLimit-Limit is the authoritative value for yours. Treat them as advisory — during a limiter outage requests are allowed through without the headers, so a client that requires them will break exactly when the platform is already degraded.

Three write operations carry an extra per-seller quota on top of that budget, each in its own bucket, because each accepted call spends something that cannot be handed back:

Operation Default quota Why
POST /contracts 20 requests per rolling 24 hours (refusals and replays count) Bounds how fast one key opens contracts; a create allocates a number from a series shared across sellers.
POST /contracts/{contractId}/amendments 200 requests per rolling 24 hours (a stale expectedBaseVersion counts) Every accepted amendment appends a contract version — up to 100 line items — to a history that is append-only and has no delete.
POST /invoices 500 requests per rolling 24 hours (refusals and replays count) Issuing allocates a number from your own gapless series, so a runaway loop burns your month of numbers.

All three refuse with the same 429, Retry-After and X-RateLimit-* headers as the surface-wide limit.

A rejected request answers 429 with Retry-After in seconds. Wait that long — retrying sooner only deepens the overage.

Bulk work is what actually hits this. Reconciling a month walks pages of up to 100 invoices and may pull a PDF per document, so a few hundred requests in one burst is normal. Pace bulk exports (a short sleep between pages costs far less than being throttled mid-walk), and if a legitimate workload cannot fit the budget, ask Fynex to raise it rather than working around it with parallel keys.

When the write quotas fail closed

Unlike the surface-wide budget, those three operations — POST /contracts, POST /contracts/{contractId}/amendments and POST /invoices — fail closed. While the limiter itself is unreachable they answer 503 with Retry-After instead of letting an uncounted burst of writes through. Nothing was created, amended or issued, so the retry is safe: wait the header out and repeat the call with the same Idempotency-Key, which is what stops the retry from opening a second contract or issuing a second document. Amendments carry no Idempotency-Key — resend the same expectedBaseVersion, and a version that did land answers 422 rather than appending a duplicate.

Two things this does not mean. It is the limiter FAILING, not the quota being absent: a deployment that switches a quota off (its request budget set to 0) is simply unmetered on that operation, exactly as before, and never answers 503 for this reason. And the quotas count requests, not successes — a refusal or a replay spends one too, so a client retrying a 400 in a loop can exhaust its day without ever writing anything.