{"title":"Pixel API","audience":"Integrating agents and people. One text for both.","conventions":"# Pixel API\n\nPublic contract for creating an account, installing a pixel, and reading contacts. The same pages are for agents and for people.\n\n## How to read this\n\n- `GET /docs.json` is the whole catalog: lessons plus every route below, each with its markdown.\n- `GET /docs/{slug}.md` is one lesson or the endpoint reference as markdown. `GET /docs/{slug}` is the same page as HTML.\n- `GET /llms.txt` is the short map.\n- `GET /openapi.json` is the generated schema. It is thinner than this catalog. The schema UI is `/swagger`.\n\nProduction API host: `https://agentpixel.io`. `https://agentpixel.io` and `https://agentpermission.io` are this same process.\n\n## Calls\n\n- JSON errors are `{\"detail\": \"...\"}` and an HTTP status.\n- A JSON body with the wrong shape is 400 `{\"detail\": \"invalid request\"}`. Field names are not listed.\n- Send `content-type: application/json` on a JSON POST.\n- Signup calls use the header `x-signup-token`. The token lasts one hour from `POST /v1/accounts` and is not refreshed.\n- Later calls use `Authorization: Bearer <api_key>`. The key starts with `px_` and is returned once. `prefix` is its first 12 characters.\n- On `GET /v1/whoami`, a bad key is 401. On pixel, contact, and setup routes, a bad key is 403.\n- Times in responses are UTC.\n\n## Lessons\n\n1. [Signup](/docs/signup.md) : account, email, checkout, API key.\n2. [Setup](/docs/setup.md) : pixel, snippet, install check.\n3. [Consent](/docs/consent.md) : permission popup and receipts.\n4. [Contacts](/docs/contacts.md) : pull people by day or by id.\n5. [Evaluation](/docs/evaluation.md) : private scoped access and synthetic examples.\n\n## Left out\n\nOperator routes under `/console`, `/api/admin`, and `/v1/admin` are not in this catalog.\n\nAn unlimited key can call [Activate](/docs/endpoints.md#post-v1-accounts-account-id-activate) and [Pipeline](/docs/endpoints.md#post-v1-pixels-pipeline). A paid key receives 403 on those two. This catalog does not say how that key is issued.\n","connections":"/connect.json","task_guides":["/guides/consented-visitor-identity","/guides/consent-pixel-installation","/guides/consented-contact-export"],"lessons":[{"slug":"signup","title":"Signup","summary":"Start a 30-day no-card trial, confirm the email, and take an API key. After the trial subscribe for $1 per account/month. Includes 3 pixels and 500 new contacts per UTC calendar month. No extra charges. Resolution pauses at the limit or until subscribed.","html_path":"/docs/signup","markdown_path":"/docs/signup.md","markdown":"# Signup\n\nStart a 30-day no-card trial, confirm the email, and take an API key. After the trial subscribe for $1 per account/month. Includes 3 pixels and 500 new contacts per UTC calendar month. No extra charges. Resolution pauses at the limit or until subscribed.\n\nCall these paths on the API host you already reached (`https://agentpixel.io` in production). Send `content-type: application/json` on every POST that has a body. A refused call returns JSON `{\"detail\": \"...\"}`. A body with the wrong shape returns 400 `{\"detail\": \"invalid request\"}` and does not list the fields.\n\n## Store these\n\n- `account_id` from step 2.\n- `signup_token` from step 2. Send it as the header `x-signup-token`. It expires one hour after the account is created. There is no call that refreshes it.\n- `api_key` from step 6. It is shown once and starts with `px_`. Later calls use `Authorization: Bearer <api_key>`.\n\n## 1. Read the prices\n\n`GET /v1/plans`\n\nNo auth. Use the selectable `free` trial. It lasts 30 days without a card. After trial the account costs $1/month. Usage charges and setup fees are zero. Ignore `unlimited`.\n\n## 2. Create the account\n\n`POST /v1/accounts`\n\n```json\n{\"email\": \"ada@example.com\", \"display_name\": \"Ada\"}\n```\n\n`external_user_id` is optional. When you send it, the same value cannot be registered twice.\n\n201 returns `account_id`, `status` `pending_email`, `plan` `free`, `signup_token`, and `email_verified` false. When the confirmation email was not sent, the object also has `verification_url`. When that field is absent, the email was sent.\n\n- 400 `invalid email`, `display name is required`, or `invalid request`\n- 409 `email already registered` or `external user already registered`\n- 502 `could not send the confirmation email`\n\n## 3. Confirm the email\n\nWhen `verification_url` is present, GET it. The path is `/v1/email/verify?token=...`. The response is HTML. 200 means the page contains \"Email confirmed\". The link expires 24 hours after it was sent.\n\nWhen `verification_url` is absent, a person opens the email. Poll step 4 until `email_verified` is true.\n\nSend it again with `POST /v1/accounts/{account_id}/verification-email` and header `x-signup-token`. A second send inside one minute is 429 `wait before sending the confirmation email again`. An address that is already confirmed returns `{\"sent\": false, \"status\": \"already_verified\"}`.\n\n## 4. Watch the account\n\n`GET /v1/accounts/{account_id}` with header `x-signup-token`.\n\n200 returns `status`, `plan`, `email`, and `email_verified`. The trial starts as `pending_email` and becomes `active` after email confirmation. Resolution pauses when the trial expires unless a paid subscription is active. A missing or expired signup token is 401.\n\n## 5. Subscribe after the trial (optional during the trial)\n\n`POST /v1/accounts/{account_id}/checkout` with header `x-signup-token`.\n\n200 returns `url`, `monthly_cents` 100, `setup_fee_cents` 0, and `resolved_contact_cents` 0. Give `url` to a person to authorize the $1 monthly subscription. No usage charges.\n\nYou do not call the return URL. After payment, check `/v1/whoami` using your API key until `plan` is `paid` and `paid_until` is in the future. An active trial is not payment confirmation. If they cancel, no subscription is activated; open checkout again when ready.\n\nUse your account Bearer API key for checkout after the signup token expires. Confirm your email first. An already-paid subscription returns 409.\n\n## 6. Take the API key\n\nWait until `status` is `active` and `email_verified` is true.\n\n`POST /v1/api-keys?account_id={account_id}` with header `x-signup-token`.\n\n201 returns `api_key` and `prefix`. Store `api_key`. 403 `account is not active` or `confirm your email before creating an API key` means step 3 is unfinished.\n\n## 7. Check the key\n\n`GET /v1/whoami` with `Authorization: Bearer <api_key>`.\n\n200 returns `account_id`, `plan` `free` during trial or `paid` after subscription, `status` `active`, and `key_prefix`. It does not return the secret.\n\nNext lesson: [Setup](/docs/setup.md).\n"},{"slug":"setup","title":"Setup","summary":"Mint a pixel, paste the tag, and confirm the homepage contains it.","html_path":"/docs/setup","markdown_path":"/docs/setup.md","markdown":"# Setup\n\nMint a pixel for one site, paste the tag, and confirm the homepage contains the pixel id.\n\nYou need an API key from [Signup](/docs/signup.md). Send `Authorization: Bearer <api_key>` and `content-type: application/json` on POSTs. These routes require the account to be `active` on the `paid` or `unlimited` plan. A bad key here is 403, not 401.\n\n`method` `cms`, `gtm`, or `utm` on these routes is 400 `that install method is not available`. Leave `method` out.\n\n## 1. Read standing\n\n`GET /v1/setup`\n\n200 returns `good_standing` true, `pixel_count`, `installed_count`, and `setup_complete`. `setup_complete` is true once any pixel is `installed`. 403 `account is not in good standing` means signup is unfinished.\n\n## 2. Create the pixel\n\n`POST /v1/pixels`\n\n```json\n{\"domain\": \"example.com\"}\n```\n\nSend a host. `https://` and a leading `www.` are stripped. A path, query, port, space, or `@` is 400 `domain must be a host, not a page` or `domain must be a host`.\n\n201 returns `pixel_id`, `domain`, `install_status` `not_checked`, `snippet`, `consent_enabled`, and `check_error`. Store `pixel_id`. Posting the same domain again on this account returns that same pixel.\n\n409 `domain already registered` means another account owns the host.\n\nThe `snippet` in this response loads identification immediately, unless consent is already on. If the site should ask before identification, do [Consent](/docs/consent.md) next and paste that lesson's snippet instead of this one.\n\n## 3. Paste the tag\n\nPut the snippet on the public homepage. The install check looks at `https://{domain}/` only, and it looks for the pixel id in the HTML.\n\n`POST /v1/pixels/{pixel_id}/email-snippet` with `{\"email\": \"person@example.com\"}` emails the immediate snippet. It does not email the consent tag. Wait 10 minutes before sending it again (429).\n\nExtra commerce snippets: `GET /v1/pixels/{pixel_id}/snippets/{action}` where `action` is `viewed_product`, `add_to_cart`, or `checkout_completed`.\n\n## 4. Check the install\n\n`POST /v1/pixels/{pixel_id}/check`\n\n200 returns `install_status` and `check_error`.\n\n- `installed` : the homepage returned 200 and contains the pixel id.\n- `not_installed` : the homepage returned 200 and does not contain it.\n- `unreachable` : the fetch failed. `check_error` says why.\n\nA failed fetch does not clear a previous `installed` status.\n\n`GET /v1/setup` then reports `setup_complete` true when `installed_count` is at least 1.\n\n## 5. Read it back\n\n`GET /v1/pixels` lists this account's pixels without snippets. `GET /v1/pixels/{pixel_id}` returns one pixel and its current snippet. Another account's id is 404 `pixel not found`. These routes want the canonical pixel id, not a script alias.\n\n`PATCH /v1/pixels/{pixel_id}` with `{\"domain\": \"other.example.com\"}` renames a pixel that is not installed. 409 `installed pixel cannot be renamed`. `DELETE /v1/pixels/{pixel_id}` is 204, and 409 `installed pixel cannot be deleted` after it is installed.\n\nNext lesson: [Consent](/docs/consent.md) if the tag should wait for a choice, or [Contacts](/docs/contacts.md) to read people.\n"},{"slug":"contacts","title":"Contacts","summary":"Pull resolved people by UTC day or by the last id you have.","html_path":"/docs/contacts","markdown_path":"/docs/contacts.md","markdown":"# Contacts\n\nPull resolved people for one pixel. You need the API key and the canonical `pixel_id` from [Setup](/docs/setup.md).\n\nHeader: `Authorization: Bearer <api_key>`. The account must be active. A paid key sees its own pixels. An unlimited key may read any pixel. Someone else's pixel is 404 `pixel not found`.\n\nPass `on` or `after`. Sending both is 400 `pass on or after, not both`. Sending neither is 400 `pass on or after`.\n\n## By day\n\n`GET /v1/pixels/{pixel_id}/contacts?on=YYYY-MM-DD`\n\n`on` is a UTC date. 400 `on must be YYYY-MM-DD` otherwise.\n\n200:\n\n```json\n{\"pixel_id\": \"...\", \"on\": \"2026-10-06\", \"contacts\": [{\"id\": 1, \"hem\": \"<64 hex chars>\", \"resolved_at\": \"2026-10-06T15:04:05+00:00\", \"contact\": {}}]}\n```\n\nThe list is people whose first resolved UTC day is that date, in `id` order. An update does not move the day. `contact` is whatever was stored.\n\n## By id\n\n`GET /v1/pixels/{pixel_id}/contacts?after=0`\n\nStart at `0`. 200 returns `after`, `high_water`, and `contacts` with id greater than `after`, in id order. `high_water` is the last id in this response, or the `after` you sent when the list is empty.\n\nThe next call is `after={high_water}`. The same `after` returns the same rows. This cursor is not consumed.\n\n`after` must be zero or greater.\n\n## What a contact is\n\nEach item is `id` (integer), `hem` (64 hex characters), `resolved_at` (UTC ISO-8601), and `contact` (object). Read `contact` for the person's fields. Do not assume a fixed set of name or email keys beyond what that object contains.\n\n## Storing people\n\nCustomers read with GET. `POST /v1/pixels/{pixel_id}/contacts` stores people that are already resolved:\n\n```json\n{\"contacts\": [{\"hem\": \"<64 hex chars>\", \"seen_at\": \"2026-10-06T15:04:05Z\", \"contact\": {\"email\": \"ada@example.com\"}}]}\n```\n\nA `contact` of `null` or `{}` is skipped. A bad `hem` or `seen_at` fails the whole call with 400. 200 returns `stored`, which counts writes and updates and skips misses. Updating a known `hem` replaces `contact` and keeps `id` and the first `resolved_at`.\n\n## Quote\n\n`GET /v1/billing/quote` returns `contact_count`, `setup_cents`, `contact_cents`, `usage_cents`, and `total_cents`. A paid account is $1 per month, with zero usage charges. An unlimited account is zero. This call does not charge the card.\n"},{"slug":"consent","title":"Consent","summary":"Turn on the permission popup, then read and verify receipts.","html_path":"/docs/consent","markdown_path":"/docs/consent.md","markdown":"# Consent\n\nTurn on the permission popup so identification runs only after the visitor accepts \"identify this visit\" (`resolution`) on the current policy.\n\nYou need an active account's API key and a canonical `pixel_id`. Header: `Authorization: Bearer <api_key>` and `content-type: application/json`.\n\n## 1. Publish the policy\n\n`POST /v1/pixels/{pixel_id}/consent`\n\n```json\n{\n  \"controller_name\": \"Ada Studio\",\n  \"controller_type\": \"company\",\n  \"controller_contact\": \"privacy@example.com\",\n  \"controller_jurisdiction\": \"United States\",\n  \"data_sharing\": false,\n  \"contact_channels\": [\"email\"],\n  \"purposes\": [\"analytics\", \"resolution\"],\n  \"disclosed_parties\": [],\n  \"site_policy_url\": \"https://example.com/privacy\"\n}\n```\n\n`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.\n\nAgents on MCP use the `configure_consent` tool. It takes the same fields.\n\n200 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.\n\nSaving 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.\n\nThe 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.\n\n## 2. What the browser sends\n\nThe 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:\n\n- 202 with `receipt`, `signature`, and `signature_alg` `hmac-sha256` when the choice is stored.\n- 403 `{\"recorded\": false, \"reason\": \"site_mismatch\", \"domain\": \"<pixel domain>\"}` when the page host is a different site.\n- 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.\n\nAn 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.\n\n## 3. Ask if a visitor is allowed\n\n`GET /v1/consent/check?pixel_id={pixel_id}&visitor_id={visitor_id}&required_purpose=resolution`\n\n200 returns `permitted` and `reason`.\n\n- `ok` : `permitted` is true. The latest row accepts the current policy and includes `required_purpose` when you sent one.\n- `no_consent_record` : nothing stored. No receipt in the body.\n- `consent_off` : the pixel no longer has consent enabled.\n- `declined_or_withdrawn` : the latest choice is not an acceptance.\n- `policy_version_stale` : the acceptance is for an older policy hash.\n- `purpose_not_granted` : the acceptance does not include `required_purpose`.\n\n`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`.\n\n`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`.\n\n## 4. Verify a signature\n\n`POST /v1/consent/verify`\n\n```json\n{\"receipt\": {\"pixel_id\": \"...\", \"receipt_id\": 1, \"visitor_id\": \"...\", \"origin\": \"example.com\", \"consent_state\": \"accepted\", \"policy_hash\": \"...\", \"purposes\": [\"resolution\"], \"recorded_at\": \"...\"}, \"signature\": \"...\"}\n```\n\n200 is `{\"valid\": true}` or `{\"valid\": false}`. Send the receipt object you were given. Do not recompute the signature. The signing secret is not published.\n\n## 5. Read the log\n\n`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.\n"},{"slug":"evaluation","title":"Evaluate AgentPixel safely","summary":"Private scoped evaluation with consent and labeled synthetic examples.","html_path":"/docs/evaluation","markdown_path":"/docs/evaluation.md","markdown":"# Evaluate AgentPixel safely\n\nEvaluation uses a permanently isolated account, not a production customer account. It cannot send email, issue another key, subscribe, call the live pixel provider, read customer contacts, or run IDGraph resolution. Samples are clearly labeled synthetic and are kept in a separate table.\n\n## 1. Connect privately\n\nAsk the account owner for the private evaluator profile or the registry's private test profile. Credentials are never shown on this public page. Connect to `https://agentpixel.io/mcp` with `Authorization: Bearer <private evaluator key>` or `X-API-Key: <private evaluator key>`. Do not pass the key as a tool argument or publish it.\n\nCall `whoami`. Confirm `evaluation_mode` is true and `data_kind` is `synthetic`. Check `evaluation_expires_at`. The profile ends at its original 30-day trial boundary; it does not renew or charge automatically. The owner can revoke the key at any time. Ask the owner for a renewed private profile when access needs attention; expiration or missing scope never changes this account into a production account.\n\n## 2. Review the controlled site\n\nThe only allowed hostname is `staging.allsourcedata.io`. The controlled page is [AgentPixel evaluation page](https://staging.allsourcedata.io/evaluation-test/). `create_pixel` returns an `eval_` pixel ID and consent tag. Repeating the same domain returns that account's existing evaluation pixel. `check_install` fetches only this exact page and rejects redirects, including redirects within the same host. It does not inspect the staging homepage.\n\nThe owner installs the tag on this controlled page. Configure consent, then use the page to accept identification. GPC, decline, and withdrawal prevent tag activation for that visitor. Reading the account's synthetic examples requires at least one saved, current acceptance of resolution consent. It does not infer consent from a browser cookie. A checked installation is only Done for the tag installation, not identity resolution.\n\n## 3. Read the examples\n\nExample agent request: List my pixels, then get_contacts for my eval_ pixel with after set to 0. Tell me which fields show that these are synthetic examples and whether consent is saved.\n\nUse exactly one of `on` (UTC date YYYY-MM-DD) or `after` (nonnegative contact cursor). Use returned `high_water` as the next cursor. Before accepted current resolution consent, contacts are empty and `next_action` tells you to accept consent on the controlled page. After consent, two static synthetic example records are available. Their addresses end in `example.invalid`; they are not identified visitors. Withdrawal hides the examples again. The tag does not contact live identity or advertising providers.\n\n## Limits and readiness\n\nThe normal public terms remain a 30-day no-card trial, then $1 per account/month, 3 pixels and 500 new contacts per UTC calendar month, with no overage charges. This evaluator cannot pay or switch to live processing. It contains two synthetic fixture records and is bounded by the original trial end. No customer data or live resolution is evidence from this evaluation. A real IDGraph outcome and real payment settlement require separate authorized tests.\n\nFor 400 errors, correct arguments. For 401, check the private connection key. For 403, follow the scope or expiry message and contact the owner. For 404, use your own pixel ID. For 409, ask the owner about domain registration. For 429, wait. For 502/503, retry with bounded backoff. Do not retry signup or key issuance blindly; evaluator connections cannot use those tools.\n"}],"endpoints":[{"id":"get-healthz","group":"account","method":"GET","path":"/healthz","auth":"none","summary":"Database is answering.","also":[],"markdown":"## GET /healthz\n\nDatabase is answering.\n\nAuth: none.\n\nSuccess: 200 `{\"ok\": true}`\n\nErrors:\n- 503 `database unavailable`\n"},{"id":"get-v1-plans","group":"account","method":"GET","path":"/v1/plans","auth":"none","summary":"Prices a public signup can choose.","also":[],"markdown":"## GET /v1/plans\n\nPrices a public signup can choose.\n\nAuth: none.\n\nSuccess: 200 `{\"plans\":[{\"id\":\"free\",\"selectable\":true,\"trial_days\":30,\"setup_fee_cents\":0,\"resolved_contact_cents\":0},{\"id\":\"unlimited\",\"selectable\":false}]}`\n\nNotes:\n- Public signup starts a 30-day no-card trial, then $1 per account/month. Includes 3 pixels and 500 new contacts per UTC calendar month. No extra charges. Resolution pauses at the limit or after trial until subscribed.\n- `unlimited` is not selectable on public signup.\n"},{"id":"post-v1-accounts","group":"account","method":"POST","path":"/v1/accounts","auth":"none","summary":"Start a 30-day trial without a card. The signup token lasts one hour.","also":[],"markdown":"## POST /v1/accounts\n\nStart a 30-day trial without a card. The signup token lasts one hour.\n\nAuth: none.\n\nBody:\n- `email` (string, required). Stored lowercased.\n- `display_name` (string, required).\n- `external_user_id` (string, optional). Unique when you send it.\n\nSuccess: 201 `account_id`, `status` `pending_email`, `plan` `free`, `signup_token`, `external_user_id`, `email_verified` false. `verification_url` is present only when the confirmation email was not sent.\n\nErrors:\n- 400 `invalid email`\n- 400 `display name is required`\n- 400 `invalid request`\n- 409 `email already registered`\n- 409 `external user already registered`\n- 502 `could not send the confirmation email`\n\nNotes:\n- Send the signup token as `x-signup-token`. It expires one hour after this response. There is no refresh call.\n- An unlimited key on this same call skips the email and also returns `api_key` and `prefix`. A paid signup does not.\n"},{"id":"get-v1-accounts-account-id","group":"account","method":"GET","path":"/v1/accounts/{account_id}","auth":"signup token","summary":"Status, plan, email, and whether the email is confirmed.","also":[],"markdown":"## GET /v1/accounts/{account_id}\n\nStatus, plan, email, and whether the email is confirmed.\n\nAuth: signup token.\n\nSuccess: 200 `account_id`, `status`, `plan`, `email`, `email_verified`\n\nErrors:\n- 401 `signup token required`\n- 401 `signup token expired`\n\nNotes:\n- Poll this until `email_verified` is true and, after checkout, until `status` is `active`.\n"},{"id":"get-v1-email-verify","group":"account","method":"GET","path":"/v1/email/verify","auth":"none","summary":"Confirm the email. The confirmation link lands here.","also":[],"markdown":"## GET /v1/email/verify\n\nConfirm the email. The confirmation link lands here.\n\nAuth: none.\n\nQuery:\n- `token` (string, required).\n\nSuccess: 200 HTML. The page says \"Email confirmed\".\n\nErrors:\n- 400 `verification token required`\n- 404 `verification link not found`\n- 400 `verification link expired` (24 hours after the send)\n\nNotes:\n- Use `verification_url` from signup when it is present. Otherwise a person opens the email.\n"},{"id":"post-v1-accounts-account-id-verification-email","group":"account","method":"POST","path":"/v1/accounts/{account_id}/verification-email","auth":"signup token","summary":"Send the confirmation email again.","also":[],"markdown":"## POST /v1/accounts/{account_id}/verification-email\n\nSend the confirmation email again.\n\nAuth: signup token.\n\nSuccess: 200 `{\"sent\": true, \"status\": \"sent\"}`, plus `verification_url` only when the mailer skipped the send. Already confirmed: `{\"sent\": false, \"status\": \"already_verified\"}`.\n\nErrors:\n- 404 `account not found`\n- 401 `signup token required`\n- 401 `signup token expired`\n- 429 `wait before sending the confirmation email again` (one minute)\n- 502 `could not send the confirmation email`\n"},{"id":"post-v1-accounts-account-id-checkout","group":"account","method":"POST","path":"/v1/accounts/{account_id}/checkout","auth":"signup token or account Bearer API key","summary":"Open Stripe Checkout for the $1 per account/month subscription. No usage charges.","also":[],"markdown":"## POST /v1/accounts/{account_id}/checkout\n\nOpen Stripe Checkout for the $1 per account/month subscription. No usage charges.\n\nAuth: signup token or account Bearer API key.\n\nSuccess: 200 `url`, `monthly_cents` 100, `setup_fee_cents` 0, `resolved_contact_cents` 0\n\nErrors:\n- 404 `account not found`\n- 401 `signup token required`\n- 401 `signup token expired`\n- 502 when Checkout cannot be opened. `detail` says why.\n\nNotes:\n- Give `url` to a person. This call does not charge.\n- After payment the browser lands on `/v1/checkout/success`. Check `plan` `paid` and a future `paid_until` using `/v1/whoami`. An active trial is not payment confirmation. Do not call the success URL yourself.\n- Cancel leaves the subscription unpaid. Open checkout again when ready.\n"},{"id":"get-v1-checkout-success","group":"account","method":"GET","path":"/v1/checkout/success","auth":"none","summary":"Browser return after Checkout. Marks the account active when the session is paid.","also":[],"markdown":"## GET /v1/checkout/success\n\nBrowser return after Checkout. Marks the account active when the session is paid.\n\nAuth: none.\n\nQuery:\n- `session_id` (string, required). Stripe adds this.\n\nSuccess: 200 HTML. The page says \"Setup payment received\".\n\nErrors:\n- 400 `session_id is required`\n- 400 `stripe checkout is not configured`\n- 402 HTML when the session is not paid\n- 502 HTML when the session cannot be finished\n\nNotes:\n- Integrators poll `GET /v1/accounts/{account_id}` instead of calling this.\n"},{"id":"get-v1-checkout-cancelled","group":"account","method":"GET","path":"/v1/checkout/cancelled","auth":"none","summary":"Browser return when Checkout is cancelled. No charge.","also":[],"markdown":"## GET /v1/checkout/cancelled\n\nBrowser return when Checkout is cancelled. No charge.\n\nAuth: none.\n\nSuccess: 200 HTML. No subscription is activated.\n"},{"id":"post-v1-stripe-webhook","group":"account","method":"POST","path":"/v1/stripe/webhook","auth":"Stripe signature","summary":"Stripe notifies this process. An integrator does not call it.","also":[],"markdown":"## POST /v1/stripe/webhook\n\nStripe notifies this process. An integrator does not call it.\n\nAuth: Stripe signature.\n\nSuccess: 200 `{\"received\": true}` for a completed checkout and for ignored event types.\n\nErrors:\n- 400 `invalid signature`\n- 400 `event missing account`\n- 502 when Stripe cannot be read\n\nNotes:\n- Header `stripe-signature`. The success page can finish the account without this call.\n"},{"id":"post-v1-api-keys","group":"account","method":"POST","path":"/v1/api-keys","auth":"signup token, or an existing API key","summary":"Issue an API key. The secret is in this response only.","also":[],"markdown":"## POST /v1/api-keys\n\nIssue an API key. The secret is in this response only.\n\nAuth: signup token, or an existing API key.\n\nQuery:\n- `account_id` (required with `x-signup-token`). Omit it when you send a Bearer key.\n\nSuccess: 201 `{\"api_key\": \"px_...\", \"prefix\": \"px_........\"}`. `prefix` is the first 12 characters.\n\nErrors:\n- 401 `api key or signup token required`\n- 401 `signup token required` or `signup token expired`\n- 403 `account is not active`\n- 403 `confirm your email before creating an API key` (signup token only)\n- 401 `api key required`, `invalid api key`, or `api key revoked` on the Bearer path\n\nNotes:\n- The signup-token path needs `status` `active` and a confirmed email.\n- A Bearer key on an active account can issue another key. This catalog has no customer revoke route.\n"},{"id":"get-v1-whoami","group":"account","method":"GET","path":"/v1/whoami","auth":"API key","summary":"Whose key this is. The secret is not returned.","also":[],"markdown":"## GET /v1/whoami\n\nWhose key this is. The secret is not returned.\n\nAuth: API key.\n\nSuccess: 200 `account_id`, `plan`, `status`, `key_prefix`\n\nErrors:\n- 401 `api key required`\n- 401 `invalid api key`\n- 401 `api key revoked`\n\nNotes:\n- A bad key on this route is 401. On setup routes a bad key is 403.\n"},{"id":"get-v1-setup","group":"pixel","method":"GET","path":"/v1/setup","auth":"API key, account in good standing","summary":"Whether this account has a pixel installed.","also":[],"markdown":"## GET /v1/setup\n\nWhether this account has a pixel installed.\n\nAuth: API key, account in good standing.\n\nSuccess: 200 `good_standing` true, `pixel_count`, `installed_count`, `setup_complete`\n\nErrors:\n- 401 `api key required` when the Authorization header is missing\n- 403 `invalid api key`\n- 403 `api key revoked`\n- 403 `account is not in good standing` when the account is not `active`, or the plan is not `free`, `paid`, or `unlimited`\n- 400 `that install method is not available`\n\nNotes:\n- `setup_complete` is true when at least one pixel has `install_status` `installed`.\n"},{"id":"post-v1-pixels","group":"pixel","method":"POST","path":"/v1/pixels","auth":"API key, account in good standing","summary":"Mint a pixel for a site and return the install snippet.","also":[],"markdown":"## POST /v1/pixels\n\nMint a pixel for a site and return the install snippet.\n\nAuth: API key, account in good standing.\n\nBody:\n- `domain` (string, required). A host, such as `example.com`. `https://` and a leading `www.` are removed. A path, query, port, space, or `@` is refused.\n- `method` (string, optional). `cms`, `gtm`, and `utm` are refused.\n\nSuccess: 201 `pixel_id`, `domain`, `install_status` (`not_checked` on a new pixel), `checked_at`, `snippet`, `consent_enabled`, `check_error`.\n\nErrors:\n- 401 `api key required` when the Authorization header is missing\n- 403 `invalid api key`\n- 403 `api key revoked`\n- 403 `account is not in good standing` when the account is not `active`, or the plan is not `free`, `paid`, or `unlimited`\n- 400 `domain is required`\n- 400 `domain must be a host, not a page`\n- 400 `domain must be a host`\n- 400 `that install method is not available` when `method` is `cms`, `gtm`, or `utm` (body or query)\n- 409 `domain already registered` when another account owns that host\n- 502 `pixel could not be created`\n\nNotes:\n- The same account posting the same domain again gets the existing pixel, still as 201.\n- When `consent_enabled` is true the snippet is the consent tag (`/px/{pixel_id}.js`). Otherwise it is the immediate pixel snippet.\n- Paste the consent tag if you are about to turn consent on. The immediate snippet starts identification without a choice.\n"},{"id":"get-v1-pixels","group":"pixel","method":"GET","path":"/v1/pixels","auth":"API key, account in good standing","summary":"Pixels on this key's account, oldest first. Snippets are not included.","also":[],"markdown":"## GET /v1/pixels\n\nPixels on this key's account, oldest first. Snippets are not included.\n\nAuth: API key, account in good standing.\n\nSuccess: 200 `{\"pixels\": [{\"pixel_id\", \"domain\", \"install_status\", \"checked_at\"}]}`\n\nErrors:\n- 401 `api key required` when the Authorization header is missing\n- 403 `invalid api key`\n- 403 `api key revoked`\n- 403 `account is not in good standing` when the account is not `active`, or the plan is not `free`, `paid`, or `unlimited`\n- 400 `that install method is not available`\n"},{"id":"get-v1-pixels-pixel-id","group":"pixel","method":"GET","path":"/v1/pixels/{pixel_id}","auth":"API key, account in good standing","summary":"One pixel, including the current snippet.","also":[],"markdown":"## GET /v1/pixels/{pixel_id}\n\nOne pixel, including the current snippet.\n\nAuth: API key, account in good standing.\n\nSuccess: 200 same shape as create.\n\nErrors:\n- 401 `api key required` when the Authorization header is missing\n- 403 `invalid api key`\n- 403 `api key revoked`\n- 403 `account is not in good standing` when the account is not `active`, or the plan is not `free`, `paid`, or `unlimited`\n- 404 `pixel not found`\n- 400 `that install method is not available`\n\nNotes:\n- Another account's pixel is 404. A script alias is not accepted here. Use the canonical `pixel_id`.\n"},{"id":"post-v1-pixels-pixel-id-check","group":"pixel","method":"POST","path":"/v1/pixels/{pixel_id}/check","auth":"API key, account in good standing","summary":"Fetch `https://{domain}/` and record whether the HTML contains this pixel id.","also":[],"markdown":"## POST /v1/pixels/{pixel_id}/check\n\nFetch `https://{domain}/` and record whether the HTML contains this pixel id.\n\nAuth: API key, account in good standing.\n\nSuccess: 200 `pixel_id`, `domain`, `install_status`, `checked_at`, `check_error`\n\nErrors:\n- 401 `api key required` when the Authorization header is missing\n- 403 `invalid api key`\n- 403 `api key revoked`\n- 403 `account is not in good standing` when the account is not `active`, or the plan is not `free`, `paid`, or `unlimited`\n- 404 `pixel not found`\n- 400 `that install method is not available`\n- 500 `install checker is not configured`\n\nNotes:\n- `installed` means the pixel id is in a 200 page. `not_installed` means a 200 page without the id. `unreachable` means the fetch failed.\n- A failed fetch leaves a previous `installed` status in place and still sets `check_error`.\n- One same-host redirect is followed. The fetch times out in a few seconds.\n"},{"id":"patch-v1-pixels-pixel-id","group":"pixel","method":"PATCH","path":"/v1/pixels/{pixel_id}","auth":"API key, account in good standing","summary":"Change the domain. Refused after the pixel is installed.","also":[],"markdown":"## PATCH /v1/pixels/{pixel_id}\n\nChange the domain. Refused after the pixel is installed.\n\nAuth: API key, account in good standing.\n\nBody:\n- `domain` (string, required). Same host rules as create.\n- `method` optional, and `cms` / `gtm` / `utm` are refused.\n\nSuccess: 200 the pixel, with snippet.\n\nErrors:\n- 401 `api key required` when the Authorization header is missing\n- 403 `invalid api key`\n- 403 `api key revoked`\n- 403 `account is not in good standing` when the account is not `active`, or the plan is not `free`, `paid`, or `unlimited`\n- 400 `domain is required`\n- 400 `domain must be a host, not a page`\n- 400 `domain must be a host`\n- 400 `that install method is not available` when `method` is `cms`, `gtm`, or `utm` (body or query)\n- 404 `pixel not found`\n- 409 `installed pixel cannot be renamed`\n- 409 `domain already registered`\n"},{"id":"delete-v1-pixels-pixel-id","group":"pixel","method":"DELETE","path":"/v1/pixels/{pixel_id}","auth":"API key, account in good standing","summary":"Delete a pixel that is not installed.","also":[],"markdown":"## DELETE /v1/pixels/{pixel_id}\n\nDelete a pixel that is not installed.\n\nAuth: API key, account in good standing.\n\nSuccess: 204 empty body.\n\nErrors:\n- 401 `api key required` when the Authorization header is missing\n- 403 `invalid api key`\n- 403 `api key revoked`\n- 403 `account is not in good standing` when the account is not `active`, or the plan is not `free`, `paid`, or `unlimited`\n- 404 `pixel not found`\n- 409 `installed pixel cannot be deleted`\n- 400 `that install method is not available`\n"},{"id":"get-v1-pixels-pixel-id-snippets-action","group":"pixel","method":"GET","path":"/v1/pixels/{pixel_id}/snippets/{action}","auth":"API key, account in good standing","summary":"Extra paste snippet for one commerce action.","also":[],"markdown":"## GET /v1/pixels/{pixel_id}/snippets/{action}\n\nExtra paste snippet for one commerce action.\n\nAuth: API key, account in good standing.\n\nSuccess: 200 `action`, `pixel_id`, `snippet`, `button_snippet` (null when that file is absent).\n\nErrors:\n- 401 `api key required` when the Authorization header is missing\n- 403 `invalid api key`\n- 403 `api key revoked`\n- 403 `account is not in good standing` when the account is not `active`, or the plan is not `free`, `paid`, or `unlimited`\n- 404 `pixel not found`\n- 404 `snippet not found`\n\nNotes:\n- `action` is `viewed_product`, `add_to_cart`, or `checkout_completed`.\n"},{"id":"post-v1-pixels-pixel-id-email-snippet","group":"pixel","method":"POST","path":"/v1/pixels/{pixel_id}/email-snippet","auth":"API key, account in good standing","summary":"Email the immediate install snippet.","also":[],"markdown":"## POST /v1/pixels/{pixel_id}/email-snippet\n\nEmail the immediate install snippet.\n\nAuth: API key, account in good standing.\n\nBody:\n- `email` (string, required).\n- `method` optional. `cms`, `gtm`, and `utm` are refused.\n\nSuccess: 200 `{\"sent\": true, \"install_status\": \"...\"}`\n\nErrors:\n- 401 `api key required` when the Authorization header is missing\n- 403 `invalid api key`\n- 403 `api key revoked`\n- 403 `account is not in good standing` when the account is not `active`, or the plan is not `free`, `paid`, or `unlimited`\n- 400 `invalid email`\n- 404 `pixel not found`\n- 429 `wait before sending the snippet again` (10 minutes)\n\nNotes:\n- This emails the immediate snippet, including when consent is on. Paste the consent tag from the consent call when the popup should gate identification.\n"},{"id":"get-v1-pixels-pixel-id-contacts","group":"contacts","method":"GET","path":"/v1/pixels/{pixel_id}/contacts","auth":"API key, account in good standing","summary":"Read resolved people. Pass `on` or `after`, one of them.","also":[],"markdown":"## GET /v1/pixels/{pixel_id}/contacts\n\nRead resolved people. Pass `on` or `after`, one of them.\n\nAuth: API key, account in good standing.\n\nQuery:\n- `on` (`YYYY-MM-DD`). People whose first resolved UTC day is that date, in id order.\n- `after` (integer, zero or greater). People with a greater id. The same `after` returns the same rows.\n\nSuccess: Day: `pixel_id`, `on`, `contacts`. Cursor: `pixel_id`, `after`, `high_water`, `contacts`. Each contact is `id`, `hem`, `resolved_at` (UTC ISO), `contact` (the stored object).\n\nErrors:\n- 401 `api key required` when the Authorization header is missing\n- 403 `invalid api key`\n- 403 `api key revoked`\n- 403 `account is not in good standing` when the account is not `active`, or the plan is not `free`, `paid`, or `unlimited`\n- 400 `pass on or after`\n- 400 `pass on or after, not both`\n- 400 `on must be YYYY-MM-DD`\n- 400 `after must be zero or greater`\n- 404 `pixel not found`\n\nNotes:\n- `high_water` is the last id in this page, or the `after` you sent when there are no rows. Pass it as the next `after`.\n- An update keeps the original id and the original UTC day. The day filter follows that first day.\n- An unlimited key may read any pixel. A paid key may read its own. Another account's pixel is 404.\n"},{"id":"post-v1-pixels-pixel-id-contacts","group":"contacts","method":"POST","path":"/v1/pixels/{pixel_id}/contacts","auth":"API key, account in good standing","summary":"Store people that are already resolved. A customer read uses GET.","also":[],"markdown":"## POST /v1/pixels/{pixel_id}/contacts\n\nStore people that are already resolved. A customer read uses GET.\n\nAuth: API key, account in good standing.\n\nBody:\n- `contacts` (array). Each item is `hem`, `seen_at`, `contact`.\n- `hem` is 64 hex characters when `contact` is present.\n- `seen_at` is an ISO timestamp. A trailing `Z` is accepted.\n- `contact` is an object. `null` or `{}` is a miss and is skipped.\n\nSuccess: 200 `{\"pixel_id\", \"stored\"}`. `stored` counts people written, including updates. Misses are not counted.\n\nErrors:\n- 401 `api key required` when the Authorization header is missing\n- 403 `invalid api key`\n- 403 `api key revoked`\n- 403 `account is not in good standing` when the account is not `active`, or the plan is not `free`, `paid`, or `unlimited`\n- 400 `contact entry must be an object`\n- 400 `contact must be an object`\n- 400 `hem must be a 64-character hex string`\n- 400 `seen_at must be an ISO timestamp`\n- 400 `invalid request`\n- 404 `pixel not found`\n\nNotes:\n- Updating a known `hem` replaces `contact` and keeps the id and the first `resolved_at`.\n"},{"id":"get-v1-billing-quote","group":"contacts","method":"GET","path":"/v1/billing/quote","auth":"API key, account in good standing","summary":"What this account would owe. This call does not charge.","also":[],"markdown":"## GET /v1/billing/quote\n\nWhat this account would owe. This call does not charge.\n\nAuth: API key, account in good standing.\n\nSuccess: 200 `plan`, `contact_count`, `setup_cents`, `contact_cents`, `usage_cents`, `total_cents`. Paid is $1 per account/month. Trial and unlimited are zero. No usage charges.\n\nErrors:\n- 401 `api key required` when the Authorization header is missing\n- 403 `invalid api key`\n- 403 `api key revoked`\n- 403 `account is not in good standing` when the account is not `active`, or the plan is not `free`, `paid`, or `unlimited`\n"},{"id":"post-v1-pixels-pixel-id-consent","group":"consent","method":"POST","path":"/v1/pixels/{pixel_id}/consent","auth":"API key, account active","summary":"Turn on the permission popup and publish the policy.","also":[],"markdown":"## POST /v1/pixels/{pixel_id}/consent\n\nTurn on the permission popup and publish the policy.\n\nAuth: API key, account active.\n\nBody:\n- `controller_name` (string, required).\n- `purposes` (array, at least one). Allowed: `analytics`, `resolution`, `newsletter`, `email_sharing`, `phone_sharing`, `partner_email`, `partner_calls`, `partner_sms`. Unknown names are dropped.\n- `controller_type` optional. `company` (default), `person`, or `organization`.\n- `controller_contact`, `controller_jurisdiction`, `rule_version` optional strings.\n- `data_sharing` boolean. False when omitted.\n- `contact_channels` optional. Allowed: `email`, `phone`, `sms`, `agentic`.\n- `disclosed_parties` optional array of strings.\n- `site_policy_url` optional. The site's own privacy policy, a full https:// link. The popup links to it, and the hosted page becomes a short identification notice that adds to it.\n\nSuccess: 200 `pixel_id`, `consent_enabled` true, `policy_version`, `policy_hash`, `policy_url`, `snippet`, `purposes`. The snippet is `<script async src=\"{origin}/px/{pixel_id}.js\"></script>`.\n\nErrors:\n- 400 `invalid request`\n- 400 `controller name is required`\n- 400 `choose at least one purpose`\n- 400 `controller type must be company, person, or organization`\n- 401 `invalid api key`\n- 403 `account is not active`\n- 404 `pixel not found`\n\nNotes:\n- Install `snippet`, not the immediate pixel snippet. Identification loads only after the visitor accepts `resolution` on the current policy.\n- Saving the same policy text keeps `policy_version`. A text change increments it. Visitors must accept the new hash.\n- An unlimited key may configure any pixel. The path id must be the canonical pixel id.\n"},{"id":"get-v1-pixels-pixel-id-consent","group":"consent","method":"GET","path":"/v1/pixels/{pixel_id}/consent","auth":"API key, account active","summary":"Consent log for one pixel, newest first, at most 200 rows.","also":[],"markdown":"## GET /v1/pixels/{pixel_id}/consent\n\nConsent log for one pixel, newest first, at most 200 rows.\n\nAuth: API key, account active.\n\nQuery:\n- `state` optional: `accepted`, `declined`, or `withdrawn`.\n\nSuccess: 200 `pixel_id`, `events`. Each event includes the receipt fields plus `receipt`, `signature`, and `signature_alg` `hmac-sha256`.\n\nErrors:\n- 400 `invalid state`\n- 401 `invalid api key`\n- 403 `account is not active`\n- 404 `pixel not found`\n"},{"id":"get-px-pixel-id-js","group":"consent","method":"GET","path":"/px/{pixel_id}.js","auth":"none","summary":"The permission popup. Browsers load this. It is not cached.","also":[],"markdown":"## GET /px/{pixel_id}.js\n\nThe permission popup. Browsers load this. It is not cached.\n\nAuth: none.\n\nSuccess: 200 `application/javascript`. `Cache-Control: no-store`.\n\nErrors:\n- 404 `consent tag not found` when the pixel is missing, consent is off, or there is no policy hash\n\nNotes:\n- The path accepts the canonical pixel id or a script alias. The banner records the canonical id.\n- A visitor with Global Privacy Control is treated as a decline and nothing is stored.\n- Accepting `resolution` on the current policy is what loads the identification tag.\n"},{"id":"get-v1-policy-pixel-id","group":"consent","method":"GET","path":"/v1/policy/{pixel_id}","auth":"none","summary":"The current privacy notice as HTML.","also":["GET /api/v1/policy/{pixel_id}"],"markdown":"## GET /v1/policy/{pixel_id}\n\nThe current privacy notice as HTML.\n\nAuth: none.\n\nSame handler: `GET /api/v1/policy/{pixel_id}`.\n\nSuccess: 200 HTML.\n\nErrors:\n- 404 `policy not found`\n\nNotes:\n- A script alias in the path resolves to the canonical pixel.\n"},{"id":"post-v1-consent-record","group":"consent","method":"POST","path":"/v1/consent/record","auth":"none","summary":"The popup writes one choice. A saved choice is 202. An ignored choice is 204 with an empty body.","also":["POST /api/v1/pixel/fire"],"markdown":"## POST /v1/consent/record\n\nThe popup writes one choice. A saved choice is 202. An ignored choice is 204 with an empty body.\n\nAuth: none.\n\nSame handler: `POST /api/v1/pixel/fire`.\n\nBody:\n- `pixel_id` (string). Canonical id or script alias.\n- `visitor_id` (string).\n- `url` (string). Page URL. The host must be the pixel domain or a subdomain of it.\n- `consent`: `accepted`, `declined`, or `withdrawn`.\n- `affirmative` true is required for `accepted`.\n- `policy_version_hash` must equal the current policy hash for `accepted`.\n- `purposes` for `accepted` must be a non-empty subset of the configured purposes.\n- `gpc` true is ignored and stored as nothing.\n\nSuccess: 202 `recorded` true, `receipt_id`, `policy_version_hash`, `purposes`, `receipt`, `signature`, `signature_alg`. The receipt is `pixel_id`, `receipt_id`, `visitor_id`, `origin`, `consent_state`, `policy_hash`, `purposes`, `recorded_at`.\n\nErrors:\n- 403 `{\"recorded\": false, \"reason\": \"site_mismatch\", \"domain\": \"...\"}` when the page host is a different site\n\nNotes:\n- 204 (empty) covers bad JSON, an unknown shape, Global Privacy Control, a missing id, an unknown pixel, consent turned off, an accept that is not affirmative, a stale policy hash, or a purpose that was not configured.\n- Decline and withdraw store an empty purpose list.\n- Verify a receipt with `POST /v1/consent/verify`. The signing secret is not in this response.\n"},{"id":"get-v1-consent-check","group":"consent","method":"GET","path":"/v1/consent/check","auth":"none","summary":"Whether the latest receipt allows a purpose.","also":[],"markdown":"## GET /v1/consent/check\n\nWhether the latest receipt allows a purpose.\n\nAuth: none.\n\nQuery:\n- `pixel_id` (required). Canonical id or a script alias.\n- `visitor_id` (required).\n- `required_purpose` optional, one of the purpose names.\n\nSuccess: 200 `permitted`, `reason`, and when a row exists: `receipt_id`, `policy_version_hash`, `current_policy_hash`, `purposes`, `recorded_at`, `receipt`, `signature`, `signature_alg`.\n\nErrors:\n- 400 `invalid required_purpose`\n\nNotes:\n- `reason` is `ok`, `no_consent_record`, `consent_off`, `declined_or_withdrawn`, `policy_version_stale`, or `purpose_not_granted`.\n- `permitted` is true only when `reason` is `ok`. No row, and an unknown pixel id, both return `permitted` false and `reason` `no_consent_record` without a receipt.\n"},{"id":"get-v1-consent-receipt","group":"consent","method":"GET","path":"/v1/consent/receipt","auth":"none","summary":"The latest receipt for a visitor, with a signature.","also":["GET /api/v1/consent/receipt"],"markdown":"## GET /v1/consent/receipt\n\nThe latest receipt for a visitor, with a signature.\n\nAuth: none.\n\nSame handler: `GET /api/v1/consent/receipt`.\n\nQuery:\n- `pixel_id` (required).\n- `visitor_id` (required).\n\nSuccess: 200 the check object plus `valid` true, when a receipt exists.\n\nErrors:\n- 404 `no consent record`\n\nNotes:\n- Accepts a script alias. An unknown id is 404 `no consent record`.\n"},{"id":"post-v1-consent-verify","group":"consent","method":"POST","path":"/v1/consent/verify","auth":"none","summary":"Check a receipt signature.","also":[],"markdown":"## POST /v1/consent/verify\n\nCheck a receipt signature.\n\nAuth: none.\n\nBody:\n- `receipt` (object, required). The receipt object from record, check, or the log.\n- `signature` (string).\n\nSuccess: 200 `{\"valid\": true}` or `{\"valid\": false}`.\n\nErrors:\n- 400 `invalid request`\n- 400 `receipt is required`\n"},{"id":"get-pixel-js","group":"scripts","method":"GET","path":"/pixel.js","auth":"none","summary":"Identification script referenced by the immediate snippet.","also":[],"markdown":"## GET /pixel.js\n\nIdentification script referenced by the immediate snippet.\n\nAuth: none.\n\nSuccess: 200 `application/javascript`.\n"},{"id":"get-pixel-dev-js","group":"scripts","method":"GET","path":"/pixel.dev.js","auth":"none","summary":"Dev beacon. Customer installs use `/pixel.js` or the consent tag.","also":[],"markdown":"## GET /pixel.dev.js\n\nDev beacon. Customer installs use `/pixel.js` or the consent tag.\n\nAuth: none.\n\nSuccess: 200 `application/javascript`.\n"},{"id":"get-pixel-popup-js","group":"scripts","method":"GET","path":"/pixel_popup.js","auth":"none","summary":"Popup script referenced by the immediate snippet.","also":[],"markdown":"## GET /pixel_popup.js\n\nPopup script referenced by the immediate snippet.\n\nAuth: none.\n\nSuccess: 200 `application/javascript`.\n"},{"id":"get-pixels-pixel-id-p-js","group":"scripts","method":"GET","path":"/pixels/{pixel_id}/p.js","auth":"none","summary":"Redirect to Delivr's tag for this pixel id.","also":[],"markdown":"## GET /pixels/{pixel_id}/p.js\n\nRedirect to Delivr's tag for this pixel id.\n\nAuth: none.\n\nSuccess: 302 `Location` on the Delivr CDN.\n\nErrors:\n- 404 `pixel not found` when the id is not 1 to 80 letters, digits, `_`, or `-`\n\nNotes:\n- This process does not host that file. A stub pixel id has no tag on the CDN.\n"},{"id":"get-external-api-install-pixel-delivr-pixel","group":"scripts","method":"GET","path":"/external_api/install-pixel/delivr-pixel","auth":"none","summary":"Whether this pixel id belongs on this host. Used by the tag, not by account setup.","also":[],"markdown":"## GET /external_api/install-pixel/delivr-pixel\n\nWhether this pixel id belongs on this host. Used by the tag, not by account setup.\n\nAuth: none.\n\nQuery:\n- `pid` or `dpid` (the pixel id).\n- `host` (the page host).\n\nSuccess: 200 `{\"pixel_id\": \"<id>\"}` when the host is that pixel's domain or a subdomain. Otherwise `{\"pixel_id\": null}`.\n\nNotes:\n- Response header `Access-Control-Allow-Origin: *`.\n"},{"id":"post-v1-accounts-account-id-activate","group":"service","method":"POST","path":"/v1/accounts/{account_id}/activate","auth":"unlimited API key","summary":"Mark an account active without changing its plan. A paid customer key receives 403.","also":[],"markdown":"## POST /v1/accounts/{account_id}/activate\n\nMark an account active without changing its plan. A paid customer key receives 403.\n\nAuth: unlimited API key.\n\nSuccess: 200 `account_id`, `status` `active`, `plan` (unchanged).\n\nErrors:\n- 401 `unlimited key required`\n- 401 `invalid api key`\n- 403 `unlimited key required`\n- 404 `account not found`\n\nNotes:\n- Public trial becomes active through email confirmation. This call is for a holder of the unlimited key.\n"},{"id":"post-v1-pixels-pipeline","group":"service","method":"POST","path":"/v1/pixels/pipeline","auth":"unlimited API key","summary":"Mint a `cms` or `utm` pixel on a customer's paid account. A paid key receives 403.","also":[],"markdown":"## POST /v1/pixels/pipeline\n\nMint a `cms` or `utm` pixel on a customer's paid account. A paid key receives 403.\n\nAuth: unlimited API key.\n\nBody:\n- `domain` (string, required). Same host rules as `POST /v1/pixels`.\n- `pipeline` (string, required). `utm` or `cms`.\n- `account_id` or `external_user_id`. One of them is required.\n\nSuccess: 201 `pixel_id`, `account_id`, `domain`, `install_status`, `pipeline`, `snippet`, `pixel_js_url`, `delivr_script_url`.\n\nErrors:\n- 400 `pipeline must be utm or cms`\n- 400 `account_id or external_user_id is required`\n- 400 domain host errors\n- 401 `api key required` or `invalid api key`\n- 403 `unlimited key required`\n- 403 `customer account is not in good standing` (the target must be `active` and `paid`)\n- 404 `account not found`\n- 409 `domain already registered`\n- 502 `pixel could not be created`\n"},{"id":"get","group":"pages","method":"GET","path":"/","auth":"none","summary":"HTML front. The Host header picks the pixel site, the permission site, or the API home.","also":[],"markdown":"## GET /\n\nHTML front. The Host header picks the pixel site, the permission site, or the API home.\n\nAuth: none.\n\nSuccess: 200 HTML.\n"},{"id":"get-site-pixel","group":"pages","method":"GET","path":"/site/pixel","auth":"none","summary":"Pixel marketing page.","also":[],"markdown":"## GET /site/pixel\n\nPixel marketing page.\n\nAuth: none.\n\nSuccess: 200 HTML.\n"},{"id":"get-site-permission","group":"pages","method":"GET","path":"/site/permission","auth":"none","summary":"Permission marketing page.","also":[],"markdown":"## GET /site/permission\n\nPermission marketing page.\n\nAuth: none.\n\nSuccess: 200 HTML.\n"},{"id":"get-demo","group":"pages","method":"GET","path":"/demo","auth":"none","summary":"HTML form that provisions a consent tag on the shared demo account.","also":[],"markdown":"## GET /demo\n\nHTML form that provisions a consent tag on the shared demo account.\n\nAuth: none.\n\nSuccess: 200 HTML.\n\nNotes:\n- Customer pixels come from `POST /v1/pixels` after signup. `POST /demo/provision` writes the demo account.\n"},{"id":"post-demo-provision","group":"pages","method":"POST","path":"/demo/provision","auth":"none","summary":"Form post for the demo page. Creates or reuses a pixel on the demo account and turns consent on.","also":[],"markdown":"## POST /demo/provision\n\nForm post for the demo page. Creates or reuses a pixel on the demo account and turns consent on.\n\nAuth: none.\n\nSuccess: 200 HTML containing the tag.\n\nNotes:\n- Do not call this to open a customer account.\n"},{"id":"get-log","group":"pages","method":"GET","path":"/log","auth":"none","summary":"HTML page. A person pastes a pixel id and an API key to read that pixel's consent log.","also":[],"markdown":"## GET /log\n\nHTML page. A person pastes a pixel id and an API key to read that pixel's consent log.\n\nAuth: none.\n\nSuccess: 200 HTML. The page calls `GET /v1/pixels/{pixel_id}/consent`.\n"}],"omitted":["/console","/api/admin","/v1/admin"]}