End-user attribution

Set x-odnoga-end-user on every call. Use a stable, opaque id (your DB primary key, not email). The pre-rebrand x-airouter-end-user is still honoured, as is the OpenAI-compatible user body field.

What you unlock

FeatureRequires header?
Total cost / requests per workspaceno
Top end users rollupyes
Stripe meter passthrough (bill your tenants)yes
Per-user credits and hard stopsyes
Subscriptions billed to your own Stripeyes
Data-subject export and erasure for one useryes
Sticky A/B prompt bucketyes

Constraints

  • Max 256 chars.
  • Send an opaque pseudonym, never personal data. If the value looks like an email address or a phone number, odnoga replaces it with a stable pii_<digest> hash before storing it — attribution keeps working, but the raw value is discarded.
  • The identifier stays inside odnoga: it is never forwarded to the model vendor (the OpenAI user field is stripped from the upstream payload).
  • It is deleted with the rest of the request data by your workspace retention window, and can be erased on demand for one user — see Data protection.
  • Use the same id across all calls for that user — switching ids splits their analytics and breaks A/B stickiness.

Per-user limits

Per-user hard stops are enforced, through wallets: give each end user a balance in your own unit, and odnoga debits it per request and refuses with 402 end_user_exhausted when it reaches zero — before any vendor is called. Turn them on under Billing → End-user wallets.

Budgets set in dollars remain workspace- and tenant-level. The per-user control is the wallet.

Anonymous / signed-out users

Use a stable session cookie value, prefixed (anon_…). Don't send a literal "anonymous" for everyone — you'll end up with one mega-user in your analytics.

Server-rendered vs. EUT

CallerHow to set
Your backend on behalf of a userx-odnoga-end-user: <userId> on every fetch.
Browser using an EUTThe end-user id is baked into the JWT. The header is ignored.