Errors
The edge API returns conventional HTTP status codes with a JSON body. Two are worth understanding in depth, because they encode Surfil's safety guarantees: a 402 never writes, and a 503 fails closed.
Status codes
400- bad request: invalid id or malformed JSON.401- missing or invalid credential (device token or Supabase session).402- insufficient credits on meter/consume. Zero writes - the balance is untouched, so a retry after topping up is safe.404- not found, or not yours: another tenant's resource is an indistinguishable 404 by design.405- method not allowed on that path.503- fail-closed: signing is unavailable, so no decrement or receipt is produced. Retry with the same idempotency key is safe.
Idempotency and safe retries
Every state-mutating request carries an idempotency key. A retry with the same key replays the stored result rather than repeating the effect - a duplicated consume never double-charges. This is why a 503 mid-operation is recoverable: retrying finishes the exact operation once, never twice.
Crypto outcomes are data, not errors
POST /v1/verify returns 200 even when a signature is invalid - the verification result (valid / tampered) is data in the body, not a transport error. A bad signature is an answer, not a failure to answer.
CLI exit codes mirror these semantics and are finalized in
surfil <command> --help. See the CLI reference.