# Create a form with no account

> Draft. This page describes the behaviour as implemented. The production host names are not live yet. The claim email itself is sent by the email step, which is not built yet: until then the claim link is returned to the agent and mail is queued, never sent.

## How it works

An agent that has only an email address can make a working form. The form and its key are real, but the key is **weak** and** sleeps** until the owner of that address claims it. Claiming changes nothing for the website: the same key and the same form address keep working.

| Time | State | What happens |
| --- | --- | --- |
| 0 to 72 hours | unclaimed, awake | The key works within its caps. Real submissions are stored and held, up to a bounded allowance; nothing is delivered. |
| 72 hours to day 90 | asleep | The key and the form address keep existing. Real submissions are still stored (up to the allowance) and held. REST answers 403 `claim_required` with the claim link; over MCP the only tool is `resend_claim_email`. |
| claimed, any time before day 90 | claimed | The same key becomes a normal account key. Claiming alone does not enable delivery: held leads stay held and are released, in order, and counted only when the owner's plan or trial entitles delivery, within that plan's allowance. |
| day 90 | retired | Data is purged. The key answers 410 `key_gone` and is never issued again. Create a new key. |

## Create

### Create a form and a key with no account

*Example: Create a form and a key with no account*

```
POST /api/forms
Content-Type: application/json

{
  "email": "owner@example.com",
  "name": "Contact"
}

-> 201: form, apiKey, claimUrl and replaySecret (shown once)
```

### Ask for the claim email again

*Example: Ask for the claim email again*

```
POST /api/claim/resend
Authorization: Bearer <the unclaimed key>

-> 202: queued: the mail goes only to the address the key was created for
```

### Claim a key (a signed-in person, whose verified email is the claim address)

*Example: Claim a key (a signed-in person, whose verified email is the claim address)*

```
POST /api/claim
Authorization: Bearer <a signed-in person's token>
Content-Type: application/json

{
  "token": "clm_Ab3dE5gH7jK9mN2p"
}

-> 200: formId, siteId and formKey: the same key and form address are now the person's
```

The answer holds the form key, the `apiKey` (cf_agent_..., shown once), a `claimUrl` and a `replaySecret`. The claim link is also queued for the address. Over MCP, call `create_form` with an `email` and no key.

- At most one new key per hour per network and per machine (send a stable `X-CF-Client-Id`; without one the user-agent identifies the machine), and three a day per network. Over the limit: 429 with `Retry-After`.
- One waiting key per email address (asleep counts) until it is claimed or retired: 409 `claim_pending`.
- A global daily ceiling is the last backstop (503 `capability_paused`).

**If the answer was lost,** send the same call again with the `replaySecret` the first answer carried and the same email: the same key comes back. Without the secret it never does, even for someone behind the same network with the same user-agent.

*Retry with the replay secret*

```
# the first answer was lost: send the replaySecret it carried (and the same email)
curl https://contactforms.dev/api/forms \
  -H "Content-Type: application/json" \
  -H "X-CF-Client-Id: <a stable random id your tool keeps>" \
  -d '{"email":"owner@example.com","replaySecret":"rps_..."}'
```

## While it waits

- One form (a second one is refused with a fix). Real submissions are stored and held pending claim and entitlement, up to a bounded allowance. Nothing is delivered and nothing counts toward a plan before someone owns the key. When the allowance is used up the answer is 429 `unclaimed_cap`.
- The only address that can ever hear about the form is the one it was created for, saved unverified. Mail about the key is limited to one a minute and three a day per address.
- A test lead (`send_test`) goes only to that address.
- No export. Leads stay held until the claim: they are never delivered or counted before someone owns them.

## Claim it

The owner opens the claim link (`/c/<code>`) while signed in with the address the key was created for, or pastes the key on the Agents page. The address must be **verified** in their account; the link alone is not enough, because the agent holds it too. Opening the link changes nothing; the button claims.

### Create a form and a key with no account

*Example: Create a form and a key with no account*

```
POST /api/forms
Content-Type: application/json

{
  "email": "owner@example.com",
  "name": "Contact"
}

-> 201: form, apiKey, claimUrl and replaySecret (shown once)
```

### Ask for the claim email again

*Example: Ask for the claim email again*

```
POST /api/claim/resend
Authorization: Bearer <the unclaimed key>

-> 202: queued: the mail goes only to the address the key was created for
```

### Claim a key (a signed-in person, whose verified email is the claim address)

*Example: Claim a key (a signed-in person, whose verified email is the claim address)*

```
POST /api/claim
Authorization: Bearer <a signed-in person's token>
Content-Type: application/json

{
  "token": "clm_Ab3dE5gH7jK9mN2p"
}

-> 200: formId, siteId and formKey: the same key and form address are now the person's
```

Claiming the key does not by itself start delivery or give a fresh allowance: delivery needs an active plan or trial. If the person's plan has no site left for it, the claim is refused with `plan_limit_sites` and nothing is lost until day 90.

## Lost the email

`POST /api/claim/resend` (or the MCP tool `resend_claim_email`) asks for the claim email again. It goes only to the stored address; nothing in the request can name another. At most three a day per key. A sleeping key can still call it. Until email sending ships, the answer says `delivery: "pending_email_sending"`.

## After 90 days

The leads, form and site are deleted, the claim address is removed, and the key becomes a tombstone that answers 410. It is never re-issued; a new request for the same address makes a different key.
