Attribution headers
CloakAPI supports two optional relay headers that let a build attribute its usage back to the app — and, if you choose, to a pseudonymous end-user — so CloakAPI can map the ecosystem of products built on it. This is metadata-only: it never carries your end customers’ real identity and never carries request or response content. The blind-relay guarantee is unchanged.
The two headers
| Header | What it is | Shape |
|---|---|---|
X-Cloak-App-Id | An opaque slug for your app or site. Which independent product this call came from. | lower-cased, [a-z0-9._-], ≤64 chars |
X-Cloak-End-User | An opaque pseudonymous token for one of your end users. Optional. | [A-Za-z0-9._:-], ≤128 chars |
Both are optional and additive:
- If
X-Cloak-App-Idis unset or invalid, the header is omitted and the call is attributed only at your builder-account level (coarser, but still works). X-Cloak-End-Useris never auto-filled. You pass your own opaque token, or nothing. It is never serialised into the relay body — it rides as a header only, and is lifted out before the request is sent upstream.
The app-id rides on every request the gateway relays; the end-user token never reaches the upstream provider in the body. There is no plaintext relay tier to opt into: the gateway is Level-1-only and fail-closed — a request that would relay un-tokenised content upstream is rejected (HTTP 428), so attribution only ever accompanies tokenised traffic.
The privacy line (non-negotiable)
Pseudonymous and metadata-only. Never end-customer PII. Never content.
X-Cloak-End-Usermust be an opaque token — a random id, a salted hash, a session handle you control. Never a real name, email, phone number, or any other PII. You hold any mapping from that token back to a real person on your side; CloakAPI holds only the token. We cannot re-identify your end customer, by design — the same posture as the tokeniser (we hold no re-identification map).- A PII-shaped value is dropped, not stored. The gateway validates the
end-user value against the opaque-token shape and runs it through the same
structured PII detectors used for tokenisation (on the header value only,
content-free). If it looks like an email / phone / name / national-id / card /
bank identifier, the value is dropped — the call is still attributed at the
app level, and a
pii_in_end_user_refcounter increments so we can nudge you to fix it. The SDKs apply the same shape guard client-side, before egress, so a careless value is dropped before egress. This guard is detector-bound, not absolute — an identifier the structured detectors do not match (for example a free-form name outside the on-device dictionary) can still be passed in this header, so treatend_user_refas a field you must keep pseudonymous yourself (see Detection coverage). - Never content. These headers carry no prompt or response text. The recorder reads only already-parsed usage metadata and the two headers — it has no access to request or response bodies (asserted by a code-scan test).
What is recorded (content-free)
When the recorder is enabled, CloakAPI stores one content-free fact row per completed call, rolled up per app and per pseudonymous end-user. Everything below is metadata CloakAPI already sees as the billed relay — plus the two attribution keys:
- Provenance: builder account,
app_id,end_user_ref(nullable), which surface (rest/mcp/sdk-*/proxy/starter-kit), SDK and engine version. - Call usage: timestamp, model, provider, tier, token counts (input / output / total), provider cost, markup, latency, status.
- Content-free privacy signals: how many PII spans were tokenised (a count, never the content), whether a receipt was issued / verified.
- Coarse geo (optional): region/country derived from IP at region level — the raw IP is not stored.
Rolled up, this becomes usage, cost, and growth metrics (active apps, active end-users, retention) — CloakAPI’s internal ecosystem analytics. It is not a builder-facing dashboard today. There is no table that holds content, a raw IP, or a pseudonym-to-identity map.
Setting the headers per surface
You do not need to touch the header names. Each build-surface sets
X-Cloak-App-Id for you from a config value, and exposes an optional per-call
parameter for the end-user token.
SDKs (TypeScript, Python, Go, Rust, Java, PHP, Ruby, .NET)
Set the app id via the client option or the CLOAK_APP_ID environment variable;
pass the pseudonymous end-user token per call.
// TypeScriptconst client = new CloakAPI({ apiKey: "cloak_…", appId: "acme-site" });// or: export CLOAK_APP_ID=acme-site
await client.messages.create({ model: "gpt-4o", messages: [{ role: "user", content: "…" }], endUser: "usr_8f3a…", // opaque token you control — never PII});# Pythonclient = CloakAPI(api_key="cloak_…", app_id="acme-site") # or CLOAK_APP_ID env
client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "…"}], end_user="usr_8f3a…", # opaque token — never PII)The app id is resolved from the client option first, then CLOAK_APP_ID. An
invalid value is dropped (header omitted), never an error.
Hosted MCP (@cloakapi/mcp-server)
Set the app id via the CLOAKAPI_APP_ID environment variable. The
private_completion / private_messages tools accept an optional end_user
argument for the pseudonymous token.
{ "mcpServers": { "cloakapi": { "command": "npx", "args": ["-y", "@cloakapi/mcp-server"], "env": { "CLOAKAPI_API_KEY": "cloak_live_…", "CLOAKAPI_APP_ID": "acme-site" } } }}Drop-in proxy (cloak-proxy)
Set the app id via the CLOAK_APP_ID environment variable. The proxy attributes
at app level only — it has no per-call end-user surface (a proxy sits below
your request routing, so it cannot know your end-user token).
CLOAK_APP_ID=acme-site cloak-proxyStarter-kit
The app id defaults from your brand config: CLOAK_APP_ID, then brand.appId,
then a slug of brand.name in src/lib/brand.config.ts. The chat route lifts an
optional endUser from the request body into X-Cloak-End-User (shape-guarded).
export const brand: BrandConfig = { name: "Acme Private AI", appId: "acme-private-ai", // opaque app slug // …};REST (raw HTTP)
Send the headers yourself. Absence just means builder-account-level attribution.
curl https://api.cloakapi.io/api/v1/chat/completions \ -H "Authorization: Bearer cloak_…" \ -H "X-Cloak-App-Id: acme-site" \ -H "X-Cloak-End-User: usr_8f3a…" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}]}'Your responsibilities as a builder
- Pass an opaque token, never PII.
X-Cloak-End-Usermust be a pseudonym you control. Do not put a name, email, phone number, or any identifier that could directly identify a person. The guard drops PII-shaped values, but designing for it is your obligation. - Disclose to your users. If you enable end-user attribution, disclose in your own privacy notice that usage metadata (and a pseudonym you supply) is processed by CloakAPI as your sub-processor. CloakAPI processes this as content-free metadata on your behalf — see the sub-processor list and the DPA.
- Keep the pseudonym stable and meaningless. A salted hash of your internal user id works well — stable enough for growth metrics, meaningless to anyone who does not hold your salt.
Summary
Two optional headers, both content-free. X-Cloak-App-Id attributes at the app
level; X-Cloak-End-User optionally attributes at a pseudonymous end-user
level. Never PII, never content — and a PII-shaped end-user value is dropped, not
stored. The recorder is off by default, so nothing is recorded until CloakAPI
enables it.