Skip to content

GuidesAccess and rate limits

Getting started

Access and rate limits

Tiers

PublicSelf-serve keyPartner key
Sign-upNone"Get an API key": sign a message with your walletIssued (or raised) by the operator
LimitRATE_LIMIT_PER_MIN per IPPublic tier, shared with your IPIts own allowance, per key
Usage tracking—✅ GET /v1/keys/me✅

Below, "partner key" covers both operator keys (API_KEYS) and self-serve keys the operator raised.

PublicPartner key
Sign-upNoneKey issued by the operator
HowJust call the APIAuthorization: Bearer <key> header
EndpointsAllAll
LimitRATE_LIMIT_PER_MIN requests/minute per IP (default 120)Its own allowance per key, set by the operator (default 10× public)
Use fromBrowsers, scriptsYour backend only

⚠️ Never ship a partner key to a browser or a mobile app. Anyone can read it there and spend your allowance. Call the API from your server, or use the public tier from the client.

Sending a key

curl -H "Authorization: Bearer pk_live_abc123" "https://api.lane.markets/v1/health"
  • No Authorization header → public tier.
  • A valid key → that key's allowance, counted per key (shared across all your servers).
  • An unknown key → 401 {"error":"invalid API key"}. The request is not silently downgraded to the public tier, so a typo shows up immediately. Wrong keys count against the caller's IP public limit, so keys can't be brute-forced.

Getting a key yourself ("Get an API key")

No email or password: the key belongs to a wallet.

const { message } = await fetch(`${API}/keys/challenge?address=${address}`).then((r) => r.json());
const signature = await walletClient.signMessage({ account: address, message }); // not a transaction, costs nothing
const { key } = await fetch(`${API}/keys`, {
  method: "POST", headers: { "content-type": "application/json" },
  body: JSON.stringify({ address, signature, label: "my bot" }),
}).then((r) => r.json());
// store `key` now: it is shown once
  • Smart-contract wallets work too (ERC-1271/6492 signatures).
  • A challenge is valid 10 minutes and works once. A wallet can hold 3 active keys; one IP can create 5 keys a day.
  • GET /v1/keys/me (with the key) shows its limit and daily usage over 30 days; DELETE /v1/keys/me revokes it.
  • Why the public tier? Wallets are free, so a self-serve key can't add capacity by itself (anyone could create hundreds). It identifies you and tracks your usage; ask the operator for more and they raise it with pnpm keys:limit <prefix> <perMinute>, which gives the key its own bucket.

Rate-limit headers

Every rate-limited response carries:

HeaderMeaning
x-ratelimit-limitRequests allowed per minute for this caller
x-ratelimit-remainingLeft in the current window
x-ratelimit-resetSeconds until the window resets
retry-afterOn 429 only: seconds to wait

GET /v1/health is exempt from rate limits, so load balancers and monitors can poll it.

When REDIS_URL is set, counters are shared by every API instance, so the limit holds across a load balancer.

Issuing keys (operators)

Keys are configured with the API_KEYS environment variable: comma-separated key:perMinute entries. perMinute is optional.

# partner A: 2,000/min; partner B: default (10 × RATE_LIMIT_PER_MIN)
API_KEYS=pk_live_partnerA_9f3c...:2000,pk_live_partnerB_71aa...

Keys must be at least 24 characters (the API refuses to start otherwise). Generate them with openssl rand -hex 24. Restart the API to apply changes. To revoke a key, remove it and restart: it gets 401 from then on.

Request ids

Every response carries an x-request-id header, and error bodies repeat it as requestId. Send your own x-request-id (letters, digits, _, ., -, up to 64 chars) and the API reuses it, so your logs and the server's logs share one id. Quote it when reporting a problem.

Next: Quickstart

lane.markets · Make the most of every move.