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. Use opaque metadata values only: do not put an end customer’s identity or request or response content in these headers. Do not rely on the gateway to clean a value after it has been sent.
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 is carried on relayed requests when it is set and valid; the end-user token is carried as a header, not in the upstream request body. There is no plaintext relay tier to opt into: where a route requires Level-1 client proof, the relay rejects a call that lacks that proof. The proof gate does not establish that every identifier was detected or tokenised; detection is bounded by the published recall.
The privacy line (non-negotiable)
Use pseudonymous metadata only. Do not send end-customer PII or prompt 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 receives the header value your client sends, but not your mapping; a directly identifying value can still identify the person.- Pass a pseudonym before egress.
X-Cloak-End-Usermust be an opaque token such as a random id, salted hash, or session handle. Do not use a real name, email address, phone number, or another identifier. If your SDK provides client-side pseudonymisation, it runs before the request leaves your machine and is limited to the values that implementation recognises; a missed value can pass unchanged (see Detection coverage). Raw REST callers must provide an opaque token themselves. Do not rely on a gateway-side fallback to detect, rewrite, or drop a value. - Keep the headers metadata-only. Do not put prompt or response text in either header. The recorder reads usage metadata and the two headers; it does not need 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.
- Protocol signals: whether a receipt was issued and whether the call arrived with the required client proof. These are not measures of detection recall.
- No geo, no IP: no region or IP is recorded.
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.
// TypeScriptimport { CloakAPI } from "@cloakapi/sdk";
declare function showReviewDialog(egressBody: string): Promise<boolean>;
const client = new CloakAPI({ apiKey: "cloak_…", appId: "acme-site", // or: export CLOAK_APP_ID=acme-site reviewRequest: async (egressBody) => { const approved = await showReviewDialog(egressBody); return { approved: approved === true, text: egressBody }; },});
await client.messages.create({ model: "gpt-4o", messages: [{ role: "user", content: "…" }], endUser: "usr_8f3a…", // opaque token you control — never PII});# Pythonfrom cloakapi import CloakAPI
def approve(review): shown = review.body return confirm_exact(shown) is True and shown == review.body
client = CloakAPI(api_key="cloak_…", app_id="acme-site", review_callback=approve)
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.
Self-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.
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 gateway is not a supported cleanup step for this header; design the value to be pseudonymous before sending it. - 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 for metadata attribution. X-Cloak-App-Id attributes at
the app level; X-Cloak-End-User can carry an end-user pseudonym. Keep both
values opaque and content-free. Client-side SDK transformations, where
implemented, are bounded; do not rely on a server-side cleanup step. The
recorder is off by default, so no attribution facts are recorded until
CloakAPI enables it. A signed receipt may include an end-user pseudonym when
the header is present; send a pseudonym, not a real identifier.