openapi: 3.1.0 info: title: 'ai API documentation' description: 'AI text generation, model listing and request tracking for solidpixels.' version: 1.0.0 servers: - url: 'https://ai.app.solidpixels.com' tags: - name: Models description: 'Available AI models.' - name: Prompts description: 'Available AI prompts.' - name: 'Text Generation' description: 'AI text generation endpoints.' - name: 'Generation Requests' description: 'AI generation request status and results.' - name: Agents description: 'AI agents with tool-use capabilities.' - name: Chat description: 'AI chat with opinionated defaults (MCP + system prompt auto-injected).' - name: Conversations description: 'Durable AI chat conversation history, owner-scoped.' components: securitySchemes: default: type: http scheme: bearer description: 'Use a JWT issued by the solidpixels service. Website scope comes from the token claims.' schemas: AgentRunRequest: type: object properties: messages: type: array description: 'AG-UI messages [{id, role, parts: [{type, text}]}].' items: type: string system_prompt: type: string description: 'System prompt with context.' model: type: string description: 'Model identifier.' mcp: type: object description: 'Optional MCP conversation config.' properties: {} provider: type: string description: 'Provider override.' max_tokens: type: integer description: 'Max output tokens.' max_steps: type: integer description: 'Max agent steps (1-30).' temperature: type: number description: 'Temperature (0-2).' stream_protocol: type: string description: 'Stream format: agui (default), vercel, or raw.' required: - messages - system_prompt - model AiAsyncResult: type: object properties: request_id: type: string status: type: string AiAsyncResultResponse: type: object properties: success: const: true data: $ref: '#/components/schemas/AiAsyncResult' message: type: string required: - success - data AiCachedResult: type: object properties: text: type: string usage: type: object properties: prompt_tokens: type: integer completion_tokens: type: integer total_tokens: type: integer cost_usd: type: string AiCachedResultResponse: type: object properties: success: const: true data: $ref: '#/components/schemas/AiCachedResult' message: type: string required: - success - data AiModelCatalog: type: object properties: default_provider: type: string default_model: type: string maxLength: 64 models: type: array items: type: object properties: provider: type: string model: type: string maxLength: 64 display_name: type: - string - 'null' maxLength: 100 description: type: - string - 'null' effort: type: - string - 'null' enum: - low - medium - high - null context_window: type: - integer - 'null' supports_vision: type: boolean pricing: type: object properties: prompt_per_million: type: string completion_per_million: type: string required: - prompt_per_million - completion_per_million required: - provider - model - display_name - description - effort - context_window - supports_vision - pricing required: - default_provider - default_model - models AiModelCatalogResponse: type: object properties: success: const: true data: $ref: '#/components/schemas/AiModelCatalog' message: type: string required: - success - data AiRequest: type: object properties: id: type: string type: type: string provider: type: string model: type: string mode: type: string status: type: string usage: type: object properties: prompt_tokens: type: integer completion_tokens: type: integer total_tokens: type: integer cost_usd: type: string error_message: type: - string - 'null' started_at: type: string completed_at: type: string created_at: type: string AiRequestResponse: type: object properties: success: const: true data: $ref: '#/components/schemas/AiRequest' message: type: string required: - success - data AiTextResult: type: object properties: request_id: type: string text: type: string usage: type: object properties: prompt_tokens: type: integer completion_tokens: type: integer total_tokens: type: integer cost_usd: type: string AiTextResultResponse: type: object properties: success: const: true data: $ref: '#/components/schemas/AiTextResult' message: type: string required: - success - data ChatRequest: type: object properties: messages: type: array description: 'AG-UI messages [{id, role, parts: [{type, text}]}].' items: type: object properties: id: type: string description: 'Message ID.' role: type: string description: 'Message role.' enum: - user - assistant - tool - system - reasoning - thinking parts: type: array description: 'Message parts.' items: type: object properties: type: type: string description: 'Part type.' text: type: string description: 'Text content for text parts.' required: - type required: - role - parts prompt: type: string description: 'Prompt key from /api/v1/prompts. Defaults to the default chat prompt.' context: type: object description: 'Prompt-specific caller context. Does not select tenant or permissions.' properties: editor: type: object description: 'Selected editor node context.' properties: node_id: type: string description: 'Selected editor node ID.' node_type: type: string description: 'Selected editor node type.' node_tag: type: string description: 'Selected editor node tag.' cms_type: type: string description: 'Selected CMS type.' route: type: object description: 'Current route context.' properties: page_id: type: string description: 'Current page ID.' page_title: type: string description: 'Current page title.' collection_id: type: string description: 'Current collection ID.' collection_title: type: string description: 'Current collection title.' collection_item_id: type: string description: 'Current collection item ID.' language_code: type: string description: 'Current language code.' language_id: type: string description: 'Current language ID.' model: type: string description: 'Model override.' provider: type: string description: 'Provider override.' max_tokens: type: integer description: 'Max output tokens.' max_steps: type: integer description: 'Max agent steps (1-30).' temperature: type: number description: 'Temperature (0-2).' stream_protocol: type: string description: 'Stream format: agui (default), vercel, raw.' enum: - agui - vercel - raw required: - messages Conversation: type: object properties: conversation_id: type: string title: type: string message_count: type: integer models: type: array items: type: object properties: provider: type: string model: type: string last_message_at: type: string created_at: type: string ConversationCollectionResponse: type: object properties: success: const: true data: type: array items: $ref: '#/components/schemas/Conversation' message: type: string required: - success - data ConversationDetail: type: object properties: conversation_id: type: string title: type: string message_count: type: integer models: type: array items: type: object properties: provider: type: string model: type: string messages: type: array items: type: object properties: id: type: string role: type: string parts: type: array items: type: object properties: type: type: string text: type: string last_message_at: type: string created_at: type: string ConversationDetailResponse: type: object properties: success: const: true data: $ref: '#/components/schemas/ConversationDetail' message: type: string required: - success - data Error: type: object properties: success: type: boolean message: type: string errors: type: object required: - success - message PromptMetadata: type: object properties: key: type: string label: type: string description: type: string is_default_chat: type: boolean context_schema: type: object properties: editor: type: object properties: node_id: type: object properties: type: type: string nullable: type: boolean max: type: integer node_type: type: object properties: type: type: string nullable: type: boolean max: type: integer route: type: object properties: page_id: type: object properties: type: type: string nullable: type: boolean max: type: integer language_code: type: object properties: type: type: string nullable: type: boolean max: type: integer PromptMetadataCollectionResponse: type: object properties: success: const: true data: type: array items: $ref: '#/components/schemas/PromptMetadata' message: type: string required: - success - data RenameConversationRequest: type: object properties: title: type: string maxLength: 100 description: 'New conversation title.' required: - title TextGenerationRequest: type: object properties: model: type: string description: 'Model identifier.' prompt: type: string description: 'Input prompt.' messages: type: array description: 'Conversation messages.' items: type: string system_prompt: type: string description: 'System instructions.' provider: type: string description: 'Provider override.' max_tokens: type: integer description: 'Max output tokens.' temperature: type: number description: 'Temperature (0-2).' required: - model ValidationError: type: object properties: success: type: boolean message: type: string errors: type: object required: - success - message - errors responses: Forbidden: description: Forbidden. content: application/json: schema: $ref: '#/components/schemas/Error' InternalServerError: description: 'Internal server error.' content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: 'Not found.' content: application/json: schema: $ref: '#/components/schemas/Error' Unauthenticated: description: Unauthenticated. content: application/json: schema: $ref: '#/components/schemas/Error' ValidationFailed: description: 'Validation failed.' content: application/json: schema: $ref: '#/components/schemas/ValidationError' security: - default: [] paths: /api/v1/models: get: summary: 'List models' operationId: getApiV1Models description: 'List available AI models with capabilities and pricing.' parameters: [] responses: 200: description: 'Available AI models with the configured default provider and model.' content: application/json: schema: $ref: '#/components/schemas/AiModelCatalogResponse' example: success: true data: default_provider: gemini default_model: anthropic/claude-opus-5 models: - provider: anthropic model: gpt-5.6-sol display_name: 'Opus 5' description: 'Iure quos explicabo eaque dolores vel cupiditate.' effort: medium context_window: 200000 supports_vision: false pricing: prompt_per_million: '5.000000' completion_per_million: '30.000000' message: 'Models retrieved' 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 500: $ref: '#/components/responses/InternalServerError' tags: - Models /api/v1/prompts: get: summary: 'List prompts' operationId: getApiV1Prompts description: 'List available prompt metadata.' parameters: [] responses: 200: description: 'Prompt metadata for the configured chat prompts.' content: application/json: schema: $ref: '#/components/schemas/PromptMetadataCollectionResponse' example: success: true data: - key: solidpixels.editor.secondary label: 'saepe unde at' description: 'Quos explicabo eaque dolores vel cupiditate.' is_default_chat: true context_schema: editor: node_id: type: string nullable: true max: 37 node_type: type: string nullable: true max: 59 route: page_id: type: string nullable: true max: 44 language_code: type: string nullable: true max: 2 - key: solidpixels.editor.secondary label: 'saepe unde at' description: 'Quos explicabo eaque dolores vel cupiditate.' is_default_chat: true context_schema: editor: node_id: type: string nullable: true max: 37 node_type: type: string nullable: true max: 59 route: page_id: type: string nullable: true max: 44 language_code: type: string nullable: true max: 2 message: 'Prompts retrieved' 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 500: $ref: '#/components/responses/InternalServerError' tags: - Prompts /api/v1/text: post: summary: 'Generate text' operationId: postApiV1Text description: 'Synchronous text generation with AI model.' parameters: [] responses: 200: description: '' content: application/json: schema: $ref: '#/components/schemas/AiTextResultResponse' example: success: true data: request_id: air_gzu16lymnbzh text: 'Qui architecto veniam aspernatur neque quis et officia vel quo.' usage: prompt_tokens: 24 completion_tokens: 8 total_tokens: 32 cost_usd: '0.000012' message: 'Text generated' 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 422: $ref: '#/components/responses/ValidationFailed' 500: $ref: '#/components/responses/InternalServerError' tags: - 'Text Generation' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TextGenerationRequest' /api/v1/text/stream: post: summary: 'Stream text' operationId: postApiV1TextStream description: 'Streaming text generation via SSE. Returns `text/event-stream` with `{"type":"text","delta":"..."}` chunks followed by `{"type":"done","usage":{...}}`.' parameters: [] responses: 200: description: 'SSE stream' content: text/plain: schema: type: string examples: - "data: {\"type\":\"text\",\"delta\":\"Fresh baked \"}\n\ndata: {\"type\":\"text\",\"delta\":\"happiness.\"}\n\ndata: {\"type\":\"done\",\"usage\":{\"prompt_tokens\":24,\"completion_tokens\":8,\"total_tokens\":32,\"cost_usd\":\"0.000012\"}}\n\n" 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 422: $ref: '#/components/responses/ValidationFailed' 500: $ref: '#/components/responses/InternalServerError' tags: - 'Text Generation' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TextGenerationRequest' /api/v1/text/async: post: summary: 'Async text' operationId: postApiV1TextAsync description: 'Queue text generation for async processing. Poll status via GET /api/v1/requests/{id}.' parameters: [] responses: 202: description: '' content: application/json: schema: $ref: '#/components/schemas/AiAsyncResultResponse' example: success: true data: request_id: air_gzu16lymnbzh status: pending message: 'Request queued' 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 422: $ref: '#/components/responses/ValidationFailed' 500: $ref: '#/components/responses/InternalServerError' tags: - 'Text Generation' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TextGenerationRequest' '/api/v1/requests/{id}': get: summary: 'Get request' operationId: getApiV1RequestsById description: 'Get status of an AI request.' parameters: [] responses: 200: description: '' content: application/json: schema: $ref: '#/components/schemas/AiRequestResponse' example: success: true data: id: air_gzu16lymnbzh type: text provider: openai model: gpt-5.6-terra mode: sync status: completed usage: prompt_tokens: 24 completion_tokens: 8 total_tokens: 32 cost_usd: '0.000144' error_message: null started_at: '2026-01-01T00:00:00+00:00' completed_at: '2026-01-01T00:00:00+00:00' created_at: '2026-01-01T00:00:00+00:00' message: 'Request retrieved' 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 404: $ref: '#/components/responses/NotFound' 500: $ref: '#/components/responses/InternalServerError' tags: - 'Generation Requests' parameters: - in: path name: id description: 'Prefixed request ID.' required: true schema: type: string examples: - air_abc123 '/api/v1/requests/{id}/result': get: summary: 'Get result' operationId: getApiV1RequestsByIdResult description: 'Get cached result of an async AI request. Results are cached for 24 hours after completion.' parameters: [] responses: 200: description: '' content: application/json: schema: $ref: '#/components/schemas/AiCachedResultResponse' example: success: true data: text: 'Autem et saepe unde at iure quos explicabo eaque dolores vel cupiditate ex.' usage: prompt_tokens: 24 completion_tokens: 8 total_tokens: 32 cost_usd: '0.000012' message: 'Result retrieved' 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 404: $ref: '#/components/responses/NotFound' 500: $ref: '#/components/responses/InternalServerError' tags: - 'Generation Requests' parameters: - in: path name: id description: 'Prefixed request ID.' required: true schema: type: string examples: - air_abc123 /api/v1/agent/run: post: summary: 'Run agent' operationId: postApiV1AgentRun description: 'Execute an AI agent with MCP tool access. Returns SSE stream.' parameters: [] responses: 200: description: 'SSE stream' content: application/json: schema: type: - object - 'null' 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 422: $ref: '#/components/responses/ValidationFailed' 500: $ref: '#/components/responses/InternalServerError' tags: - Agents requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AgentRunRequest' /api/v1/chat: post: summary: Chat operationId: postApiV1Chat description: 'Run an agent with a server-rendered prompt and MCP config. Returns SSE stream.' parameters: [] responses: 200: description: 'SSE stream' content: text/plain: schema: type: string examples: - "data: {\"type\":\"text\",\"delta\":\"Done\"}\n\ndata: {\"type\":\"done\",\"usage\":{\"prompt_tokens\":24,\"completion_tokens\":8,\"total_tokens\":32,\"cost_usd\":\"0.000012\"}}\n\n" 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 422: $ref: '#/components/responses/ValidationFailed' 500: $ref: '#/components/responses/InternalServerError' tags: - Chat requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ChatRequest' '/api/v1/conversations/{conversationId}': patch: summary: 'Rename conversation' operationId: patchApiV1ConversationsByConversationId description: 'Rename one conversation owned by the caller.' parameters: [] responses: 200: description: '' content: application/json: schema: $ref: '#/components/schemas/ConversationDetailResponse' example: success: true data: conversation_id: cc656455-2d38-3250-9c6f-5797c7051fc6 title: 'Create a new landing page' message_count: 1 models: - provider: openrouter model: openai/gpt-5.6-terra messages: - id: m1 role: user parts: - type: text text: Hello last_message_at: '2026-01-01T00:00:00+00:00' created_at: '2026-01-01T00:00:00+00:00' message: 'Conversation renamed' 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 404: $ref: '#/components/responses/NotFound' 422: $ref: '#/components/responses/ValidationFailed' 500: $ref: '#/components/responses/InternalServerError' tags: - Conversations requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RenameConversationRequest' get: summary: 'Get conversation' operationId: getApiV1ConversationsByConversationId description: 'Get one conversation with its full message history.' parameters: [] responses: 200: description: '' content: application/json: schema: $ref: '#/components/schemas/ConversationDetailResponse' example: success: true data: conversation_id: cc656455-2d38-3250-9c6f-5797c7051fc6 title: 'Create a new landing page' message_count: 1 models: - provider: openrouter model: openai/gpt-5.6-terra messages: - id: m1 role: user parts: - type: text text: Hello last_message_at: '2026-01-01T00:00:00+00:00' created_at: '2026-01-01T00:00:00+00:00' message: 'Conversation retrieved' 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 404: $ref: '#/components/responses/NotFound' 500: $ref: '#/components/responses/InternalServerError' tags: - Conversations delete: summary: 'Delete conversation' operationId: deleteApiV1ConversationsByConversationId description: 'Delete one conversation owned by the caller.' parameters: [] responses: 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 404: $ref: '#/components/responses/NotFound' 500: $ref: '#/components/responses/InternalServerError' tags: - Conversations parameters: - in: path name: conversationId description: 'Client conversation id.' required: true schema: type: string examples: - conv-abc /api/v1/conversations: get: summary: 'List conversations' operationId: getApiV1Conversations description: 'List the caller conversations (no message bodies), newest activity first.' parameters: [] responses: 200: description: '' content: application/json: schema: $ref: '#/components/schemas/ConversationCollectionResponse' example: success: true data: - conversation_id: cc656455-2d38-3250-9c6f-5797c7051fc6 title: 'Create a new landing page' message_count: 12 models: - provider: openrouter model: openai/gpt-5.6-terra last_message_at: '2026-01-01T00:00:00+00:00' created_at: '2026-01-01T00:00:00+00:00' - conversation_id: cc656455-2d38-3250-9c6f-5797c7051fc6 title: 'Create a new landing page' message_count: 12 models: - provider: openrouter model: openai/gpt-5.6-terra last_message_at: '2026-01-01T00:00:00+00:00' created_at: '2026-01-01T00:00:00+00:00' message: 'Conversations retrieved' 401: $ref: '#/components/responses/Unauthenticated' 403: $ref: '#/components/responses/Forbidden' 500: $ref: '#/components/responses/InternalServerError' 503: description: 'When conversation matching is unavailable.' content: application/json: schema: type: object properties: success: type: boolean examples: - false message: type: string examples: - 'Search service unavailable.' examples: - success: false message: 'Search service unavailable.' tags: - Conversations