← Back to Docs
Integration

End-user identification header

If one DeRouter API key serves many of your own users, add one request header so we can attribute upstream policy refusals to the user who caused them and block only that user. Without it, the only unit we can act on is your entire key.

Why

Model providers refuse some requests on policy grounds. When refusals pile up on one API key within an hour, the key is temporarily suspended for that model to protect the account serving it — even if a single end user caused all of them.

With the header, the platform sees which of your users produced the refusals, writes a refusal rule scoped to that key × model × those ids, and lifts the suspension as soon as every offending request is covered. Everyone else on your key keeps working.

The header

NameX-DeRouter-End-User: <id>
Also acceptedX-End-User: <id> — same meaning. If both headers are present, X-DeRouter-End-User wins.
Format^[A-Za-z0-9_.:-]{1,128}$ — letters, digits and _ . : - only, 1–128 characters
ValueYour internal user id or a hash of it. It must be stable (the same user always gets the same id) and must not contain personal data such as an email address or phone number.
Who sets itYour server, on every request, overwriting anything the end user sent. Never pass through a client-supplied value: a user could impersonate someone else or rotate ids to evade a block.
Invalid valueTreated as absent. The request is processed normally and nothing is logged for the id — no error is returned.
Where it worksAll API entry points: /proxy/v1/messages, /openai/v1/* (chat completions, responses) and the Realtime WebSocket upgrade request.

Examples

curl "https://beta1-api.derouter.network/proxy/v1/messages" \
  -H "x-api-key: $DEROUTER_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "X-DeRouter-End-User: u_8812" \
  -H "content-type: application/json" \
  -d '{"model":"claude-sonnet-4-6","max_tokens":64,"messages":[{"role":"user","content":"hi"}]}'

# Same header on the OpenAI-compatible endpoints:
#   POST https://beta1-api.derouter.network/openai/v1/chat/completions
#   POST https://beta1-api.derouter.network/openai/v1/responses
# and on the Realtime WebSocket upgrade request.
// Anthropic TypeScript SDK — set it once per end user
const client = new Anthropic({
  apiKey: process.env.DEROUTER_KEY,
  baseURL: "https://beta1-api.derouter.network/proxy",
  defaultHeaders: { "X-DeRouter-End-User": currentUser.id },  // your id, set server-side
});

// OpenAI Python SDK
client = OpenAI(
    api_key=os.environ["DEROUTER_KEY"],
    base_url="https://beta1-api.derouter.network/openai/v1",
    default_headers={"X-DeRouter-End-User": current_user.id},
)

What the platform does with it

  • It is stripped at the gateway and never forwarded to the model provider.
  • It is recorded with the request so refusals can be attributed to a user id.
  • When a user has to be blocked, the rule is scoped to your key, the affected model and that id. Rules are written by platform operators, or automatically after repeated upstream policy refusals on one key × model.
  • Blocked users get 403 content_refusal; the rest of your users on the same key are unaffected.

What you get back

Every response carries a request-id header. Error bodies also include request_id — quote it when contacting support.

SituationHow it looks
Normal completion200 · standard Anthropic / OpenAI response body
The model provider refused the request on policy grounds200 · stop_reason=refusal · empty content; counts toward the per-key suspension threshold
This end user (or this content) is blocked by a refusal rule403 · content_refusal · not dispatched, not billed; you know which id you sent, so you can act on that user on your side
Your key is temporarily suspended for one model after repeated refusals403 · permission_error · other models keep working; lifted automatically once the offending users are covered by rules, or by support
Rate limit / balance429 / 402 · see the error reference; retry-after header on 429

The 403 bodies never echo the end-user id back; you already know which id you attached to the request.

HTTP/1.1 403
request-id: req_f240ce55d3104bf3b94c8be4

{ "type": "error",
  "error": {
    "type": "content_refusal",
    "message": "This request was declined by platform policy and will not be processed. If you believe this is an error, contact support."
  },
  "request_id": "req_f240ce55d3104bf3b94c8be4" }

# OpenAI-compatible endpoints use the OpenAI envelope: { "error": { "type": "content_refusal", "message": "…" } }
HTTP/1.1 403
{ "type": "error",
  "error": {
    "type": "permission_error",
    "message": "Access to claude-opus-5 is temporarily suspended for this API key after repeated upstream policy refusals. Other models are unaffected. Contact support to restore access."
  } }
HTTP/1.1 200
{ "type": "message", "role": "assistant", "content": [],
  "stop_reason": "refusal",
  "usage": { ... } }

Full list of status codes and messages: Need help with API errors?

FAQ

Do I have to send the header?

No. Without it, everything keeps working as before — but refusals can then only be attributed and enforced at the level of your whole key.

What if we use a different id later?

Keep ids stable. Rules are written against the exact id you sent; a renamed user is a new user to us.

How do I unblock a user?

Contact support with the request_id of a refused request. Blocks are scoped to one key × model × id and can be lifted by the platform.

Is the id visible to anyone else?

No. It stays inside the platform's request logs, is never forwarded to model providers and is never shown to other customers.