Chat & completions
The native GPUniq chat API — completions, server-side chat sessions with full history, shareable transcripts, the generations library, and natural-language to shell commands.
The native surface at /v1/llm/* returns GPUniq's wrapped envelope and adds
what the OpenAI protocol has no room for: server-side chat sessions, USD cost
on every response, and the generations library.
If you want a drop-in replacement for api.openai.com instead — for Claude
Code, Cursor, Aider or the official SDKs — use
OpenAI compatibility.
Requests are authenticated with X-API-Key: gpuniq_... on native routes
(a dashboard JWT via Authorization: Bearer also works). The default model
when you omit model is claude-haiku-4-5.
Simple chat
response = client.llm.chat("claude-haiku-4-5", "Explain how transformers work")
print(response)
curl -X POST "https://api.gpuniq.com/v1/llm/chat/completions" \
-H "X-API-Key: gpuniq_your_key" \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "Explain how transformers work"}],
"model": "claude-haiku-4-5"
}'
Chat completion (full)
data = client.llm.chat_completion(
messages=[
{"role": "system", "content": "You are a helpful AI assistant."},
{"role": "user", "content": "What is gradient descent?"},
],
model="claude-sonnet-4-6",
temperature=0.7,
max_tokens=1000,
top_p=0.9,
)
print(data["content"])
print(f"Tokens used: {data['tokens_used']} cost: ${data['cost_usd']:.6f}")
Parameters
List of message objects with role ("system", "user", "assistant") and content.
Model slug (e.g., claude-opus-4-7, gpt-5.2, gemini-3-pro). Defaults to claude-haiku-4-5.
Maximum tokens in the response.
Sampling temperature (0.0-2.0). Higher = more creative.
Top-p nucleus sampling parameter.
A non-streaming request asking for max_tokens > 4096 is served through an
internal keep-alive path rather than refused — but it holds the connection
for the whole generation and reports upstream failures inside a 200. Set
"stream": true or use the job-based long-poll API — see
Long generations.
Cached input. When the provider that served the call gave GPUniq a
cache-read discount, the cached part of your prompt
(usage.prompt_tokens_details.cached_tokens) is billed at the catalog's
retail_cached_input_usd_per_mtok instead of the input rate. When it did
not, cached tokens are billed as normal input — the discount mirrors what
GPUniq actually paid, so two identical requests served by different routes
can cost differently. usage.cost on every response is the amount charged.
Chat sessions
Persistent conversations stored server-side — the model sees the full history on every call, so you send one message rather than re-uploading the transcript.
Persistent conversations stored server-side — the model sees the full history on every call:
# Create a session
session = client.llm.create_chat_session(
model="claude-sonnet-4-6",
title="Research Assistant",
)
# Send messages within the session
reply = client.llm.send_message(
chat_id=session["id"],
message="What are the key papers on attention mechanisms?",
temperature=0.5,
)
# List all sessions
sessions = client.llm.list_chat_sessions(limit=50)
# Get a session with full message history
full = client.llm.get_chat_session(chat_id=session["id"])
# Update title
client.llm.update_chat_session(chat_id=session["id"], title="New Title")
# Delete
client.llm.delete_chat_session(chat_id=session["id"])
Sharing a transcript
POST /v1/llm/chats/{chat_id}/share returns a public link to a read-only copy
of the conversation, reusing the existing link if one was already created.
Anyone with the token can read it through
GET /v1/llm/chats/shared/{share_token} without a key.
curl -X POST https://api.gpuniq.com/v1/llm/chats/42/share \
-H "X-API-Key: gpuniq_your_key"
Messages and context
| Endpoint | What it does |
|---|---|
GET /v1/llm/chats/{chat_id}/messages | Page through a session's messages. |
DELETE /v1/llm/chats/{chat_id}/messages/{message_id} | Remove one message from the history. |
GET /v1/llm/chats/{chat_id}/context | The messages the next turn will be sent with. Pass ?model= to see the history as trimmed for that model's context window. |
POST /v1/llm/chats/{chat_id}/turns | Record a turn you already generated and paid for on a streaming surface, so it lands in the session history. Never charges. |
Generating images inside a session
Posting to /v1/llm/chats/{chat_id}/messages with an image model puts the
prompt and the resulting image into the chat history as a turn. See
Generating an image inside a chat session.
The generations library
Every image, video, music and audio generation you run is recorded. GET /v1/llm/generations returns a page of them, newest first.
| Query parameter | Effect |
|---|---|
kind | Filter by media kind. |
model | Filter by model slug. |
q | Free-text search over prompts. |
limit | Page size, default 40. |
before_id | Pass the previous page's next_before_id to continue. A null there means there is nothing older. |
GET /v1/llm/generations/{generation_id} returns one in full; DELETE on the
same path removes it.
curl "https://api.gpuniq.com/v1/llm/generations?kind=video&limit=20" \
-H "X-API-Key: gpuniq_your_key"
Generate terminal commands
POST /v1/llm/generate-commands turns a natural-language task into an ordered
list of shell commands, each annotated with a danger_level. The Python SDK has
no helper for this endpoint — call it over HTTP.
| Field | Type | Notes |
|---|---|---|
prompt | string | Required, 1–1000 characters. |
max_commands | int | 1–10, default 5. |
model | string | Optional; the server default is used when omitted. |
context | object | Optional — OS, shell history, environment. |
import requests
body = requests.post(
"https://api.gpuniq.com/v1/llm/generate-commands",
headers={"X-API-Key": "gpuniq_your_key"},
json={"prompt": "find all Python files larger than 1MB and sort by size",
"max_commands": 5},
).json()
if body["exception"] != 0: # refusals arrive as HTTP 200
raise RuntimeError(body["data"])
for c in body["data"]["commands"]:
print(f"[{c['danger_level']}] {c['command']} # {c['description']}")
The response also carries explanation, warnings, alternatives,
tokens_used, cost_usd and balance_usd. Per-command fields beyond
command, description and danger_level (step, requires_confirmation,
expected_output) are written by the model and may be missing. If the model's
reply cannot be parsed, commands comes back empty with the reason in
explanation — treat an empty list as a result to handle, not as success.
This endpoint predates the shared error catalog: its error_code values are
upper-case (INSUFFICIENT_BALANCE), unlike the lower-case codes on every
other surface. Compare case-insensitively if you branch on them.
Errors
Every failure returns a structured error_code you can branch on. The full
catalog, envelope shapes and recovery recipes live in the
Error reference.