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 keyLists the agent platform's models. Not scope-gated.
GET/v1/models/{id}API keySingle model.
POST/v1/embeddingsAPI keyProxies to the configured embedding model.
POST/v1/audio/transcriptionsAPI keyProxies 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.
  • streamfalse (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 headersContent-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