Skip to content
Esc
navigateopen⌘Jpreview
On this page

API versioning and errors

Read a Steer Phones problem response, branch on its error code, retry safely, and stay compatible as version 1 grows.

A client that survives platform changes does two things: it tolerates additions it did not expect, and it reacts to failures by machine-readable code rather than by reading English. This page covers both — how version 1 is allowed to change, and the exact failure envelope every operation returns.

What version 1 may change

The major version is part of the base URL, https://api.steerphones.com/v1.

Inside version 1, these changes can happen without warning and are not breaking:

  • A new operation appears.
  • A new field appears on an existing response.
  • A new optional field is accepted on an existing request.
  • A new error code appears for a condition that previously used a broader one.

So parse leniently: ignore response fields you do not recognize, do not validate responses against a closed schema, and do not treat an unknown error code as a parsing failure — fall back to the HTTP status. A change that would break a conforming client requires a new major version in the URL, published with migration guidance; version 1 is not retired underneath you.

Each published operation also carries a lifecycle stage in its approval record — stable, beta, or deprecated. Depend on stable operations for anything you cannot easily change; treat beta as subject to revision and deprecated as scheduled for removal.

The failure envelope

Every documented failure returns application/problem+json (RFC 9457) with a flat body:

Field Always present Meaning
code Yes The stable machine identifier. This is what your code branches on.
status Yes Mirrors the HTTP status code.
title Yes Short human summary of the problem type.
detail Yes A user-safe explanation. Display it if you must; never match on it.
type Yes A URL identifying the problem type. An identifier to match, not a link.
correlationId Yes The request identifier to quote to support.
retryable Yes Whether this condition is transient. Derived from code, never from text.
retry_after No Seconds to wait before retrying, when a meaningful delay exists.
errors No Field-level validation problems. Present on validation failures.
params No Extra primitive detail about the condition, such as a limit that was hit.
{
  "type": "https://errors.steerphones.example/validation-error",
  "title": "Validation error",
  "status": 400,
  "code": "VALIDATION_ERROR",
  "detail": "The request contains invalid data.",
  "correlationId": "9f3ab21c",
  "retryable": false,
  "errors": [{ "pointer": "/name", "code": "REQUIRED", "detail": "This field is required." }]
}

detail is user-safe by construction: internal messages, stack traces, and identifiers of other tenants never reach it. That also means it is a poor branching key — it is written for a person.

Field-level validation errors

A validation failure returns 400 with code: "VALIDATION_ERROR" and an errors array. Each entry names the offending field by JSON Pointer into the request body, along with a per-field code:

Field code Meaning
REQUIRED The field was absent.
TOO_SMALL Below the minimum allowed.
TOO_BIG Above the maximum allowed.
INVALID_FORMAT Present but wrongly formatted.
INVALID Present but not an accepted value.

Pointers let you highlight the exact input a person got wrong instead of showing a whole-request failure — /eventTypes points at that member of the body you sent.

Codes worth handling

Code Typical status What to do
VALIDATION_ERROR 400 Fix the request. Use errors to say which field.
AUTH_UNAUTHORIZED, AUTH_INVALID_TOKEN, AUTH_EXPIRED 401 Check the key is present, current, and not revoked.
AUTH_FORBIDDEN 403 The key lacks the scope, or is for another phone system.
NOT_FOUND 404 The record does not exist, or is outside this phone system.
CONFLICT 409 The request contradicts current state. Re-read, then decide.
RATE_LIMITED 429 Back off and retry after retry_after.
EXTERNAL_SERVICE_TIMEOUT 502 A dependency timed out. Retry with backoff.
EXTERNAL_SERVICE_ERROR 502 or 503 A dependency is unavailable. Do not hammer it; alert instead.
INTERNAL_ERROR 500 Capture correlationId and contact support.

Some conflicts carry a more specific code than CONFLICT so a client can react precisely rather than parsing a message. Handle the specific codes you know, and fall back to the status for the rest.

Retry safely

retryable is computed from the error code, so it never drifts from the message text. Only genuinely transient conditions — rate limiting and dependency timeouts — are marked retryable. A validation failure, a permission failure, or a conflict will fail identically no matter how often you resend it.

For writes, one more rule applies: do not retry a write unless the published reference for that operation states that retrying is safe. A timeout means the request may have succeeded, so blind retries can duplicate work. Where the reference documents no retry guarantee, re-read the state and decide from there.

Use exponential backoff with jitter, honour retry_after when present, and cap total attempts so a sustained outage does not become an unbounded queue in your own system.

Rate limits

Requests are counted per API key. Every response carries the current window:

Header Meaning
X-RateLimit-Limit Requests allowed in the current window.
X-RateLimit-Remaining Requests left in the window.
X-RateLimit-Reset Unix time, in seconds, when the window resets.
Retry-After Seconds to wait. Sent with a 429 response.

Over the limit, you get 429 with code: "RATE_LIMITED" and a retry_after value. The exact allowance depends on the tier your key was issued with; Steer Phones tells you the rate when the key is issued, and can raise it for an integration with a legitimate need. Design for the headers rather than a hard-coded number — read X-RateLimit-Remaining and slow down before you are refused.

Spread bulk work over time instead of firing it in parallel bursts, and back off on 429 rather than retrying immediately.

When you contact support

Every response carries an X-Request-Id header, and every problem body repeats it as correlationId. Quote that value, the approximate time, and the operation you called. It identifies your exact request in Steer Phones’ records without exposing your key or the request body, so it is the fastest route to an answer — and it is safe to paste into a ticket.