Auth & sign-in
Magic-link and CLI device-code sign-in. These public endpoints mint the API key everything else needs.
The /v1/auth/* routes are public — they take no API key because they are how you get
one. There are two flows that share one magic link: a browser sign-in and a CLI device-code
poll. Completing either mints a veto_… key (broad default scopes) bound to your project.
Flow at a glance
POST /v1/auth/email/start with your email (and, for the CLI, a device_code). A magic link is emailed.GET /v1/auth/verify?token=…), which provisions the account and mints the key.POST /v1/auth/cli/poll returns pending until the link is clicked, then ready with the api_key.POST /v1/auth/email/start
Trigger a magic link. Always returns 200 for a valid email (no account enumeration).
| Auth | Public |
|---|
Request
{
"email": "you@example.com",
"device_code": "optional — present for the CLI device flow"
}| Field | Required | Notes |
|---|---|---|
email | ✅ | Validated; invalid → 400 EMAIL_INVALID. |
device_code | optional | Binds this link to a CLI poll. Bad format → 400 DEVICE_CODE_INVALID. |
Response — 200
{ "ok": true, "expires_in": 900 }Dev convenience
Outside production (NODE_ENV !== 'production') the response also includes verify_url and
dev_token so the flow is automatable without SMTP. In production the link is emailed
only and never returned.
Status codes
| Status | Reason code | Meaning |
|---|---|---|
200 | — | Link issued (or silently no-op for enumeration safety). |
400 | EMAIL_INVALID / DEVICE_CODE_INVALID | Bad input. |
429 | RATE_LIMITED | Too many sign-in requests; back off ~15 min. |
curl -X POST https://api.veto-ai.com/v1/auth/email/start \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com"}'GET /v1/auth/verify
The link the browser opens. Provisions the account on first use, mints the key, and renders
a small HTML confirmation page (not JSON). If a device_code was attached, it also flips the
CLI poll to ready.
| Auth | Public (single-use token in the query string) |
|---|
| Status | Meaning |
|---|---|
200 | Verified — HTML "You're signed in" page. |
400 | Missing / invalid / already-used token. |
410 | The link expired — sign in again. |
curl "https://api.veto-ai.com/v1/auth/verify?token=<token>"POST /v1/auth/cli/poll
Long-poll for the CLI device flow. Returns pending until the user clicks the link, then
ready once — with the minted key.
| Auth | Public |
|---|
Request
{ "device_code": "the code shown by the CLI" }Responses
{ "status": "pending" }{
"status": "ready",
"api_key": "veto_test_8f2c…",
"project_id": "proj_01J…",
"org_id": "org_01J…"
}Status codes
| Status | Reason code | Meaning |
|---|---|---|
200 | — | pending or ready (see above). |
400 | DEVICE_CODE_INVALID | Missing / malformed device_code. |
404 | DEVICE_CODE_NOT_FOUND | Unknown device code. |
410 | MAGIC_LINK_EXPIRED | The sign-in link expired before it was clicked. |
curl -X POST https://api.veto-ai.com/v1/auth/cli/poll \
-H "Content-Type: application/json" \
-d '{"device_code":"WDJB-MJHT"}'Where to next
Once you hold a key, see Authentication for headers, scopes, and mode isolation — then create a merchant.
Errors & conventions
The single protocol error shape, status-code semantics, idempotency, ID prefixes, and the money rule shared by every hosted REST endpoint.
Merchants
Create and manage merchants. Creating a merchant mints its Ed25519 receipt-signing key; the public JWKS is served for offline receipt verification.