---
title: "Create account-bootstrap session"
method: POST
path: "/api/v1/accounts"
tags: ["Accounts"]
---

# Create account-bootstrap session

`POST /api/v1/accounts`

## Request

- Base URL: `https://api.proof.holdings`
- URL: `https://api.proof.holdings/api/v1/accounts`
- Auth: none declared

Create an account-bootstrap session for agent-driven onboarding. Public endpoint (rate limited: 15/min per IP, plus a per-identity budget of 3/hour for the email and SMS channels that no amount of IP rotation avoids).

The session is ALWAYS tagged `type="account_creation"`. It does NOT branch on whether the submitted identity already maps to a User — the response is uniform by design, because a differing shape or timing would answer "does this address have an account here?" to anyone who asks. Completing verification transparently creates a User when none exists or signs the existing one in; an agent that needs to tell those apart calls GET /auth/me afterwards and reads `created_at`.

THE EMAIL CHANNEL SENDS NOTHING HERE (`email_sent: false`, `send_required: true`). Delivering a message from our domain to an address nobody has proven is a separate, explicit act: POST /api/v1/accounts/{id}/send. The other channels are inbound — the user messages us (reverse OTP) — so nothing is dispatched to them at all.

No credential is required. A client that DOES present an API key (`Authorization: Bearer pk_…`) has it checked: a key the API no longer accepts is refused with 401 `invalid_api_key` before anything is stored, and a live one is recorded so the session issued on completion is BOUND to it — revoking the key by any path ends that session.

## Request body

- CreateAccountRequest: Request body for creating an account-bootstrap session. The server transparently pivots to a login session when the submitted identity already belongs to an active User, so the agent polls identically regardless of outcome.
  - `channel` 'telegram' | 'whatsapp' | 'sms' | 'email', required: Authentication channel
  - `phone_number` string: Required for SMS channel to determine DID routing.
  - `email` string, email: Required for email channel.

## Response `201`

Account session created

- AccountSessionResponse: Account-session response. Same wire format whether the request created a new account or pivoted to login — the `type` field distinguishes the outcome.
  - `id` string, required
  - `type` 'account_creation' | 'login', required: Outcome of an account-session creation. `account_creation` means a new User will be written on verification. `login` indicates the submitted identity matched an existing User and the session will log them in.
  - `channel` 'telegram' | 'whatsapp' | 'sms' | 'email', required: Authentication channel
  - `status` 'pending' | 'verified' | 'expired', required: Status of an auth session
  - `deep_link` string
  - `qr_code` string: Base64-encoded PNG QR code for the deep link
  - `qr_text` string: UTF-8 text QR for terminal/agent rendering
  - `sms_code` string: Short code for SMS-channel reverse-OTP
  - `sms_message` string
  - `email` string
  - `email_sent` boolean
  - `email_error` string
  - `expires_at` string, date-time, required
  - `instructions` string
  - `verified_at` string, date-time
  - `user_id` string
  - `created_at` string, date-time

## Other responses

- `400`: Validation error
- `401`: `invalid_api_key` — the request presented an API key the API no longer accepts (unknown, revoked or expired). Requests without a `pk_` bearer never receive it.
- `429`: The IP rate limit, the per-identity budget (`identity_rate_limited`, 3/hour for the email and SMS channels), or `identity_declined` — the owner of this address used the "this wasn't me" link recently. The last one means DO NOT RETRY for that address.
- `503`: Email service unavailable (email channel only), or `identity_check_unavailable` — the eligibility check could not be completed, so the request is refused rather than allowed through.

---

[API](https://skmtc.dev/proof/apis/proof-holdings-api.md) · [All operations](https://skmtc.dev/proof/apis/proof-holdings-api/llms.txt) · [OpenAPI document](https://skmtc.dev/proof/apis/proof-holdings-api/revisions/b15f506de696?raw)
