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 curl on 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"}'