Keys

Secret API keys (c8n_sk_ followed by 43 base64url characters) are made in the hub dashboard (proxy-admin.infinimesh.net for the test network), and revoked there too: the API has no key routes. A server sends its key in x-api-key on every call (Authentication). Every key of your organization reaches the same organization: the same order history, the same status reads, and the same rate limits. A key you revoke, or one past its expiry, is refused from then on: EPX0002 (401).

Keep keys secure

  • Keep secret keys on your servers. Never ship one to a browser, a mobile app or a repository: whoever holds it acts as your organization, and can read its whole order history.
  • A web page uses your public project id, or calls your backend, which holds the key (Authentication — project ids). The gateway never takes a key from a browser.
  • Give each server or environment its own key, so that revoking one stops only that caller.
  • Rotate by make-then-revoke: make the new key in the hub dashboard, move the caller to it, then revoke the old one.
  • A key that leaked: revoke it in the hub dashboard at once, then look through your organization’s orders (GET /orders, under each subject you use) for any you did not place.

Rate limits

Two layers limit your traffic: the gateway, which every call reaches first, and the hub behind it.

The gateway

  • The pools are separate. Your project id’s traffic has its own pool, so a busy frontend never spends the pool of your server keys.
  • A refusal carries its back-off. EPX0080 and EPX0082 carry it twice: in error_params[0], in milliseconds, and in a Retry-After header, in seconds. EPX0081 carries it in Retry-After only: the seconds left in the lockout. Wait that long, then retry. The SDK exposes it as HubError.retryAfterMs (SDK errors).
  • Every call whose credential is accepted reports the budget left in three headers: RateLimit-Limit (the bucket’s size), RateLimit-Remaining (the calls left in it right now) and RateLimit-Reset (the seconds until it is full again). For a project id they describe whichever of its two buckets is tighter. A page can read them across origins, with X-Request-Id and Retry-After.
  • Visitors behind one shared IP, such as an office or a mobile carrier, share one visitor bucket.
  • Only refused credentials count toward EPX0081, a call with no credential included; a valid key or project id never does. During a lockout, though, every call from that IP is refused, so a server whose key is refused must stop, not retry.

The hub

The hub then applies its own limits. It counts your organization as one tenant: every call your organization makes, with a key or with its project id, draws on the same buckets. A request is charged after it has validated and before any work: one that fails validation costs nothing, while one the service refuses afterwards, or answers as an idempotent duplicate, has already been charged. A caller idle for 600 s loses its buckets.
The hub’s values are its defaults; the production hub’s values are those of its deployment configuration. The gateway’s pool sizes are set for your organization.

EIN0080 versus EIN0081

EIN0080 is the hub’s per-organization bucket. error_params[0] is the back-off in milliseconds: wait that long, then retry. The SDK exposes it as retryAfterMs on the HubError (SDK errors). EIN0081 is an admission shed with several causes: the in-flight quote cap, the hub’s outbound limiter to the upstream not admitting a forward in time, or the upstream’s own 429. Back off, then retry. On POST /order/openfor, an EIN0081 caused by the upstream’s 429 keeps the reservation, so a re-post answers EIN0052 until an operator reconciles it; the other causes sent nothing, and a re-post after a back-off is safe (Submit an order (openFor)).

Stay under the limit

  • Fetch the catalog once and cache it. GET /catalog changes only on restart.
  • Browse with preview and debounce it. Pause POST /quote/preview while a wallet prompt is open: a preview the user cannot act on yet is budget spent for nothing.
  • Prepare once on confirmation. One POST /quote/prepare per deliberate signing attempt, not per render.
  • Poll status every 2–3 seconds, and stop at a terminal state. Both status views are cached for 2 seconds server-side, so polling faster buys nothing. The stop set per view is on Order lifecycle.
  • Meter your own users. Everyone behind your backend shares your organization’s pool at the gateway and its buckets at the hub: rate-limit each of your users yourself before forwarding.
  • Stop on a refused key. Retrying a refused key locks your server’s IP out of every call for the rest of the minute (EPX0081).
  • Prefer the SDK. It classifies every failure for you: HubError.retryable says whether a retry can help, and on a rate limit retryAfterMs says how long to wait (SDK errors).
There are no webhooks or websockets: polling is the only channel.

See also