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 statusBodyReturned by
401The standard error, with error.code 401Any 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), message

Handle each kind of error

Decide what to do from the kind of error, not the individual message:

KindHow it looksWhat to doRetry?
Invalid requesterror.code 400, 404, or 422, or HTTP 422Fix the request. The message says what's wrong.No
Expired tokenHTTP 401Get a new access token and try again. Tokens last 5 minutes.Once
Wrong keys or accountHTTP 400 from Create an access token, error.code 403, or HTTP 403Check your keys match the environment and the widget's public key. If a feature isn't enabled, contact your Teak integration contact.No
Already doneerror.code 409 from Submit an orderThe order was already submitted, for example by an earlier attempt. Treat it as success.No
Not created yetHTTP 404 from Retrieve order details just after Submit returned 202Teak 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 unavailableerror.code 503Retry with backoff, for example after 1, 5, and 30 seconds. If it continues, check status.helloteak.com.Yes
No responseA timeout or network errorYou 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.code 409, which means it's submitted. Send the same body each time. Teak keeps the first submitted order.
  • Keep reference_id unique 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 the policy_number

Never send your secret key or an access token.

Next steps

To see each endpoint's errors, or put this into practice:


Did this page help you?