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.
{ "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."] }}Status codes
Section titled “Status codes”| 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. |
Retrying
Section titled “Retrying”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)) }}Partial failures
Section titled “Partial failures”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.
