Reference Markdown

Errors

Every error SkimmerDB answers with: its shape, each status and code, what to do, and which requests are safe to retry.

Every error has the same shape, whatever the status:

json
{"error": "password is too short (at least 8)", "code": "invalid", "fields": {"password": "is too short (at least 8)"}}
FieldWhat
errorWhat went wrong, in a sentence
codeA word to act on (below)
fieldsWith invalid input: each field, and what's wrong with it
retry_afterWith write_busy: seconds to wait (also the Retry-After header)

Branch on code, not on error's wording, which may change. The OAuth endpoints answer with OAuth's own error shape, and /mcp with JSON-RPC errors.

Codes

StatusCodeMeaningWhat to do
400bad_requestThe request is malformed: not JSON, a missing bodyFix the request
401unauthorizedNo key, or a bad, revoked or expired oneSend Authorization: Bearer <key> with a live key
402payment_requiredThe free plan's allowance for this period is used up (the message says which)Wait for the next period, or upgrade at skimmerdb.com/pricing
402namespace_limitThe plan's namespace count, or the namespaces it may make in a dayDelete namespaces, or upgrade
402email_unverifiedA free tenant whose owner hasn't verified their email is past its reduced allowanceVerify the email (the signup email's link, or a password reset)
403forbiddenThe key's scope or namespaces don't allow this, or the tenant is suspendedUse a key with the right scope
404not_foundNo such namespace, document or key, or one this key can't seeCheck the name; a write makes a namespace, a delete doesn't
409conflictIt exists already (a namespace name), or a linked namespace refused a writeUse another name; linked namespaces are read-only
413too_largeThe body, a document or a query's reads are over a limitSplit it; see Limits
422invalidThe input is understood but wrong: the message (and fields) says whyFix the input
422query_timeoutA count or SQL query ran past the plan's time limitNarrow it (a filter, a smaller window)
422scan_refusedA regex the index can't narrow, on a big namespaceAdd a literal to the pattern, or send "scan": true
429rate_limitedOver the tenant's or the key's requests a second, requests in flight, SQL queries at once, or a key's capWait Retry-After seconds and retry
429write_busyWrites are arriving faster than the namespace commits them. Nothing was writtenWait retry_after seconds and resend the same batch
500internalA bug on our sideRetry; tell us if it persists
503unavailableObject storage failed. Nothing was writtenRetry
503write_unknownThe write may or may not have been appliedRetry it: writes are by id, so a retry is safe
422embedding_key_missingThe namespace embeds with a provider this tenant has no key forStore one (PUT /v1/embedding-keys/{provider} or the dashboard), or send vectors
422embedding_key_invalidThe provider refused the tenant's keyStore a working key
422embedding_rejectedThe provider refused the request (an unknown model, say)Check the model
409embedding_dimsThe model can't make vectors of the namespace's lengthAnother model, or a new namespace
429embedding_rate_limitedThe provider is rate-limiting the key; retry_after says whenWait, then retry: nothing was written
502embedding_failedThe embedding provider failedRetry: nothing was written, and writes are by id
507quota_fullThe tenant's storage quota is fullDelete documents or namespaces, or upgrade

A search that hits its time limit doesn't fail: it answers what it found with "partial": true and "timed_out": true. A search whose object-storage reads failed answers "partial": true. Treat partial answers as incomplete, never as "nothing else matched".

Retrying

  • Safe to retry as is: every read; writes, updates and deletes by id (POST .../docs, PATCH .../docs, POST .../delete) after a 429, a 503 or a timeout, since writing the same ids again gives the same result; imports with an id column.
  • Wait first: every 429 carries Retry-After in seconds. Back off for at least that long.
  • Don't retry unchanged: 400, 401, 403, 404, 409, 413 and 422 (except query_timeout, which may pass on a warmer namespace or a narrower query).
  • Creating a namespace (POST /v1/namespaces) answers 409 if a retry finds it made already: treat that as success.

The Python and TypeScript clients retry 429 and 503 for you, honouring Retry-After.

Rate limit headers

Answers that count against a rate (namespaces, documents, search, count, SQL, import, MCP) carry:

HeaderMeaning
RateLimit-LimitRequests allowed in the window: the tighter of the tenant's plan and the key's own rate_per_s
RateLimit-RemainingRequests left in the window
RateLimit-ResetSeconds until the window resets
RateLimit-PolicyThe limit and its window, e.g. 100;w=1 (one-second windows)
Retry-AfterOn a 429: seconds to wait

These follow the IETF RateLimit headers draft (07). Limits are per server: see Limits and plans.