Develop / Reference
Authentication & headers
How to send the API key, control Noirdoc Shield per request, and read from the response which provider answered and how Shield inspected the request.
Base URL and key
All endpoints are under the base URL https://api.noirdoc.de/v1. Every request needs an API key of your organisation. You create it under Models → API keys.
API keys start with px-. Send the key in one of these three headers:
| Header | Form | Typical sender |
|---|---|---|
Authorization | Bearer px-... | OpenAI SDK, curl |
x-api-key | px-... | Anthropic SDK |
api-key | px-... | Azure OpenAI client |
All three forms are equivalent. The gateway checks them in the order of the table and takes the first header whose value starts with px-. A value without this prefix counts as a missing key.
curl https://api.noirdoc.de/v1/models \
-H "Authorization: Bearer $NOIRDOC_API_KEY"import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.noirdoc.de/v1",
api_key=os.environ["NOIRDOC_API_KEY"],
)import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.noirdoc.de/v1",
apiKey: process.env.NOIRDOC_API_KEY,
});Missing or invalid key
Without a valid key, the gateway answers with status code 401 (Unauthorized) and the code invalid_api_key in the usual error object. message names the cause:
message | Cause |
|---|---|
Missing or invalid API key | No header with a value that starts with px-. |
Invalid or inactive API key | The key is unknown or deactivated, or the organisation is not active. |
All other gateway errors are listed under Error codes.
{
"error": {
"type": "authentication_error",
"code": "invalid_api_key",
"message": "Invalid or inactive API key",
"param": null
}
}Shield per request
The X-Noirdoc-Shield header turns Noirdoc Shield on (on) or off (off) for a single request. Case does not matter; true, false, 1 and 0 are also accepted, and the gateway ignores other values. It removes X-Noirdoc-Shield and X-Noirdoc-Mask before forwarding the request to the provider.
If all five protection filters of your organisation are set to off, Shield does not run. Otherwise, this five-tier precedence applies from top to bottom (see Policy and precedence):
| Tier | Setting | Effect |
|---|---|---|
| 1 | Organisation policy enforced | Shield always runs; overrides keys, request tags and headers. |
| 2 | API key shield_mode (on / off) | Overrides request tags, headers and the organisation default. |
| 3 | Request tag shield_mode (on / off) | Across multiple tags on beats off; overrides headers and the default. |
| 4 | X-Noirdoc-Shield header (on / off) | Overrides the organisation default default_on and default_off. |
| 5 | Organisation default (Models → Shield) | default_on or default_off (new organisations start with default_off). |
On organisations using Noirdoc Shield, X-Noirdoc-Mask continues to work as a compatible alias for X-Noirdoc-Shield. On pre-existing organisations that still use legacy masking until 30.11.2026, X-Noirdoc-Mask controls legacy masking while X-Noirdoc-Shield: on explicitly activates Noirdoc Shield for that request.
Noirdoc Shield inspects requests on /v1/chat/completions, /v1/responses, /v1/messages, /v1/embeddings and /v1/rerank. On /v1/audio/*, /v1/images/generations and when uploading via /v1/files or creating under /v1/skills, the gateway rejects enforced and X-Noirdoc-Mask: on (and on /v1/images/generations also X-Noirdoc-Shield: on) with status code 403 and the code masking_not_supported_for_endpoint; other requests to those endpoints are forwarded uninspected. Inspected fields lists which fields are inspected. Standalone text inspection and policy management are available via POST /v1/shield/inspect and /v1/shield/policy.
curl https://api.noirdoc.de/v1/chat/completions \
-H "Authorization: Bearer $NOIRDOC_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Noirdoc-Shield: on" \
-d '{
"model": "qwen3.8-27b",
"messages": [{
"role": "user",
"content": "What does Mr Müller write to the firm?"
}]
}'response = client.chat.completions.create(
model="qwen3.8-27b",
messages=[
{
"role": "user",
"content": "What does Mr Müller write to the firm?",
}
],
extra_headers={"X-Noirdoc-Shield": "on"},
)const response = await client.chat.completions.create(
{
model: "qwen3.8-27b",
messages: [
{
role: "user",
content: "What does Mr Müller write to the firm?",
},
],
},
{ headers: { "X-Noirdoc-Shield": "on" } },
);Request tags per request (Preview)
In organisations that have Request tags (Preview) enabled, the X-Noirdoc-Tags header attaches 1 to 5 comma-separated tags (such as matters, cost centres or teams) to a request. Each tag matches ^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,63}$.
Before forwarding, the gateway checks the allowed models, provider restrictions and euro budgets of all named tags and removes X-Noirdoc-Tags before the request reaches the provider. If the Preview feature is not enabled for the organisation, the gateway answers with 403 request_tags_disabled. Details on templates, budgets and management via /v1/tags are in Request tags (Preview).
curl https://api.noirdoc.de/v1/chat/completions \
-H "Authorization: Bearer $NOIRDOC_API_KEY" \
-H "X-Noirdoc-Tags: mandat-2026-042,team-arbeitsrecht" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3.8-27b",
"messages": [{"role": "user", "content": "Hello"}]
}'response = client.chat.completions.create(
model="qwen3.8-27b",
messages=[{"role": "user", "content": "Hello"}],
extra_headers={
"X-Noirdoc-Tags": "mandat-2026-042,team-arbeitsrecht",
},
)const response = await client.chat.completions.create(
{
model: "qwen3.8-27b",
messages: [{ role: "user", content: "Hello" }],
},
{
headers: {
"X-Noirdoc-Tags": "mandat-2026-042,team-arbeitsrecht",
},
},
);Provider of the response
The response contains the X-Noirdoc-Provider header. It names the provider that answered the request by its short name (slug). If the gateway falls back to another provider (failover), the header names the provider that answered in the end. For streams, the response carries the header only if the provider answered within 15 seconds; otherwise a comment line : provider=<slug> names the provider, see Streaming.
The gateway sets the header
- on successful responses, including streaming,
- on error responses in which a provider was involved,
- on
/v1/audio/speechand/v1/audio/transcriptions.
The header is missing on successful responses from /v1/files and /v1/skills, on endpoints that call no model provider (/v1/models, /v1/prices, /v1/shield/*), and on errors that the gateway reports before calling a provider, such as invalid_api_key, shield_violation or key_budget_exhausted. Providers & routing explains how the gateway selects the provider.
curl -si https://api.noirdoc.de/v1/chat/completions \
-H "Authorization: Bearer $NOIRDOC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3.8-27b",
"messages": [{"role": "user", "content": "Hello"}]
}' \
| grep -i x-noirdoc-providerraw = client.chat.completions.with_raw_response.create(
model="qwen3.8-27b",
messages=[{"role": "user", "content": "Hello"}],
)
print(raw.headers.get("x-noirdoc-provider"))
completion = raw.parse()const { data, response } = await client.chat.completions
.create({
model: "qwen3.8-27b",
messages: [{ role: "user", content: "Hello" }],
})
.withResponse();
console.log(response.headers.get("x-noirdoc-provider"));Shield in the response
Two response headers report how the request was inspected:
| Header | Value | Meaning |
|---|---|---|
X-Noirdoc-Shield | redacted=<n> | Shield inspected all text in the request; <n> gives the number of redacted findings (including 0). X-Noirdoc-Masked is true. |
X-Noirdoc-Shield | partial; redacted=<n> | Shield inspected the text fields of a non-enforced request that also carried uninspectable binary, media or reference attachments (X-Noirdoc-Masked: false). |
X-Noirdoc-Shield | (absent) | Noirdoc Shield did not run on this request. |
X-Noirdoc-Masked | true | false | true on full Shield inspection (redacted=<n>) or active legacy masking; false on partial, on uninspected requests and endpoints without Shield, and on errors raised before routing. |
Details on partial inspection and uninspectable attachments under enforced are in Files and attachments.
Responses processed by legacy masking during the transition period until 30.11.2026 additionally carry the RFC 8594 headers Deprecation: true and Sunset: Mon, 30 Nov 2026 23:59:59 GMT.
If a provider itself sends headers named X-Noirdoc-Shield, X-Noirdoc-Masked or X-Noirdoc-Provider, the gateway removes them. All three values always come from the gateway.
curl -si https://api.noirdoc.de/v1/chat/completions \
-H "Authorization: Bearer $NOIRDOC_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Noirdoc-Shield: on" \
-d '{
"model": "qwen3.8-27b",
"messages": [{
"role": "user",
"content": "What does Mr Müller write to the firm?"
}]
}' \
| grep -iE "x-noirdoc-(shield|masked)"x-noirdoc-shield: redacted=1
x-noirdoc-masked: trueOther headers
The gateway passes to the provider only the headers that the request format needs: Content-Type, Accept, anthropic-version, anthropic-beta and OpenAI-Beta. With your own provider key (BYOK), OpenAI-Organization, OpenAI-Project and Idempotency-Key are added, because they concern your organisation’s account; with the providers managed by Noirdoc, the gateway removes these three as well (so retries from your SDK are not merged there based on the idempotency key). It also removes all other headers, including your key, X-Noirdoc-Shield, X-Noirdoc-Mask, X-Noirdoc-Tags, User-Agent, cookies and custom X- headers. It sets the credentials for the provider itself.
The exception is anthropic-version: with Anthropic, the gateway sets this header itself to 2023-06-01 and overwrites your value. For Claude models through Google Vertex AI, it removes anthropic-version and anthropic-beta and moves the beta identifiers into the body, as Vertex requires.
Provider selection
The header in which you send the key does not select a provider. On all model-based endpoints, the model field decides together with your organisation’s catalogue. The exceptions are the endpoints without model: /v1/skills always goes to Anthropic. For /v1/files, the key header determines the API format: x-api-key selects Anthropic, the other two headers select OpenAI. The X-Provider header can override this. Details are under Files & Skills.