Authentication and errors

Authenticate with an API-scoped key via X-Api-Key or Bearer, understand the two scopes (API versus Ingest), tell 401 from 403, read the full error code table with troubleshooting, and handle 429 rate limiting with a Retry-After backoff example.

public-apiauthenticationapi-keyscopeerrors401403429rate-limitretrybackoff

How authentication works

Every endpoint except GET /v1/health requires an API key. Send it one of two ways — pick whichever suits your client:

# Header form 1
X-Api-Key: ta_live_xxxxxxxxxxxxxxxxxxxxxxxx

# Header form 2 (equivalent)
Authorization: Bearer ta_live_xxxxxxxxxxxxxxxxxxxxxxxx

Both are checked on every request; you never need to send both. There is no login step, no token exchange, and no session — the key is the credential.

The two scopes

An API key carries one or more scopes, set when it is created under Settings -> API Keys (an administrator action):

Scope Grants Public API access?
API Access to the Public API described in these docs. Yes
Ingest Pushing telemetry through agents and collectors. No

To call the Public API, the key must have the API scope enabled. The Ingest scope alone is not enough.

401 versus 403

These two are the most common stumbling blocks, and they mean different things:

Status Code Cause Fix
401 unauthorized No key was sent, or the key is wrong, revoked, or expired. Check the header name and value. Confirm the key still exists in Settings -> API Keys.
403 forbidden The key is genuine but does not carry the API scope. Edit the key in Settings -> API Keys and enable the API scope, or create a new key with it.
Note: In short: 401 is about the key's identity (we do not recognize it) and 403 is about the key's permission (we recognize it, but it may not do this).

The full error table

Every non-2xx response has the same shape:

{"error": {"code": "<string>", "message": "<human readable>"}}
Code Status What it means What to do
bad_request 400 A field is missing, the wrong type, or out of range (for example a step without an agg). Read the message; fix the request. Do not retry unchanged.
unauthorized 401 Missing or invalid API key. Fix the key or header. Do not retry unchanged.
forbidden 403 Valid key, but it lacks the API scope. Enable the API scope on the key. Do not retry unchanged.
not_found 404 The requested resource does not exist for your organization. Check the id or path. Do not retry unchanged.
rate_limited 429 Per-minute rate limit exceeded. Wait Retry-After seconds, then retry.
internal_error 500 An unexpected error on our side. Retry with exponential backoff.
unavailable 503 The service is temporarily unavailable. Retry with exponential backoff.

Rate limiting and 429 in depth

Each key has its own per-minute budget. When you exceed it, you receive:

HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json

{"error": {"code": "rate_limited", "message": "rate limit exceeded"}}

The Retry-After value is a whole number of seconds. Honor it: sleep for at least that long before retrying. A robust client combines Retry-After with exponential backoff and a cap on attempts:

# Bash: retry a POST on 429 and 5xx with backoff, up to 5 attempts
attempt=0
while :; do
  attempt=$((attempt+1))
  resp=$(curl -s -o body.json -w "%{http_code}" \
    -X POST https://api.verops.io/v1/custom/metrics \
    -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
    -d @payload.json)
  case "$resp" in
    2*) echo "ok"; break ;;
    429|5*)
      [ "$attempt" -ge 5 ] && { echo "giving up"; break; }
      sleep $((2 ** attempt)); ;;    # 2s, 4s, 8s, 16s ...
    *) echo "client error $resp"; cat body.json; break ;;  # 4xx: do not retry
  esac
done

To avoid hitting the limit in the first place: batch your writes (up to 1000 items per call — see Send custom data), poll read endpoints on a sensible interval rather than in a tight loop, and spread bursts out over the minute.

Best practice: Retry only 429 and 5xx, and always with backoff. Retrying 400, 401, or 403 just repeats a request that cannot succeed until you change something — it wastes your rate budget and delays the real fix.