Configure Your App
Reference for configuring your workspace to support mobile and web app integration. Covers external auth providers, the CMS storefront, credit setup, and push notification preferences.
Base URL: https://athena-dev-api.leanscale.com
Swagger UI: https://athena-dev-api.leanscale.com/swagger-ui/
1. Architecture Overview
Athena is an AI agent orchestration platform. Each workspace is a self-contained application backend that hosts agents, manages consumer customers, handles billing, and serves CMS content.
┌──────────────┐ ┌────────────────────────────────────┐│ │ eak_ (verified) │ Athena Workspace ││ Mobile App │────────────────────▶│ ││ │ ◀── SSE stream ── │ ┌─ Agent Execution (sync/stream) │└──────────────┘ │ ├─ Conversations + Memory │ │ ├─ Credit System (billing) │┌──────────────┐ wak_ (server) │ ├─ CMS Storefront (pages/agents) ││ Your Server │────────────────────▶│ ├─ External Auth (Firebase/Clerk) ││ │ │ └─ RevenueCat (subscriptions) │└──────────────┘ │ │ │ → OpenAI, Anthropic, Google, ... │┌──────────────┐ webhook └────────────────────────────────────┘│ RevenueCat │────────────────────▶ POST /webhooks/{ws}/revenuecat└──────────────┘Two Integration Patterns
| Pattern | Auth | Use Case |
|---|---|---|
| Mobile/Web app | Firebase JWT → token exchange → eak_ key | End-user facing apps with per-user billing |
| Server-to-server | wak_ key directly, or wak_ → mint eak_ keys | Backend services, bots, internal tools |
Key Concepts
wak_— Workspace API Key. Long-lived, server-side only. Never expose to clients.eak_— Ephemeral API Key. Short-lived (1–24h), safe for client-side. Scoped to specific agents.- External Customer — A user from your auth system (Firebase UID, Clerk ID). Has credit balances, conversation history, execution records.
- Credits — Three-bucket ledger: active (spendable now), frozen (top-ups locked while subscription lapsed), inactive (voided/refunded, audit only). Subscription credits forfeit at period end; top-ups freeze on lapse and unfreeze on any future subscription purchase.
2. EAK-Callable Endpoint Matrix
Every endpoint in this guide is reachable with an ephemeral API key (eak_)
or a workspace API key (wak_) via the X-API-Key header.
| Method | Path | Required scope | Notes |
|---|---|---|---|
| POST | /auth/external/{workspace_id} | none (token exchange) | Issues eak_ from Firebase/Clerk JWT |
| POST | /ephemeral-keys/create | ephemeral_key:create (wak_) | Server-side mint |
| POST | /agents/{id}/execute | agent:execute | Sync, JSON response |
| POST | /agents/{id}/execute/stream | agent:execute | SSE |
| POST | /agents/{id}/execute/batch | agent:execute | Bulk |
| POST | /agents/{id}/preview-credits | agent:execute | Cost calc, no execution |
| POST | /executions/{id}/tool-result | agent:execute | Client-tool resume |
| POST | /executions/{id}/resume | agent:execute | HumanGate / crashed-execution resume |
| POST | /attachments/upload | agent:execute | Multipart |
| GET | /my/conversations | conversation:read | List own |
| POST | /my/conversations | conversation:create | Get-or-create |
| POST | /my/conversations/{id}/archive | conversation:create | Soft-close |
| GET | /my/conversations/{id}/messages | conversation:read | Page of messages |
| POST | /my/conversations/{cid}/messages/{mid}/feedback | conversation:read or conversation:create | Thumbs ± memory feedback |
| GET | /my/conversations/{id}/variables | conversation:read | Sticky vars snapshot |
| PATCH | /my/conversations/{id}/variables | conversation:create | Patch sticky vars |
| GET | /my/media | media:read | Generated assets |
| GET | /my/attachments | agent:execute | List own uploads |
| DELETE | /my/attachments/{id} | agent:execute | Delete own upload |
| GET | /my/preferences | agent:execute | Push prefs read |
| POST | /my/preferences | agent:execute | Push prefs write |
| GET | /my/credits | agent:execute | Balance |
| GET | /my/notifications/deliveries | agent:execute | Push delivery log |
| GET | /public/cms/... | cms:read | Storefront reads |
Scope hierarchy: conversation:create implies conversation:read. The
default scopes minted by token exchange are agent:execute + conversation:create.
3. Authentication
Token Exchange (Mobile/Web Apps)
Your app authenticates users through Firebase (or Clerk), then exchanges
that token for an Athena eak_ key.
POST /auth/external/{workspace_id}Content-Type: application/json
{ "provider": "firebase", "token": "eyJhbGciOiJSUzI1NiIs..."}Response (200):
{ "data": { "key": "eak_a1b2c3d4e5f6...", "expires_at": "2026-03-19T12:00:00Z", "customer": { "id": "019f902b-db70-7ea4-8ef2-60a8ff5747cd", "external_id": "firebase_uid_xyz", "email": "user@example.com", "display_name": "Jane Doe", "active_credits": 100.0, "frozen_credits": 0.0, "inactive_credits": 0.0 } }}The returned eak_ key is valid for 24 hours. Use it in all subsequent requests
as X-API-Key: eak_....
Rate limits:
- Per IP: 10/min, 50/hr (checked before JWT verification)
- Per identity: 5/min, 20/hr (checked after JWT verification)
Error responses:
403— External auth not enabled for workspace400— Invalid provider or token format401— JWT verification failed429— Rate limit exceeded
Server-Side Ephemeral Key Minting
POST /ephemeral-keys/createX-API-Key: wak_your_workspace_key
{ "name": "user-session-12345", "purpose": "agent_execution", "scopes": ["agent:execute"], "ttl_seconds": 3600, "agent_ids": [ "019f902b-db6f-7ea4-8ef2-60a8ff5747cd", "019f902b-db71-7ea4-8ef2-60a8ff5747cd" ]}agent_idsrestricts which agents this key can execute. Empty array = all agents.ttl_secondsrange: 60–86400 (1 min to 24 hours).- The raw key is returned only once — store it for the session.
Key Renewal
eak_ keys expire. Your app should:
- Track
expires_atfrom the creation response. - Renew before expiry by calling token exchange or ephemeral key creation again.
- Handle
401responses by triggering re-auth.
4. Agent Execution
All execution endpoints accept X-API-Key: eak_... (or wak_...).
Request Body
{ "message": "Compose a follow-up email for the client meeting", "attachments": [ { "filename": "notes.pdf", "content_type": "application/pdf", "storage_key": "attachments/user_abc/a1b2c3.pdf" } ], "variables": { "tone": "professional", "recipient": "John Smith" }, "conversation_scope": "019f902b-db72-7ea4-8ef2-60a8ff5747cd", "external_user_id": "firebase_uid_xyz", "client_tools": [ { "name": "query_transactions", "description": "Query the user's local transaction ledger", "parameters": { "type": "object", "properties": { "start_date": { "type": "string", "format": "date" }, "end_date": { "type": "string", "format": "date" } }, "required": ["start_date", "end_date"] } } ]}| Field | Required | Description |
|---|---|---|
message | Conditional | User input text (1–65,536 chars). Required when agent’s requires_user_message: true. |
attachments | No | File attachments via storage_key reference (max 10). |
variables | No | Input variables for wizard/workflow agents. Capped at 16 KiB. |
conversation_scope | No | Conversation ID — enables message history injection and auto-persistence. |
external_user_id | No | Your user’s ID — enables per-user memory personalization. |
client_tools | No | Client-fulfilled tool definitions. Max 32 per request. |
When Is message Required?
Each published AgentVersion carries requires_user_message.
| Flag value | Caller obligation | Server behaviour if message omitted |
|---|---|---|
true | Must send non-empty message | 400 Bad Request with code: "MESSAGE_REQUIRED" |
false | May omit message | Executes; no synthetic user-message row inserted |
Sync Execution
POST /agents/{agent_id}/executeX-API-Key: eak_...Blocks until the workflow completes.
Response (200):
{ "data": { "execution_id": "019f902b-db71-7ea4-8ef2-60a8ff5747cd", "status": "completed", "output": { "text": "Dear John,\n\nThank you for the productive meeting...", "usage": { "input_tokens": 150, "output_tokens": 280, "total_tokens": 430 }, "cost_microdollars": 4200 }, "latency_ms": 2341 }}Status values: completed, awaiting_approval, waiting_for_external_event,
awaiting_client_tool, failed.
Streaming Execution (SSE)
POST /agents/{agent_id}/execute/streamX-API-Key: eak_...Content-Type: application/jsonReturns a text/event-stream.
Key events:
| Event | When | Use For |
|---|---|---|
start | Always first | Get execution_id for tracking |
text | Each LLM token chunk | Append to displayed text |
reasoning | Thinking/reasoning chunks | Show thinking indicator |
tool_call | LLM invokes a server tool | Show tool execution UI |
tool_result | Server tool returns result | Show tool output |
tool_call_awaiting_client | LLM invokes a client tool | Execute tool locally and POST result |
node_start / node_complete | Workflow node lifecycle | Progress indicators |
media_gen_submitted | Image/video gen started | Show generating indicator |
media_gen_status_update | Generation progress | Update queue position |
media_gen_complete | Image/video gen done | Render the generated media |
heartbeat | Every 15s during gen wait | Keep connection alive (ignore in UI) |
final | Workflow complete | Stream is done. Extract final usage/cost. |
error | Execution failed | Show error, credits are auto-refunded |
If the client disconnects before final, the execution is marked Failed and
pre-deducted credits are refunded.
Batch Execution
POST /agents/{agent_id}/execute/batchX-API-Key: eak_...
{ "items": [ { "key": "item-1", "message": "Summarize this article: ..." }, { "key": "item-2", "message": "Translate to French: ..." } ]}Returns SSE with batch_started → batch_progress (per item) → batch_completed.
Idempotency
Prevent duplicate executions (and double credit charges) by sending an
Idempotency-Key header.
POST /agents/{agent_id}/executeX-API-Key: eak_...Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000| Scenario | Result |
|---|---|
First request with key abc | Processed normally |
Second request with same key abc | 409 Conflict — no execution, no credit charge |
Request without Idempotency-Key | Processed normally (idempotency is opt-in) |
| Same key after 24 hours | Processed normally (key expired) |
Credit Preview
POST /agents/{agent_id}/preview-creditsX-API-Key: eak_...
{ "inputs": { "tone": "professional", "length": "long" } }Returns the credit calculation with base cost and applied rule breakdown.
Client-Fulfilled Tools (Suspend/Resume)
Client-fulfilled tools let the LLM invoke functions that run on the consumer device rather than on the server. Essential for apps where data lives locally.
Prerequisites:
- The AI node must have
accepts_client_tools: true. max_client_toolscaps how many tools a single request can declare (default 32).
Flow:
- Consumer app sends
client_toolsin the execute request. - If the LLM chooses a client tool, execution suspends — status becomes
awaiting_client_tool. - SSE emits
tool_call_awaiting_client+suspended_awaiting_client_tools. - Consumer app runs each tool locally and POSTs the result:
POST /executions/{execution_id}/tool-resultX-API-Key: eak_...
{ "tool_call_id": "tc_abc123", "outcome": "success", "result": { "transactions": [...], "total": 5.50 }}- Last fulfilled call resumes execution. Response carries the terminal state.
Add Accept: text/event-stream to the final-fulfillment POST to stream
the resumed execution’s events instead of blocking.
Error results:
{ "tool_call_id": "tc_abc123", "outcome": "error", "code": "database_locked", "message": "Local database is currently syncing"}Tool name rules:
- Must match
^[a-z][a-z0-9_]{0,63}$ - Must not collide with server-side tools on the agent
parametersmust be a JSON Schema object ("type": "object")
Structured Output (Native JSON Mode)
When an agent’s AI node has output_schema configured, the execution response
includes parsed_output alongside text:
{ "data": { "output": { "text": "{\"type\":\"answerCard\",\"hero\":{\"amount\":\"$2,450\"}}", "parsed_output": { "type": "answerCard", "hero": { "amount": "$2,450" } } } }}Use parsed_output when available. Fall back to text and parse manually if
parsed_output is null.
5. File Attachments & Vision
Athena supports sending files (images, PDFs, documents) alongside messages. Vision-capable models (GPT-4o, Claude 3.5 Sonnet, Gemini 1.5) automatically interpret image content.
Upload Flow (Two-Step)
Step 1: Upload the file
POST /attachments/uploadX-API-Key: eak_...Content-Type: multipart/form-data
file: <binary file data>Response (200):
{ "data": { "storage_key": "attachments/user_abc123/a1b2c3d4.jpg", "filename": "photo.jpg", "content_type": "image/jpeg", "size_bytes": 245120 }}Step 2: Reference in execute request
Pass the returned storage_key in the attachments array.
Limits
| Constraint | Value |
|---|---|
| Max attachments | 10 per request |
| Max file size | 50 MiB per file |
storage_key | Must start with "attachments/" |
Supported Content Types
| Category | Types | Handling |
|---|---|---|
| Images | image/jpeg, image/png, image/gif, image/webp | Sent as vision content |
| PDFs | application/pdf | Sent as document content |
| Text | text/plain, text/csv, text/markdown | Inlined as text in prompt |
Listing & Deleting Uploads
GET /my/attachments — list the calling customer’s uploads.
Query params: conversation_id, content_type, cursor, limit (default 20, max 100).
DELETE /my/attachments/{attachment_id} — soft-delete one row. Returns 204 No Content.
6. Conversations & Messages
For multi-turn chat agents, use conversations to maintain message history.
Create or Resume a Conversation
POST /my/conversationsX-API-Key: eak_...
{ "agent_id": "019f902b-db6f-7ea4-8ef2-60a8ff5747cd" }Returns 201 Created (new) or 200 OK (existing active conversation).
Start a Fresh Chat
Archive the current conversation first — the next get-or-create will create a fresh row:
POST /my/conversations/{conversation_id}/archiveX-API-Key: eak_...Idempotency-Key: <uuid>Execute with Conversation History
Pass conversation_scope in your execution request:
{ "message": "Make it more formal", "conversation_scope": "019f902b-db72-7ea4-8ef2-60a8ff5747cd"}This injects prior messages as context and auto-persists the user message + agent response.
Message Roles
role | Author | Replay to LLM? |
|---|---|---|
"user" | The end user | Yes |
"assistant" | The agent (LLM output) | Yes |
"tool" | Tool execution result | Yes |
"system" | The Athena platform (failure events, handoffs) | No |
System Messages — Failure & Lifecycle Events
When an execution fails, the backend appends a system message to the conversation:
{ "role": "system", "content": "Generation failed: storage timeout while uploading thumbnail", "execution_id": "019f902b-db71-7ea4-8ef2-60a8ff5747cd", "metadata": { "failed": true, "error_type": "stream_error", "source": "stream" }}Client rendering requirements:
- Render distinct from assistant messages (different bubble style — error state).
- Show
contentverbatim — it is already user-safe. - Surface a retry affordance using
execution_id. - Filter from any local LLM replay.
Conversation Variables
Read: GET /my/conversations/{id}/variables
Write: PATCH /my/conversations/{id}/variables
{ "variables": { "tone": "friendly", "recipient": "Jane" }, "labels": { "tone": "Tone", "recipient": "Recipient Name" }}Merge semantics — existing keys not in the patch body are preserved.
Message Feedback
POST /my/conversations/{cid}/messages/{mid}/feedbackX-API-Key: eak_...
{ "value": 1 }value: 1 (thumbs up) or -1 (thumbs down). Triggers memory reinforcement
or suppression for the associated memory node.
7. Public CMS API (Storefront)
These endpoints serve your storefront content — agent catalog, pages, categories.
All require cms:read scope.
Common headers:
X-API-Key: wak_...(oreak_withcms:readscope)X-Preferred-Language: ar(optional)?lang=ar(query param, highest priority)- Response:
Cache-Control: public, max-age=1800 - Response:
ETag: W/"abc..."(conditional requests supported)
Language Resolution
Priority order: ?lang=ar → X-Preferred-Language header → workspace default.
CMS Endpoints
| Endpoint | Description |
|---|---|
GET /public/cms/settings | Workspace CMS config (languages, cache TTL, consent) |
GET /public/cms/pages | List all active pages (metadata only) |
GET /public/cms/pages/{slug} | Full page with components + resolved_agents map |
GET /public/cms/agents | Paginated agent catalog (search, category, tag filters) |
GET /public/cms/agents/{id} | Single agent detail with input_schema for wizard agents |
GET /public/cms/categories | Category tree with agent counts |
GET /public/cms/tags | Flat list of tags |
Component Types
| Type | Description |
|---|---|
hero_banner | Full-width hero section |
agent_card | Single featured agent |
grid | Grid of agent cards |
category_tabs | Tabbed category navigation |
carousel | Image/content carousel |
static_text | Rich text/markdown block |
section | Visual grouping wrapper |
slider | Horizontal scroll |
example_gallery | Gallery of agent examples |
8. Credit System
Credits are workspace-scoped. Each external customer has two buckets:
| Bucket | Source | Behavior |
|---|---|---|
| Subscription Credits | Active subscription grant | Reset on renewal. Consumed first. |
| Consumable Credits | Ad-hoc purchases | Never expires. Consumed second. |
Checking Balance
Every response to an eak_ request with an external customer includes:
X-Credits-Remaining: 75.0For an explicit balance fetch: GET /my/credits[?generation_cost=N]
{ "data": { "active_credits": 50.0, "frozen_credits": 25.0, "inactive_credits": 0.0, "next_refresh_at": "2026-05-05T10:00:00Z", "recommended_topup_pack_id": null, "recommended_topup_pack_credits": null, "recommended_topup_pack_currency": null, "frozen_card_dismissed_at": null }}active_credits is the spendable balance. frozen_credits > 0 means the
customer has top-ups locked behind a lapsed subscription — render the
“Resubscribe to unlock” CTA. Add ?generation_cost=N to receive a
recommended top-up pack big enough to cover that cost.
Insufficient Credits
When credits run out, execution endpoints return:
HTTP 402 Payment Required
{ "error": { "code": "payment_required", "message": "Purchase credits to continue." }}Catch 402 and show a “buy credits” prompt.
Trials and Free Credits
Athena does not mint workspace-configured free credits. Trial or intro access comes from App Store / Play products managed through RevenueCat; when RevenueCat sends a trial or intro subscription purchase, Athena applies the normal subscription entitlement and product credit grant.
9. Response Headers & App Versioning
Every authenticated response includes:
| Header | Value |
|---|---|
X-Credits-Remaining | Current credit balance (eak_ + customer only) |
X-App-Version | The workspace’s current published AppVersion ID |
X-Workspace-Id | Echo of the workspace |
Use X-App-Version to detect when your app’s CMS content needs revalidation.
Cache the value locally; when it changes, refetch CMS data.
10. Error Reference
All errors follow:
{ "error": { "code": "snake_case_error_code", "message": "Human-readable description.", "details": {} }}| HTTP Status | Meaning |
|---|---|
400 | Validation error or bad request body |
401 | Missing or invalid API key / JWT |
402 | Insufficient credits |
403 | Valid credentials but missing permission / scope |
404 | Resource not found (or hidden — no existence leak) |
409 | Conflict (duplicate idempotency key, execution not in expected state) |
422 | Semantic validation failed (e.g. agent config invalid) |
429 | Rate limit exceeded — see Retry-After header |
500 | Internal server error — credits auto-refunded for execution failures |
11. Customer Preferences & Push Registration
POST /my/preferencesX-API-Key: eak_...
{ "push_token": "ExponentPushToken[xxx]", "push_provider": "expo", "language": "en", "timezone": "America/New_York", "notifications_enabled": true}Supported push_provider values: expo, fcm, apns.
12. Push Notifications
Athena can deliver push notifications to customers when:
- An async execution completes
- A HumanGate is approved or rejected
- A batch job finishes
- Workspace admins trigger a broadcast
Rate limits: 10 notifications per customer per hour, with 60-second deduplication.
Delivery log: GET /my/notifications/deliveries
13. Apple Live Activities
For iOS apps using Live Activities (Dynamic Island / Lock Screen widgets):
POST /my/preferences{ "live_activity_push_token": "...", "live_activity_id": "019f902b-db71-7ea4-8ef2-60a8ff5747cd"}The server sends ActivityKit push updates during long-running executions.
14. Message Validation Rules & Error Codes
| Rule | Limit | Error Code |
|---|---|---|
message max length | 65,536 chars | validation_error |
variables payload | 16 KiB | validation_error |
attachments count | 10 per request | validation_error |
client_tools count | 32 per request (default) | validation_error |
| Tool name format | ^[a-z][a-z0-9_]{0,63}$ | invalid_tool_name |
Attachment storage_key | Must start with "attachments/" | invalid_storage_key |
15. Per-Conversation Turn Lock
Only one execution can be in-flight per conversation at a time.
If your app submits while a turn is processing, the server returns:
HTTP 409 Conflict
{ "error": { "code": "conversation_turn_in_progress", "message": "A turn is already in progress for this conversation." }}Wait for the current final event (or poll GET /executions/{id}/status)
before submitting the next message.
16. Multi-Suspend Execution Contract
An execution can suspend multiple times in a single run — e.g. the LLM calls multiple client tools sequentially. After each resume:
- If the LLM calls another client tool → execution suspends again (new
awaiting_client_toolstatus, newpending_tool_call_ids). - If the LLM finishes → execution transitions to
completed/failed.
The loop is bounded by the agent’s max-turns config, not the resume mechanism.
17. Background → Foreground Recovery
When the app returns to foreground:
- Check
X-App-Version— if changed, refetch CMS data. - For any conversation that was executing, poll
GET /executions/{id}/status. - If status is
completedorfailed, fetch the last message from the conversation (GET /my/conversations/{id}/messages?limit=1) to sync the UI. - If status is still in-flight, reconnect the SSE stream (streaming is
resumable — pass the
execution_idas the stream URL parameter).