Errors
Failures come back as RFC 7807 problem documents. The machine-readable code lives in the type field — there is no separate code property.
Shape of an error
{
"type": "/errors/Reseller.InsufficientQuota",
"title": "Insufficient quota for the requested product.",
"status": 409,
"traceId": "00-8a1b2c3d4e5f...-01",
"timestamp": 1754380800
}Branch on type, not on the status code alone — several distinct conditions share
a status. The title is written
for humans and may be reworded, so do not match on it.
const problem = await response.json().catch(() => null);
const code = problem?.type?.replace("/errors/", "");Validation errors
Malformed requests use a slightly different shape, with a per-field breakdown:
{
"type": "/errors/validation",
"title": "Validation Failed",
"status": 400,
"detail": "One or more validation errors occurred.",
"instance": "/billing/partner/licenses",
"errors": [
{
"field": "PlanSlug",
"code": "InvalidField",
"message": "PlanSlug is required."
}
],
"traceId": "00-8a1b2c3d4e5f...-01",
"timestamp": 1754380800
}Authentication failures
A 401 has an empty body — no problem document, nothing to parse. Check for it before attempting to read JSON.
Full catalogue
Quota
Your balance for this product is too low for the whole request. Nothing was consumed. Top up before retrying.
count must be greater than zero.
count exceeds the per-request maximum of 1000. Split the batch.
Recipient
No account matches that login. Check it with the customer — retrying alone will not help.
The recipient is banned and cannot receive a subscription. No quota was consumed.
Product
Unknown planSlug. Read the valid values from your balances response rather than hardcoding them.
The product exists but is no longer sold. Any quota you hold for it is stranded — get in touch.
Idempotency
The field was missing or blank. It is mandatory on every write.
This key was already used with a different body. Almost always a key reused across two sales. Nothing was charged.
An identical request is still running. Back off and retry with the same key.
Request shape
planSlug was missing or blank.
One or more fields failed validation. The errors array names each one.
Server-side
The batch could not be generated and the charge was reversed. No quota was consumed — safe to retry with the same key.
A downstream service was unreachable. Retry with the same key.
The login could not be resolved right now. Retry with the same key.
Unexpected failure. Retry with the same key; if it persists, send us the traceId.
What is safe to retry
Retry on network failures, on Reseller.RequestInProgress, and on 5xx — always with the same idempotency key. Every other
4xx is final: the request needs changing before it will succeed.
Where an error says no quota was consumed, that is a guarantee rather than a likelihood. You will not be charged for a request that failed this way.
