Skip to content

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

HeaderWhat it isShape
X-Cloak-App-IdAn opaque slug for your app or site. Which independent product this call came from.lower-cased, [a-z0-9._-], ≤64 chars
X-Cloak-End-UserAn 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-Id is 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-User is 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-User must 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_ref counter 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 treat end_user_ref as 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.

// TypeScript
const 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
});
# Python
client = 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).

Terminal window
CLOAK_APP_ID=acme-site cloak-proxy

Starter-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).

src/lib/brand.config.ts
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.

Terminal window
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-User must 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.