vike
API Endpoints

Errors

The HTTP status codes every Vike data surface returns, the JSON error envelope, and the one endpoint where an auth failure is not an HTTP error at all.

Every Vike data surface answers failures with an HTTP status code, and usually a JSON body. The status code is the part you should branch on; the body shape varies by which hop rejected you.

Status codes

CodeMeaning
200Success — and an MCP auth failure. See the MCP exception.
400Your input was rejected: an unknown market, a malformed identifier, both identity forms at once.
401Invalid or missing credential — on /v1 and on the archive alike.
404No such route or file: a missing archive object, or a path that matches no endpoint.
429Hourly rate limit exceeded. Carries Retry-After.
503An upstream data-store failure. Not your fault; retry.

Two properties worth relying on:

  • A failed query is never a 200 with an empty body. An upstream failure comes back as a 503 rather than an empty list, deliberately — a silent empty result is indistinguishable from "no data", which is how bad backfills get built on top of an outage.
  • There is no 403 today. There are no scopes to be refused: every valid credential has the same reach across the whole API. The code exists in the error vocabulary for the day one appears.

The JSON envelope

The API emits one envelope shape:

Error envelope
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Hourly rate limit exceeded",
    "retry_after": 60
  }
}

retry_after appears only on 429. The archive's own 401 carries a docs field instead:

Archive 401
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "invalid or missing credential — see https://vike.io/docs/data/ for how to authenticate",
    "docs": "https://vike.io/docs/data/"
  }
}

Branch on the status code, not the body

The envelope above is what the API emits. It is not always what reaches you.

A credential rejection on /v1 or on the archive is produced ahead of the API — the request is turned away before anything can build that envelope — so its body is not the shape above. Measured on 2026-09-02, a keyless request to a /v1 path or to https://data.vike.io/archive/ returns a correct 401 whose body is a stock HTML error page. Do not assume a 401 carries JSON.

The status code and the Retry-After header are correct on every path and always have been. A client that parses the body to decide what happened will break on a hop it did not expect; a client that reads the status code will not.

429 and Retry-After

A rate-limit trip returns 429 with Retry-After: 60 on every surface. Every layer that can return one quotes the same 60, deliberately, so two hops cannot disagree.

The value is an advisory back-off hint, not a promise. The hourly window slides continuously and the counter refreshes on its own schedule, so "when will capacity exist" has no per-request answer anyone stores. Sixty seconds is a sane hint chosen over a false precision.

What a client should do:

Retry on 429
r = requests.get(url, headers={"X-API-Key": key})
if r.status_code == 429:
    time.sleep(int(r.headers.get("Retry-After", 60)))
    # ...then retry. Do not retry immediately, and do not retry in a tight loop:
    # a rate-limited request is itself metered.

A rejected request is recorded and counted like any other, so retrying without backing off keeps you rejected. Almost every 429 in practice has one cause — see Rate limits, particularly the section on Range requests against the archive.

MCP auth failures are HTTP 401

https://vike.io/mcp speaks JSON-RPC 2.0, and both failure modes carry a JSON-RPC error object in the body as well as an HTTP status:

FailureHTTP statusBody
Invalid or missing credential401 + WWW-AuthenticateJSON-RPC error object, code -32000
Hourly rate limit exceeded429 + Retry-AfterJSON-RPC error object, code -32000

An unauthenticated call returns this, with HTTP 401 and WWW-Authenticate: Bearer resource_metadata="https://vike.io/.well-known/oauth-protected-resource" (the MCP authorization spec — an OAuth client starts discovery from that URL):

MCP 401
{"jsonrpc": "2.0", "id": null, "error": {"code": -32000, "message": "Invalid or missing API key"}}

A rate-limited call returns HTTP 429, and puts the envelope's fields in the JSON-RPC error's data member so both kinds of client can read it:

MCP 429
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32000,
    "message": "Hourly rate limit exceeded",
    "data": { "code": "RATE_LIMITED", "retry_after": 60 }
  }
}

Other JSON-RPC errors (-32700 parse error, -32601 unknown method or tool) still arrive at HTTP 200, so if you are writing a raw HTTP client against /mcp, checking response.ok is not enough — you must also look for an error member in the body.

Getting a 401 you did not expect

Three causes, in rough order of likelihood:

  • The header name. The archive and /v1 want X-API-Key. An OAuth token goes in Authorization: Bearer. A key in a query string is never read.
  • The key is inactive or was deleted. Deletion is a soft delete; an inactive key authenticates no better than a deleted one.
  • You just changed something. Credential decisions are cached for up to 45 seconds (positive) and 8 seconds (negative), so a newly fixed key can take a few seconds to start working and a revoked one a few seconds to stop. See Authentication.

On this page