Chat completions
The main endpoint — request shape, streaming, parameter passthrough, and cost reporting.
POST /openai/v1/chat/completionsOpenAI-compatible, so existing clients work by changing the base URL and the key.
Request#
curl https://api.portmodels.com/openai/v1/chat/completions \
-H "Authorization: Bearer $PORTMODELS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-ai/DeepSeek-V3",
"messages": [
{"role": "system", "content": "You are concise."},
{"role": "user", "content": "Summarize the Treaty of Westphalia in two sentences."}
]
}'| Field | Required | Notes |
|---|---|---|
model | yes | A model key from /v1/models |
messages | yes | List of {role, content} objects; both fields are strings |
stream | no | true for server-sent events |
session_id | no | Conversation identifier, 1–1,024 bytes; reuse it across turns and use a different value for each conversation |
provider | no | With provider_key, pins the request to an enabled provider route |
provider_key | no | Encrypted pmbyok:v1 envelope produced by a trusted client; never plaintext |
Parameter passthrough#
You can send model parameters such as temperature, max_tokens, top_p,
stop, response_format, tools, and reasoning-effort settings. Support
depends on the selected model.
PortModels request fields include action, model, messages, stream,
stream_options, session_id, routing, provider, provider_key, app_id,
connected, and user.
For the browser setup flow, threat model, and billing details, see Bring your own provider key.
{
"model": "deepseek-ai/DeepSeek-V3",
"messages": [{"role": "user", "content": "..."}],
"temperature": 0.2,
"max_tokens": 800,
"response_format": {"type": "json_object"}
}Tip
If a request fails after you add an optional parameter, check that the selected model supports it. See Errors for retry and support guidance.
Response#
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1771200000,
"model": "deepseek-ai/DeepSeek-V3",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "..." },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 128,
"total_tokens": 170,
"cost": 0.00031
}
}usage.cost is a PortModels addition: what this request actually cost, in
credits, already including the app markup where one applies. Standard OpenAI
clients ignore the extra field.
Streaming#
Set "stream": true and read server-sent events:
curl -N https://api.portmodels.com/openai/v1/chat/completions \
-H "Authorization: Bearer $PORTMODELS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "deepseek-ai/DeepSeek-V3", "stream": true,
"messages": [{"role": "user", "content": "Count to five."}]}'data: {"choices":[{"delta":{"content":"One"}}]}
data: {"choices":[{"delta":{"content":", two"}}]}
data: {"usage":{"cost":0.00012}}
data: [DONE]A cost event is emitted immediately before the [DONE] terminator, so a
streaming client can report spend the same way a non-streaming one does.
Using an OpenAI client library#
from openai import OpenAI
client = OpenAI(
api_key="pm_...",
base_url="https://api.portmodels.com/openai/v1",
)
response = client.chat.completions.create(
model="deepseek-ai/DeepSeek-V3",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)On behalf of a user#
Swap the API key for the user's Connect access token — nothing else changes:
curl https://api.portmodels.com/openai/v1/chat/completions \
-H "Authorization: Bearer pmc_..." \
-H "Content-Type: application/json" \
-d '{"model": "deepseek-ai/DeepSeek-V3", "messages": [{"role": "user", "content": "Hello"}]}'The token must carry the models.run scope, the model must be in your app's
allowed_models list, and the charge lands on the user's credits with your
markup applied.