Skip to content

Errors

Errors use ordinary HTTP status codes. Anything other than a 2xx carries a JSON body with an error string meant to be readable:

{
"error": "The funding account doesn't hold the order's currency."
}

Validation failures are the exception: those come back as a problem details document, with the offending fields listed.

400 from a validation failure
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Recipients[0].Email": ["Enter a valid recipient email address."]
}
}
Code Meaning What to do
400 The request is malformed or breaks a rule — a bad value, both recipients and codeQuantity, a currency mismatch. Fix the request. Retrying unchanged will fail again.
401 No key, an unrecognised key, or a key for a different environment. Check the key and the base URL match. See Environments.
402 Either the funding account cannot cover the order, or the API is not available on your plan. Top up, or talk to us about your plan.
404 The product, collection, template or subscription does not exist, or is not visible to you. Re-read the id from a list endpoint.
409 The externalReference has already been used. Treat the original order as placed. See Idempotency.
5xx Something broke on our side. Retry with backoff. Keep your externalReference the same so the retry is safe.

Always send an externalReference on sends. It is what makes a retry safe: a duplicate is rejected with 409 instead of placing a second order, so you never have to decide whether a timed-out request got through.

async function withRetry(fn, attempts = 4) {
for (let i = 0; i < attempts; i++) {
try {
const res = await fn()
if (res.status < 500) return res
} catch (e) {
if (i === attempts - 1) throw e
}
await new Promise((r) => setTimeout(r, 2 ** i * 500))
}
}

There are none on a send. An order is placed in full or not at all — if the call fails, no codes were issued, nothing was emailed and nothing was debited.