Skip to Content
API referenceOpenAI-compatible API

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.

Agents list in the UI

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

ParameterTypeRequiredDescription
modelstringYesThe 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”)
messagesarrayYesThe conversation. This endpoint is stateless: send the whole history every turn
temperaturenumberNoApplied to the run’s LLM steps for this request only
max_completion_tokensnumberNoApplied to the run’s LLM steps for this request only. Wins over max_tokens when both are sent
max_tokensnumberNoThe older name for the same cap
streambooleanNoStream the answer as SSE
stream_optionsobjectNo{ "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

RoleDescription
systemClient instructions. Folded into the conversation the agent reads; it does not replace the agent’s own system prompt
userA user turn. String content or an array of text parts
assistantA previous assistant turn: send these back for multi-turn conversations
toolA 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_call node 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 } }
Statustype / codeWhenSDK error
400invalid_request_error / unsupported_parameter, model_required, bad_requestA refused field, a missing model, bad messages, an agent that is not activeBadRequestError
401authentication_error / invalid_api_keyMissing, unknown, expired, gateway or scoped keyAuthenticationError
404invalid_request_error / model_not_foundAn agent that does not exist or that this key may not runNotFoundError
409invalid_request_error / agent_needs_inputAn autonomous run stopped to wait on a personConflictError
429rate_limit_error / rate_limit_exceededMore than 60 completions a minute on this keyRateLimitError
429insufficient_quota / insufficient_quotaAn organization spend budget set to reject is used upRateLimitError
502api_error / agent_execution_failedThe run failedInternalServerError
502api_error / answer_supersededStreaming only: the agent revised its answer after part of it had gone out (see Streaming)APIError
504api_error / agent_timeoutAn autonomous run did not finish in timeInternalServerError

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; temperature and max_completion_tokens / max_tokens are 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