Skip to content

Chat Completions ​

POST /v1/chat/completions is the OpenAI Chat Completions API, and the one most apps speak. Point any OpenAI SDK at http://127.0.0.1:4010/v1 with your plugboard key.

python
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:4010/v1", api_key=PLUGBOARD_KEY)
reply = client.chat.completions.create(model="fast", messages=[{"role": "user", "content": "reply with exactly: pong"}])

The same with curl and jq. PLUGBOARD_BASE_URL is http://127.0.0.1:4010/v1 and PLUGBOARD_KEY is your key. post sends a JSON body:

sh
post() { # $1 = JSON body
  curl -sS -f "$PLUGBOARD_BASE_URL/chat/completions" \
    -H "Authorization: Bearer $PLUGBOARD_KEY" -H "Content-Type: application/json" -d "$1"
}
sh
out=$(post "$(jq -cn --arg m "$1" '{model: $m, messages: [{role: "user", content: "Reply with exactly: pong"}]}')")

Plugboard is stateless here: every request carries the whole conversation in messages.

Request fields ​

FieldWhat plugboard does
modelAn alias, or provider/model[#variant] that models allows.
messagessystem, developer, user, assistant and tool messages. User content can hold text and images. A file part takes file_data (a PDF data URL) or a file_id from the Files API. An unknown file_id is a 404.
stream, stream_options.include_usageServer-sent events. With include_usage, the last chunk carries the usage.
tools, tool_choice, parallel_tool_callsTool calls come back to you; plugboard never runs them. parallel_tool_calls: false keeps the first call and sets x-plugboard-parallel-tools: limited.
response_formatjson_schema or json_object. See structured output.
nSeveral answers, one full model call each, up to maxChoices. See completions.
temperature, top_p, presence_penalty, frequency_penaltyPassed to the model. Values a model is known to reject are dropped and listed in x-plugboard-dropped-params.
seedAn integer, passed to the model. A model whose protocol sends no seed (Claude, GPT-5 and 6) drops it and lists seed in x-plugboard-dropped-params. There is no top-k field here.
max_completion_tokens, max_tokensThe output cap. max_completion_tokens wins when both are sent.
stopUp to 4 sequences. The answer ends before the first match.
reasoning_effortPicks the model's variant of that name, else the nearest one above it, else its highest. A model with no variants answers without thinking and sets x-plugboard-thinking: unavailable.
userThe end user, for usage per user and the history title.

Refused ​

Errors are 400s with the code below and param naming the field.

RequestCode
logprobs: true, or any top_logprobsunsupported_parameter
n above maxChoicestoo_many_choices
A tool_choice that names a tool, with response_format json_schemaunsupported_parameter
A tool named respond, with response_format json_schemareserved_tool_name

Streaming ​

sh
out=$(curl -sS -N "$PLUGBOARD_BASE_URL/chat/completions" -H "Authorization: Bearer $PLUGBOARD_KEY" -H "Content-Type: application/json" \
  -d "$(jq -cn --arg m "$1" '{model: $m, stream: true, stream_options: {include_usage: true}, messages: [{role: "user", content: "Reply with exactly: pong"}]}')")

The status and headers go out with the first model event. A slow model gets a keep-alive comment every 15 seconds. If the run fails after the stream has started, the client gets an error event and then data: [DONE].

Tools ​

The model's tool calls come back with finish_reason: "tool_calls". Run the tool yourself and send the result back as a tool message with the same call id. The whole round trip with curl:

sh
body=$(jq -cn --arg m "$model" --argjson tool "$tool" \
  '{model: $m, messages: [{role: "user", content: "What is the weather in Paris? Use the tool."}], tools: [$tool]}')
out1=$(post "$body")
call_id=$(jq -r '.choices[0].message.tool_calls[0].id // ""' <<<"$out1")
args=$(jq -r '.choices[0].message.tool_calls[0].function.arguments // "{}"' <<<"$out1")
# The assistant message goes back as the model sent it, then the tool result with the same call id.
body=$(jq -cn --arg m "$model" --argjson tool "$tool" --argjson turn1 "$(jq -c '.choices[0].message' <<<"$out1")" \
  --arg id "$call_id" --rawfile result "$SHARED/get_weather.result.json" \
  '{model: $m, tools: [$tool], messages: [{role: "user", content: "What is the weather in Paris? Use the tool."}, $turn1, {role: "tool", tool_call_id: $id, content: $result}]}')
out2=$(post "$body")

Tool call ids differ by model, so do not parse them. maxTools (default 64) caps the tools one request may carry. An app with opencodeTools can also let OpenCode run some of its own tools; see Tools, structured output and images.

Structured output ​

response_format: {"type": "json_schema", ...} returns the JSON as the message content. The model answers through a respond tool, and the provider does not enforce the schema, so validate the result. json_object carries no schema: name the keys in the prompt.

With caller tools as well, the model may either call one of your tools or give the schema answer.

Errors ​

Errors use OpenAI's shape, {"error": {"message", "type", "code", "param"}}. See Limits and errors for the codes and Response headers for the x-plugboard-* headers.