Skip to content

Examples use fictional data. Every company, person, address and count in them is invented, and their domains use the reserved .example TLD.

Getting started

Errors

One envelope, nine codes. Branch on the code — never on the message, which is written for humans and may be reworded.

The error envelope

Any non-2xx response has exactly this shape. details is optional and carries structured context — the failing field for validation errors, the shortfall for credit errors.

422-style validation failure, returned as 400
{
  "error": {
    "code": "bad_request",
    "message": "employeeMin must be less than or equal to employeeMax",
    "details": {
      "field": "filters.employeeMin",
      "received": 500,
      "max": 50
    }
  }
}

Status codes

CodeHTTPWhen you see it
bad_request400A field is missing, malformed or out of range. Details name the offending field.
unauthorized401No credential, an expired access token, or a revoked API key.
payment_required402Not enough credits for the rows requested, or an empty sending wallet.
forbidden403Authenticated, but the role or workspace does not own the resource. Also a GET that spends credits (a company profile, an enrichment job poll) that a browser sent as a navigation, an image or frame, or from another site — details.reason is paid_read_refused, and nothing was charged.
not_found404Unknown id, or an id belonging to another workspace.
conflict409The action collides with current state — duplicate email, campaign already running.
rate_limited429Bucket exhausted. Wait for retry-after and try again.
upstream_error502A dependency failed. Safe to retry with backoff.
service_unavailable503A service behind the API is not answering — or, on a paid endpoint, billing did not confirm the charge, so the result was withheld. Retry shortly; see "When billing cannot confirm" under Credits.

Handling errors in code

The TypeScript client throws ApiClientError with the status, the machine code and the parsed details attached, so you can branch without re-reading the response body.

import { ApiClientError } from '@salvo/sdk';

try {
  await salvo.exports.create({ kind: 'people', filters: { segments: ['event-management'] } });
} catch (err) {
  if (err instanceof ApiClientError && err.code === 'payment_required') {
    await salvo.billing.topup({ amount_cents: 5_000 });
  } else if (err instanceof ApiClientError && err.status === 400) {
    console.error(err.message, err.details);
  } else {
    throw err;
  }
}

Every response carries a request id

Include x-request-id when you write to support@salvo.example and the exact call can be pulled from the logs, including which internal service failed.