cryptoinfraDocs

#Authentication

Every /v1 endpoint is authenticated with a single credential: your tenant API key, presented as a bearer token.

Open as MarkdownFor AI assistants
bash
curl https://api.cryptoinfra.build/v1/assets \
  -H 'Authorization: Bearer ck_live_your_key'

There is no OAuth flow, no per-user token, and no signature on outbound requests. One key, one header, on every call.

Your own dashboard talks to the same API with a session cookie instead of the key. A few actions that need a second factor — rotating the key, inviting teammates, approving parked payouts, moving your own balance — exist only there, and answer 403 session_required to an API key. Within the dashboard, 403 forbidden_role means your login's role does not cover that action.

#The key authenticates you, not your user

This is the part that most often goes wrong in a first integration.

The key identifies the merchant. It is scoped to your tenant, and it grants full access to every user, balance and payout under that tenant. An external_user_id in a path or a body is only a selector — it carries no authority, and passing a different one does not reduce what the caller can do.

#Key format and lifecycle

Keys look like ck_…. You mint and rotate your own from the dashboard (Settings → API keys), and the plaintext is shown exactly once — the platform stores only a hash, so a lost key is replaced, never recovered. Rotating needs your authenticator code and the code we email you, like every change made on the dashboard; there is no way to obtain a key without both. Signup does not hand you a key: generate it from the dashboard once your account is set up.

A missing, malformed or unknown key gets the same answer, deliberately:

json
{ "error": "unauthorized" }

with HTTP 401. The response never distinguishes "no key" from "wrong key", so an attacker learns nothing from probing.

A key that is real but whose account is not live answers 403 with a code that says why, because there is nothing to hide from its owner:

CodeMeaningWhat to do
email_unverifiedThe owner login has not verified its emailOpen the link in your inbox, or ask for a new one from the sign-in page
account_pausedAn operator put the account on holdSign in; the dashboard says why. Nothing to retry
account_suspendedThe account was closed by the operatorWrite to support

All three answer every call on the key until the state changes on our side; nothing in the request will fix them.

Rotating is one step: mint the new key on the dashboard, deploy it, confirm traffic is flowing on it. The old key stops working the moment the new one is minted — there is no overlap window — so have the new key ready to deploy, rotate during a window you can watch, and expect 401 from anything still on the old key until you do. Every owner login is emailed when the key changes, whether a teammate rotated it or the operator did.

#Your account and your team

An account is active from signup — there is no approval step. The operator can put it on paused hold, or close it as suspended. A paused account keeps its dashboard: you can read, register webhooks and turn the key while the hold lasts — not create users, move money, open a card programme or invite teammates — and the key answers 200 again the moment it is lifted. A suspended account reaches nothing. Before any of that comes the email: an owner login that has not verified its address cannot sign in and cannot see, generate or rotate the API key (403 email_unverified), and a key on such an account answers the same.

Dashboard logins carry a role — owner, finance, developer or viewer. The API key is generated, rotated and inspected (rotated_at) by owner and developer logins only; money out and payout approvals are owner and finance; the team itself is owner-only. A login outside the role gets 403 forbidden_role.

#Signing in to the dashboard

The dashboard is a separate credential from the API key: email, password, and an authenticator app you enrol at first sign-in. Every change made there — a payout, a swap, a key rotation, a teammate, an approval — asks for the authenticator code and a 6-digit code sent to your email, bound to that action and good for ten minutes. Security notices for every change to your login (password, authenticator, email, keys, teammates, access) are emailed and cannot be turned off.

#What is outside the auth scope

4 operations are declared security: [] in the contract — they take no API key at all. They fall into three groups.

EndpointSummary
GET /v1/a/{token}PUBLIC — a merchant's affiliate ledger
GET /v1/pricingPUBLIC — the live price sheet
GET /healthzLiveness
GET /readyzReadiness

GET /v1/a/{token} — a merchant's public affiliate ledger. The page behind the link on your Profit page (/a/{token} on the merchant dashboard's origin). There is no key because there is no merchant on the reading side: the token is 256 bits and holding it is the authorization to read that one ledger — your share of our fee, total and line by line, with our own reference ids and nothing about your customers. Treat the link as you would any share link: hand it to whom you want, and replace it from the dashboard (owner, both codes) if it gets around; the old one stops working at once.

GET /v1/pricing — the public price sheet. Every price the platform charges, read live and cached for a minute, with no key: the documentation renders its tables from it, and so can your own pricing page. It carries the platform's schedule only — your own rates, if your account has been given any, are on GET /v1/fees with your key.

The webhooks cryptoinfra sends to you are not an endpoint of ours at all: you register your own URLs under POST /v1/developer/webhooks and each delivery is signed with that endpoint's HMAC secret. See Webhooks.

/healthz and /readyz — liveness and readiness. Unauthenticated by design so an uptime monitor can reach them. They carry no tenant data.

#Failure modes worth handling

SymptomCauseWhat to do
401 unauthorized on every callKey missing or wrongCheck the header spelling and the Bearer prefix; if the key is lost, generate a new one from the dashboard
403 email_unverified / account_paused / account_suspended on every callYour account is not liveNothing to fix in the request; verify the email, or sign in to the dashboard to see the state
403 session_required / forbidden_roleThe action needs a dashboard login, or a different role on itDo it from the dashboard with an owner (or the named role) login
401 only on some callsYou are hitting a second environment with the wrong keyKeys are per-tenant and per-environment; they are not interchangeable
404 not_found where you expected 403The record belongs to another tenantBy design — cross-tenant reads are indistinguishable from "does not exist"

That last row is a deliberate property of the API, not a bug: an unknown id and another merchant's id return the same 404, so you cannot enumerate anyone else's records.

#Seeing your own traffic

GET /v1/developer/logs returns the method, route, status and duration of your recent calls, kept for 14 days. It records no request bodies and no query strings, so it is safe to read while debugging an integration — and it is the fastest way to confirm whether a call reached the platform at all before you go hunting for the response.