OpenAI-compatible API
BasePeak.AI exposes an OpenAI-compatible /v1 surface that the
official openai SDKs for Python and Node speak natively. The endpoint
targets one of the user's agents per request (selected via the
X-BPAI-Agent header); the agent's own configured model and tools
take over from the request body's model field.
Use this surface to point existing OpenAI-based code at an agent. If you want the agent's tool calls, step transitions and prompts — which this surface deliberately hides — use the Invoke API instead.
Base URL & authentication
Each platform instance runs at its own hostname, e.g.
https://your-instance.platform.basepeak.ai. The OpenAI surface lives under
/v1/…. SDKs accept this directly as base_url / baseURL.
Authentication is via a scoped sk-bpai-… bearer token. See
API Keys for the full token model. The minimum
viable request looks like:
POST /v1/chat/completions HTTP/1.1
Host: your-instance.platform.basepeak.ai
Authorization: Bearer sk-bpai-1-42-…
X-BPAI-Agent: a17jlm7
Content-Type: application/json
{"messages":[{"role":"user","content":"Hello"}]}
BasePeak.AI strips Authorization after authentication so it never
reaches the downstream handler or logs.
Endpoints
| Method | Path | Auth | Scope check | Notes |
|---|---|---|---|---|
POST | /v1/chat/completions | API key | agent:chat:<X-BPAI-Agent> | Streaming + non-streaming. |
GET | /v1/models | API key | — | Lists the agent platform's models. Not scope-gated. |
GET | /v1/models/{id} | API key | — | Single model. |
POST | /v1/embeddings | API key | — | Proxies to the configured embedding model. |
POST | /v1/audio/transcriptions | API key | — | Proxies to the configured transcription model. |
Only /v1/chat/completions checks the per-agent scope on the key.
The other routes accept any valid sk-bpai-… token.
POST /v1/chat/completions
Headers
| Header | Required | Purpose |
|---|---|---|
Authorization | yes | Bearer sk-bpai-<user>-<key>-<secret> |
X-BPAI-Agent | yes | Agent ID (a17jlm7), alias, or thread ID. |
X-BPAI-Thread-Id | optional | Resume an existing thread instead of creating a new one. |
Content-Type | yes | application/json |
The response always includes X-BPAI-Thread-Id (echoed for new and
resumed threads), so the SDK can record it and pass it back on the
next turn.
X-BPAI-Agent verbatimThe required scope is agent:chat:<whatever you sent in X-BPAI-Agent>.
Since that header also accepts a thread ID, sending one there means a
key scoped to agent:chat:a17jlm7 gets 403 — it would need
agent:chat:<thread-id> or a wildcard.
Send the agent in X-BPAI-Agent and the thread in X-BPAI-Thread-Id;
that combination works with a normal agent-scoped key.
Request body
{
"model": "ignored",
"messages": [
{"role": "system", "content": "Optional system context"},
{"role": "user", "content": "Hello"}
],
"stream": false
}
messages— required, non-empty. The lastusermessage's text becomes the prompt for the agent. If absent, the last non-empty message of any role is used. History is not reconstructed from this array — sending prior turns does not give the agent context. State lives on the thread, so passX-BPAI-Thread-Idfor stateful chat; see Threads.model— accepted and ignored. The agent's configured model is used. SDK clients that hard-codemodel="gpt-4"keep working.stream—false(or unset) returns one JSON envelope;trueswitches to OpenAI's chunked-SSE format.- Other fields (
temperature,top_p,tools, …) — accepted (the JSON decoder ignores unknown fields) but not honored in v1. The agent's manifest controls these.
messages[].content
Either a JSON string or OpenAI's structured-content array:
{"role": "user", "content": [
{"type": "text", "text": "Describe this image"}
]}
type: text entries are concatenated. Other types (image_url,
input_audio) are silently skipped — this endpoint is text-only, and
you get no error to tell you an image never reached the agent. For ways to
give an agent documents, see Files.
Non-streaming response
{
"id": "chatcmpl-fa2c08d9e1b3",
"object": "chat.completion",
"created": 1717459200,
"model": "a17jlm7",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "Hello!"},
"finish_reason": "stop"
}
],
"usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
}
Notes:
modelechoes the agent ref the caller sent (the agent's actual model is the agent's configuration; we don't expose it here).usageis present for SDK compatibility but token counts are not yet populated — they ship as zeros. This is forward-compatible; populating the counts is tracked separately and adding values won't change the schema.
Streaming wire format
stream: true switches to text/event-stream with the OpenAI
chunked-SSE shape. The sequence is:
- Response headers —
Content-Type: text/event-stream,Cache-Control: no-cache,Connection: keep-alive,X-Accel-Buffering: no(defeats proxy buffering),X-BPAI-Thread-Id: <thread>. Status is always200. - Role chunk — announces the assistant role.
- Zero or more content chunks — each carries one
delta.contentfragment. - Zero or more SSE comment lines — non-content events (tool calls,
prompts, step markers) surface as
: <kind>\n\n. SDK parsers discard SSE comments per the spec; they're visible to operators tailing the raw stream. - Terminal chunk — empty delta,
finish_reason: "stop". data: [DONE]\n\n— the absence of this sentinel signals abnormal end.
Chunk shapes
Role chunk:
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":…,"model":"a17jlm7","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
Content chunk:
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":…,"model":"a17jlm7","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}
Terminal chunk:
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":…,"model":"a17jlm7","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
Wire-format detail.
finish_reasonisnullon non-terminal chunks, never the empty string — the OpenAI Python SDK treats""as a stream-end signal, so emitting it would truncate the stream.
Operator visibility
Non-content events (tool calls, prompts, etc.) become SSE comments:
: tool_call
: step
These are dropped by every conformant SSE parser, so the SDK iterator
sees only content + terminal. Tailing the raw stream from curl -N
shows them.
Mid-stream errors
If the agent fails mid-stream the server emits one OpenAI-shape
error frame and then closes the connection without [DONE]:
data: {"error":{"message":"agent timed out","type":"server_error","code":"agent_error","param":null}}
This mirrors OpenAI's own behavior. SDKs surface this as a stream-end
exception; manual consumers can detect it by checking for error in
the parsed JSON of the last data: frame.
Client disconnect
When the client closes the connection, the server detects the write
failure on the next chunk, stops streaming, and cancels the underlying
agent run. No terminal chunk and no [DONE] are emitted — the connection
is already gone. Closing the connection is therefore the way to cancel a
run from the client side.
Errors
Pre-stream errors
Returned as a single JSON envelope with the appropriate status code,
even when the request body had stream: true (we haven't committed
SSE headers yet).
{
"error": {
"message": "messages array is required and must not be empty.",
"type": "invalid_request_error",
"param": null,
"code": "invalid_request"
}
}
| Status | code | When |
|---|---|---|
| 400 | invalid_request | Body parse, missing messages, content extract failure. |
| 400 | missing_agent_header | X-BPAI-Agent empty. |
| 401 | invalid_api_key | No bearer, or one that is not an sk-bpai-… token. A malformed or unknown sk-bpai-… token is rejected earlier, as application/problem+json — see API Keys — Troubleshooting. |
| 403 | agent_forbidden | The key's owner has no access to the requested agent. A scope failure also returns 403, but as application/problem+json (authz/scope-missing) rather than this envelope. |
| 404 | model_not_found | Agent resolution failed (alias unknown / id misspelled). |
| 500 | internal_error | Server-side: a database outage while resolving the agent, or a failure starting the run. |
| 502 | agent_error (non-stream only) | The agent run errored. Any partial output is discarded, so this can occur after the agent had already produced content. |
Mid-stream errors
Only for stream: true. Emitted as one SSE data frame with the same
JSON envelope shape, no terminal chunk, no [DONE]. See
Streaming wire format.
Differences vs OpenAI's API
| Aspect | OpenAI | BasePeak.AI /v1 |
|---|---|---|
| Auth header | Authorization: Bearer sk-… | Authorization: Bearer sk-bpai-… |
| Model selection | Body's model field | Agent selection via X-BPAI-Agent — body model is ignored. |
| Multi-turn state | Stateless; client sends full message history each turn | Server-side — pass X-BPAI-Thread-Id to resume. |
stream:true | Chunked SSE | Same shape, byte-for-byte compatible with the SDK iterators. |
| Tool calls | Function-call deltas inside choices[].delta.tool_calls | Surfaced as SSE comments invisible to SDK parsers; v1 doesn't expose them as deltas. |
| Token usage in stream | Optional usage on terminal chunk | Not yet populated; field reserved. |
| Error envelopes | Mixed (HTTP errors + mid-stream error chunks) | Same shape; mid-stream chunks match OpenAI's format. |
Examples
Python — non-streaming
from openai import OpenAI
client = OpenAI(
api_key="sk-bpai-1-42-…",
base_url="https://your-instance.platform.basepeak.ai/v1",
default_headers={"X-BPAI-Agent": "a17jlm7"},
)
raw = client.chat.completions.with_raw_response.create(
model="ignored",
messages=[{"role": "user", "content": "Capital of Germany?"}],
)
thread = raw.headers.get("x-bpai-thread-id") # response header
resp = raw.parse() # the ChatCompletion body
print(resp.choices[0].message.content)
Python — streaming
from openai import OpenAI
client = OpenAI(api_key="…", base_url="…/v1",
default_headers={"X-BPAI-Agent": "a17jlm7"})
stream = client.chat.completions.create(
model="ignored",
messages=[{"role": "user", "content": "Stream me an answer."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
print()
Python — thread continuity
# Turn 1 — fresh thread.
raw1 = client.chat.completions.with_raw_response.create(
model="ignored",
messages=[{"role": "user", "content": "What's the capital of Germany?"}],
)
thread = raw1.headers.get("x-bpai-thread-id") # response header
r1 = raw1.parse() # the ChatCompletion body
# Turn 2 — resume the thread; the agent remembers turn 1.
client2 = OpenAI(api_key="…", base_url="…/v1",
default_headers={"X-BPAI-Agent": "a17jlm7",
"X-BPAI-Thread-Id": thread})
r2 = client2.chat.completions.create(
model="ignored",
messages=[{"role": "user", "content": "And of France?"}],
)
print(r2.choices[0].message.content) # "The capital of France is Paris."
curl — streaming, line-by-line
curl -N \
-H "Authorization: Bearer sk-bpai-1-42-…" \
-H "X-BPAI-Agent: a17jlm7" \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Stream me a haiku"}],"stream":true}' \
https://your-instance.platform.basepeak.ai/v1/chat/completions
Output (abridged):
data: {"id":"chatcmpl-…","choices":[{"delta":{"role":"assistant"},…}]}
data: {"id":"chatcmpl-…","choices":[{"delta":{"content":"Cherry"},…}]}
data: {"id":"chatcmpl-…","choices":[{"delta":{"content":" blossoms"},…}]}
: step
data: {"id":"chatcmpl-…","choices":[{"delta":{},"finish_reason":"stop"}]}
data: [DONE]
Node — streaming
import OpenAI from "openai";
const client = new OpenAI({
apiKey: "sk-bpai-1-42-…",
baseURL: "https://your-instance.platform.basepeak.ai/v1",
defaultHeaders: { "X-BPAI-Agent": "a17jlm7" },
});
const stream = await client.chat.completions.create({
model: "ignored",
messages: [{ role: "user", content: "Stream me a fact." }],
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (delta) process.stdout.write(delta);
}
process.stdout.write("\n");
Related docs
- API Overview — choosing a surface
- API Keys — token model, scopes, audit log
- Invoke API — tool calls and steps in the stream
- Threads — multi-turn and parallel conversations
- Files — giving an agent documents