For the complete documentation index, see llms.txt. This page is also available as Markdown.

Errors

Every error code the API can return, what triggers it, and how to fix it.

Every Millimetric error is a JSON response with a stable string error field and an HTTP status code. The string is the contract — the human-readable message is not.

{
  "error": "invalid_payload",
  "details": { "fieldErrors": { "event": ["Required"] } }
}

Auth errors (401 / 403)

Status

error

Trigger

Fix

401

missing_bearer_token

No Authorization header.

Send Authorization: Bearer {key}.

401

malformed_api_key

Key doesn't match `(pk

sk

401

invalid_api_key

No matching key in Supabase, or HMAC doesn't verify.

Mint a new key. Old one was revoked or never existed.

401

key_kind_mismatch

Stored key has a different kind than the prefix claims.

Re-mint. Likely a copy-paste from another row.

401

invalid_session

Admin endpoint: user JWT failed Supabase validation.

Sign in again.

403

origin_not_allowed

pk_* key from an origin not in the project's allowed_origins.

Add the origin in the dashboard, or use sk_* server-side.

403

insufficient_scope

Read endpoint called with pk_*/sk_*, or write endpoint with rk_*.

Use a key with the right scope: pk_/sk_ for ingest, rk_ for read.

403

forget_requires_secret_key

/v1/forget called with pk_*.

Use sk_*. Browser keys are explicitly rejected to prevent leak-induced wipes.

Validation errors (400)

Status

error

Trigger

Fix

400

invalid_payload

Body failed Zod parse. details.fieldErrors is the flattened tree.

Inspect details. Common causes: event missing, properties > 8 KB, malformed url.

400

invalid_params

Query string failed validation.

Check from/to are ISO-8601, metric is one of count/uniques.

400

invalid_group_by

Unknown column passed to /v1/stats?group_by=. Response includes the allowed list.

Use only: event_name, source, medium, country, device_type, browser, os, path.

Rate limits (429)

Status

error

Trigger

Fix

429

rate_limited

Token bucket exhausted for this (project_id, route). Retry-After header present.

Back off Retry-After seconds, then retry. For sustained throughput, switch to /v1/batch.

Limits:

Endpoint
Refill
Burst

POST /v1/track

50/sec

200

POST /v1/batch

5/sec

20

(Per-Worker-instance today. Move to a Durable Object if needed.)

Server errors (5xx)

Status

error

Trigger

Fix

500

internal_error

Unhandled exception. Worker logs have the trace.

Retry once with jitter. If persistent, check wrangler tail.

500

forget_failed

ClickHouse rejected the ALTER TABLE … DELETE.

Worker logs. Likely a transient ClickHouse issue.

502

upstream_failed

Couldn't reach ClickHouse / Supabase.

Same as above — retry.

MCP-specific errors (JSON-RPC)

The MCP transport uses JSON-RPC error envelopes:

Code
Meaning

-32600

invalid_request — body not a valid JSON-RPC envelope.

-32601

method_not_found — unknown method.

-32602

unknown_tool / unknown_resource / unhandled_tool.

-32000

tool_failed (server error) / insufficient_scope.

Retry guidance

Error class
Retry?
How?

4xx (validation, auth, scope, origin)

No

The payload is wrong. Fix it.

429 rate_limited

Yes

Honour Retry-After. Use /v1/batch for sustained writes.

5xx

Yes

Exponential backoff. The Node SDK does 100/200/400/800 ms by default.

Network errors

Yes

Same as 5xx.

Worker logs

The HTTP response only ever returns a short error code. To see the full traceback:

Every error logs the request path, the project (when known), and the failure point.

See also

Last updated

Was this helpful?