Skip to content

Help

Fohrkast public API v1 (/api/in/v1)

Every machine call a business's website, server, scanner or AI gate makes to Fohrkast. Fixed on 19 Oct 2026 before anything printed a path; v1 changes only by adding. Everything Fohrkast prints (the banner script, setup screens, the scanner and the AI gate) uses these paths.

Paths

MethodPathKey scopeTest keyWhat it does
GET/api/in/v1/shield/config/<embed key>none (public)n/aThe banner's settings: business name, purposes, notice version
POST/api/in/v1/shield/consentnone; the site's own origin (CORS)n/aRecords one person's choice from the banner or the hosted page: { recorded, receipt } (Consent receipts, below)
POST/api/in/v1/shield/heldnone; the site's own origin (CORS)n/aFrom the banner in report-only, the known trackers it would have held (tracker id, script host), once per browser per IST day; 204, or 409 when the website is not in report-only
GET/api/in/v1/shield/consent?token=&purpose=consent checkyesOne consent check: { granted, notice, at } (plus chooser, check, ask_again where they apply)
GET/api/in/v1/shield/events?since=<seq>consent checkyesConsent given and withdrawn after since, 500 at a time (next, more)
POST/api/in/v1/shield/guardianconsent checkrefusedThe business's own system confirms or refuses a parent or guardian (Rule 10(1)(a))
GET/api/in/v1/shield/no-offers?after=<hex>consent checkyesThe NO OFFERS list: keyed identifiers of people who were erased or withdrew, 1,000 at a time (next, more)
GET/api/in/v1/gate/stategateyesThe signed lease and the AI register
POST/api/in/v1/gate/summarygaterefusedCounts per agent for a window
POST/api/in/v1/gate/eventsgaterefusedagent.tripped, tool.changed, gate.local_stop
POST/api/in/v1/gate/log-rootgaterefusedThe daily root of the gate's own log
POST/api/in/v1/scan/activatescanneryesBefore every run. { version, sha384, nonce } (SHA-384 of the scanner file, 48 random bytes as hex) to { token, expires_in }, below
POST/api/in/v1/scanscannerrefusedA scan report: labels and counts
POST/api/in/v1/scan/locatescannerrefusedWhere one person's records sit, for a rights request: counts
GET/api/in/v1/scan/notify?incident=I-1A2B3CscanneryesThe approved job for one incident: tables, contact columns, channels
POST/api/in/v1/scan/notifyscannerrefusedHow many people the list holds and how they can be reached: counts

The paths before v1 answer exactly the same (one handler each), for sites, scanners and gates set up before: /api/shield/config/<key>, /api/shield/consent (POST), /api/shield/v1/consent (GET), /api/shield/v1/events, /api/shield/v1/guardian, /api/gate/v1/{state,summary,events,log-root}, /api/scan, /api/scan/locate, /api/scan/notify.

Scanner activation

fohrkast-scan reads nothing until Fohrkast activates the run. POST /api/in/v1/scan/activate answers only for a live scanner key of an active workspace, at most 120 times a key an hour (429), for a version not below Fohrkast's minimum and a file SHA-384 Fohrkast released (403 otherwise). The token is <base64url payload>.<base64url signature>: payload v, kh (the key's SHA-256), ws (the workspace), ver, sha (the file's SHA-384), nonce, iat, exp (15 minutes); the signature is ECDSA P-384 with SHA-384 by Fohrkast's activation key (AWS KMS), which each release carries the public half of. Every scanner call after it (/scan, /scan/locate, /scan/notify, and their paths before v1) sends the token in X-Fohrkast-Activation, and is refused with 401 unless the token verifies, has not expired, and names the same key and workspace; a report whose scanner version differs from the token's is refused too.

Keys

Authorization: Bearer <key>. A key is shown once at creation; Fohrkast keeps only its SHA-256. Each key has one scope (consent check, gate, scanner, front-desk tablet) and one business.

  • fk_live_ + 96 hex (384 random bits): may add to the business's record (its hash-chained log).
  • fk_test_ + 96 hex: reads only. A call that would add to the record answers 403 and records nothing.
  • Earlier keys (fk_live_ or fk_test_ + 32 hex) work until retired. Keys made before v1 (fks_, fkg_, fkc_, fkd_ + 32 hex) are live.

Errors

JSON { "error": "<short reason>" } with the status: 400 malformed question, 401 unknown, expired or wrong-scope key, or a scanner call without a valid activation, 403 test key on a call that writes, 404 nothing to answer, 413 body too large, 422 body refused by the schema (scanner reports add "reason": the error class, never an echo of what was sent). The browser call POST /api/in/v1/shield/consent answers with the status alone (400, 403 other origin, 404 unknown site, 413).

Consent receipts

receipt in the POST answer is a 384-bit token (96 hex; earlier receipts have 32 hex and still open) for that one record; Fohrkast keeps only its SHA-256. The person's receipt is <Fohrkast>/c/<embed key>/receipt#w=<receipt>: the token sits in the URL fragment, which no browser sends to a server, and the page posts it in a request body. It shows that record (purposes and choices, notice version, rendition id, time in IST, a reference) and lets the person withdraw. It cannot give consent. A withdrawal there reaches webhooks and the feed as consent.withdrawn, like one from the banner.

Webhook and feed body (v1)

Sent to each registered https endpoint within five minutes of consent being given or withdrawn, retried for about two days. The events feed returns the same objects.

{
  "v": 1,
  "type": "consent.given",
  "seq": 1042,
  "property": "site.shop",
  "token_hash": "<SHA-256 of the 16 bytes behind window.Fohrkast.token(), 64 hex>",
  "purposes": [{ "purpose": "offers", "granted": true }],
  "chooser": "parent",
  "at": "2026-10-19T04:00:00.000Z"
}
  • v: this format's version. A new field may appear within v1; a field never changes meaning or disappears.
  • type: consent.given or consent.withdrawn. seq: the business's log position (the feed's since).
  • token_hash is absent once the person's erasure has started. chooser appears only for a parent or guardian; their consent is sent only once the check confirms it, and their withdrawals carry only the purposes refused.
  • at is UTC (ISO 8601); screens and exports show IST.

Headers: X-Fohrkast-Signature-V2: t=<unix seconds>,v2=<hex HMAC-SHA384 of "<t>.<raw body>" under the endpoint secret> (check this one), X-Fohrkast-Signature: t=<the same>,v1=<hex HMAC-SHA256 of the same> (kept for receivers built earlier), X-Fohrkast-Delivery (one id per delivery, kept across retries), X-Fohrkast-Event (the type). Verify against the raw body; refuse a signature older than five minutes and a delivery id already seen.

NO OFFERS list (v1)

{ "v": 1, "keyed": ["<64 hex>"], "next": "<64 hex>", "more": false }. Each entry is the hex part of a keyed identifier (k1:<scheme>:<hex>): HMAC-SHA256 under the business's identifier key over <scheme>:<normalised email or mobile>. A person joins when the business records its own deletion on their erasure request, or when staff check who is asking on their withdrawal. A marketing export or connector keys its own list the same way and leaves out every match; Fohrkast never holds the identifier. Kept after erasure pending counsel.