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:

HeaderFormTypical sender
AuthorizationBearer px-...OpenAI SDK, curl
x-api-keypx-...Anthropic SDK
api-keypx-...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.

Shell
curl https://api.noirdoc.de/v1/models \
  -H "Authorization: Bearer $NOIRDOC_API_KEY"
Python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.noirdoc.de/v1",
    api_key=os.environ["NOIRDOC_API_KEY"],
)
TypeScript
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:

messageCause
Missing or invalid API keyNo header with a value that starts with px-.
Invalid or inactive API keyThe key is unknown or deactivated, or the organisation is not active.

All other gateway errors are listed under Error codes.

JSON
{
  "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):

TierSettingEffect
1Organisation policy enforcedShield always runs; overrides keys, request tags and headers.
2API key shield_mode (on / off)Overrides request tags, headers and the organisation default.
3Request tag shield_mode (on / off)Across multiple tags on beats off; overrides headers and the default.
4X-Noirdoc-Shield header (on / off)Overrides the organisation default default_on and default_off.
5Organisation 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.

Shell
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?"
    }]
  }'
Python
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"},
)
TypeScript
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).

Shell
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"}]
  }'
Python
response = client.chat.completions.create(
    model="qwen3.8-27b",
    messages=[{"role": "user", "content": "Hello"}],
    extra_headers={
        "X-Noirdoc-Tags": "mandat-2026-042,team-arbeitsrecht",
    },
)
TypeScript
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/speech and /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.

Shell
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-provider
Python
raw = 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()
TypeScript
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:

HeaderValueMeaning
X-Noirdoc-Shieldredacted=<n>Shield inspected all text in the request; <n> gives the number of redacted findings (including 0). X-Noirdoc-Masked is true.
X-Noirdoc-Shieldpartial; 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-Maskedtrue | falsetrue 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.

Shell
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)"
Text
x-noirdoc-shield: redacted=1
x-noirdoc-masked: true

Other 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.