Skip to main content

Verification flow

POST /v1/users (bootstrap) creates the user, returns a restricted mk_user_* key, and emails the user a 6-digit code. The agent then submits the code to POST /v1/users/:userId/verify — the same key is upgraded in place to full scope. No key rotation; store the value once.

Step 1 — POST /v1/users

Required scope: developer:bootstrap. Returns 201 Created (or 207 Multi-Status if a starter manifest had over-cap products):
sourceAgent is required (1–64 chars, [A-Za-z0-9 _.-]+ only). It is embedded in the verification email so the user can see which agent triggered the account. Response:
Store userKey immediately. It is shown once. It starts with the restricted scope set: Until verify, the restricted key cannot create/update catalog data or publish.

Step 2 — submit the code

Required scope: me:verify. The :userId path param must match the key’s owner — cross-tenant or wrong-id attempts return 404 user_not_found (leak-less, never 403). On success (200):
The same key’s scope set is upgraded in place to catalog:read, catalog:write, storefront:publish. Cache propagation is bounded by the 30s positive auth cache; the planLimits cache for the user is invalidated immediately so GET /v1/me reflects verified on the next call.

Code rules

Plain-text storage is intentional. The user-reads-aloud flow requires the agent to be able to say the code on screen. A bcrypt-hashed code would only support submit-then-check, not the agentic read-aloud variant. Defense-in-depth lives elsewhere: 3-attempts lockout, 15-min TTL, per-user resend rate-limit, and the leak-less 404 on cross-tenant verify.

Verify error shapes

Step 3 — resend (when needed)

Required scope: me:resendVerification. Per-user rate-limited at 3 / hour and 5 / day. Overwrites the existing code doc — the old code is invalidated on resend. Returns:

Variants of step 2

A — User reads the code aloud. Default. Agent says “I sent you a 6-digit code; tell me the number.” User reads from email. Agent submits. B — Silent verification via Gmail MCP. If the user’s mailbox is connected to the agent (e.g. Gmail MCP), the agent reads the verification email programmatically, extracts the 6-digit code, and submits — no manual step. The API contract is identical; only the source of the code differs.

Account-cancellation hatch

Every bootstrap email also embeds a single-use cancel link (24h TTL) pointing at https://api.mareaalcalina.com/public/v1/bootstrap/:previewToken. The user opens it, confirms once (GET → POST, two-step to defeat email-preview crawlers), and the account is hard-deleted. This is the LFPDPPP-required cancellation hatch for accounts created by an agent (see ARCO procedures).

Verification in code

  • src/api/v1/users.bootstrap.ts
  • src/api/v1/users.verify.ts
  • src/api/v1/users.resendVerification.ts
  • src/api/services/verification.service.ts — 3-attempt lockout + scope upgrade.
  • src/services/verification/sendVerificationCodeCore.ts — code generation + email delivery (15-min API TTL).
  • src/api/public/bootstrap.cancel.ts — emergency cancel endpoint.