Skip to content

OpenAI SDK ​

The official openai SDKs (Python, Node.js) talk to Console unchanged. Point base_url at Console, use an API token as the key, and put agent:<key> in model. Everything you know from the OpenAI API (streaming, background, retrieve, cancel, structured output) works against an agent.

When to use it: you already have OpenAI SDK code, or you want the shortest path from a script to an agent. For CI, scheduling and callbacks, the runs API offers more; the SDK reaches the same engine, so you can mix both.

Set up ​

python
import os
from openai import OpenAI

client = OpenAI(
    base_url=f"{os.environ['CONSOLE_URL']}/api/client/v1",
    api_key=os.environ["COGNIPEER_API_TOKEN"],   # cpeer_...
)
AGENT = "agent:support-bot"
typescript
import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: `${process.env.CONSOLE_URL}/api/client/v1`,
  apiKey: process.env.COGNIPEER_API_TOKEN, // cpeer_...
});
const AGENT = 'agent:support-bot';

The agent runs its published version. Add version=3 or version="draft" in extra_body to pin or test (see extras).

Responses API ​

Synchronous ​

python
resp = client.responses.create(model=AGENT, input="Where is order 1234?")
print(resp.output_text)
print(resp.id)            # resp_<runId>
print(resp.status)        # completed | in_progress | queued | failed | ...
typescript
const resp = await client.responses.create({ model: AGENT, input: 'Where is order 1234?' });
console.log(resp.output_text, resp.id, resp.status);

The call waits up to the server's wait window (10 minutes by default). If the turn is still going then, the answer is 202 with status: "in_progress"; the SDK returns it like any response. Follow it with retrieve:

python
import time
while resp.status in ("queued", "in_progress"):
    time.sleep(2)
    resp = client.responses.retrieve(resp.id)

Stream ​

python
with client.responses.stream(model=AGENT, input="Draft a status update") as stream:
    for event in stream:
        if event.type == "response.output_text.delta":
            print(event.delta, end="", flush=True)
    final = stream.get_final_response()
print("\n", final.id)
typescript
const stream = await client.responses.create({ model: AGENT, input: 'Draft a status update', stream: true });
for await (const event of stream) {
  if (event.type === 'response.output_text.delta') process.stdout.write(event.delta);
}

Events: response.created, response.in_progress, response.output_item.added, response.content_part.added, response.output_text.delta, response.output_text.done, response.content_part.done, response.output_item.done, then response.completed (the full object), response.failed or response.incomplete. When the agent shows reasoning, reasoning-summary events precede the text. Closing the connection cancels the run, unless you sent background=True.

Background, retrieve, cancel ​

background=True returns at once with a run you follow later, and a dropped connection no longer cancels it.

python
resp = client.responses.create(
    model=AGENT,
    input="Research competitor pricing and write a summary",
    background=True,
    extra_headers={"Idempotency-Key": "pricing-2026-10-18"},
)
print(resp.id, resp.status)            # resp_..., queued | in_progress

# Poll
while resp.status in ("queued", "in_progress"):
    time.sleep(3)
    resp = client.responses.retrieve(resp.id)
print(resp.status, resp.output_text)

# Or stop it
client.responses.cancel(resp.id)       # POST /responses/{id}/cancel
typescript
let r = await client.responses.create(
  { model: AGENT, input: 'Research competitor pricing', background: true },
  { headers: { 'Idempotency-Key': 'pricing-2026-10-18' } },
);
while (r.status === 'queued' || r.status === 'in_progress') {
  await new Promise((res) => setTimeout(res, 3000));
  r = await client.responses.retrieve(r.id);
}
await client.responses.cancel(r.id); // only while it still runs

resp_<runId> is the run's id in the Responses API. The same run is run_<runId> in the runs API, and GET /responses/{id} accepts either. Cancelling a finished response returns the finished response, as OpenAI does.

Structured output ​

Ask for a JSON answer with text.format. The agent's own instructions and tools still run; only the final answer is shaped by the schema, which Console validates.

python
import json

schema = {
    "type": "object",
    "properties": {
        "category": {"type": "string", "enum": ["billing", "bug", "feature", "other"]},
        "urgency": {"type": "integer"},
        "summary": {"type": "string"},
    },
    "required": ["category", "urgency", "summary"],
    "additionalProperties": False,
}

resp = client.responses.create(
    model=AGENT,
    input="Classify: 'I was charged twice and the invoice page throws a 500'",
    text={"format": {"type": "json_schema", "name": "ticket", "schema": schema, "strict": True}},
)
ticket = json.loads(resp.output_text)
# Console also returns the validated object (or why validation failed):
parsed = resp.model_extra.get("output_parsed")
error = resp.model_extra.get("output_error")
typescript
const resp = await client.responses.create({
  model: AGENT,
  input: "Classify: 'I was charged twice and the invoice page throws a 500'",
  text: { format: { type: 'json_schema', name: 'ticket', schema, strict: true } },
});
const ticket = JSON.parse(resp.output_text);
const parsed = (resp as any).output_parsed; // validated object

A schema Console cannot convert (an empty schema, an unsupported keyword) is 400 invalid_response_format, before any work starts. {"type": "json_object"} asks for any JSON object.

Files and images ​

python
resp = client.responses.create(
    model=AGENT,
    input=[{
        "role": "user",
        "content": [
            {"type": "input_text", "text": "Extract the totals from this invoice"},
            {"type": "input_file", "file_url": "https://files.example.com/invoice-1042.pdf"},
        ],
    }],
)

