# Consent

Turn on the permission popup so identification runs only after the visitor accepts "identify this visit" (`resolution`) on the current policy.

You need an active account's API key and a canonical `pixel_id`. Header: `Authorization: Bearer <api_key>` and `content-type: application/json`.

## 1. Publish the policy

`POST /v1/pixels/{pixel_id}/consent`

```json
{
  "controller_name": "Ada Studio",
  "controller_type": "company",
  "controller_contact": "privacy@example.com",
  "controller_jurisdiction": "United States",
  "data_sharing": false,
  "contact_channels": ["email"],
  "purposes": ["analytics", "resolution"],
  "disclosed_parties": [],
  "site_policy_url": "https://example.com/privacy"
}
```

`controller_name` is required. `purposes` needs at least one of: `analytics`, `resolution`, `newsletter`, `email_sharing`, `phone_sharing`, `partner_email`, `partner_calls`, `partner_sms`. `controller_type` is `company`, `person`, or `organization`. `site_policy_url` is optional and must be https. With it, the popup shows two links (the site's privacy policy and our identification notice). Without it, the hosted page is the whole policy.

Agents on MCP use the `configure_consent` tool. It takes the same fields.

200 returns `policy_version`, `policy_hash`, `policy_url`, `snippet`, and `purposes`. Paste `snippet`. It loads `/px/{pixel_id}.js`. Do not also paste the immediate pixel snippet from setup. The popup loads identification only after an acceptance of `resolution` whose policy hash is still current.

Saving identical text keeps the version. A text change increments `policy_version` and replaces `policy_hash`. Older acceptances then fail the check with `policy_version_stale` until the visitor accepts again.

The public notice is `GET /v1/policy/{pixel_id}` (same page as `GET /api/v1/policy/{pixel_id}`). `/px/{pixel_id}.js` and the policy URL also accept a script alias. The stored id and the receipt use the canonical pixel id. Account routes such as `GET /v1/pixels/{pixel_id}` do not accept an alias.

## 2. What the browser sends

The popup POSTs `/v1/consent/record` (same handler as `POST /api/v1/pixel/fire`). You do not need to call it for a normal install. The contract, if you record a choice yourself:

- 202 with `receipt`, `signature`, and `signature_alg` `hmac-sha256` when the choice is stored.
- 403 `{"recorded": false, "reason": "site_mismatch", "domain": "<pixel domain>"}` when the page host is a different site.
- 204 with an empty body when the call is ignored: bad JSON, Global Privacy Control, a missing id, consent turned off, an accept that is not affirmative, a stale `policy_version_hash`, or a purpose that was not configured.

An acceptance must send `affirmative` true, `policy_version_hash` equal to the current hash, and `purposes` that are a non-empty subset of the configured list. The page `url` host must be the pixel's domain or a subdomain.

## 3. Ask if a visitor is allowed

`GET /v1/consent/check?pixel_id={pixel_id}&visitor_id={visitor_id}&required_purpose=resolution`

200 returns `permitted` and `reason`.

- `ok` : `permitted` is true. The latest row accepts the current policy and includes `required_purpose` when you sent one.
- `no_consent_record` : nothing stored. No receipt in the body.
- `consent_off` : the pixel no longer has consent enabled.
- `declined_or_withdrawn` : the latest choice is not an acceptance.
- `policy_version_stale` : the acceptance is for an older policy hash.
- `purpose_not_granted` : the acceptance does not include `required_purpose`.

`required_purpose` must be one of the purpose names or the call is 400 `invalid required_purpose`. The check and the receipt accept a script alias and answer for the canonical pixel. An unknown id on the check is `no_consent_record`.

`GET /v1/consent/receipt?pixel_id={pixel_id}&visitor_id={visitor_id}` returns the latest receipt plus `valid` true, or 404 `no consent record`. The alias path is `GET /api/v1/consent/receipt`.

## 4. Verify a signature

`POST /v1/consent/verify`

```json
{"receipt": {"pixel_id": "...", "receipt_id": 1, "visitor_id": "...", "origin": "example.com", "consent_state": "accepted", "policy_hash": "...", "purposes": ["resolution"], "recorded_at": "..."}, "signature": "..."}
```

200 is `{"valid": true}` or `{"valid": false}`. Send the receipt object you were given. Do not recompute the signature. The signing secret is not published.

## 5. Read the log

`GET /v1/pixels/{pixel_id}/consent` with the API key. Optional `state` is `accepted`, `declined`, or `withdrawn`. 200 returns `events`, newest first, at most 200. Each event includes `receipt` and `signature`. The HTML page at `/log` calls this route.
