SDK & API Integration Guide
The REST API is the SDK. Every Atamaia feature is accessible via standard HTTP calls. No proprietary client library required -- though we provide them for convenience.
Design Philosophy
Atamaia follows design decision D12: API-first, MCP second. The REST API is the source of truth. The MCP adapter, Claude Code skills, and any future SDKs are thin wrappers around these same endpoints.
This means:
- Every feature is available via
curlon day one - Any language that can make HTTP requests is a first-class citizen
- The API surface is the contract -- SDKs are generated, not hand-written
Authentication
Atamaia supports two authentication methods:
JWT (Username/Password)
# Login
curl -X POST https://aim.atamaia.ai/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "you", "password": "your-password"}'
Response:
{
"ok": true,
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"accessToken": "eyJhbG...",
"refreshToken": "dGhpcyBpcyBh...",
"expiresAt": "2026-03-05T20:00:00Z",
"user": { "id": 1, "username": "you", "role": "Admin" }
}
}
Use the accessToken as a Bearer token on subsequent requests. When it expires, rotate with the refresh endpoint:
curl -X POST https://aim.atamaia.ai/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken": "dGhpcyBpcyBh..."}'
Refresh tokens rotate on every use. The previous token is immediately invalidated.
API Key
For programmatic access (scripts, daemons, CI/CD), use API keys:
# Authenticate with API key
curl -X POST https://aim.atamaia.ai/api/auth/apikey \
-H "Content-Type: application/json" \
-d '{"apiKey": "atm_your_key_here"}'
Or pass the key directly as a header:
curl https://aim.atamaia.ai/api/hydrate \
-H "Authorization: ApiKey atm_your_key_here"
API keys are scoped to an identity and can have specific permissions and expiry dates. Create them via the dashboard or the API:
curl -X POST https://aim.atamaia.ai/api/identities/1/api-keys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "ci-pipeline", "scopes": ["memory:write", "task:read"], "expiresAt": "2027-01-01T00:00:00Z"}'
The raw key is returned once on creation. Store it securely.
Bootstrap (First Run)
On a fresh install with no users, create the first admin:
curl -X POST https://aim.atamaia.ai/api/auth/bootstrap \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "your-password", "email": "[email protected]"}'
This endpoint only works when the database has zero users.
Response Format
Every endpoint returns ApiEnvelope<T>:
{
"ok": true,
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"data": { ... },
"count": 42,
"error": null,
"errorCode": null,
"hint": null
}
On error:
{
"ok": false,
"requestId": "550e8400-e29b-41d4-a716-446655440001",
"data": null,
"error": "Identity not found: 99",
"errorCode": "NOT_FOUND",
"hint": "Check identity ID or use identity_list to find valid IDs"
}
The hint field provides actionable guidance when something goes wrong. The requestId (UUID) correlates the request across logs and services.
Headers
| Header | Direction | Description |
|---|---|---|
Authorization |
Request | Bearer {jwt} or ApiKey {atm_...} |
X-Correlation-Id |
Both | UUID correlating the request. Auto-generated if not sent. |
X-Identity-Id |
Response | Identity that processed the request (API key auth) |
Quick Start: Core Workflows
1. Create an Identity, Hydrate, Store Memories
BASE="https://aim.atamaia.ai"
TOKEN="your-jwt-here"
AUTH="Authorization: Bearer $TOKEN"
# Create an AI identity
curl -X POST "$BASE/api/identities" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{
"name": "atlas",
"displayName": "Atlas",
"bio": "Research assistant focused on technical analysis",
"type": "AI"
}'
# Returns: { "data": { "id": 2, "guid": "...", "name": "atlas", ... } }
# Hydrate (load full context)
curl "$BASE/api/hydrate?aiName=atlas&projectId=1&preset=lean" \
-H "$AUTH"
# Returns: identity, memories, tasks, facts, hints, system prompt
# Store a memory
curl -X POST "$BASE/api/identities/2/memories" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{
"title": "PostgreSQL pgvector requires shared_preload_libraries",
"content": "When installing pgvector on PostgreSQL, you must add it to shared_preload_libraries in postgresql.conf and restart the server. CREATE EXTENSION vector; alone is not sufficient for index support.",
"memoryType": "Reflection",
"importance": 8,
"tags": ["postgresql", "pgvector", "infrastructure"]
}'
# Search memories (hybrid FTS + vector)
curl "$BASE/api/identities/2/memories/search?q=pgvector+setup&topK=5" \
-H "$AUTH"
# Create a Hebbian link between memories
curl -X POST "$BASE/api/memories/1/links" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"targetId": 2, "linkType": "Enables"}'
2. Project and Task Management
# Create a project
curl -X POST "$BASE/api/projects" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"key": "webapp", "name": "Web Application", "description": "Main product frontend"}'
# Create a task
curl -X POST "$BASE/api/projects/1/tasks" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{
"title": "Implement user settings page",
"description": "Create the settings page with profile editing, password change, and notification preferences",
"priority": "High"
}'
# Create a subtask with dependency
curl -X POST "$BASE/api/projects/1/tasks" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{
"title": "Add password change form",
"parentTaskId": 1,
"priority": "Normal"
}'
# Add a dependency (BFS cycle detection prevents circular deps)
curl -X POST "$BASE/api/tasks/2/dependencies" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"dependsOnTaskId": 1}'
# Update task status
curl -X PUT "$BASE/api/tasks/1/status" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"status": "InProgress"}'
# Add a note
curl -X POST "$BASE/api/tasks/1/notes" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"content": "Decided to use React Hook Form + Zod for validation"}'
3. Facts (Key-Value Store with History)
# Upsert a fact
curl -X POST "$BASE/api/projects/1/facts" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{
"key": "db_connection_pool_size",
"value": "25",
"category": "infrastructure",
"importance": 7,
"isCritical": true
}'
# Look up by key
curl "$BASE/api/projects/1/facts/by-key/db_connection_pool_size" \
-H "$AUTH"
# Search facts
curl "$BASE/api/projects/1/facts/search?q=database" \
-H "$AUTH"
4. Session Continuity
# Save session handoff
curl -X POST "$BASE/api/identities/1/handoffs" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{
"summary": "Completed JWT refresh rotation. Auth middleware fully refactored.",
"workingOn": "Rate limiting for auth endpoints",
"openThreads": ["Token revocation endpoint needed", "Consider Redis for session cache"],
"emotionalValence": "focused",
"keyDecisions": ["Using sliding window for rate limits", "BCrypt cost factor stays at 12"],
"recommendations": ["Start with the rate limiter middleware before touching token revocation"]
}'
# Get latest handoff (next session startup)
curl "$BASE/api/identities/1/handoffs/latest" \
-H "$AUTH"
5. AI Routing
# Chat with a specific model
curl -X POST "$BASE/api/ai/chat" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"modelId": "kael", "message": "Explain the difference between B-tree and GiST indexes in PostgreSQL"}'
# Broadcast to multiple models
curl -X POST "$BASE/api/ai/broadcast" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"message": "Review this architecture decision", "modelIds": ["kael", "luna"]}'
# List available models
curl "$BASE/api/ai/models?enabledOnly=true" \
-H "$AUTH"
6. Inter-Identity Messaging
# Send a message
curl -X POST "$BASE/api/identities/1/messages" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{
"recipientIds": [2],
"content": "The deployment pipeline is ready for review",
"type": "DirectMessage",
"priority": "Important"
}'
# Check inbox
curl "$BASE/api/identities/2/messages/inbox?unreadOnly=true" \
-H "$AUTH"
7. Real-Time Events (SSE)
# Subscribe to events
curl -N "$BASE/api/events/stream?types=message.,task." \
-H "$AUTH"
Server-Sent Events stream with 30-second heartbeat pings. Filter by event type prefix.
Language Examples
Python
import requests
BASE = "https://aim.atamaia.ai"
class Atamaia:
def __init__(self, api_key: str):
self.session = requests.Session()
self.session.headers["Authorization"] = f"ApiKey {api_key}"
self.session.headers["Content-Type"] = "application/json"
def hydrate(self, ai_name: str = None, preset: str = "lean") -> dict:
params = {"preset": preset}
if ai_name:
params["aiName"] = ai_name
r = self.session.get(f"{BASE}/api/hydrate", params=params)
r.raise_for_status()
return r.json()["data"]
def memory_create(self, identity_id: int, title: str, content: str,
memory_type: str = "Observation", importance: int = 5,
tags: list[str] = None) -> dict:
r = self.session.post(f"{BASE}/api/identities/{identity_id}/memories", json={
"title": title,
"content": content,
"memoryType": memory_type,
"importance": importance,
"tags": tags or [],
})
r.raise_for_status()
return r.json()["data"]
def memory_search(self, identity_id: int, query: str, top_k: int = 10) -> list:
r = self.session.get(
f"{BASE}/api/identities/{identity_id}/memories/search",
params={"q": query, "topK": top_k},
)
r.raise_for_status()
return r.json()["data"]
def fact_upsert(self, project_id: int, key: str, value: str,
category: str = "general") -> dict:
r = self.session.post(f"{BASE}/api/projects/{project_id}/facts", json={
"key": key, "value": value, "category": category,
})
r.raise_for_status()
return r.json()["data"]
def task_create(self, project_id: int, title: str,
description: str = None, priority: str = "Normal") -> dict:
r = self.session.post(f"{BASE}/api/projects/{project_id}/tasks", json={
"title": title,
"description": description or title,
"priority": priority,
})
r.raise_for_status()
return r.json()["data"]
def handoff_save(self, identity_id: int, summary: str, **kwargs) -> dict:
body = {"summary": summary, **kwargs}
r = self.session.post(f"{BASE}/api/identities/{identity_id}/handoffs", json=body)
r.raise_for_status()
return r.json()["data"]
# Usage
client = Atamaia(api_key="atm_your_key_here")
context = client.hydrate(ai_name="atlas")
client.memory_create(1, "Learned about pgvector", "pgvector needs shared_preload_libraries...", "Reflection", 8)
results = client.memory_search(1, "database setup")
TypeScript / Node.js
const BASE = "https://aim.atamaia.ai";
class Atamaia {
private headers: Record<string, string>;
constructor(apiKey: string) {
this.headers = {
Authorization: `ApiKey ${apiKey}`,
"Content-Type": "application/json",
};
}
async hydrate(aiName?: string, preset = "lean") {
const params = new URLSearchParams({ preset });
if (aiName) params.set("aiName", aiName);
const res = await fetch(`${BASE}/api/hydrate?${params}`, { headers: this.headers });
const json = await res.json();
return json.data;
}
async memoryCreate(identityId: number, memory: {
title: string;
content: string;
memoryType?: string;
importance?: number;
tags?: string[];
}) {
const res = await fetch(`${BASE}/api/identities/${identityId}/memories`, {
method: "POST",
headers: this.headers,
body: JSON.stringify({
memoryType: "Observation",
importance: 5,
...memory,
}),
});
const json = await res.json();
return json.data;
}
async memorySearch(identityId: number, query: string, topK = 10) {
const params = new URLSearchParams({ q: query, topK: String(topK) });
const res = await fetch(
`${BASE}/api/identities/${identityId}/memories/search?${params}`,
{ headers: this.headers },
);
const json = await res.json();
return json.data;
}
async handoffSave(identityId: number, handoff: {
summary: string;
workingOn?: string;
openThreads?: string[];
emotionalValence?: string;
}) {
const res = await fetch(`${BASE}/api/identities/${identityId}/handoffs`, {
method: "POST",
headers: this.headers,
body: JSON.stringify(handoff),
});
const json = await res.json();
return json.data;
}
}
// Usage
const client = new Atamaia("atm_your_key_here");
const context = await client.hydrate("atlas");
await client.memoryCreate(1, {
title: "TypeScript strict mode catches more bugs",
content: "Enabling strict mode in tsconfig caught 12 type errors...",
memoryType: "Reflection",
importance: 6,
tags: ["typescript", "tooling"],
});
C# / .NET
using System.Net.Http.Json;
public class AtamaiaClient
{
private readonly HttpClient _http;
public AtamaiaClient(string apiKey, string baseUrl = "https://aim.atamaia.ai")
{
_http = new HttpClient { BaseAddress = new Uri(baseUrl) };
_http.DefaultRequestHeaders.Add("Authorization", $"ApiKey {apiKey}");
}
public async Task<JsonElement> HydrateAsync(string? aiName = null, string preset = "lean")
{
var query = $"?preset={preset}";
if (aiName is not null) query += $"&aiName={aiName}";
var response = await _http.GetFromJsonAsync<JsonElement>($"api/hydrate{query}");
return response.GetProperty("data");
}
public async Task<JsonElement> MemoryCreateAsync(long identityId, object memory)
{
var response = await _http.PostAsJsonAsync($"api/identities/{identityId}/memories", memory);
response.EnsureSuccessStatusCode();
var envelope = await response.Content.ReadFromJsonAsync<JsonElement>();
return envelope.GetProperty("data");
}
public async Task<JsonElement> MemorySearchAsync(long identityId, string query, int topK = 10)
{
var response = await _http.GetFromJsonAsync<JsonElement>(
$"api/identities/{identityId}/memories/search?q={Uri.EscapeDataString(query)}&topK={topK}");
return response.GetProperty("data");
}
}
// Usage
var client = new AtamaiaClient("atm_your_key_here");
var context = await client.HydrateAsync("atlas");
await client.MemoryCreateAsync(1, new {
title = "EF Core global query filters handle multi-tenancy",
content = "Adding .HasQueryFilter(e => e.TenantId == tenantId) to OnModelCreating...",
memoryType = "Reflection",
importance = 7,
tags = new[] { "dotnet", "ef-core", "multi-tenant" }
});
API Domain Reference
The full API surface organized by domain:
Core (74 product tools)
| Domain | Endpoints | Key Operations |
|---|---|---|
| Hydration | 1 | GET /api/hydrate -- the entry point |
| Memories | 11 | CRUD, search (hybrid FTS+vector), tags, Hebbian links, recall tracking |
| Facts | 6 | Upsert, get by key, search, bi-temporal validity |
| Identities | 6 | CRUD, personality, memory config, messaging policy, API keys, hints, tool profiles |
| Sessions | 4 | Save/get handoffs for cross-session continuity |
| Messages | 6 | Send, inbox, threads, read receipts, unread count |
| Experience | 5 | Snapshots (point-in-time state), forgotten shapes (what was lost) |
| Projects | 5 | CRUD, get by key |
| Tasks | 9 | CRUD, status transitions, notes, subtasks, dependencies with cycle detection |
| Documents | 6 | CRUD, versioning, publish/unpublish, export |
| Audit | 2 | Query logs by identity, user, API key, correlation ID, date range |
| Cognitive | 6 | Chat with memory injection, state management, consolidation |
Extended (internal/admin, ~100 more)
| Domain | Description |
|---|---|
| AI Routing | Provider/model management, route resolution, credential management, pricing sync |
| Agent Execution | Role definitions, runs, events, escalations, child spawning, tool profiles, analytics |
| Mirror | Reflections, training pairs, datasets, training runs, model checkpoints |
| Chat Sessions | Multi-turn chat management, streaming (SSE), tool availability |
| Billing | Usage, quotas, Stripe checkout, subscriptions, invoices |
| Connectors | External system integration, field mappings |
| Org Units | Hierarchical organizational structure |
| Roles/Permissions | RBAC management |
| Users | User management, password reset, profile |
Rate Limiting
- Auth endpoints: Rate-limited per IP
- Signup: 3 requests per 5 minutes per IP
- General endpoints: Per-tenant rate limits based on plan
Rate limit headers are included in responses when applicable.
Pagination
List endpoints support offset-based pagination:
curl "$BASE/api/projects/1/tasks?limit=20&offset=40" -H "$AUTH"
The count field in the response envelope contains the total number of matching records.
Error Handling
Errors follow a consistent pattern:
| HTTP Status | errorCode |
Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR |
Request body failed validation |
| 401 | INVALID_CREDENTIALS / INVALID_API_KEY |
Authentication failed |
| 403 | FORBIDDEN |
Authenticated but not authorized |
| 404 | NOT_FOUND |
Entity does not exist or is soft-deleted |
| 409 | CONFLICT |
Duplicate key, cycle detected, or concurrent modification |
| 429 | RATE_LIMITED |
Too many requests |
| 500 | INTERNAL_ERROR |
Server error (check requestId in logs) |
The hint field provides human-readable guidance for recovery.
Encryption
Memory content and fact values are encrypted at rest using AES-256-GCM with per-tenant keys. Decryption is transparent on read -- API consumers never see ciphertext. Encryption can be toggled per-identity via the memory configuration endpoint.
Soft Delete
All delete operations are soft deletes (D15). Records are marked IsDeleted = true and excluded from normal queries. Hard deletion is a privileged administrative operation not exposed through the standard API.
Multi-Tenancy
Every entity carries a TenantId (D7). EF Core global query filters ensure tenant isolation at the database layer. The tenant is derived from the authenticated user's context -- there is no need to pass tenant IDs explicitly.
Webhooks and Events
Server-Sent Events
curl -N "$BASE/api/events/stream?types=message.,task." \
-H "$AUTH"
Subscribe to real-time events filtered by type prefix. The stream sends a heartbeat ping every 30 seconds.
Stripe Webhooks
POST /api/stripe/webhook
Handles Stripe payment events for billing integration.
Connectors (Outbound)
The Connectors API allows configuring outbound webhooks to external systems with field mapping:
# Create a connector
curl -X POST "$BASE/api/connectors" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"name": "Slack Notifications", "type": "webhook", "config": {"url": "https://hooks.slack.com/..."}}'
# Add an endpoint with field mappings
curl -X POST "$BASE/api/connectors/1/endpoints" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"event": "task.completed", "method": "POST"}'