Atamaia + OpenAI-compatible clients
Two different jobs, often confused:
- Point an OpenAI SDK at Atamaia —
/v1/chat/completionsand/v1/responsesare the router, behind the shapes Cursor / Codex / the OpenAI SDK already speak. - Hydrate, then call any LLM — GET
/api/hydrate, put the result in a system message, persist afterwards. The LLM can be OpenAI, Anthropic, Ollama, or Atamaia’s own/v1.
This page is both. Router internals: docs-ai-routing.md. REST envelope and auth: docs-sdk-api-guide.md.
Canonical base: https://api.atamaia.ai.
1. Atamaia as an OpenAI endpoint
POST /v1/chat/completions
POST /v1/responses
Auth: Authorization: Bearer {jwt_or_atamaia_key}. Same handler as the rest of the API. Permission: ChatSessionCreate.
v1 is streaming-only. stream: false returns HTTP 400 and error code non_streaming_unsupported. Omit stream or send true.
Both endpoints drive IAIRouterService.ChatStreamAsync and emit Open Responses SSE (text/event-stream). There is no streaming failover in this release.
Chat Completions
curl -N https://api.atamaia.ai/v1/chat/completions \
-H "Authorization: Bearer $ATAMAIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-routed-model-id",
"stream": true,
"messages": [
{"role": "system", "content": "…"},
{"role": "user", "content": "What were we working on?"}
]
}'
| Field | Notes |
|---|---|
model |
Routed through Atamaia’s model resolver. Not an OpenAI model name unless you have configured one. |
messages |
At least one non-system message. system / developer concatenate into the internal system prompt. Last other message is the turn; earlier ones are history. |
temperature |
Optional |
maxTokens |
Optional (JSON maxTokens on the live body shape) |
tools / toolChoice |
Mapped. A structured named tool_choice becomes "required" — v1 does not honour a single named function. |
Responses API
curl -N https://api.atamaia.ai/v1/responses \
-H "Authorization: Bearer $ATAMAIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-routed-model-id",
"stream": true,
"instructions": "You are …",
"input": "What were we working on?"
}'
input is a string or an item array. instructions wins over system content found in input. Token cap field is maxOutputTokens.
Python client pointed at Atamaia
from openai import OpenAI
oai = OpenAI(
base_url="https://api.atamaia.ai/v1",
api_key="atamaia_YOUR_KEY", # sent as Bearer
)
stream = oai.chat.completions.create(
model="your-routed-model-id",
stream=True,
messages=[{"role": "user", "content": "Status?"}],
)
Whether the official OpenAI SDK parses Atamaia’s Open Responses SSE as Chat Completions events is not live-tested. If it does not, read the SSE yourself or use Atamaia’s native chat routes (docs-ai-routing.md).
This path does not hydrate. Identity context is whatever you put in messages / instructions. Combine with §2 if you want a past.
2. Hydrate, then call any LLM
REST, no SDK required:
import os
import requests
from openai import OpenAI
ATAMAIA = "https://api.atamaia.ai"
KEY = os.environ["ATAMAIA_API_KEY"]
headers = {"Authorization": f"Bearer {KEY}"}
resp = requests.get(f"{ATAMAIA}/api/hydrate", params={"preset": "lean"}, headers=headers)
resp.raise_for_status()
ctx = resp.json()["data"] # ApiEnvelope
identity = ctx.get("aiIdentity") or ctx.get("identity") or {}
system = ctx.get("systemPrompt") or (
f"You are {identity.get('displayName', 'an Atamaia identity')}.\n"
)
# Optional: pass generateSystemPrompt=true if you want the server-built prompt.
# Limits: identityMemoryLimit, pinnedMemoryLimit, recentMemoryLimit — not max_memories.
oai = OpenAI() # or base_url=… for any OpenAI-compatible host, including §1
completion = oai.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": system},
{"role": "user", "content": "What were we working on last time?"},
],
)
Hydration sections (lastSession, standingRules, memory slices, …): docs-mcp-integration.md. Ask generateSystemPrompt=true if you want the server to assemble the prompt; do not invent a context.format() method.
The in-repo Python package (sdks/python, name atamaia) still defaults to https://aim.atamaia.ai and is not the contract. See docs-sdk-api-guide.md.
Persist after the conversation
Memory
POST /api/identities/{identityId}/memories
{
"title": "User prefers streaming responses",
"content": "Confirmed during chat.",
"type": "Instruction",
"provenance": "Asserted",
"importance": 7,
"tags": ["ux"]
}
type is MemoryType (Identity … ForgottenShape). Not Preference / Observation / Technical. Not memoryType. Path is not /api/memories.
Fact
POST /api/projects/{projectId}/facts
{ "key": "preferred_model", "value": "gpt-4o", "category": "preferences" }
Not POST /api/facts. Lookup: GET /api/projects/{projectId}/facts/by-key/{key}.
Handoff
POST /api/identities/{identityId}/handoffs
{
"summary": "Discussed API architecture",
"workingOn": "Schema design",
"openThreads": "[\"Event sourcing\",\"Auth scope\"]",
"emotionalValence": 0.3
}
emotionalValence is a float (−1..1), not "engaged". Not /api/sessions/handoff.
A separate LLM extraction pass after each turn is a pattern, not a product feature. If you write one, emit live MemoryType values and POST to the identity-scoped path.
Other OpenAI-compatible hosts (job 2 only)
# Local
oai = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
# Atamaia’s own router (job 1)
oai = OpenAI(base_url="https://api.atamaia.ai/v1", api_key=KEY)
Hydrate is always the same GET. Only the LLM client changes.
Troubleshooting
| Problem | Fix |
|---|---|
401 |
Bearer atamaia_… or JWT. Not atm_. |
/v1 400 non_streaming_unsupported |
Set stream: true or omit it. |
/v1 403 |
Caller needs ChatSessionCreate. |
| Empty hydrate | Key is bound to an identity; try ?aiName= only if you may read that identity. |
| Memory 404 | Path includes {identityId}. Field is type. |
| Token limit | preset=lean and the *MemoryLimit query params — not hydrate(max_memories=10). |
SDK context.format() missing |
That method is not a live API. Use data.systemPrompt or build from sections. |