Develop / Guides

Audio & images

How to transcribe audio, have text read aloud and generate images, and which limits the gateway checks along the way.

Three endpoints process audio and images. All three use the same base URL and the same key as chat. The model ID must match the endpoint’s family; GET /v1/models shows which models your key reaches. The IDs in the examples come from the catalogue and can change.

Transcribe audio

POST /v1/audio/transcriptions expects multipart/form-data with the fields file and model.

  • The request may be at most 26 MB. The gateway rejects larger requests with 413 request_too_large.
  • Audio counts as file content. If Allow file and image attachments in requests under Models → Shield (in Advanced Settings) is off, the gateway answers with 403 file_content_not_allowed.
  • Streaming is not available here. stream=true results in 400 streaming_not_supported_for_endpoint.

The gateway bills by the duration of the recording.

Shell
curl https://api.noirdoc.de/v1/audio/transcriptions \
  -H "Authorization: Bearer $NOIRDOC_API_KEY" \
  -F model="whisper" \
  -F file="@dictation.mp3"
Python
import os
from openai import OpenAI

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

with open("dictation.mp3", "rb") as audio:
    transcript = client.audio.transcriptions.create(
        model="whisper", file=audio
    )
print(transcript.text)
TypeScript
import fs from "node:fs";
import OpenAI from "openai";

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

const transcript = await client.audio.transcriptions.create({
  model: "whisper",
  file: fs.createReadStream("dictation.mp3"),
});
console.log(transcript.text);

Have text read aloud

POST /v1/audio/speech expects JSON and answers with audio data instead of JSON. Save the response as a file.

  • input is required and has at most 4,096 characters.
  • voice is required. The provider determines which voices a model knows.
  • The gateway answers violations with 400 invalid_tts_request.
  • Streaming is not available here.

The gateway bills by the number of characters in input.

Shell
curl https://api.noirdoc.de/v1/audio/speech \
  -H "Authorization: Bearer $NOIRDOC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tts",
    "voice": "<voice>",
    "response_format": "mp3",
    "input": "Your appointment is on Tuesday at 10:00."
  }' \
  --output appointment.mp3
Python
import os
from openai import OpenAI

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

speech = client.audio.speech.create(
    model="tts",
    voice="<voice>",
    response_format="mp3",
    input="Your appointment is on Tuesday at 10:00.",
)
speech.write_to_file("appointment.mp3")
TypeScript
import fs from "node:fs";
import OpenAI from "openai";

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

const speech = await client.audio.speech.create({
  model: "tts",
  voice: "<voice>",
  response_format: "mp3",
  input: "Your appointment is on Tuesday at 10:00.",
});
const audio = Buffer.from(await speech.arrayBuffer());
fs.writeFileSync("appointment.mp3", audio);

Generate images

POST /v1/images/generations generates images from a prompt. Before forwarding, the gateway checks:

FieldLimit
promptrequired, not empty
ninteger from 1 to 10, default 1
sizeWIDTHxHEIGHT, each side at most 2048
totalat most 16 megapixels per request

The gateway counts the megapixels per image in started units of 1024 × 1024 pixels, times n. Without size, it calculates with 1024x1024. It answers violations with 400 invalid_image_request, including "size": "auto". The provider additionally determines which sizes and how many images a model accepts.

The gateway does not support image editing (/v1/images/edits). Streaming is not available here.

With the connected image models, the images are Base64-encoded in data[].b64_json. Depending on the model, the gateway bills per image or per megapixel.

Shell
curl https://api.noirdoc.de/v1/images/generations \
  -H "Authorization: Bearer $NOIRDOC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "black-forest-labs/FLUX.1-schnell",
    "prompt": "A lighthouse on the North Sea at sunrise",
    "size": "1024x1024",
    "n": 1
  }' \
  | jq -r '.data[0].b64_json' | base64 --decode > lighthouse.png
Python
import base64
import os

from openai import OpenAI

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

image = client.images.generate(
    model="black-forest-labs/FLUX.1-schnell",
    prompt="A lighthouse on the North Sea at sunrise",
    size="1024x1024",
    n=1,
)
with open("lighthouse.png", "wb") as f:
    f.write(base64.b64decode(image.data[0].b64_json))
TypeScript
import fs from "node:fs";
import OpenAI from "openai";

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

const image = await client.images.generate({
  model: "black-forest-labs/FLUX.1-schnell",
  prompt: "A lighthouse on the North Sea at sunrise",
  size: "1024x1024",
  n: 1,
});
const png = Buffer.from(image.data![0].b64_json!, "base64");
fs.writeFileSync("lighthouse.png", png);

Read the provider from the response

Audio and image responses also carry the X-Noirdoc-Provider header. It names the provider that answered the request. Details on fields and sub-routes are under Audio and Images.

Errors

Status Code Meaning
400 wrong_endpoint_for_model The model belongs to a different endpoint family or API format than the endpoint called.
400 streaming_not_supported_for_endpoint The request asks for streaming, but the endpoint does not support it.
400 invalid_image_request prompt, n or size is outside the gateway's limits; message names the parameter.
400 invalid_tts_request input or voice is missing or empty, or input is too long.
400 invalid_multipart_body The body is not valid multipart/form-data, or model, stream or response_format appears more than once.
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.
403 file_content_not_allowed The request contains files, images or audio, and the organisation does not allow file content.
405 method_not_allowed The endpoint accepts only POST.
413 request_too_large The body exceeds the endpoint's size limit.