Skip to main content

OpenAI-compatible API

Premium feature

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​

MethodPathAuthScope checkNotes
POST/v1/chat/completionsAPI keyagent:chat:<X-BPAI-Agent>Streaming + non-streaming.
GET/v1/modelsAPI key—Lists the agent platform's models. Not scope-gated.
GET/v1/models/{id}API key—Single model.
POST/v1/embeddingsAPI key—Proxies to the configured embedding model.
POST/v1/audio/transcriptionsAPI 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​

HeaderRequiredPurpose
AuthorizationyesBearer sk-bpai-<user>-<key>-<secret>
X-BPAI-AgentyesAgent ID (a17jlm7), alias, or thread ID.
X-BPAI-Thread-IdoptionalResume an existing thread instead of creating a new one.
Content-Typeyesapplication/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.

The scope is built from X-BPAI-Agent verbatim

The 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 last user message'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 pass X-BPAI-Thread-Id for stateful chat; see Threads.
  • model — accepted and ignored. The agent's configured model is used. SDK clients that hard-code model="gpt-4" keep working.
  • stream — false (or unset) returns one JSON envelope; true switches 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:

  • model echoes the agent ref the caller sent (the agent's actual model is the agent's configuration; we don't expose it here).
  • usage is 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:

  1. 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 always 200.
  2. Role chunk — announces the assistant role.
  3. Zero or more content chunks — each carries one delta.content fragment.
  4. 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.
  5. Terminal chunk — empty delta, finish_reason: "stop".
  6. 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_reason is null on 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"
}
}
StatuscodeWhen
400invalid_requestBody parse, missing messages, content extract failure.
400missing_agent_headerX-BPAI-Agent empty.
401invalid_api_keyNo 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.
403agent_forbiddenThe 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.
404model_not_foundAgent resolution failed (alias unknown / id misspelled).
500internal_errorServer-side: a database outage while resolving the agent, or a failure starting the run.
502agent_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​

AspectOpenAIBasePeak.AI /v1
Auth headerAuthorization: Bearer sk-…Authorization: Bearer sk-bpai-…
Model selectionBody's model fieldAgent selection via X-BPAI-Agent — body model is ignored.
Multi-turn stateStateless; client sends full message history each turnServer-side — pass X-BPAI-Thread-Id to resume.
stream:trueChunked SSESame shape, byte-for-byte compatible with the SDK iterators.
Tool callsFunction-call deltas inside choices[].delta.tool_callsSurfaced as SSE comments invisible to SDK parsers; v1 doesn't expose them as deltas.
Token usage in streamOptional usage on terminal chunkNot yet populated; field reserved.
Error envelopesMixed (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");
  • 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