Lynkist Developers

Errors

Status codes, response envelopes, and recovery strategies for the Lynkist Public API.

The Lynkist Public API uses standard HTTP status codes and a small, predictable JSON body. This page describes the envelopes you actually receive today, the HTTP codes you should plan for, and the headers that help you correlate, retry, and back off.

Error envelope

Every error response has a single top-level detail field. The contents are usually a string, but a few endpoints return a typed object when the structured form is more useful to clients (e.g. scope-denial). Both shapes are supported as the contract:

String form (the default)

{ "detail": "Invalid or revoked API key" }

Object form

Returned by endpoints that have structured information for the caller. The current example is a missing-scope 403, which lists exactly what was required vs. what the key holds — useful for UI prompts like "ask an admin for campaigns:send":

{
  "detail": {
    "message": "Missing required scope(s): campaigns:send",
    "required_permissions": ["campaigns:send"],
    "current_permissions": ["campaigns:read", "campaigns:write"]
  }
}

Clients should accept both forms. Treat typeof detail === 'string' ? detail : detail.message as the human-readable message.

Standard response headers

Every response — success or error — carries:

HeaderAlways presentPurpose
X-Request-IdYesreq_<12-hex> (or whatever you sent). Log it. Quote it in support tickets.
X-RateLimit-Limit-MinuteYesYour per-minute budget
X-RateLimit-Remaining-MinuteYesRequests left in the current minute
X-RateLimit-Limit-DayYesYour per-day budget
X-RateLimit-Remaining-DayYesRequests left in the current day
X-RateLimit-ResetYesUnix epoch seconds when the current window resets
Retry-AfterOnly on 429Seconds to wait before the next request

See Rate Limits for the full backoff guidance.

HTTP status codes

StatusWhen you see it
200Success
201Resource created (e.g. POST /contacts, POST /webhooks)
400Malformed request body, missing required field, invalid format
401Missing, malformed, expired, or revoked API key
403Authenticated, but the key lacks the required scope or the request IP is not in the allowlist
404The resource ID does not exist (or belongs to another tenant)
409Conflict — usually a unique-constraint violation
422Validation error — the body is well-formed but semantically invalid (Pydantic validation)
429Rate-limited — back off until the time in Retry-After
500Lynkist encountered an unexpected error — safe to retry with backoff
502, 503, 504Transient upstream issue — retry with backoff

Common error messages by category

Authentication (401)

detailMeaning
Not authenticatedNo Authorization header (or wrong scheme)
Invalid API key formatToken does not match lk_sk_{live|test}_{40-hex}
Invalid or revoked API keyToken does not match any active key
API key has expiredKey passed its expires_at
Tenant not foundKey resolves to a tenant that no longer exists

Authorisation (403)

detailMeaning
Object form with required_permissions / current_permissionsKey is missing one or more required scopes
Request IP not in allowlistCaller's IP is outside the key's IP allowlist
API access is not enabled for your planTenant is on a plan that disables API access (e.g. free)
Tenant context missingInternal — should not occur in normal request paths

Validation (400 / 422)

Pydantic-driven validation failures use FastAPI's default 422 body, which lists every offending field:

{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "to"],
      "msg": "Field required",
      "input": { "template_name": "hello_world" }
    }
  ]
}

Validate request bodies on your side against the schemas in the per-resource API Reference.

Resource (404 / 409)

detailMeaning
Contact not foundThe contact ID does not exist or belongs to another tenant
Template not foundThe template ID does not exist or is not approved
Media not foundThe media ID does not exist
No active WABA account for this tenantThe tenant has no connected, active WhatsApp Business Account

Rate limiting (429)

{ "detail": "Rate limit exceeded. Please slow down." }

Sleep until the timestamp in X-RateLimit-Reset (or use the easier Retry-After seconds) and try again. See Rate Limits for the per-plan budgets.

Handling errors well

  • Don't pattern-match on the human message. Messages are written for people and may change. Match on the HTTP status, plus — for 403 — the structure of the detail object.
  • Log X-Request-Id on every failure. It is the single fastest signal we can correlate against on our side. Include it in support tickets and bug reports.
  • Retry only what is safe. 5xx and 429 are retryable; 4xx (except 409 against the same Idempotency-Key) are bugs in the request and will not pass on retry.
  • Pair retries with Idempotency-Key. A retried POST without an idempotency key risks creating duplicate resources. See API Reference.
  • Treat 403 not in allowlist as fatal. A network move is the only fix; retrying from the same IP will fail forever.
Errors — Lynkist Developers | Lynkist