# Confirm a publication sign-up

> The leads.confirm operation: POST /api/v1/public/{brandSlug}/leads/confirm in the REST API and the leads_confirm tool in the MCP server.

Confirms a "Tell me when it's published" sign-up (`leads.create` with `kind: "jurisdiction_notify"`) with the
token from its confirmation email: double opt-in. The token is the proof; no other key is needed. Only the
latest link sent works, for 7 days. Once confirmed, the `jurisdiction_notify` job sends one email when the
jurisdiction's page is published.

Confirming twice changes nothing and answers the same, even after the 7 days. The website calls this from the
page the link opens, when the visitor presses Confirm: opening the page alone confirms nothing, so a mail
scanner that follows links can't confirm on the visitor's behalf. Works only on the platform brand (`ownrecord`).

Public: the website calls it, no API key needed.

## At a glance

- **REST:** `POST /api/v1/public/{brandSlug}/leads/confirm`
- **MCP tool:** `leads_confirm`
- **Operation ID:** `leads.confirm`
- **Scope:** Brand
- **Permission:** `customers:write`
- **Risk tier:** `write`
- **Dry run:** Not supported
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Authentication:** None (a public endpoint, rate-limited per IP address)
- **Since:** M13

## Request

### Path parameters

| Field       | Type   | Description                                                                     |
| ----------- | ------ | ------------------------------------------------------------------------------- |
| `brandSlug` | string | The platform brand's slug (`ownrecord`): leads belong to Ownrecord's own brand. |

### Body

A JSON object (`Content-Type: application/json`):

| Field   | Type   | Required | Description                                                                            |
| ------- | ------ | -------- | -------------------------------------------------------------------------------------- |
| `token` | string | Yes      | The token from the confirmation link (`?token=` of /notify/confirm), exactly as it is. |

## Response

`200 OK` with a JSON object:

| Field              | Type                  | Description                                        |
| ------------------ | --------------------- | -------------------------------------------------- |
| `object`           | `"lead_confirmation"` |                                                    |
| `status`           | `"confirmed"`         |                                                    |
| `jurisdictionId`   | string                | The jurisdiction the sign-up waits for, e.g. `HR`. |
| `jurisdictionName` | string                | Its name, e.g. `Croatia`.                          |
| `pagePath`         | string                | Its page on the website, e.g. `/jurisdictions/hr`. |
| `message`          | string                | What to tell the visitor.                          |

## Examples

### Confirm from the email

```http
POST /api/v1/public/ownrecord/leads/confirm
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "token": "q9VhN3bXk2mR7tY4wP8sZ1cL6fJ0dG5aE-uB_nH3oKx"
}
```

Response (`200`):

```json
{
  "object": "lead_confirmation",
  "status": "confirmed",
  "jurisdictionId": "HR",
  "jurisdictionName": "Croatia",
  "pagePath": "/jurisdictions/hr",
  "message": "Your email address is confirmed. We'll send you one email when this page is published, and nothing else."
}
```

## Errors

| Code                                                                                                   | HTTP status | When                                                                                                   | What to do                                                                                                                                 |
| ------------------------------------------------------------------------------------------------------ | ----------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| [`lead_confirmation_invalid`](https://ownrecord.io/docs/reference/errors.md#lead_confirmation_invalid) | 400         | The token doesn't belong to a sign-up: mistyped, replaced by a newer link, or the sign-up was deleted. | Use the link exactly as it is in the email. Only the latest link we sent works; sign up again on the jurisdiction's page to get a new one. |
| [`lead_confirmation_expired`](https://ownrecord.io/docs/reference/errors.md#lead_confirmation_expired) | 400         | The link is more than 7 days old and wasn't confirmed; sign up again for a new one.                    | Links work for 7 days. Sign up again on the jurisdiction's page to get a new one; it's sent at most once an hour.                          |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found)               | 404         | `brandSlug` isn't the platform brand.                                                                  | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.                                       |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed), [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused), [`idempotency_in_progress`](https://ownrecord.io/docs/reference/errors.md#idempotency_in_progress), [`dry_run_not_supported`](https://ownrecord.io/docs/reference/errors.md#dry_run_not_supported), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
