Portal
Documentation: all sections

Responses API

POST /v1/responses speaks the OpenAI Responses API, the shape the current OpenAI SDKs reach for by default. If your code calls client.responses.create(...), pointing it at Sable is the same one-line swap as everything else:

from openai import OpenAI

client = OpenAI(
    base_url="https://api.buildsable.com/v1",
    api_key="sk-sable_...",
)

resp = client.responses.create(
    model="sable",
    instructions="Answer in one sentence.",
    input="What is a signed receipt?",
)
print(resp.output_text)

It is the chat path underneath

The handler translates your request into a chat-completions request, runs the existing chat handler in-process, and translates the result back. It never opens an upstream connection of its own. That is deliberate: everything that makes the chat path correct — sealing on ingress, credit authorization and holds, per-key scopes and spend caps, policy-as-code, metering, signed receipts, provider failover, and the confidential fail-closed guard — therefore applies here for free and cannot drift out of sync with a second implementation.

Practically: a /v1/responses call is billed, capped, and receipted exactly like the equivalent /v1/chat/completions call. The x-sable-receipt headers come back unchanged.

Request fields

FieldNotes
modelA Sable model id. Same catalog as chat.
inputA string (one user turn), or an array of input items.
instructionsBecomes the leading system message.
max_output_tokensThe Responses spelling of max_tokens.
temperature, top_pPassed through.
toolsfunction tools only — see below.
tool_choice"auto" / "required" / "none" / {type:"function", name}.
streamtrue emits Responses SSE events.
metadataEchoed back on the response object.

Every Sable request field works here too: sable_privacy_tier, sable_region, sable_scrub, sable_run_id, and sable_context.

Input items may be message objects ({role, content}), bare strings, a function_call (an assistant turn that requested a tool), or a function_call_output (the tool's result). Text parts are flattened; image and file parts reach the model in their original form.

What is not supported, and why

previous_response_id returns a 400. It is not silently ignored. Server- side conversation state would mean persisting your prompts and completions, and Sable never does that — so there is genuinely nothing on our side to resume from. Send the prior turns in input, the way the chat surface works. Refusing is the honest answer; pretending to continue a conversation we did not keep would not be.

Hosted tools return a 400. web_search, file_search, and code_interpreter run on the vendor's own infrastructure, not on this gateway. A caller who asked for a web search and got an answer with no search performed has been misled, so these are refused rather than dropped. function tools — the ones your own code executes — translate cleanly and are fully supported. For running code, Sable has metered sandboxes, which are receipted like everything else.

Streaming

With stream: true the response is a Responses event stream:

event: response.created
event: response.in_progress
event: response.output_item.added
event: response.content_part.added
event: response.output_text.delta      ← repeated
event: response.output_text.done
event: response.content_part.done
event: response.output_item.done
event: response.completed

Tool calls stream as response.output_item.added followed by response.function_call_arguments.delta / .done. A response cut short by max_output_tokens ends with response.incomplete and an incomplete_details.reason instead of response.completed.

Sable's own trailing sable.receipt event passes through untouched. An OpenAI-shaped client ignores the unknown event type; a Sable-aware one still gets its receipt, since response headers are already on the wire by the time the final token count is known.

In batches

/v1/responses is one of the three endpoints a batch may target, alongside /v1/chat/completions and /v1/embeddings.