Handling errors
PreviewHow the RowCall API reports errors, which to fix, which to retry and how, and how to branch on the stable error code.
Every response has ok. When it is false, you also get a sentence you can log or show (error) and a stable code you can branch on (errorCode), with an HTTP status that matches.
{ "ok": false, "error": "Fill in Order.", "errorCode": "INVALID_INPUT" }Every code, its status, what it means and what to do is in the error table. This page is about handling them well.
Branch on the code, not the words
error is written for people and may be reworded. errorCode never changes meaning, so branch on it. New codes may be added: treat a code you don't know like a generic failure of its status.
import { RowCallError } from '@rowcall/sdk'
try {
await rowcall.runs.start(input, { idempotencyKey: `verify-${order.id}` })
} catch (error) {
if (!(error instanceof RowCallError)) throw error
if (error.errorCode === 'NO_CONSENT' || error.errorCode === 'OPTED_OUT') return markOrder(order, 'call-not-allowed')
if (error.status === 429 || error.status >= 500) return retryLater(order)
throw error
}Fix, wait, or retry
Errors fall into three kinds:
- Fix the request.
INVALID_INPUT,FORBIDDEN,NOT_FOUND,IDEMPOTENCY_MISMATCHand the like: the same request will fail again.errornames what's wrong, such as the missing input. Log it and fix the cause; don't retry as is. - The shopper or your account isn't ready. A run refused as
NO_CONSENT,OPTED_OUT,NOT_PUBLISHED,NOT_VERIFIED,NO_NUMBERorNO_CREDITmade no run and placed no call. Show the reason in your own system: record consent at checkout, respect the opt-out, publish the flow, or top up. Test runs are never refused for verification, numbers or credit. - Try again later.
RATE_LIMITED(429),IDEMPOTENCY_IN_PROGRESS(409) and any5xxare worth retrying, with backoff and a little random jitter, and the sameIdempotency-Key.
Retrying safely
A POST that timed out may have worked. Retry it with the same Idempotency-Key, and you get the first response back instead of a second call. A request that was refused is kept too, so after you fix the cause, use a new key. A 5xx isn't kept, so retrying it runs the request again. See Idempotency.
Back off between tries (for example 1, 2, 4, 8 seconds), give up after a few, and alert someone when you do.
When a run stops later
Not every problem shows up as a refused request. A run that was accepted can wait, keeping its attempts, with the reason in lastError: for credit, for business verification, or for one of your org's numbers to be declared for automated calls on DLT (NUMBER_NOT_DECLARED; RowCall declares them). See Registration (DLT).
A run that later stops without reaching the shopper ends as gave_up or canceled, with a sentence in lastError and a run.failed or run.canceled event. For example: every attempt went unanswered, the shopper opted out, consent no longer allows the call, or calling hours never opened. Handle these from your webhook, not from the request.
OAuth errors
The OAuth endpoints (/v1/oauth/token, /v1/oauth/revoke) follow the OAuth standard instead: { "error": "invalid_grant", "error_description": "…" }. See Build an integration.
How many requests you can send.