API documentation

Integrate the SAMO-TRANS exchange into your TMS or CRM

API documentation

Errors

The stable response envelope and the full list of error codes.

Every error arrives in one and the same envelope, regardless of endpoint or cause:

{
  "error": {
    "code": "validation_error",
    "message": "capacity must be a number",
    "requestId": "0f2b8c1e-6a4d-4a4b-9a30-1c9a2e5f8b77"
  }
}
  • code — a stable machine-readable code. It does not change, and this is what your logic should branch on.
  • message — a human-readable explanation. The wording can change at any time — do not parse it.
  • requestId — a unique identifier for the request. Log it: support can find that exact call in the server logs by this value.

The HTTP status always agrees with the code, so you can check either — but code is more precise: two different codes can share one status.

Code list

CodeHTTPWhen it happensWhat to do
unauthorized401no Authorization header, invalid or revoked key, or the company's plan no longer includes API accesscheck the key and the plan; retrying unchanged will not help
forbidden403the key lacks the required scope; the request comes from an IP outside the allowlist; no rights over that listingissue a key with the right scopes, or fix the IP allowlist
not_found404the object does not exist or belongs to another companycheck the id; the API deliberately does not distinguish the two
conflict409another request with the same Idempotency-Key is still runningretry in a second or two
validation_error400, 422body or parameters failed validation; an unexpected field was sent (e.g. companyId); the same Idempotency-Key was reused with a different body (422); a listing was bumped too soon or its bump limit is exhaustedfix the request; retrying unchanged gives the same result
rate_limited429the per-minute request limit was exceededwait as instructed by Retry-After
quota_exceeded429the daily write quota is exhaustedcontinue the next day; Retry-After says how long is left
internal5xxa failure on our sideretry with exponential backoff; if it persists, send the requestId to support

Why there are only this many

Internal failure reasons are deliberately not promoted to their own codes. Bumping a listing, for instance, can fail because it is "too soon" or because the bump limit is used up — both arrive as validation_error, with the specific reason in message. That keeps the code set small and stable: an integration written today will not break when a new server-side check appears.

The practical rule follows: branch on code, show message to the user, send requestId to support.

Examples

Missing authorization header:

{ "error": { "code": "unauthorized", "message": "Missing API key", "requestId": "…" } }

Key without the required scope:

{ "error": { "code": "forbidden", "message": "Insufficient API key scope", "requestId": "…" } }

An unexpected field in the body (here companyId, which always comes from the key):

{ "error": { "code": "validation_error", "message": "property companyId should not exist", "requestId": "…" } }

Daily quota exhausted:

{ "error": { "code": "quota_exceeded", "message": "Daily write quota exceeded", "requestId": "…" } }

Client-side handling

async function call(path, init) {
  const res = await fetch(`${API_BASE}/api/public/v1${path}`, init);
  if (res.ok) return res.json();

  const { error } = await res.json();
  switch (error.code) {
    case "rate_limited":
    case "quota_exceeded":
      throw new RetryableError(error, Number(res.headers.get("Retry-After") ?? 60));
    case "internal":
      throw new RetryableError(error, 5);
    case "unauthorized":
    case "forbidden":
      throw new ConfigurationError(error); // retrying will not help — someone must intervene
    default:
      throw new RequestError(error); // fix the request
  }
}