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=trueresults in 400streaming_not_supported_for_endpoint.
The gateway bills by the duration of the recording.
curl https://api.noirdoc.de/v1/audio/transcriptions \
-H "Authorization: Bearer $NOIRDOC_API_KEY" \
-F model="whisper" \
-F file="@dictation.mp3"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)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.
inputis required and has at most 4,096 characters.voiceis 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.
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.mp3import 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")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:
| Field | Limit |
|---|---|
prompt | required, not empty |
n | integer from 1 to 10, default 1 |
size | WIDTHxHEIGHT, each side at most 2048 |
| total | at 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.
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.pngimport 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))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. |