# Sign up

An agent can create its own Ackuna organization and API key. Two requests, one emailed
code. No key is needed for either request.

API sign-up can be closed by the operator, independently of the console's registration form.
While it is closed, both requests answer **503** `registration_disabled` and nothing is created
or mailed; a code issued earlier cannot be redeemed until it reopens.

## 1. Ask for a code

```
POST /v1/signup
Content-Type: application/json

{"email": "ops@yourcompany.com", "organization_name": "Robot Co"}
```

```json
{"data": {"object": "signup", "signup_id": "sgn_01k4c2v8qf3m7d5b0y6xz4rt1c", "email": "ops@yourcompany.com", "expires_at": "2026-09-04T12:15:00.000Z"}}
```

The status is **202**: nothing has been created yet, a code has only been mailed to that
address. It is valid for 15 minutes and 5 attempts. The address is normalized before use —
lower-cased, a `+tag` in the local part is dropped, and for Gmail (`gmail.com`,
`googlemail.com`) dots are dropped and the domain becomes `gmail.com` — so
`ops+robots@yourcompany.com` and `ops@yourcompany.com` are the same account, and so are
`jane.doe@gmail.com` and `janedoe@googlemail.com`. Disposable mail providers are refused with
422 `invalid_request`. If the address already belongs to an Ackuna user you get
409 `email_already_registered`: sign in to the console with it and issue a key there instead.

One code per address at a time. While a code is live, asking again answers
409 `signup_pending` and mails nothing; the first code keeps working, and `Retry-After` says
when it expires. Keep the `signup_id` from the first response.

Rate limits on this path are deliberately tight: 5 requests per hour per IP, 3 per hour per
address, and 5 completed sign-ups per IP per day shared with the console's registration form;
the verify step allows 30 per hour per IP on top of the 5 attempts per code. Read the code
from the mailbox — do not loop on this request.

## 2. Prove the mailbox

```
POST /v1/signup/verify
Content-Type: application/json

{"signup_id": "sgn_01k4c2v8qf3m7d5b0y6xz4rt1c", "code": "K7MP2XQ9"}
```

```json
{"data": {
  "object": "signup_completed",
  "organization": {"id": "org_01k4c2v8qf3m7d5b0y6xz4rt2d", "name": "Robot Co"},
  "api_key": {"key": "ak_9f2c1e7b4a6d8035f1c9a2b7e4d6f803a1b2c3d4", "prefix": "ak_9f2c1e7b4", "scopes": ["inboxes:read", "inboxes:write", "messages:read", "messages:send", "webhooks:read", "webhooks:write", "domains:read", "domains:write"]},
  "console_url": "https://agents.ackuna.com/login?product=agent-mail",
  "docs_url": "https://agents.ackuna.com/docs/quickstart",
  "console_claim": "Use \"Forgot password\" on the console login page; the owner account has no password yet."
}}
```

The status is **201**: the organization, its owner account and that key now exist. The key is
shown once — store it now. `console_claim` is the sentence to hand a human who wants the
console: the owner account was created without a password, so it is claimed through "Forgot
password" rather than a normal sign-in. A wrong code answers 422 `signup_code_invalid` with
the attempts remaining; an expired, used or exhausted sign-up answers 410 `signup_expired` —
ask for a new code; and if a person registered that address through the web form while the
code was in flight, verification answers 409 `email_already_registered` — sign in to the
console and issue a key there instead.

## What you have now

- An organization on the **Free** plan: 3 inboxes, 100 outbound messages a day, 3,000 a
  month, 1 webhook endpoint. `GET /v1/me` shows the exact numbers.
- An owner account for that organization, already verified — the code was the proof — so
  sending is unlocked immediately. See [Errors](errors#email_verification_required) for what
  organizations registered through the web form see before their owner clicks the link.
- The console at `console_url`, where a person can watch what the agent is doing once they
  have claimed the account as described above.

Continue with the [Quickstart](quickstart) from step 2.
