Develop / Guides

OpenAI SDK

How to point the OpenAI SDK for Python and TypeScript at the gateway, set Noirdoc headers and tell gateway errors apart from provider errors.

The gateway accepts requests in the format of the OpenAI API. The OpenAI SDK therefore works unchanged as soon as base_url and api_key point to Noirdoc. The examples use the model ID qwen3.8-27b. GET /v1/models returns the model IDs your key may call (see Quickstart).

Install the SDK

Install the openai package for Python or for Node.js.

Shell
pip install openai
Shell
npm install openai

Set up the client

Set base_url to https://api.noirdoc.de/v1 and api_key to your px- key. The SDK sends the key in the Authorization: Bearer header.

Both SDKs also read the values from the environment variables OPENAI_BASE_URL and OPENAI_API_KEY. This lets you set up existing code without changing it.

The client covers all OpenAI endpoints of the gateway, for example client.responses.create for POST /v1/responses and client.embeddings.create for POST /v1/embeddings.

Python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.noirdoc.de/v1",
    api_key=os.environ["NOIRDOC_API_KEY"],
)

response = client.chat.completions.create(
    model="qwen3.8-27b",
    messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)
TypeScript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.noirdoc.de/v1",
  apiKey: process.env.NOIRDOC_API_KEY,
});

const response = await client.chat.completions.create({
  model: "qwen3.8-27b",
  messages: [{ role: "user", content: "Hello" }],
});
console.log(response.choices[0].message.content);

Turn on Noirdoc Shield

The X-Noirdoc-Shield header controls Noirdoc Shield per request (X-Noirdoc-Mask remains supported as a compatible alias). With default_headers, the client sends it with every request. For a single request, set it in Python with extra_headers and in TypeScript with the headers option.

Shield statelessly inspects inbound text on Chat Completions, Responses and Embeddings, redacts personal data such as [PERSON] or [IBAN] and blocks detected secrets ([SECRET]) and prompt injections before forwarding with status code 403 (shield_violation). Responses are not reverse-translated. The gateway evaluates on and off and removes the header before forwarding the request to the provider. Whether the header takes effect depends on the settings of your organisation, API key and any attached request tags (see Set up Noirdoc Shield).

Shield does not inspect audio, image generation or file uploads via /v1/files. On /v1/images/generations, the gateway rejects X-Noirdoc-Shield: on (and X-Noirdoc-Mask: on) with masking_not_supported_for_endpoint (status code 403); on /v1/audio/* and /v1/files, it rejects enforced and X-Noirdoc-Mask: on and forwards other requests uninspected. Use a separate client without the header in default_headers for image generation.

Python
client = OpenAI(
    base_url="https://api.noirdoc.de/v1",
    api_key=os.environ["NOIRDOC_API_KEY"],
    default_headers={"X-Noirdoc-Shield": "on"},
)

# Turn off Shield for this request
response = client.chat.completions.create(
    model="qwen3.8-27b",
    messages=[{"role": "user", "content": "Hello"}],
    extra_headers={"X-Noirdoc-Shield": "off"},
)
TypeScript
const client = new OpenAI({
  baseURL: "https://api.noirdoc.de/v1",
  apiKey: process.env.NOIRDOC_API_KEY,
  defaultHeaders: { "X-Noirdoc-Shield": "on" },
});

// Turn off Shield for this request
const response = await client.chat.completions.create(
  {
    model: "qwen3.8-27b",
    messages: [{ role: "user", content: "Hello" }],
  },
  { headers: { "X-Noirdoc-Shield": "off" } },
);

Read the provider from the response

The X-Noirdoc-Provider response header names the provider that answered the request. The SDK exposes headers through the raw response: in Python with with_raw_response, in TypeScript with .withResponse().

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"))
response = 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"));

Handle errors

Every error response from the gateway has an error object with type, code and message. The SDK exposes type and code directly on the error. In your code, check the code, not the message. The type follows the status code, for example rate_limit_error for 429.

  • The gateway reports provider errors in the uniform error object: capacity problems as 429 resource_exhausted and overload as 503 provider_overloaded, each with Retry-After, rejected requests as provider_rejected_request with the provider’s message, and all other errors as 502 provider_error. On routes with your own provider key (BYOK), the provider’s status codes 401, 402, 403 and 429 arrive as provider_rejected_request with the provider’s status code and message, as described under Error codes. The X-Noirdoc-Provider header names the provider that answered last.
  • A missing or invalid key results in status code 401 with the code invalid_api_key. The SDK reports it as AuthenticationError.
  • The SDK retries requests with the status codes 408, 409, 429 and 5xx until max_retries is reached. It waits for the time given in Retry-After if the header states 60 seconds or less; otherwise it increases the wait exponentially.

If a stream breaks off, the SDK raises an APIError, see Streaming.

Python
import openai

try:
    response = client.chat.completions.create(
        model="qwen3.8-27b",
        messages=[{"role": "user", "content": "Hello"}],
    )
except openai.APIStatusError as e:
    if e.code == "provider_rejected_request":
        provider = e.response.headers.get("x-noirdoc-provider")
        print("Provider rejected:", provider, e.body["message"])
    else:
        print("Error:", e.status_code, e.code)
TypeScript
try {
  const response = await client.chat.completions.create({
    model: "qwen3.8-27b",
    messages: [{ role: "user", content: "Hello" }],
  });
} catch (err) {
  if (!(err instanceof OpenAI.APIError)) throw err;
  if (err.code === "provider_rejected_request") {
    const provider = err.headers?.get("x-noirdoc-provider");
    console.log("Provider rejected:", provider, err.message);
  } else {
    console.log("Error:", err.status, err.code);
  }
}

Retry requests

The SDK retries failed requests itself, by default up to twice, among others for 429 and status codes from 500 upwards. For 429, 5xx and network errors, the gateway has already tried the next provider for the same model before that (failover, see Providers & routing).

A retry does not fix errors such as insufficient_credit or key_budget_exhausted (status code 402). Handle them in your code.

All codes and how to fix them are listed under Error codes.

Status Code Meaning
400 wrong_endpoint_for_model The model belongs to a different endpoint family or API format than the endpoint called.
402 insufficient_credit The organisation's credit is used up or lower than the estimated maximum cost of the request (including Noirdoc Shield, also on BYOK routes when Shield is active).
402 key_budget_exhausted This key's budget is used up for the current period or does not cover the estimated cost of the request.
403 model_not_allowed_for_key The model is not one of this key's allowed models.
403 shield_violation Noirdoc Shield blocked the request before forwarding to the provider because an active protection filter configured with block matched; message names the triggered rule (secrets, pii, ner, injection or custom).
403 masking_not_supported_for_endpoint The endpoint supports neither Noirdoc Shield nor legacy masking, yet the policy is enforced or the request sends X-Noirdoc-Mask: on (and on /v1/images/generations also X-Noirdoc-Shield: on). During the legacy masking transition until 30.11.2026, this also affects /v1/embeddings and /v1/rerank on pre-existing organisations that have not switched to Shield yet.
404 model_not_available The model ID is not available to the organisation.
502 provider_unreachable The gateway could not connect to the provider, or the connection dropped.
504 provider_timeout The provider did not answer within the gateway's timeout. The response contains Retry-After with 10 seconds.