{"openapi":"3.0.0","servers":[{"url":"https://api.elyxir.ai","description":"Elyxir API"}],"info":{"title":"elyxir API","version":"1.0","description":"OpenAI-compatible inference API with built-in key management, model catalog, and usage tracking.\n\n**OAuth endpoints** (login, tokens, consent) live on **elyxir-studio** — see the OAuth section below.\n**Everything else** (Platform API, Agent, Chat, Health) is served from **this service**.\n\n## Quick Start\n\n1. Click **Authorize** and enter your `elyxir_...` API key\n2. Use **Chat Completions** with any model ID from `GET /v1/models`\n\n**Chat:** `POST /v1/chat/completions` with any model ID (e.g. `gpt-4o`, `claude-sonnet-4-6`). Your request goes to that model as sent.\n\n**Need an API key?** [Open Studio](https://studio.elyxir.ai) -> Organization Settings -> API Keys, or use the Platform API endpoints below to provision one programmatically via OAuth."},"tags":[{"name":"Chat","description":"OpenAI-compatible LLM inference, passed straight through to the model you name"},{"name":"Platform API","description":"API key provisioning, model catalog, and usage stats"},{"name":"Memory","description":"Read and manage user memories as an opaque flat resource"},{"name":"Agent","description":"Genie agent — per-user agent seats and a streaming chat proxy (Elyxir API key auth)"},{"name":"Health","description":"Service health checks"},{"name":"Documentation","description":"OpenAPI spec download"}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Elyxir API Key","description":"Elyxir API key for LLM inference (e.g., elyxir_xxxxxxxxxxxxx)"},"OAuthJWT":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"OAuth 2.0 access token (RS256 JWT) for Platform API management endpoints"}},"parameters":{"OrganizationId":{"name":"X-Organization-Id","in":"header","required":true,"schema":{"type":"string","format":"uuid"},"description":"Organization UUID from the userinfo endpoint","example":"00000000-0000-0000-0000-000000000000"}}},"security":[{"BearerAuth":[]}],"paths":{"/api/v1/api-keys":{"get":{"summary":"List API keys","description":"List all API keys for the organization. Returns metadata only — the actual key value is never returned after creation.","tags":["Platform API"],"security":[{"OAuthJWT":[]}],"parameters":[{"$ref":"#/components/parameters/OrganizationId"}],"responses":{"200":{"description":"List of API keys (no key values)","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"My App Production"},"keyPrefix":{"type":"string","example":"a1b2c3d4e5f6","description":"First 12 chars for identification"},"createdAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":"string","format":"date-time","nullable":true},"totalRequests":{"type":"integer","example":1542}}}}}}}}},"400":{"description":"Missing X-Organization-Id header"},"401":{"description":"Invalid or expired JWT"},"403":{"description":"Not a member of this organization"}}},"post":{"summary":"Create API key","description":"Provision an API key for LLM inference. The key value (`elyxir_...`) is returned **once** in this response and cannot be retrieved again. Store it securely.\n\nUse the returned key as `Authorization: Bearer <key>` when calling `/v1/chat/completions`.","tags":["Platform API"],"security":[{"OAuthJWT":[]}],"parameters":[{"$ref":"#/components/parameters/OrganizationId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":3,"description":"Display name for the key (min 3 characters)","example":"My App Production"}}}}}},"responses":{"201":{"description":"API key created (key value returned once)","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"My App Production"},"key":{"type":"string","example":"elyxir_a1b2c3d4e5f6...","description":"Full key value (shown once)"},"keyPrefix":{"type":"string","example":"a1b2c3d4e5f6"},"createdAt":{"type":"string","format":"date-time"}}}}}},"400":{"description":"Invalid name or missing org header"},"401":{"description":"Invalid or expired JWT"},"402":{"description":"No API credits — purchase credits first"},"403":{"description":"Not a member of this organization"},"409":{"description":"Conflict - An API key with this name already exists"}}}},"/api/v1/api-keys/{keyId}":{"delete":{"summary":"Revoke API key","description":"Permanently revoke an API key. LLM calls using this key will fail immediately with 401.","tags":["Platform API"],"security":[{"OAuthJWT":[]}],"parameters":[{"$ref":"#/components/parameters/OrganizationId"},{"name":"keyId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"API key ID"}],"responses":{"200":{"description":"Key revoked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"My App Production"}}}}}},"401":{"description":"Invalid or expired JWT"},"403":{"description":"Not a member of this organization"},"404":{"description":"API key not found"}}}},"/api/v1/usage":{"get":{"summary":"Usage and billing stats","description":"Current billing period usage, token counts, and credit balance for the organization.","tags":["Platform API"],"security":[{"OAuthJWT":[]}],"parameters":[{"$ref":"#/components/parameters/OrganizationId"}],"responses":{"200":{"description":"Usage stats","content":{"application/json":{"schema":{"type":"object","properties":{"organization":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Acme Corp"},"slug":{"type":"string","example":"acme-corp"},"plan":{"type":"string","example":"standard"}}},"currentPeriod":{"type":"object","properties":{"start":{"type":"string","format":"date-time"},"end":{"type":"string","format":"date-time"}}},"usage":{"type":"object","properties":{"totalCost":{"type":"string","example":"12.45"},"totalTokens":{"type":"integer","example":2450000},"promptTokens":{"type":"integer","example":1800000},"completionTokens":{"type":"integer","example":650000}}},"limits":{"type":"object","properties":{"inferenceTokensLimit":{"type":"integer","example":5000000}}},"credits":{"type":"object","description":"The authenticated user's credit. Scoped to the USER, not the organization.","properties":{"scope":{"type":"string","enum":["user"],"example":"user"},"userId":{"type":"string","format":"uuid"},"currentBalance":{"type":"number","description":"Spendable credit in dollars, floored at zero. Display only.","example":37.55},"spendableMicros":{"type":"integer","example":37550000},"netMicros":{"type":"integer","description":"Spendable minus debt, NOT floored — may be negative. Test thresholds against this, never rounded dollars.","example":37550000},"unallocatedDebtMicros":{"type":"integer","example":0},"lotOverdrawMicros":{"type":"integer","example":0},"openLots":{"type":"integer","example":2},"nextExpiresAt":{"type":"string","format":"date-time","nullable":true,"description":"When the soonest-expiring credit dies; null when nothing held expires. Credit expires in v3; it did not before."}}}}}}}},"401":{"description":"Invalid or expired JWT"},"403":{"description":"Not a member of this organization"},"502":{"description":"Credit ledger temporarily unreadable (code: balance_service_unavailable). Retry; this is NOT a zero balance."}}}},"/api/v1/memory":{"get":{"summary":"List memories","description":"Return all active memories for the user associated with the authenticated credential.\n\nPass `?since=<cursor>` for an incremental pull: only memories changed since that point are returned, plus a `deleted` array of IDs the client should remove locally. Omit `since` for a full pull — `deleted` is not present in that case.\n\nThe `cursor` in every response is an opaque high-watermark string; store it and pass it back as `?since=` on the next sync.\n\n**Feature gate:** returns `404` when the memory feature is not enabled for the organization.\n\n**User override:** pass `?user=<uuid>` to act on behalf of a different org member (membership-verified server-side; the key can never reach a user outside the authenticated org).","tags":["Memory"],"security":[{"OAuthJWT":[]}],"parameters":[{"$ref":"#/components/parameters/OrganizationId"},{"name":"since","in":"query","required":false,"schema":{"type":"string"},"description":"Cursor from a previous response. Returns only records changed since that point plus a `deleted` array.","example":"2026-07-18T10:00:00.000Z"},{"name":"user","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"UUID of an org member to act on behalf of (defaults to key owner)"},{"name":"project","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Read a project's shared memory pool instead of a user's memories. Requires project access; cannot be combined with `user`."}],"responses":{"200":{"description":"Memory list (full or incremental)","content":{"application/json":{"schema":{"type":"object","properties":{"memories":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"content":{"type":"string","example":"Prefers concise answers with code examples"},"category":{"type":"string","enum":["identity","preference","goal","project","relationship","constraint"],"example":"preference"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}},"deleted":{"type":"array","items":{"type":"string","format":"uuid"},"description":"IDs to remove from local storage. Present only on incremental (?since) responses.","example":["b2c3d4e5-f6a7-8901-bcde-f12345678901"]},"cursor":{"type":"string","description":"Opaque high-watermark string. Pass back as ?since= on the next sync.","example":"2026-07-18T10:00:00.000Z"}}},"examples":{"Full pull":{"summary":"No ?since — full set, no deleted array","value":{"memories":[{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","content":"Prefers concise answers with code examples","category":"preference","createdAt":"2026-07-18T10:00:00.000Z","updatedAt":"2026-07-18T10:00:00.000Z"}],"cursor":"2026-07-18T10:00:00.000Z"}},"Incremental pull":{"summary":"With ?since — changed + deleted","value":{"memories":[{"id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","content":"Working on a TypeScript monorepo project","category":"project","createdAt":"2026-07-18T11:00:00.000Z","updatedAt":"2026-07-18T11:00:00.000Z"}],"deleted":["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],"cursor":"2026-07-18T11:00:00.000Z"}}}}}},"400":{"description":"Bad Request - Missing or invalid X-Organization-Id or user override"},"401":{"description":"Unauthorized - Invalid or expired JWT"},"403":{"description":"Forbidden - Account suspended or not a member of this organization"},"404":{"description":"Not Found - Memory feature not enabled for this organization"},"429":{"description":"Too Many Requests - Rate limit exceeded (60 req/min per credential)"},"500":{"description":"Internal server error"}}},"post":{"summary":"Add a memory","description":"Save a new memory for the user. Returns the resulting `MemoryItem`.\n\n**category** is optional. If omitted or unrecognized, the memory is saved without a category.\n\n**User override:** pass `user` in the request body to act on behalf of a different org member.","tags":["Memory"],"security":[{"OAuthJWT":[]}],"parameters":[{"$ref":"#/components/parameters/OrganizationId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","description":"The memory text. Must be non-empty.","example":"Prefers TypeScript over JavaScript"},"category":{"type":"string","enum":["identity","preference","goal","project","relationship","constraint"],"description":"Optional category. Unrecognized values are silently dropped.","example":"preference"},"project":{"type":"string","format":"uuid","description":"Contribute to a project's shared memory pool. Requires project editor access; cannot be combined with `user`."},"user":{"type":"string","format":"uuid","description":"UUID of an org member to act on behalf of (defaults to key owner)"}}}}}},"responses":{"201":{"description":"Memory saved","content":{"application/json":{"schema":{"type":"object","properties":{"memory":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"c3d4e5f6-a7b8-9012-cdef-123456789012"},"content":{"type":"string","example":"Prefers TypeScript over JavaScript"},"category":{"type":"string","example":"preference"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad Request - content is required, or invalid user override"},"401":{"description":"Unauthorized - Invalid or expired JWT"},"403":{"description":"Forbidden - Account suspended or not a member of this organization"},"404":{"description":"Not Found - Memory feature not enabled for this organization"},"429":{"description":"Too Many Requests - Rate limit exceeded (60 req/min per credential)"},"500":{"description":"Internal server error"}}}},"/api/v1/memory/{id}":{"get":{"summary":"Get a memory","description":"Retrieve a single memory by ID.","tags":["Memory"],"security":[{"OAuthJWT":[]}],"parameters":[{"$ref":"#/components/parameters/OrganizationId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Memory ID","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},{"name":"user","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"UUID of an org member to act on behalf of (defaults to key owner)"}],"responses":{"200":{"description":"Memory item","content":{"application/json":{"schema":{"type":"object","properties":{"memory":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"content":{"type":"string"},"category":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Unauthorized - Invalid or expired JWT"},"403":{"description":"Forbidden - Account suspended or not a member of this organization"},"404":{"description":"Not Found - Memory not found, or feature not enabled for this organization"},"429":{"description":"Too Many Requests - Rate limit exceeded (60 req/min per credential)"},"500":{"description":"Internal server error"}}},"patch":{"summary":"Update a memory","description":"Update the `content` and/or `category` of a memory. At least one field must be provided.\n\n**Optimistic concurrency (coming soon):** a future release will accept `baseUpdatedAt` matching the `updatedAt` you last read; if another writer changed the record since then, the server returns `409 Conflict`.","tags":["Memory"],"security":[{"OAuthJWT":[]}],"parameters":[{"$ref":"#/components/parameters/OrganizationId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Memory ID","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},{"name":"user","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"UUID of an org member to act on behalf of (defaults to key owner)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","description":"New content text. Must be non-empty if provided.","example":"Prefers concise TypeScript answers with working code examples"},"category":{"type":"string","enum":["identity","preference","goal","project","relationship","constraint"],"description":"New category value","example":"preference"}}}}}},"responses":{"200":{"description":"Updated memory item","content":{"application/json":{"schema":{"type":"object","properties":{"memory":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"content":{"type":"string"},"category":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad Request - Neither content nor category provided"},"401":{"description":"Unauthorized - Invalid or expired JWT"},"403":{"description":"Forbidden - Account suspended or not a member of this organization"},"404":{"description":"Not Found - Memory not found, or feature not enabled for this organization"},"409":{"description":"Conflict - Concurrent edit detected (coming soon — requires baseUpdatedAt)"},"429":{"description":"Too Many Requests - Rate limit exceeded (60 req/min per credential)"},"500":{"description":"Internal server error"}}},"delete":{"summary":"Delete a memory","description":"Delete a memory. The operation is idempotent — deleting an already-deleted ID returns `{ok: true}`. Deleted IDs appear in the `deleted` array of subsequent incremental GET pulls.","tags":["Memory"],"security":[{"OAuthJWT":[]}],"parameters":[{"$ref":"#/components/parameters/OrganizationId"},{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Memory ID","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},{"name":"user","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"UUID of an org member to act on behalf of (defaults to key owner)"}],"responses":{"200":{"description":"Memory deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","example":true}}}}}},"401":{"description":"Unauthorized - Invalid or expired JWT"},"403":{"description":"Forbidden - Account suspended or not a member of this organization"},"404":{"description":"Not Found - Feature not enabled for this organization"},"429":{"description":"Too Many Requests - Rate limit exceeded (60 req/min per credential)"},"500":{"description":"Internal server error"}}}},"/v1/chat/completions":{"post":{"summary":"Create chat completion (OpenAI compatible)","description":"Generate a model response for the given chat conversation.\nThis endpoint is fully compatible with the OpenAI Chat Completions API.\nPass your Elyxir API key in the Authorization header.\n\nSpecify any model by ID (e.g. `gpt-4o`, `claude-sonnet-4-6`). Full model catalog with usage-based billing.\n\nYour request is passed straight through to that model: Elyxir does not pick the model, inject memory, or rewrite the messages or the reply. It only checks your key and plan, caps `max_tokens` to the model's limit, and adds web search when you send `web_search: true` and your plan includes it.","tags":["Chat"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["model","messages"],"properties":{"model":{"type":"string","description":"Model ID from GET /v1/models (e.g. \"gpt-4o\", \"claude-sonnet-4-6\")"},"messages":{"type":"array","description":"A list of messages comprising the conversation","items":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["system","user","assistant"]},"content":{"type":"string"}}}},"temperature":{"type":"number","description":"Sampling temperature between 0 and 2","default":0.7},"max_tokens":{"type":"integer","description":"Maximum number of tokens to generate"},"stream":{"type":"boolean","description":"Whether to stream responses via SSE","default":false}}},"examples":{"Direct model":{"summary":"Use a specific model","value":{"model":"gpt-4o","messages":[{"role":"user","content":"Hello, how are you?"}],"temperature":0.7,"max_tokens":1000}}}}}},"responses":{"200":{"description":"Chat completion response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","example":"chatcmpl-abc123"},"object":{"type":"string","example":"chat.completion"},"created":{"type":"integer"},"model":{"type":"string","example":"gpt-4o"},"choices":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer","example":0},"message":{"type":"object","properties":{"role":{"type":"string","example":"assistant"},"content":{"type":"string","example":"Hello! I'm doing well, thanks for asking."}}},"finish_reason":{"type":"string","example":"stop"}}}},"usage":{"type":"object","properties":{"prompt_tokens":{"type":"integer","example":12},"completion_tokens":{"type":"integer","example":15},"total_tokens":{"type":"integer","example":27}}}}}}}},"400":{"description":"Bad Request - Invalid parameters, malformed JSON, or `model: \"alvin\"` (auto-routing is paused)"},"401":{"description":"Unauthorized - Invalid or revoked API key"},"402":{"description":"Payment Required - No credits remaining"},"403":{"description":"Forbidden - Organization billing is suspended or cancelled, or the model is not in your plan"},"502":{"description":"Upstream LLM provider error"}}}},"/v1/responses":{"post":{"summary":"Create response (OpenAI Responses API)","description":"OpenAI Responses API — the next-generation API for LLM inference.\nProxied to LiteLLM which translates to all supported providers.\n\nSpecify any model by ID from `GET /v1/models`. Your request is passed straight through to that model, translated to and from Chat Completions; Elyxir does not pick the model or rewrite the reply.\n\nKey differences from Chat Completions:\n- `input` instead of `messages` (string or structured array)\n- `instructions` for system-level prompts\n- `previous_response_id` for stateful multi-turn conversations\n- Custom `function` tools (OpenAI-style). Built-in tools (`web_search`, `file_search`, `code_interpreter`) are NOT supported and are ignored if sent.\n- Richer streaming events","tags":["Chat"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["model","input"],"properties":{"model":{"type":"string","description":"Model ID from GET /v1/models (e.g. \"gpt-4o\")"},"input":{"description":"String prompt or array of input items","oneOf":[{"type":"string","example":"What is the capital of France?"},{"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["developer","user","assistant"]},"content":{"type":"string"}}}}]},"instructions":{"type":"string","description":"System-level instructions"},"temperature":{"type":"number","default":0.7},"max_output_tokens":{"type":"integer"},"stream":{"type":"boolean","default":false},"previous_response_id":{"type":"string","description":"Chain multi-turn conversations without resending history"},"tools":{"type":"array","description":"Custom function tools (OpenAI-style). Only `type: \"function\"` is forwarded; built-in tool types (web_search/file_search/code_interpreter) are stripped before reaching the model.","items":{"type":"object","properties":{"type":{"type":"string","enum":["function"]}}}}}},"examples":{"Direct model":{"summary":"Simple string prompt with a specific model","value":{"model":"gpt-4o","input":"Summarize the key differences between REST and GraphQL APIs"}},"With instructions":{"summary":"System instructions + structured input","value":{"model":"claude-sonnet-4-6","instructions":"You are a senior software engineer. Be concise and include code examples.","input":[{"role":"user","content":"How do I implement retry logic with exponential backoff?"}],"temperature":0.3}},"Multi-turn conversation":{"summary":"Continue a previous conversation (no need to resend history)","value":{"model":"gpt-4o","input":"Can you add error handling to that function?","previous_response_id":"resp_abc123"}}}}}},"responses":{"200":{"description":"Response object","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","example":"resp_abc123"},"object":{"type":"string","example":"response"},"status":{"type":"string","example":"completed"},"model":{"type":"string","example":"gpt-4o"},"output":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","example":"message"},"role":{"type":"string","example":"assistant"},"content":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","example":"output_text"},"text":{"type":"string","example":"The capital of France is Paris."}}}}}}},"usage":{"type":"object","properties":{"input_tokens":{"type":"integer"},"output_tokens":{"type":"integer"},"total_tokens":{"type":"integer"}}}}}}}},"400":{"description":"Bad Request - including `model: \"alvin\"` (auto-routing is paused)"},"401":{"description":"Unauthorized"},"402":{"description":"Payment Required - No credits"}}}},"/v1/models":{"get":{"summary":"List models (OpenAI compatible)","description":"Returns all models available to your API key. Proxies to LiteLLM and returns the standard OpenAI models list format.","tags":["Chat"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Model list","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","example":"list"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"gpt-4o"},"object":{"type":"string","example":"model"},"created":{"type":"integer"},"owned_by":{"type":"string","example":"openai"}}}}}}}}},"401":{"description":"Unauthorized - Invalid or revoked API key"}}}},"/v1/models/{model}":{"get":{"summary":"Retrieve model","description":"Returns details for a specific model by ID. Returns 404 if the model does not exist.","tags":["Chat"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"model","in":"path","required":true,"schema":{"type":"string"},"description":"Model ID (e.g. \"gpt-4o\")","example":"gpt-4o"}],"responses":{"200":{"description":"Model details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","example":"gpt-4o"},"object":{"type":"string","example":"model"},"created":{"type":"integer"},"owned_by":{"type":"string","example":"openai"}}}}}},"401":{"description":"Unauthorized - Invalid or revoked API key"},"404":{"description":"Model not found"}}}},"/api/v1/agent":{"get":{"summary":"Get agent status","description":"Returns whether the org has an active agent plus seat availability. Authenticated with your Elyxir API key.","tags":["Agent"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Agent status","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"available":{"type":"boolean","example":true},"tenantSlug":{"type":"string","example":"acme"},"status":{"type":"string","example":"active"},"modelId":{"type":"string","example":"claude-sonnet-4-6"},"storageLocked":{"type":"boolean","example":false},"activeSeats":{"type":"integer","example":3},"lastActiveAt":{"type":"string","format":"date-time","nullable":true}}},{"type":"object","properties":{"available":{"type":"boolean","example":false},"reason":{"type":"string","example":"No agent provisioned for this organization"}}}]}}}},"401":{"description":"Unauthorized - Invalid or revoked API key"},"500":{"description":"Internal server error"}}},"post":{"summary":"Send a message to the agent (streaming)","description":"Sends a chat message to the org's agent and streams the response back as Server-Sent Events (`text/event-stream`).","tags":["Agent"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message","conversationId","userId"],"properties":{"message":{"type":"string","minLength":1,"maxLength":64000},"conversationId":{"type":"string","format":"uuid"},"userId":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"SSE stream from the agent","content":{"text/event-stream":{"schema":{"type":"string"}}}},"400":{"description":"Bad Request - Invalid or missing fields"},"401":{"description":"Unauthorized - Invalid or revoked API key"},"403":{"description":"Forbidden - No seat for this user, or billing suspended"},"500":{"description":"Internal server error"},"502":{"description":"Agent temporarily unavailable — retry"},"503":{"description":"No agent provisioned for this organization"}}}},"/api/v1/agent/seats":{"get":{"summary":"List agent seats","description":"List the org's agent seats and the seat cap. Elyxir API key auth.","tags":["Agent"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Seats","content":{"application/json":{"schema":{"type":"object","properties":{"seats":{"type":"array","items":{"type":"object"}},"maxSeats":{"type":"integer","example":5}}}}}},"401":{"description":"Unauthorized - Invalid or revoked API key"},"500":{"description":"Internal server error"}}},"post":{"summary":"Assign an agent seat","description":"Assign an agent seat to a user in the org. The target user must be a member of the API key's organization.","tags":["Agent"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string","format":"uuid"},"agentAlias":{"type":"string","minLength":1,"maxLength":100}}}}}},"responses":{"201":{"description":"Seat assigned","content":{"application/json":{"schema":{"type":"object","properties":{"seat":{"type":"object"}}}}}},"400":{"description":"Bad Request - Invalid input or user is not a member of the organization"},"401":{"description":"Unauthorized - Invalid or revoked API key"},"403":{"description":"Forbidden - Seat cap reached or not permitted"},"404":{"description":"No agent provisioned for this organization"},"409":{"description":"Conflict - User already has a seat"},"500":{"description":"Internal server error"}}},"patch":{"summary":"Update an agent seat","description":"Update a seat's alias or per-seat config overrides.","tags":["Agent"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string","format":"uuid"},"agentAlias":{"type":"string","minLength":1,"maxLength":100},"configOverrides":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"Seat updated","content":{"application/json":{"schema":{"type":"object","properties":{"seat":{"type":"object"}}}}}},"400":{"description":"Bad Request - Invalid input"},"401":{"description":"Unauthorized - Invalid or revoked API key"},"404":{"description":"Seat not found"},"500":{"description":"Internal server error"}}},"delete":{"summary":"Revoke an agent seat","description":"Revoke a user's agent seat. Pass the target user as a query parameter.","tags":["Agent"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"query","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID of the user whose seat is revoked"}],"responses":{"200":{"description":"Seat revoked","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Seat revoked"}}}}}},"400":{"description":"Bad Request - Missing or invalid userId"},"401":{"description":"Unauthorized - Invalid or revoked API key"},"500":{"description":"Internal server error"}}}},"/api/health":{"get":{"summary":"Health check","description":"Returns the health status of the API gateway","tags":["Health"],"security":[],"responses":{"200":{"description":"API is healthy","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"timestamp":{"type":"string","format":"date-time"}}}}}},"503":{"description":"Service unavailable"}}}},"/api/swagger":{"get":{"summary":"Download OpenAPI spec","description":"Returns the full OpenAPI 3.0 specification as JSON. Import into Postman, Insomnia, or use to generate client SDKs.","tags":["Documentation"],"security":[],"responses":{"200":{"description":"OpenAPI specification JSON","content":{"application/json":{"schema":{"type":"object"}}}}}}}}}