Get started

Authentication

Every /v1 request carries a project API key. Keys are scoped, hashed at rest, bound to one network, and never belong in a browser.

Send your key

Use the Authorization header with the Bearer scheme (X-API-Key also works). Requests without a valid key get 401 UNAUTHENTICATED.

import { FomoPay } from "@fomopay/sdk";

const fp = new FomoPay({ apiKey: process.env.FOMOPAY_API_KEY }); // sent as "Authorization: Bearer …"

GET /v1/me tells you which project, mode and scopes a key has — handy as a startup check. It never echoes the key.

200 OK
{
  "project": { "id": "proj_4d6f8h0k2m4p6r8t0v2x", "name": "FomoRun", "status": "ACTIVE" },
  "key": {
    "id": "key_7q9s1u3w5y7a9c1e3g5j",
    "name": "game server",
    "mode": "live",
    "scopes": ["rewards:create", "payments:read"],
    "rate_limit_per_min": 120
  },
  "chain": { "key": "robinhood", "id": 4663, "name": "Robinhood Chain" }
}

Test and live keys

PrefixWorks againstFunds
fp_test_…The local development chain (anvil, chain id 31337) — the FomoPay local stackLocal test ETH and a local 6-dp USDG at the real USDG address. No real value.
fp_live_…Robinhood Chain mainnet (chain id 4663)Real ETH and USDG. Live keys are only issued once a deployment has mainnet enabled.

A key used against the wrong network is refused with 403 FORBIDDEN — a test key can never move mainnet funds, and a live key never touches a test deployment.

Scopes

Give each key only what it needs. A game server that pays winners needs rewards:create (and payments:read to check status) — not webhooks:write.

ScopeAllowsEndpoints
payments:createCreate wallet (checkout) and vault payments, bulk batchesPOST /v1/payments · /v1/payments/bulk
payments:readRead payments, list them, a profile's payments from this projectGET /v1/payments… · /v1/profiles/@handle/payments
rewards:createVault-funded rewards — what fp.send() usesPOST /v1/rewards
profiles:resolveResolve a handle to its identity state (no addresses)GET /v1/resolve/@handle
vaults:readVault balance, allowance and availabilityGET /v1/vaults…
webhooks:writeManage webhook endpoints and rotate secrets/v1/webhooks…

A missing scope is a 403 INSUFFICIENT_SCOPE listing what was required:

403 Forbidden
{
  "error": {
    "code": "INSUFFICIENT_SCOPE",
    "message": "Missing scope: rewards:create",
    "request_id": "req_9h2k4m6p8r0t2v4x",
    "details": { "required": ["rewards:create"] }
  }
}

How keys are stored

  • Shown once. The raw key is displayed when you create it and never again. Lose it → create a new one and revoke the old.
  • Hashed. FomoPay stores HMAC-SHA256(server pepper, key) plus a short display prefix (fp_live_Ab12) — never the key itself, so a database leak doesn't leak working keys.
  • Revocable and expirable. Revoke from the dashboard at any time; revoked keys fail immediately. A key also stops working when its project is disabled.
  • Audited. Key creation and revocation are written to the project audit log; every API call lands in the request log (method, endpoint, status, latency, request id — never bodies or secrets).
Server-side only

Never ship an API key to a browser, mobile app or public repo. The SDK refuses to run in a browser. If a key leaks, revoke it in API → Keys — FomoPay will never ask you for it, nor for any private key or seed phrase.

Rate limits

Limits apply per key (default 120 requests per minute, configurable per key) and per endpoint bucket. Resolution has extra anti-harvesting caps: a key can only look up a bounded number of distinct handles per day, because resolve exists for payments — not for building a directory.

Rate-limited endpoints answer with X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A refusal is 429 RATE_LIMITED with Retry-After (seconds); the SDK waits and retries with the same idempotency key.

429
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 12

{ "error": { "code": "RATE_LIMITED", "message": "Too many requests. Try again in 12s.", "request_id": "req_…",
  "details": { "bucket": "payments", "limit": 120, "window_sec": 60, "retry_after": 12 } } }

Request ids

Every response has an x-request-id header; error bodies repeat it as error.request_id and the SDK exposes it as err.requestId. Include it when you contact support — it finds the exact request in the logs.