v1 · draft
View .md

Core concepts

Errors

Errors use HTTP status codes and a stable, snake_case error.code. Branch on the code. error.details is optional and code-specific.

422 Unprocessable Entity
{
  "status": "fail",
  "data": null,
  "error": {
    "code": "validation_error",
    "message": "Validation failed",
    "details": {
      "fields": {
        "rules.3.price": ["price must be a decimal string with at most 2 decimals"],
        "reason": ["reason must be at least 3 characters"]
      }
    }
  },
  "metadata": { "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77", "…": "…" }
}

Generic codes#

Any endpoint can return these. Business codes (package_incomplete, claim_not_payable, …) are listed on each endpoint in the reference.

StatusCodeMeaningDo
400bad_requestMalformed JSON, missing or unknown field, or an action not allowed in the resource's current status.Fix the request. Don't retry as is.
400invalid_scopeToken request for a scope the client doesn't hold.Request only granted scopes.
401invalid_clientWrong client credentials, or client revoked.Check credentials; ask your account manager.
401unauthorizedToken missing, invalid or expired.Get a new token and retry once.
403missing_permissionThe token lacks the endpoint's scope.Ask for the scope on the client.
403forbiddenThe client can never do this.Don't retry.
403ip_not_allowedIP not on the client's allow-list.Fix the allow-list or call from an allowed IP.
403tenant_not_activeYour company is suspended on Wathiqaty.Contact your account manager.
404not_foundDoesn't exist, or isn't your company's. details.resource names it.Treat as missing.
408request_timeoutThe request took too long.Retry with the same Idempotency-Key.
409conflictConflicts with current state, e.g. a duplicate reference.Reload, then decide.
409idempotency_request_is_still_processingSame key still running.Wait 1 s, retry with the same key.
412version_conflictChanged since you read it.Reload, re-apply, resend with the new If-Match.
413payload_too_largeBody over 100 KB.Send less.
422validation_errorField values invalid. details.fields maps each path to messages.Fix the values.
422idempotency_key_already_usedSame key with a different request.New key for a new action.
429rate_limitedToo many requests.Wait Retry-After seconds.
500internal_server_errorUnexpected error on our side.Retry with backoff; report the request id if it persists.
503service_unavailableA dependency is down or we are deploying.Retry with backoff, honour Retry-After.

Retrying#

  • Retry: network errors, 408, 429, 5xx, and 409 idempotency_request_is_still_processing — always with the same Idempotency-Key.
  • Refresh and retry once: 401 unauthorized.
  • Never retry unchanged: any other 4xx.
  • Back off exponentially with jitter (1 s, 2 s, 4 s … up to 60 s), and honour Retry-After when present.
Pseudocode
async function call(request, attempt = 0) {
  const res = await send(request) // same Idempotency-Key on every attempt
  if (res.ok) return res
  const retryable = [408, 429, 500, 502, 503, 504].includes(res.status)
    || (res.status === 409 && res.code === "idempotency_request_is_still_processing")
  if (res.status === 401 && attempt === 0) {
    await refreshToken()
    return call(request, attempt + 1)
  }
  if (!retryable || attempt >= 6) throw res
  const wait = res.retryAfter ?? Math.min(60, 2 ** attempt) * (0.5 + Math.random())
  await sleep(wait * 1000)
  return call(request, attempt + 1)
}

Status not allowed

Moving a resource to a status its current status doesn't allow (e.g. approving a claim that's still submitted) is 400 bad_request with details.from and details.to. Reload the resource: someone (your staff, the customer, or the system) moved it first.