Quickstart
Create an API key in the Privateer app under Settings → API keys, then set it as your OpenAI base URL and key. The key looks like sk-priv-… and is shown only once.
from openai import OpenAI
client = OpenAI(
base_url="https://api.privateer.pro/v1",
api_key="sk-priv-…",
)
resp = client.chat.completions.create(
model="", # "" = your account's default model; or an id from /v1/models
messages=[{"role": "user", "content": "Hello!"}],
)
print(resp.choices[0].message.content)
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.privateer.pro/v1",
apiKey: process.env.PRIVATEER_API_KEY,
});
const resp = await client.chat.completions.create({
model: "",
messages: [{ role: "user", content: "Hello!" }],
});
console.log(resp.choices[0].message.content);
curl https://api.privateer.pro/v1/chat/completions \
-H "Authorization: Bearer sk-priv-…" \
-H "Content-Type: application/json" \
-d '{"model":"","messages":[{"role":"user","content":"Hello!"}]}'
Authentication
Every request needs a Privateer API key in the Authorization header:
Authorization: Bearer sk-priv-…
Create, name, and revoke keys in the Privateer app under Settings → API keys. The full key is shown once at creation — we store only a hash, so if you lose it, revoke it and mint a new one. A key inherits its owner's plan, rate limits, and credit balance; revoking it stops every request using it immediately.
Pricing. API usage is pay-as-you-go, drawn from your account credit — across chat, image, video and audio. See the current per-model rate card at privateer.pro/api-pricing.
Keep keys server-side. Anyone holding a key can spend your account balance. Never ship one in a mobile app, browser bundle, or public repo.
Privacy & encryption
The Privateer app is end-to-end encrypted — your chats are encrypted on your device and the server stores ciphertext. This API is different by nature: you send prompts to our server, which forwards them to the model provider to run inference. That traffic is processed in the clear and is not end-to-end encrypted.
What we actually guarantee is retention, not secrecy in transit. Inference is a stateless pass-through: we persist only billing metadata (timestamps, model id, token counts, cost) — never your prompts or the responses. By default requests are pinned to Zero-Data-Retention providers, and confidential models (NEAR AI, Tinfoil) run inside attested Trusted Execution Environments. Set "requireZdr": false in the request body to opt out of ZDR pinning.
Endpoints
All paths are relative to https://api.privateer.pro/v1.
POST /v1/chat/completions
Standard OpenAI Chat Completions. Tool calls, multi-part content, and finish_reason pass through unchanged. Common parameters:
| Field | Type | Notes |
|---|---|---|
messages | array | Required. The conversation, OpenAI format. |
model | string | An id from /v1/models. Empty or omitted uses your account's default model. |
stream | boolean | When true, responses stream as SSE (see below). |
max_tokens, temperature, … | — | Passed through to the provider unchanged. |
requireZdr | boolean | Privateer extension. Defaults to your account setting (ZDR on). Set false to allow non-ZDR routing. |
A successful response is the provider's standard Chat Completion object, including a usage block that determines billing.
Streaming
Set "stream": true to receive Server-Sent Events. Each event is a chat.completion.chunk; the stream ends with data: [DONE]. The final chunk carries usage.
curl -N https://api.privateer.pro/v1/chat/completions \
-H "Authorization: Bearer sk-priv-…" \
-H "Content-Type: application/json" \
-d '{"model":"","stream":true,"messages":[{"role":"user","content":"Count to 3"}]}'
With the OpenAI SDKs, pass stream=True (Python) / stream: true (Node) and iterate the returned stream as usual.
Vision (image input)
Image input works on /v1/chat/completions with no extra endpoint — pass OpenAI multi-part content and a vision-capable model:
curl https://api.privateer.pro/v1/chat/completions \
-H "Authorization: Bearer sk-priv-…" \
-H "Content-Type: application/json" \
-d '{
"model": "near/Qwen/Qwen3.5-122B-A10B",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "What is in this image?"},
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,…"}}
]
}]
}'
Pass a model whose id supports image input (see Models). Data-URI and remote image_urls are both accepted.
Models
GET /v1/models returns the models available to your account in OpenAI's list shape. Use any returned id as the model field; pass an empty string to use your account default.
curl https://api.privateer.pro/v1/models \
-H "Authorization: Bearer sk-priv-…"
{
"object": "list",
"data": [
{ "id": "near/deepseek-ai/DeepSeek-V4-Flash", "object": "model", "owned_by": "privateer" }
]
}
Image generation
OpenAI Images shape. Returns base64 by default (b64_json), or set response_format:"url" for a short-lived signed URL. n is capped at 4. Leave model empty for your account's default image model.
curl https://api.privateer.pro/v1/images/generations \
-H "Authorization: Bearer sk-priv-…" \
-H "Content-Type: application/json" \
-d '{"prompt":"an otter surfing, watercolor","n":1}'
{
"created": 1783437233,
"data": [ { "b64_json": "/9j/4AAQ…" } ]
}
| Field | Notes |
|---|---|
prompt | Required. |
model | Image model id, or empty for the account default. |
n | 1–4 images. |
size, aspect_ratio | Optional, passed to the model. |
response_format | b64_json (default) or url. |
Video generation
Video generation is asynchronous: submit a job, then poll it. This is a Privateer extension (no OpenAI equivalent). The finished video is returned as a short-lived signed URL.
curl https://api.privateer.pro/v1/videos \
-H "Authorization: Bearer sk-priv-…" \
-H "Content-Type: application/json" \
-d '{"prompt":"a butterfly landing on a flower","seconds":4}'
# → 202
{ "id": "NzX8ue0…", "object": "video.job", "status": "queued" }
curl https://api.privateer.pro/v1/videos/NzX8ue0… \
-H "Authorization: Bearer sk-priv-…"
# processing…
{ "id": "NzX8ue0…", "status": "processing" }
# done
{ "id": "NzX8ue0…", "status": "completed",
"url": "https://…signed…", "expires_at": 1783440833 }
Optional submit fields: model, seconds, size, generate_audio, requireZdr. The signed url is short-lived — each poll of a completed job returns a fresh one. Video requires a paid plan (or held pay-as-you-go credit).
Poll every few seconds. Billing is reserved at submit and settled against the provider's actual cost on completion; a failed job releases the hold.
Audio (speech-to-text & text-to-speech)
Transcription (STT) — POST /v1/audio/transcriptions
OpenAI Whisper shape: multipart upload with a file field (or JSON { "audioBase64", "format" }). Returns { "text": … }.
curl https://api.privateer.pro/v1/audio/transcriptions \
-H "Authorization: Bearer sk-priv-…" \
-F file=@speech.mp3 \
-F model=openai/whisper-1 \
-F requireZdr=false
Speech (TTS) — POST /v1/audio/speech
Returns raw audio bytes. The default voice model returns audio/wav; request response_format:"mp3" only with an mp3-capable model.
curl https://api.privateer.pro/v1/audio/speech \
-H "Authorization: Bearer sk-priv-…" \
-H "Content-Type: application/json" \
-d '{"input":"Hello from Privateer.","voice":"Zephyr"}' \
--output speech.wav
Audio is ZDR-pinned by default, like chat. The default STT model has no Zero-Data-Retention endpoint, so transcription needs requireZdr:false or a confidential (enclave) STT model.
Billing & limits
Usage is billed to your Privateer credit balance at your plan's rate, metered by the usage token counts on each completion. Top up and see your balance in the app. Requests are subject to your plan's per-minute rate limit, daily message cap, and a small minimum-balance check — the same limits that govern the app.
No key, no charge you can't see. Every billed request writes a usage record (model, tokens, cost) you can reconcile — prompts and responses are never stored.
Errors
Errors use OpenAI's shape: { "error": { "message", "type", "code" } }. Provider errors are forwarded verbatim.
| Status | Code | Meaning |
|---|---|---|
400 | INVALID_REQUEST | Malformed body (e.g. missing messages) or an unknown model. |
401 | invalid_api_key | Missing, malformed, or revoked key; or the key's account isn't active. |
402 | INSUFFICIENT_FUNDS | Account balance too low — top up in the app. |
429 | RATE_LIMITED | Per-minute rate limit or daily cap exceeded. Back off and retry. |