OpenAI-compatible API
This page is for developers who already have software that talks to OpenAI’s API. almyty answers in the same format, so that software can talk to one of your almyty agents instead by changing two settings: the address and the key. For example, an internal tool built on the OpenAI library can send its questions to your support agent, with its tools, memory and limits, instead of to a bare model. If you don’t write code, you can skip this page.

An almyty agent can be called through an OpenAI-compatible chat completions API, so an application built on the official OpenAI SDK (Python or JavaScript) reaches an agent by changing the base URL and the key. Workflow and autonomous agents both answer.
from openai import OpenAI
client = OpenAI(
base_url="https://api.almyty.com/v1",
api_key="your-almyty-api-key",
)
print([m.id for m in client.models.list()]) # the agents this key may call
response = client.chat.completions.create(
model="agent:my-agent",
messages=[
{"role": "user", "content": "My name is Frane."},
{"role": "assistant", "content": "Noted."},
{"role": "user", "content": "What is my name?"},
],
)
print(response.choices[0].message.content)An agent is not a model: it runs its own pipeline and its own tools and hands back the finished answer. Fields that would change that (client-declared tools, a forced response format, more than one choice) are refused with a 400 naming the field rather than accepted and ignored, because a caller whose tools never fire and who gets no error has nothing to debug.
Endpoints
POST https://api.almyty.com/v1/chat/completions
GET https://api.almyty.com/v1/models
GET https://api.almyty.com/v1/models/{model}The base URL must end in /v1, as it must for OpenAI itself: with
base_url="https://api.almyty.com" the SDK calls /chat/completions, which
is not this API, and gets a 404. A trailing slash (.../v1/) is fine.
Authenticate with an almyty platform API key as a bearer token
(Authorization: Bearer ..., which is what the SDKs send). An
OpenAI-Organization or OpenAI-Project header is ignored: the key decides
the organization.
Agent API access
On the agent’s page, API access decides who may call it: Only you, One team, Everyone in the organization, Outside, open or Outside, protected. Internal callers sign in to almyty. For protected outside access, enable any combination of keys, usernames and passwords, company sign-in, or signed tokens from your system. See Access and sign-in.
For an agent with an API access target, set the model to agent: followed by its UUID. An SDK can send one of that endpoint’s named keys as its Bearer token. The endpoint can also accept a company sign-in token or a token from your own system when you enable those methods. To list or retrieve that agent through /v1/models, send an x-almyty-agent-id header containing its UUID so the server knows which endpoint’s access rules to apply before authentication.
An explicitly open agent endpoint needs no credential. SDKs that require an API key argument can use a placeholder; the endpoint’s open access choice admits the request.
The organization and agent are resolved from the target; OpenAI-Organization and OpenAI-Project headers cannot change them. Revoked or expired keys are refused. Opening an endpoint does not bypass the agent’s publication checks.
Which agents a key can call
GET /v1/models lists exactly the agents a completion with the same key
would run, each as agent:<id> in the OpenAI model shape, and
GET /v1/models/{model} answers for the same set:
- only active agents; a draft or paused agent is not listed, and a completion naming it is refused with a 400;
- agents of the key’s organization only;
- org-visible agents, team agents when the key’s user is in the team (or an org owner or admin), and private agents of the key’s own user;
- a key minted for one agent lists and runs that agent only.
For a platform-key request without an API target, a key is refused with a 401 when it is a gateway key, carries scopes, has
expired or been revoked, or belongs to someone who is no longer an active
member of the organization. Anything outside the set is the same 404
(model_not_found) an agent that does not exist gets.
Request
curl -X POST https://api.almyty.com/v1/chat/completions \
-H "Authorization: Bearer $ALMYTY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "agent:my-agent",
"messages": [
{ "role": "system", "content": "You are a helpful assistant." },
{ "role": "user", "content": "What is the weather in Berlin?" }
],
"temperature": 0.7,
"max_completion_tokens": 1024,
"stream": false
}'Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | Yes | The agent: agent:<id> (what /v1/models returns), agent:<name>, a bare id or name, or the name as a slug (my-agent for “My Agent”) |
messages | array | Yes | The conversation. This endpoint is stateless: send the whole history every turn |
temperature | number | No | Applied to the run’s LLM steps for this request only |
max_completion_tokens | number | No | Applied to the run’s LLM steps for this request only. Wins over max_tokens when both are sent |
max_tokens | number | No | The older name for the same cap |
stream | boolean | No | Stream the answer as SSE |
stream_options | object | No | { "include_usage": true } adds a final usage-bearing chunk before [DONE] |
user, metadata, store and parallel_tool_calls are accepted and have no
effect. Limits: at most 100 messages, 100 KB per message, 1 MB per request
body.
Message roles
| Role | Description |
|---|---|
system | Client instructions. Folded into the conversation the agent reads; it does not replace the agent’s own system prompt |
user | A user turn. String content or an array of text parts |
assistant | A previous assistant turn: send these back for multi-turn conversations |
tool | A tool result from an earlier turn, kept as its own turn |
The whole array reaches the agent. Because an agent prompt binds one string,
the conversation is rendered into it as a labelled transcript ([system],
[user], [assistant] blocks) rather than passed as a structured list; a
single user message with no system message is passed through unchanged.
Fields that are refused
These come back as a 400 whose error.param names the field, before the
agent runs:
tools, tool_choice (other than none), functions, function_call, a
response_format other than text, n other than 1, top_p other than
1, a non-zero frequency_penalty or presence_penalty, stop, seed,
logprobs, top_logprobs.
The defaults a client library sends without being asked to (n: 1,
top_p: 1, zero penalties) are accepted, since their behaviour is already
what happens.
{
"error": {
"message": "This endpoint does not take client-declared tools. An almyty agent runs its own tools and returns the finished answer, so there is no turn at which one could be handed back to you. Give the agent the tools instead.",
"type": "invalid_request_error",
"code": "unsupported_parameter",
"param": "tools"
}
}Response
Non-streaming
{
"id": "chatcmpl-uuid",
"object": "chat.completion",
"created": 1711234567,
"model": "agent:0d6f1c2e-...",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "The current weather in Berlin is 12°C with partly cloudy skies."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 31,
"completion_tokens": 12,
"total_tokens": 43
}
}model is always the agent’s agent:<id>, whichever name the request used.
finish_reason is always stop: the answer is the agent’s finished output.
Token usage
For a workflow agent prompt_tokens and completion_tokens are what the
providers reported, summed over the run’s LLM steps. An autonomous run records
only its total, so the split comes back as 0/0 with a real
total_tokens. The x-almyty-usage-split header says which you are looking
at: measured, unavailable, or in-stream on a streamed response (read the
final usage chunk).
Streaming
With stream: true the response is Server-Sent Events. The content is the
agent’s answer, the same text the non-streaming call returns, and nothing
else: intermediate steps are never streamed as content. How it arrives
depends on the agent:
- Token by token, as the model writes it, when the answer is one model
call’s text. For a workflow agent that is an
llm_callnode feeding the output node directly, with the output mapping exactly{{nodes.<llm node>.output}}, no tools on that node and no routing policy (a stream cannot fall over to the next routed candidate once tokens are out). For an autonomous agent it is the answer step: a streamed request asks the run to write its answer with a final call that offers no tools, and that call’s tokens stream while the steps before it (tool calls and the narration around them) are never sent. This costs the run one extra model call. - In one piece when the run finishes, for every other shape: an output node that wraps or reshapes the text, a verify node or merge before the output, a model call with tools, a loop, and an autonomous agent with a verify panel on its final output or a strategy that judges candidate answers, where no answer exists until the choice is made.
The finished output is always the answer. Anything the token stream did not
carry is sent before the stream ends; should the agent revise its answer after
part of it went out (an answer call that is retried, say), the stream ends with
an answer_superseded error rather than with a wrong answer, and a retry is
the right response.
While the run works, a : keep-alive comment line goes out every 15 seconds
so a proxy does not close an idle stream; the SDKs skip it.
data: {"id":"chatcmpl-1711234567000","object":"chat.completion.chunk","created":1711234567,"model":"agent:0d6f1c2e-...","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-1711234567000","object":"chat.completion.chunk","created":1711234567,"model":"agent:0d6f1c2e-...","choices":[{"index":0,"delta":{"content":"The current weather in Berlin is 12°C."},"finish_reason":null}]}
data: {"id":"chatcmpl-1711234567000","object":"chat.completion.chunk","created":1711234567,"model":"agent:0d6f1c2e-...","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]With stream_options: {"include_usage": true} one further chunk arrives
before [DONE], carrying usage and an empty choices array.
A run that fails after the stream has started ends with a frame carrying an
error object, which both SDKs raise as an APIError:
data: {"id":"chatcmpl-1711234567000","object":"chat.completion.chunk","created":1711234567,"model":"agent:0d6f1c2e-...","error":{"message":"...","type":"api_error","code":"agent_execution_failed","param":null}}
data: [DONE]import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.almyty.com/v1",
apiKey: process.env.ALMYTY_API_KEY,
});
const stream = await client.chat.completions.create({
model: "agent:my-agent",
messages: [{ role: "user", content: "Summarize today's news" }],
stream: true,
stream_options: { include_usage: true },
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}Autonomous agents
An autonomous agent runs on the autonomous runtime and the request waits for
the run to finish, up to 10 minutes; after that the run is cancelled and the
request fails with a 504. A run that stops to wait on a person (it asks the
user a question, or reaches an approval gate) is cancelled and the request
fails with a 409 agent_needs_input: a stateless completion has no turn on
which anyone could answer it. Put what it needs in the next message, or use
the agent runs API for human-in-the-loop work.
Tools
An agent runs its own tools. A tool_call node executes inside the run and
the answer comes back finished, so there is no turn at which a tool could be
handed back for you to run, and no tool_calls in the response. Give the
agent the tools instead of declaring them on the request.
Errors
Every error has the OpenAI shape, so the SDKs raise their usual typed errors:
{ "error": { "message": "...", "type": "invalid_request_error", "code": "model_not_found", "param": null } }| Status | type / code | When | SDK error |
|---|---|---|---|
| 400 | invalid_request_error / unsupported_parameter, model_required, bad_request | A refused field, a missing model, bad messages, an agent that is not active | BadRequestError |
| 401 | authentication_error / invalid_api_key | Missing, unknown, expired, gateway or scoped key | AuthenticationError |
| 404 | invalid_request_error / model_not_found | An agent that does not exist or that this key may not run | NotFoundError |
| 409 | invalid_request_error / agent_needs_input | An autonomous run stopped to wait on a person | ConflictError |
| 429 | rate_limit_error / rate_limit_exceeded | More than 60 completions a minute on this key | RateLimitError |
| 429 | insufficient_quota / insufficient_quota | An organization spend budget set to reject is used up | RateLimitError |
| 502 | api_error / agent_execution_failed | The run failed | InternalServerError |
| 502 | api_error / answer_superseded | Streaming only: the agent revised its answer after part of it had gone out (see Streaming) | APIError |
| 504 | api_error / agent_timeout | An autonomous run did not finish in time | InternalServerError |
Every completion response carries X-RateLimit-Limit, X-RateLimit-Remaining
and X-RateLimit-Reset, and a 429 carries Retry-After, which the SDKs wait
for before their automatic retry. The window is 60 requests per key per
minute.
Limitations
- An agent’s pipeline decides which model runs each step;
temperatureandmax_completion_tokens/max_tokensare the only sampling controls a request can set - Token-by-token streaming covers the answer shapes listed under Streaming; any other agent streams its answer in one piece when the run finishes
- Client-declared tools and structured-output modes are refused, not ignored
/v1/responses, embeddings and the other OpenAI endpoints are not served