Registration & OTP

Create an account with an email and a six-digit code.

Registration is two requests. It's designed so an AI agent can drive it end to end with the human doing exactly one thing: reading a code out of their inbox.

Base URL: https://api.superior.trade · registration endpoints are unauthenticated; every other endpoint requires the API key you get here, sent as the x-api-key header.

POST/account/register

Starts registration (or re-authentication) for an email address.

bash
curl -X POST https://api.superior.trade/account/register \
  -H "content-type: application/json" \
  -d '{"email": "operator@example.com"}'

Response — 200

json
{ "status": "otp_sent", "expires_in": 600 }

A six-digit one-time code is emailed to the address. The code expires in 10 minutes and only the most recently issued code is valid.

The response is identical whether or not the account already exists — registering an existing email just starts a new key issuance for that account. Email enumeration isn't possible through this endpoint.

Abuse controls (these exist today and carry over): max 3 codes per email per 6 hours (429 with retry-after), disposable email domains rejected (400 email_not_allowed), and normalized-alias dedupe — dots and +suffix variants collapse to one canonical address (jason+bot2@ shares jason@'s code budget and account).

POST/account/verify

Exchanges the code for an API key.

Request

json
{ "email": "operator@example.com", "otp": "482913" }

Response — 200

json
{
  "api_key": "st_live_4f2a9c81d7e3b0e6…",
  "key_id": "key_01j8xq2v9…",
  "account_id": "acct_01j8xq2v9…"
}

The api_key appears only here — it is not emailed, not retrievable later, and stored only as a hash. If it's lost, mint a new one. OTP-minted keys show up in GET /account/keys with the name otp-<date>.

Your next request authenticates with it:

bash
curl https://api.superior.trade/account \
  -H "x-api-key: st_live_4f2a9c81d7e3b0e6…"

Errors

StatusCodeMeaning
400invalid_otpWrong or superseded code
400otp_expiredMore than 10 minutes old
429too_many_attempts5 failed attempts burns the code; re-register
409key_limit_reachedAccount already has 10 active keys

Why the key isn't emailed

The previous flow emailed the plaintext key to the inbox. That breaks the agent loop — an agent can't read its operator's email — and it leaves a live credential sitting in an inbox forever. A six-digit code read aloud is small enough for a human to relay and worthless ten minutes later.