input_file takes file_id (a session file), file_data (a base64 data: URL) or file_url (http or https, fetched by Console, size-capped). input_image takes image_url. The agent must have attachments enabled, otherwise 403 attachments_disabled. Several input_text parts in the last message are joined. An unsupported part (input_audio) is 400 unsupported_content_part.

Continue a conversation ​

python
second = client.responses.create(model=AGENT, input="And its shipping address?", previous_response_id=resp.id)

previous_response_id (a resp_ or run_ id), conversation and session_id all name the same Console session. One run per session at a time: a second request while the first is running is 409 agent_run_conflict.

Chat completions ​

The same agent behind the older API. Use it for clients that only know chat completions.

python
chat = client.chat.completions.create(
    model=AGENT,
    messages=[{"role": "user", "content": "Where is order 1234?"}],
)
print(chat.choices[0].message.content)

# Stream
stream = client.chat.completions.create(
    model=AGENT,
    messages=[{"role": "user", "content": "Write a haiku about queues"}],
    stream=True,
    stream_options={"include_usage": True},
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")
typescript
const stream = await client.chat.completions.create({
  model: AGENT,
  messages: [{ role: 'user', content: 'Write a haiku about queues' }],
  stream: true,
  stream_options: { include_usage: true },
});
for await (const chunk of stream) process.stdout.write(chunk.choices[0]?.delta?.content ?? '');

Structured output uses response_format; the message content is then the JSON string:

python
chat = client.chat.completions.create(
    model=AGENT,
    messages=[{"role": "user", "content": "Classify: 'App crashes on login'"}],
    response_format={"type": "json_schema", "json_schema": {"name": "ticket", "schema": schema, "strict": True}},
)
ticket = json.loads(chat.choices[0].message.content)

Session handling (no session id in the OpenAI protocol): send X-Cognipeer-Session-Id, or conversation_id in the body, to continue a session; otherwise Console matches the resent history to a stored session (details). Every response returns X-Cognipeer-Session-Id. If a non-streaming call outlasts the wait window you get 202 and a body with choices: [] and cognipeer: { status, run_id, session_id }; follow run_id in the runs API. The SDK treats that as a normal response, so check chat.choices before reading it.

Extras: extra_body and headers ​

Console accepts fields the OpenAI API does not have. Send them with extra_body (Python) or by adding them to the request object (Node.js, with a cast, since the type does not know them).

FieldEffect
service_tier: "flex"Run in the deferred class: low priority, own queue. Pair with background=True. See run classes.
metadata: {...}Up to 16 string key/values, stored on the run and echoed back. Searchable with GET /agents/runs?metadata[ticket]=OPS-12.
variables: {...}Values for the agent's variables, sent next to the message (extension). Checked before the model runs; for calls from code, not for chat.
version: 3 or "draft"Pin a version, or run the draft.
agent_model: "claude-deep"Pick one of the models the agent offers (Agent models).
reasoning: {"effort": "low"}Reasoning effort for this turn (reasoning_effort on chat).
callback_url, callback_secretFor async runs: where to POST the result, and the signing secret.
runtime_context, budgetPer-turn headers and prompt placeholders; the session budget.
header Idempotency-KeyA retry of the same request returns the same run.
python
resp = client.responses.create(
    model=AGENT,
    input="Triage the overnight alerts",
    background=True,
    service_tier="flex",
    metadata={"ticket": "OPS-12", "source": "nightly"},
    extra_body={"variables": {"environment": "prod"}, "version": 3},
    extra_headers={"Idempotency-Key": "triage-2026-10-18"},
)
typescript
const r = await client.responses.create(
  {
    model: AGENT,
    input: 'Triage the overnight alerts',
    background: true,
    service_tier: 'flex',
    metadata: { ticket: 'OPS-12', source: 'nightly' },
    ...({ variables: { environment: 'prod' }, version: 3 } as object),
  },
  { headers: { 'Idempotency-Key': 'triage-2026-10-18' } },
);

What the agent ignores ​

An agent has its own configuration. These request fields are accepted but not applied to agents, and are listed back in the X-Cognipeer-Ignored-Params response header: instructions, temperature, top_p, max_output_tokens, tools, tool_choice, parallel_tool_calls, truncation, and the system and developer messages. To call a model with those, use a model key instead of agent:<key>; the same endpoints serve models.

Errors ​

The SDK raises its normal exceptions with an OpenAI-shaped body (error.message, type, param, code):

StatusCodeMeaning
400invalid_response_format, invalid_variables, unsupported_content_part, invalid_json, invalid_version, attachment_unreachableValidation; errors lists the fields of variables. invalid_json: the body is not a JSON object; invalid_version: version is not a positive integer, a numeric string or "draft"; attachment_unreachable: a remote file or image URL did not arrive in time.
403attachments_disabled, permission_errorThe agent takes no files; or the token lacks the agents service.
404model_not_foundNo agent or model by that name in the token's project.
409agent_run_conflict, idempotency_key_conflictSession busy; key reused for a different request.
429agent_run_concurrency_limit, agent_quota_exceeded, spend_limit_exceededToo many active runs (retry later, or use flex; Retry-After: 30); the agent quota is used up (Retry-After: 60); the spend limit is used up. A stream is checked before it opens, so this is a plain 429 and not a stream error.
503agent_run_interruptedThe run the call waited on was ended elsewhere (the server shut down, the worker was lost) before it finished. Retry; tool side effects may have happened.

The SDKs retry 429 and 5xx by default. With an Idempotency-Key that is safe; without one a retry can start the turn twice, so set max_retries=0 (Python) or maxRetries: 0 (Node.js) on calls that have side effects and carry no key.

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub