Error handling
How the Teak Partner API reports errors, and what your server should do with each kind.
Every endpoint's page in the API reference lists the errors it returns, with an example of each. This page covers what they have in common: how to read an error, what to do with each kind, and how to retry safely.
Read an error
Most errors return HTTP 400, with the specific error in error.code:
{
"apiVersion": "2.0",
"error": {
"code": 404,
"message": "invalid data. see errors for more information.",
"errors": [
{
"domain": "Order",
"reason": "NotFound",
"message": "Session 3f1c9a2e-8b4d-4e6f-9a0b-1c2d3e4f5a6b not found"
}
]
}
}Branch on error.code, not the HTTP status. It uses HTTP status meanings, for example 404 for not found, 409 for a conflict, and 503 for temporarily unavailable. errors[].reason gives a machine-readable reason, and errors[].message a human-readable one.
Other formats
A few errors use a different body:
| HTTP status | Body | Returned by |
|---|---|---|
401 | The standard error, with error.code 401 | Any endpoint that needs an access token, when the token is missing, invalid, or expired |
400 | { "non_field_errors": [ ... ] } | Create an access token, when the keys are wrong |
403 | { "detail": "..." } | Submit an order, when tokenized orders aren't enabled for your account |
422 | { "detail": [ ... ] } | Submit an order, when the body doesn't match the schema. Each entry's loc names the field and msg says what's wrong |
404, 422 | { "msg": "...", "type": "..." } | Retrieve order details, when there's no order yet or primary_policy_id is invalid |
400 | { "msg": "...", "type": "..." } | Onboard a client, when affiliate isn't in the af_... format |
To handle them all in one place, turn every error into the same shape:
// Reads any Teak error response into { status, code, message }.
async function readTeakError(res) {
const body = await res.json().catch(() => ({}));
const detail = Array.isArray(body.detail)
? body.detail.map((d) => `${d.loc?.join('.')}: ${d.msg}`).join('; ')
: body.detail;
const message =
body.error?.errors?.map((e) => e.message).join('; ') ||
detail ||
body.msg ||
body.non_field_errors?.join('; ') ||
res.statusText;
return { status: res.status, code: body.error?.code ?? res.status, message };
}def read_teak_error(res):
"""Reads any Teak error response into (status, code, message)."""
try:
body = res.json()
except ValueError:
body = {}
error = body.get("error") or {}
detail = body.get("detail")
if isinstance(detail, list):
detail = "; ".join(f'{".".join(map(str, d.get("loc", [])))}: {d.get("msg")}' for d in detail)
message = (
"; ".join(e.get("message", "") for e in error.get("errors", []))
or detail
or body.get("msg")
or "; ".join(body.get("non_field_errors", []))
or res.reason
)
return res.status_code, error.get("code", res.status_code), messageHandle each kind of error
Decide what to do from the kind of error, not the individual message:
| Kind | How it looks | What to do | Retry? |
|---|---|---|---|
| Invalid request | error.code 400, 404, or 422, or HTTP 422 | Fix the request. The message says what's wrong. | No |
| Expired token | HTTP 401 | Get a new access token and try again. Tokens last 5 minutes. | Once |
| Wrong keys or account | HTTP 400 from Create an access token, error.code 403, or HTTP 403 | Check your keys match the environment and the widget's public key. If a feature isn't enabled, contact your Teak integration contact. | No |
| Already done | error.code 409 from Submit an order | The order was already submitted, for example by an earlier attempt. Treat it as success. | No |
| Not created yet | HTTP 404 from Retrieve order details just after Submit returned 202 | Teak creates the order a few seconds after Submit. Wait and try again, or call it from your Contract Issued handler. | Yes, after a few seconds |
| Temporarily unavailable | error.code 503 | Retry with backoff, for example after 1, 5, and 30 seconds. If it continues, check status.helloteak.com. | Yes |
| No response | A timeout or network error | You don't know whether the request went through. Retry it: see Retry safely. | Yes |
Retry safely
- Submit an order is safe to retry with the same session ID. The session identifies the order, so a retry can't create a second one: if an earlier attempt went through, the retry returns
error.code409, which means it's submitted. Send the same body each time. Teak keeps the first submitted order. - Keep
reference_idunique across all your orders, and reuse it only when you retry the same session. A new session with an order number that's already used is rejected. - Retrieving an order (details or links) changes nothing, so it's always safe to retry.
- Voiding or canceling a contract twice doesn't change it twice. If a retry says the contract is already voided or canceled, the first attempt went through.
- Get a new access token for each retry, rather than reusing one that may have expired.
For Submit, the Submit an order reliably recipe puts all of this in one function, in Node.js and Python.
Keep the customer's order safe
Submit to Teak only after your own order is confirmed, and never fail or undo the customer's purchase because of a Teak error. Their payment has already gone through. Log the error, retry where the table above says to, and alert your team if it keeps failing. The checkout example submits in the background, so checkout never waits on Teak.
When you contact Teak
Send your Teak integration contact:
- The endpoint and the time of the request, with its time zone
- The environment: sandbox or production
- The HTTP status and the full response body
- The IDs involved: the session ID, your
reference_id, or thepolicy_number
Never send your secret key or an access token.
Next steps
To see each endpoint's errors, or put this into practice:
- Look up an endpoint's errors in the API reference, for example Submit an order.
- Submit the order from your server once your own order is confirmed.
Updated about 1 month ago
