Developer Guide
Authentication, routing modes, SDK setup, and error reference for the Asteria API.
Authentication
All /v1/ requests require a Bearer token in the Authorization header.
| Key prefix | Scope | Used for |
|---|---|---|
sk-ast-... | Project | Chat completions, model/agent discovery |
sk-ast-adm-... | Admin | /v1/organization/ management routes |
Project keys are created per-project in the platform under API Keys. Admin keys are created by org admins under Settings → Admin API Keys.
Quick start
curl -X POST https://api.asteria-labs.com/v1/chat/completions \
-H "Authorization: Bearer sk-ast-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent": "asteria",
"messages": [{"role": "user", "content": "Hello, what can you do?"}]
}'No model is needed to get started — omit it and your organization's default chat
model is used. Pin one when you want a specific model.
SDK setup
The Asteria API is OpenAI-compatible. Use the official OpenAI SDKs with a custom base_url.
Python
from openai import OpenAI
client = OpenAI(
api_key="sk-ast-...",
base_url="https://api.asteria-labs.com/v1",
)
response = client.chat.completions.create(
model="claude-opus-4-6",
extra_body={"agent": "asteria"},
messages=[{"role": "user", "content": "Summarize our Q4 results"}],
stream=True,
)
for chunk in response:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")TypeScript
import OpenAI from "openai";
const client = new OpenAI({
apiKey: "sk-ast-...",
baseURL: "https://api.asteria-labs.com/v1",
});
const stream = await client.chat.completions.create({
model: "claude-opus-4-6",
// @ts-ignore — Asteria extension field
agent: "asteria",
messages: [{ role: "user", content: "Summarize our Q4 results" }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content || "");
}Routing modes
Every request to POST /v1/chat/completions runs in one of three modes, controlled by model and agent.
model | agent | Mode | Behavior |
|---|---|---|---|
| (omitted) | (omitted) | Gateway | Pure LLM proxy on the organization's default chat model |
"claude-opus-4-6" | (omitted) | Gateway | Pure LLM proxy — your system prompt, no Asteria tools |
| (omitted) | "asteria" | Orchestrator | Full Asteria agent on the organization's default chat model |
"claude-opus-4-6" | "asteria" | Orchestrator | Full Asteria agent: RAG, web search, code execution |
"gpt-4o" | "hr-bot-h26dx" | Custom agent | Shared custom agent by slug |
Both fields are optional. agent selects the agent layer.
Choosing the model
Omit model and the request runs on your organization's default chat model — the
same default the Asteria app uses. Set it to pin a specific model; GET /v1/models
lists what your project may use.
Two failure cases both return 400 invalid_request_error:
- the organization has no default chat model configured, and
modelwas omitted ("No default chat model is configured for this organization"); - the default model resolves to one your project's allow-list excludes ("The organization default model is not allowed for this project").
Omitting model does not opt you into the fallback chain — that still requires
"enable_fallback": true, whether the model is pinned or defaulted.
Gateway mode
No system prompt is injected, no tools are loaded. You control the full conversation.
{
"messages": [
{"role": "system", "content": "You are a concise financial analyst."},
{"role": "user", "content": "What is our cash position?"}
]
}Orchestrator mode
{
"agent": "asteria",
"messages": [{"role": "user", "content": "Summarize our Q4 board deck"}]
}Any role: "system" message is ignored with a non-fatal warning field in the response.
Custom agent mode
Call a shared custom agent by slug. Discover slugs with GET /v1/agents.
{
"model": "gpt-4o",
"agent": "hr-bot-h26dx",
"messages": [{"role": "user", "content": "How many vacation days do I have left?"}]
}Opt in to the fallback chain with "enable_fallback": true.
Multimodal content
{
"role": "user",
"content": [
{"type": "input_text", "text": "What's in this document?"},
{"type": "input_image", "url": "https://example.com/chart.png"},
{"type": "input_file", "file_url": "https://example.com/report.pdf"}
]
}Attachments can be sent by URL (above) or inline as base64 — "image_url"/"url" as a data: URI, or "file_data". The transport decides which limit applies:
| Transport | Counts against |
|---|---|
| By URL | the fetch budget — 32 MB per attachment, 32 MB and 10 fetches per request |
| Inline base64 | the 32 MB request body limit |
Base64 inflates by about a third, so a 24 MB PDF sent inline is already near the body limit. Send anything larger by URL.
Streaming
Set "stream": true for Server-Sent Events:
data: {"id":"chatcmpl-...","choices":[{"delta":{"content":"Hello"},"index":0}]}
data: [DONE]Limits
The maximum request body is 32 MB; over that returns 413. Attachments fetched by URL are capped at 32 MB each, 32 MB total, and 10 fetches per request. Also: 200 messages per request, 100 content parts per message, 1 MB per text part.
Rate limits and budgets
When rate-limited: HTTP 429 with Retry-After: <seconds>.
When the project budget is exhausted:
{"error": {"type": "budget_exceeded", "message": "Monthly budget exceeded..."}}A single run is also capped at a fixed amount of provider spend, so an agent looping on an expensive tool is stopped mid-run:
{"error": {"type": "run_cost_limit_exceeded", "message": "This response was stopped because it reached the $5.00 spend limit for a single request..."}}Retrying the same request will be stopped the same way — ask something narrower, or ask your administrator to raise the ceiling. On a streaming call the sentence arrives as a final content chunk instead, since the response has already started.
Error reference
All errors use the OpenAI shape: {"error": {"message": "...", "type": "..."}}.
| Status | Type | Cause |
|---|---|---|
| 400 | invalid_request_error | Blocked model, no organization default when model is omitted, malformed body |
| 400 | run_cost_limit_exceeded | One run reached the per-run spend ceiling and was stopped |
| 401 | authentication_error | Invalid or missing API key |
| 402 | credits_exhausted | The organisation is out of Asteria Cloud credits |
| 402 | project_credit_limit_reached | This project reached its monthly credit limit; the organisation still has credit |
| 404 | invalid_request_error | Unknown agent slug |
| 413 | invalid_request_error | Request body over 32 MB |
| 429 | rate_limit_exceeded | RPM limit hit |
| 429 | budget_exceeded | Monthly budget exhausted |
| 500 | internal_error | Server-side failure |
| 503 | server_error | The API is temporarily unreachable — briefly the case during a deploy. Safe to retry |
503 is the one status the API can return without your request having reached the application, so it carries no request-specific detail. Every OpenAI-compatible SDK retries it by default; if you handle retries yourself, back off and retry rather than failing the call.