# AIST Agent > Async REST API gateway for AI assistants. Send a task (text, files, or voice), poll or receive a webhook with the result. Supports multi-turn conversations. RS256 JWT auth. Base URL: `https://agent-api.ai.neobash.ru` ## Quick Reference Three endpoints. All require `Authorization: Bearer ` and `Accept: application/json`. **Get storage base URL:** ``` GET /api/storage/base ``` Response `200`: ```json {"url": "https://storage.example.com"} ``` Prepend this URL to `provider_data.path` to download any file from the API. Storage URLs are public — no `Authorization` header is required to download files. **Upload a file:** ``` POST /api/files Content-Type: multipart/form-data file: ``` Allowed extensions: `png`, `jpg`, `webp`, `gif`, `txt`, `pdf`, `json`, `js`, `css`, `html`, `csv`, `xml`, `docx`, `xls`, `xlsx`, `mp3`, `wav`, `ogg`, `m4a`, `webm`, `mp4`, `mkv`. Max file size: 1 GB. Response `201` — ready to use directly in `input`: ```json { "type": "file", "file": { "id": "42", "name": "report.pdf", "mime_type": "application/pdf", "provider_data": {"path": "files/a1b2c3d4e5f6..."} } } ``` **Create a task:** ``` POST /api/tasks Content-Type: application/json ``` ```json { "assistant_id": 1, "input": [ {"type": "text", "text": "Summarize this document"}, {"type": "file", "file": {"id": "42", "name": "report.pdf", "mime_type": "application/pdf", "provider_data": {"path": "files/a1b2c3d4e5f6..."}}} ] } ``` Response `201`: ```json {"uuid": "550e8400-e29b-41d4-a716-446655440000", "status": "pending"} ``` **Poll the result:** ``` GET /api/tasks/550e8400-e29b-41d4-a716-446655440000 ``` Response `200`: ```json { "uuid": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "turn": { "input": [{"type": "text", "text": "Summarize this document"}], "output": [{"type": "text", "text": "Here is the summary of your document..."}], "messages": [ {"role": "user", "content": [{"type": "text", "text": "Summarize this document"}]}, {"role": "assistant", "content": [{"type": "text", "text": "Here is the summary of your document..."}]} ], "metadata": {"cost": {"total": 0.003, "details": {"llm": 0.003, "tools": 0}}, "model": "claude-sonnet-4-20250514"} }, "error": null, "created_at": "2026-04-16T12:00:00.000000Z", "completed_at": "2026-04-16T12:00:15.000000Z" } ``` ## Input Format IMPORTANT: The `input` array items use these exact field names. Do NOT use `content` — the field is called `text`. Text: ```json {"type": "text", "text": "Your prompt here"} ``` File — attach a document, image, or video for the AI to analyze (upload first via `POST /api/files`, then use the returned `id`, `name`, and `mime_type`): ```json {"type": "file", "file": {"id": "42", "name": "report.pdf", "mime_type": "application/pdf"}} ``` Voice — attach an audio recording for speech-to-text transcription (upload first via `POST /api/files`, then use the returned `id`, `name`, and `mime_type`): ```json {"type": "voice", "voice": {"id": "43", "name": "recording.mp3", "mime_type": "audio/mpeg"}} ``` All three fields (`id`, `name`, `mime_type`) are required. File IDs are strings throughout the API. The `POST /api/files` response can be placed directly into the `input` array without modification. Use `type: "file"` when you want the AI to see/read the file content. Use `type: "voice"` when you want the audio transcribed to text first. ## Turn Format The `turn` field on a completed task is an object with the full AI turn data: ```json { "input": [{"type": "text", "text": "Your prompt"}], "output": [{"type": "text", "text": "The AI response"}], "messages": [ {"role": "user", "content": [{"type": "text", "text": "Your prompt"}]}, {"role": "assistant", "content": [{"type": "text", "text": "The AI response"}]} ], "metadata": { "cost": {"total": 0.003, "details": {"llm": 0.003, "tools": 0}}, "model": "claude-sonnet-4-20250514" } } ``` | Field | Type | Description | |------------|--------|-----------------------------------------------------------------| | `input` | array | The user input items (echoed from request) | | `output` | array | The AI-generated output items (see Output Types below) | | `messages` | array | Full LLM message history — pass this in `turns` for multi-turn | | `metadata` | object | Cost breakdown and model identifier | When `status` is `"failed"`, `turn` is `null` and `error` contains the error message string. ## Output Types The `turn.output` array contains items of different types. The output is ordered — items appear in the sequence the AI produced them. **IMPORTANT:** Text output uses the field `text`, not `content`. ### `text` — Text response The primary output type. Contains the AI's text response. ```json {"type": "text", "text": "Here is the summary of your document..."} ``` ### `file` — Generated file Produced when the AI generates a file (most commonly an image via the image generation tool). The file is stored on the backend; use `provider_data.path` to construct the download URL. ```json { "type": "file", "file": { "id": "1842", "name": "image.png", "mime_type": "image/png", "provider_data": {"path": "files/a1b2c3d4e5f6..."} } } ``` | Field | Type | Description | |------------------------|--------|----------------------------------------------------------| | `file.id` | string | File ID on the backend | | `file.name` | string | Filename with extension | | `file.mime_type` | string | MIME type (e.g. `image/png`, `image/jpeg`) | | `file.provider_data` | object | Optional. Contains `path` for constructing download URL | To download the file, prepend the storage base URL to `provider_data.path`. Retrieve the storage base URL from `GET /api/storage/base`. Storage URLs are public — no `Authorization` header is required. ### `tool_call` — Tool execution result Appears when the AI called an external tool (web search, document reader, external API, image generator, etc.). Contains the tool name, input arguments, output result, and cost. ```json { "type": "tool_call", "tool_call": { "name": "ext_get_weather", "input": {"city": "Moscow"}, "output": { "type": "content", "content": [ {"type": "text", "text": "{\"id\":15,\"status\":200,\"text\":\"{\\\"temp\\\":22,\\\"condition\\\":\\\"sunny\\\"}\"}"} ] }, "cost": 0 } } ``` | Field | Type | Description | |----------------------|--------|--------------------------------------------------| | `tool_call.name` | string | Tool name (e.g. `generate_image`, `web_search`, `ext_*` for external tools) | | `tool_call.input` | object | Arguments the AI passed to the tool | | `tool_call.output` | object | Tool result — either `content` or `error` (see below) | | `tool_call.cost` | number | Execution cost (e.g. image generation pricing) | #### Tool output: success (`content`) A successful tool call returns `type: "content"` with an array of content items. Each content item can be `text`, `file`, or `search`. **Text result** (most common — external API responses, document reads): ```json { "type": "content", "content": [ {"type": "text", "text": "The tool response text or JSON string"} ] } ``` **File result** (image generation, file-producing tools): ```json { "type": "content", "content": [ { "type": "file", "file": { "id": "1843", "name": "image.png", "mime_type": "image/png", "provider_data": {"path": "files/b2c3d4e5f6a1..."} } } ] } ``` **Mixed result** (tool returns both text and files): ```json { "type": "content", "content": [ {"type": "text", "text": "Image generated successfully"}, {"type": "file", "file": {"id": "1843", "name": "image.png", "mime_type": "image/png", "provider_data": {"path": "files/..."}}} ] } ``` #### Tool output: error A failed tool call returns `type: "error"`: ```json { "type": "error", "error": { "message": "An error occurred during image creation.", "reason": "Connection timeout" } } ``` | Field | Type | Description | |-----------------|--------|--------------------------------| | `error.message` | string | Human-readable error message | | `error.reason` | string | Technical reason (optional) | ### `search` — Web search results Produced when the AI performs a web search. Contains structured results with citations and images. ```json { "type": "search", "search": { "results": [ { "title": "Laravel - The PHP Framework For Web Artisans", "url": "https://laravel.com", "date": "2026-04-01", "last_updated": "2026-04-15", "snippet": "Laravel is a web application framework with expressive, elegant syntax...", "source": "laravel.com" } ], "citations": ["https://laravel.com", "https://laravel.com/docs"], "images": [ { "title": "Laravel Logo", "image_url": "https://laravel.com/img/logo.png", "origin_url": "https://laravel.com", "width": 200, "height": 200 } ] } } ``` | Field | Type | Description | |--------------------|--------|--------------------------------------| | `search.results` | array | Search result entries | | `search.citations` | array | URLs cited by the AI | | `search.images` | array | Image results from the search | ### `custom` — Provider-specific content Rare. Contains provider-specific data that doesn't fit other types. ```json { "type": "custom", "custom": { "from": "anthropic", "content": {} } } ``` ## Complete Output Examples ### Simple text response ```json { "output": [ {"type": "text", "text": "Laravel is a PHP framework for building web applications..."} ] } ``` ### Image generation **Important:** Generated images are not in the top-level `output` array — they are inside `tool_call.output.content`. Always walk `tool_call` items to find generated media (see "Reading Output — Quick Guide" below). The AI called the image tool, then wrote a text comment: ```json { "output": [ { "type": "tool_call", "tool_call": { "name": "generate_image", "input": {"prompt": "A serene mountain landscape at sunset", "aspect": "horizontal", "references": []}, "output": { "type": "content", "content": [ {"type": "file", "file": {"id": "1843", "name": "image.png", "mime_type": "image/png", "provider_data": {"path": "files/a1b2c3d4..."}}} ] }, "cost": 0.04 } }, {"type": "text", "text": "Here is the mountain landscape you requested."} ] } ``` ### Web search followed by answer ```json { "output": [ { "type": "tool_call", "tool_call": { "name": "web_search", "input": {"query": "Laravel 13 release date"}, "output": { "type": "content", "content": [ {"type": "text", "text": "{\"results\":[{\"title\":\"Laravel 13 Released\",\"snippet\":\"...\"}]}"} ] }, "cost": 0.005 } }, { "type": "search", "search": { "results": [{"title": "Laravel 13 Released", "url": "https://blog.laravel.com/laravel-13", "date": "2026-03-01", "last_updated": "2026-03-01", "snippet": "We are excited to announce...", "source": "blog.laravel.com"}], "citations": ["https://blog.laravel.com/laravel-13"], "images": [] } }, {"type": "text", "text": "Laravel 13 was released on March 1, 2026..."} ] } ``` ### External tool calling an API The assistant has a configured external tool (`ext_get_weather`) that calls a third-party API. The external tool returns its HTTP response as a JSON string inside a text content item: ```json { "output": [ { "type": "tool_call", "tool_call": { "name": "ext_get_weather", "input": {"city": "Moscow"}, "output": { "type": "content", "content": [ {"type": "text", "text": "{\"id\":15,\"status\":200,\"text\":\"{\\\"temp\\\":22,\\\"condition\\\":\\\"sunny\\\"}\"}"} ] }, "cost": 0 } }, {"type": "text", "text": "The current weather in Moscow is 22°C and sunny."} ] } ``` ### Tool call with error ```json { "output": [ { "type": "tool_call", "tool_call": { "name": "generate_image", "input": {"prompt": "A cat wearing a hat", "aspect": "square", "references": []}, "output": { "type": "error", "error": {"message": "An error occurred during image creation.", "reason": "Rate limit exceeded"} }, "cost": 0 } }, {"type": "text", "text": "Sorry, I was unable to generate the image due to a rate limit. Please try again in a moment."} ] } ``` ### Multiple tools and mixed output The AI reads a document, generates an image based on it, and then summarizes: ```json { "output": [ { "type": "tool_call", "tool_call": { "name": "read_document", "input": {"file_id": "42", "page": 1, "offset": 0, "limit": 100}, "output": { "type": "content", "content": [{"type": "text", "text": "{\"page\":1,\"max_page\":3,\"elements\":[...],\"has_more\":false}"}] }, "cost": 0 } }, { "type": "tool_call", "tool_call": { "name": "generate_image", "input": {"prompt": "Infographic based on the document data", "aspect": "vertical", "references": []}, "output": { "type": "content", "content": [{"type": "file", "file": {"id": "1844", "name": "image.png", "mime_type": "image/png", "provider_data": {"path": "files/c3d4e5f6..."}}}] }, "cost": 0.04 } }, {"type": "text", "text": "I've read your document and created an infographic summarizing the key data points."} ] } ``` ## Reading Output — Quick Guide To extract the main text response, filter `output` for items where `type === "text"`: ```python text_parts = [item["text"] for item in turn["output"] if item["type"] == "text"] full_text = "\n".join(text_parts) ``` To extract generated files (images, etc.): ```python files = [item["file"] for item in turn["output"] if item["type"] == "file"] # Also check inside tool_call outputs: for item in turn["output"]: if item["type"] == "tool_call": tc_output = item["tool_call"]["output"] if tc_output["type"] == "content": files += [c["file"] for c in tc_output["content"] if c["type"] == "file"] ``` To get total cost: ```python cost = turn["metadata"]["cost"]["total"] # LLM + tools combined ``` ## Multi-Turn Conversations To continue a conversation across multiple tasks, pass previous turns in the `turns` array. The AI engine uses the `messages` from each turn to reconstruct conversation context. **Step 1:** Create the first task (no `turns` needed): ```json { "assistant_id": 1, "input": [{"type": "text", "text": "What is Laravel?"}] } ``` **Step 2:** Poll until completed, save the `turn` from the response. **Step 3:** Create the next task, passing the previous turn in `turns`: ```json { "assistant_id": 1, "input": [{"type": "text", "text": "How does its routing work?"}], "turns": [ { "input": [{"type": "text", "text": "What is Laravel?"}], "output": [{"type": "text", "text": "Laravel is a PHP framework..."}], "messages": [ {"role": "user", "content": [{"type": "text", "text": "What is Laravel?"}]}, {"role": "assistant", "content": [{"type": "text", "text": "Laravel is a PHP framework..."}]} ] } ] } ``` Each subsequent task appends the previous turn to the `turns` array. The `messages` field is what the AI engine actually uses for context — `input` and `output` are informational. ### Turn Schema (each element in `turns`) | Field | Type | Required | Description | |------------|--------|----------|----------------------------------------------------------| | `input` | array | yes | Input items from that turn | | `output` | array | yes | Output items from that turn | | `messages` | array | yes | LLM messages — copy from the completed task's `turn.messages` | | `metadata` | object | no | Cost and model info (optional, not used for context) | ## Task Statuses | Status | Meaning | |--------------|---------------------------------------------| | `pending` | Created, waiting in queue | | `processing` | AI assistant is working | | `completed` | Done — read `turn` object | | `failed` | Error — read `error` string, `turn` is null | ## Task Error Field When `status` is `"failed"`, the `error` field contains a human-readable message. Some values are fixed strings produced by this gateway; others are passed through from upstream services. **Fixed strings (safe to match exactly):** | Value | Cause | |---|---| | `"Assistant is not available"` | The assistant is not accessible to this user | | `"Insufficient balance"` | User or organization balance is ≤ 0 | | `"Token revoked"` | The JWT was revoked by the backend (user logged out or session expired) | | `"AI service did not accept the task. Please try again later."` | The AI engine did not acknowledge the task within 30 seconds | | `"No turn data received"` | The AI engine reported completion but returned no output | **Pass-through strings (variable):** Any other value is the raw error message from the AI engine or backend service. Show it to the user as-is or display a generic error message. ## Authentication All client endpoints require an RS256 JWT in the `Authorization: Bearer ` header. Required headers on every request: ``` Authorization: Bearer Accept: application/json ``` JWT payload fields used: `sub` (user ID), `sanctum` (API token), `exp` (expiry), `abilities` (array). JWTs are issued by the Aist Back platform. Contact your platform administrator to obtain credentials. Tokens are blacklisted on any 403 from the backend and auto-removed once the JWT expires. Storage URLs (from `provider_data.path` prefixed with the storage base URL) are **public** — no `Authorization` header is required to download files. ## POST /api/tasks — Full Schema | Field | Type | Required | Description | |----------------------|---------|----------|----------------------------------------------------------| | `assistant_id` | integer | yes | ID of the AI assistant. Provided by the platform administrator — there is no listing endpoint in this API. | | `input` | array | yes | Array of input items (min 1), see Input Format above | | `turns` | array | no | Previous conversation turns for multi-turn context (see Multi-Turn Conversations) | | `llm_model_id` | integer | no | Specific LLM model config ID on the assistant | | `image_generator_id` | integer | no | Specific image generator config ID on the assistant | | `callback_url` | string | no | Webhook URL for result delivery (max 2048 chars) | | `callback_secret` | string | no | HMAC secret for webhook signature (max 255 chars) | If `llm_model_id` or `image_generator_id` don't match a config on the assistant, the default is used silently. ## GET /api/tasks/{uuid} — Full Response ```json { "uuid": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "turn": { "input": [{"type": "text", "text": "Your prompt"}], "output": [{"type": "text", "text": "The AI response..."}], "messages": [ {"role": "user", "content": [{"type": "text", "text": "Your prompt"}]}, {"role": "assistant", "content": [{"type": "text", "text": "The AI response..."}]} ], "metadata": {"cost": {"total": 0.003, "details": {"llm": 0.003, "tools": 0}}, "model": "claude-sonnet-4-20250514"} }, "error": null, "created_at": "2026-04-16T12:00:00.000000Z", "completed_at": "2026-04-16T12:00:15.000000Z" } ``` Fields: `uuid` (string), `status` (string), `turn` (object or null), `error` (string or null), `created_at` (ISO 8601), `completed_at` (ISO 8601 or null). ## POST /api/files Upload a file via `multipart/form-data`. Returns an object ready to use directly as an `input` item. **Allowed file types:** | Category | Extensions | |-----------|-----------------------------------------| | Images | `png`, `jpg`, `webp`, `gif` | | Documents | `pdf`, `docx`, `xls`, `xlsx` | | Text | `txt`, `json`, `js`, `css`, `html`, `csv`, `xml` | | Audio | `mp3`, `wav`, `ogg`, `m4a` | | Video | `webm`, `mp4`, `mkv` | **Max file size:** 1 GB. Images are automatically resized on upload. GIFs are converted to static images. **Response `201`:** ```json { "type": "file", "file": { "id": "42", "name": "report.pdf", "mime_type": "application/pdf", "provider_data": {"path": "files/a1b2c3d4e5f6..."} } } ``` The response object can be placed directly into the `input` array. All file IDs in this API are strings. ## Webhook Delivery Set `callback_url` on task creation to receive results via POST instead of polling. The webhook fires on any terminal status (`completed` or `failed`). **Webhook request:** ``` POST Content-Type: application/json X-Task-Uuid: 550e8400-e29b-41d4-a716-446655440000 X-Signature: <- only if callback_secret was provided ``` **Webhook body** — same shape as `GET /api/tasks/{uuid}`: ```json { "uuid": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "turn": { "input": [{"type": "text", "text": "..."}], "output": [{"type": "text", "text": "..."}], "messages": [ {"role": "user", "content": [{"type": "text", "text": "..."}]}, {"role": "assistant", "content": [{"type": "text", "text": "..."}]} ], "metadata": {"cost": {"total": 0.003, "details": {"llm": 0.003, "tools": 0}}, "model": "claude-sonnet-4-20250514"} }, "error": null, "created_at": "2026-04-16T12:00:00.000000Z", "completed_at": "2026-04-16T12:00:15.000000Z" } ``` **Signature verification** (if `callback_secret` was set): ``` expected = HMAC-SHA256(raw_request_body, your_callback_secret) valid = constant_time_compare(expected, X-Signature_header_value) ``` **Retry policy:** up to 5 attempts with backoff 10s -> 30s -> 60s -> 120s. Return 2xx to acknowledge. After all retries fail, the result is still available via polling. ## Error Codes | Code | Meaning | |-------|----------------------------------------------------| | `401` | Missing, expired, revoked, or invalid JWT | | `403` | Task belongs to another user | | `404` | Task not found | | `422` | Validation error — response includes `errors` map | | `429` | Too many active tasks (limit: 5 per user) | | `503` | Auth service temporarily unavailable | 422 response format: ```json { "message": "The assistant id field is required.", "errors": { "assistant_id": ["The assistant id field is required."] } } ``` ## Rate Limits Max 5 concurrent active (pending + processing) tasks per user. `POST /api/tasks` returns 429 when exceeded. No `Retry-After` header is sent — the limit is capacity-based, not time-based. Retry strategy: poll existing tasks until one reaches a terminal status (`completed` or `failed`), then create a new task. ## Integration Examples ### curl ```bash # 1. Create a text task curl -X POST https://agent-api.ai.neobash.ru/api/tasks \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"assistant_id": 1, "input": [{"type": "text", "text": "What is Laravel?"}]}' # -> {"uuid":"550e8400-...","status":"pending"} # 2. Poll the result curl https://agent-api.ai.neobash.ru/api/tasks/550e8400-... \ -H "Authorization: Bearer $JWT" \ -H "Accept: application/json" # -> {"uuid":"...","status":"completed","turn":{"input":[...],"output":[...],"messages":[...],"metadata":{...}},...} # 3. Upload a file and use it curl -X POST https://agent-api.ai.neobash.ru/api/files \ -H "Authorization: Bearer $JWT" \ -H "Accept: application/json" \ -F "file=@document.pdf" # -> {"id":42,"name":"document.pdf",...} curl -X POST https://agent-api.ai.neobash.ru/api/tasks \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"assistant_id": 1, "input": [{"type": "text", "text": "Summarize this"}, {"type": "file", "file": {"id": "42", "name": "document.pdf", "mime_type": "application/pdf"}}]}' # 4. Task with webhook curl -X POST https://agent-api.ai.neobash.ru/api/tasks \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{"assistant_id": 1, "callback_url": "https://yourapp.com/hook", "callback_secret": "s3cret", "input": [{"type": "text", "text": "Generate a report"}]}' ``` ### PHP (Laravel) ```php use Illuminate\Support\Facades\Http; $base = 'https://agent-api.ai.neobash.ru'; // Create task $response = Http::withToken($jwt)->acceptJson() ->post("{$base}/api/tasks", [ 'assistant_id' => 1, 'input' => [ ['type' => 'text', 'text' => 'Analyze this data'], ], ]); $uuid = $response->json('uuid'); // Poll until terminal status do { sleep(2); $task = Http::withToken($jwt)->acceptJson() ->get("{$base}/api/tasks/{$uuid}") ->json(); } while (in_array($task['status'], ['pending', 'processing'])); // Read result if ($task['status'] === 'completed') { foreach ($task['turn']['output'] as $item) { match ($item['type']) { 'text' => print($item['text']), 'file' => print("File: {$item['file']['name']} ({$item['file']['mime_type']})\n"), 'tool_call' => print("Tool: {$item['tool_call']['name']}\n"), 'search' => print("Search: " . count($item['search']['results']) . " results\n"), default => null, }; } } else { echo "Error: " . $task['error']; } ``` ```php // Multi-turn conversation $history = []; $questions = ['What is Laravel?', 'How does its routing work?', 'Show me an example']; foreach ($questions as $question) { $response = Http::withToken($jwt)->acceptJson() ->post("{$base}/api/tasks", [ 'assistant_id' => 1, 'input' => [['type' => 'text', 'text' => $question]], 'turns' => $history, ]); $uuid = $response->json('uuid'); do { sleep(2); $task = Http::withToken($jwt)->acceptJson() ->get("{$base}/api/tasks/{$uuid}") ->json(); } while (in_array($task['status'], ['pending', 'processing'])); if ($task['status'] === 'completed') { // Save the turn for next request's context $history[] = $task['turn']; echo $task['turn']['output'][0]['text'] . "\n"; } } ``` ```php // Upload file then use in task $file = Http::withToken($jwt)->acceptJson() ->attach('file', file_get_contents('/path/to/doc.pdf'), 'doc.pdf') ->post("{$base}/api/files") ->json(); Http::withToken($jwt)->acceptJson() ->post("{$base}/api/tasks", [ 'assistant_id' => 1, 'input' => [ ['type' => 'text', 'text' => 'Summarize this document'], ['type' => 'file', 'file' => ['id' => (string) $file['id'], 'name' => $file['name'], 'mime_type' => $file['mime_type']]], ], ]); ``` ```php // Webhook receiver Route::post('webhooks/aist', function (Request $request) { if ($secret = config('services.aist.webhook_secret')) { $expected = hash_hmac('sha256', $request->getContent(), $secret); abort_unless(hash_equals($expected, $request->header('X-Signature', '')), 403); } $data = $request->all(); // $data['uuid'], $data['status'], $data['turn'], $data['error'] // Output: $data['turn']['output'][0]['text'] // Messages for multi-turn: $data['turn']['messages'] return response()->noContent(); }); ``` ### Python ```python import requests, time BASE = "https://agent-api.ai.neobash.ru" HEADERS = {"Authorization": f"Bearer {jwt}", "Accept": "application/json"} # Create task resp = requests.post(f"{BASE}/api/tasks", headers=HEADERS, json={ "assistant_id": 1, "input": [{"type": "text", "text": "Explain quantum computing"}], }) uuid = resp.json()["uuid"] # Poll while True: task = requests.get(f"{BASE}/api/tasks/{uuid}", headers=HEADERS).json() if task["status"] in ("completed", "failed"): break time.sleep(2) # Read result if task["status"] == "completed": for item in task["turn"]["output"]: if item["type"] == "text": print(item["text"]) elif item["type"] == "file": print(f"File: {item['file']['name']} ({item['file']['mime_type']})") elif item["type"] == "tool_call": print(f"Tool: {item['tool_call']['name']}") elif item["type"] == "search": print(f"Search: {len(item['search']['results'])} results") else: print(f"Error: {task['error']}") ``` ```python # Multi-turn conversation history = [] for question in ["What is Laravel?", "How does its routing work?", "Show me an example"]: resp = requests.post(f"{BASE}/api/tasks", headers=HEADERS, json={ "assistant_id": 1, "input": [{"type": "text", "text": question}], "turns": history, }) uuid = resp.json()["uuid"] while True: task = requests.get(f"{BASE}/api/tasks/{uuid}", headers=HEADERS).json() if task["status"] in ("completed", "failed"): break time.sleep(2) if task["status"] == "completed": history.append(task["turn"]) # save for next request print(task["turn"]["output"][0]["text"]) ``` ```python # Upload file with open("doc.pdf", "rb") as f: file_resp = requests.post(f"{BASE}/api/files", headers={"Authorization": f"Bearer {jwt}", "Accept": "application/json"}, files={"file": ("doc.pdf", f, "application/pdf")}) file_id = file_resp.json()["id"] # Use in task requests.post(f"{BASE}/api/tasks", headers=HEADERS, json={ "assistant_id": 1, "input": [ {"type": "text", "text": "Summarize this"}, {"type": "file", "file": {"id": str(file_id), "name": file_resp.json()["name"], "mime_type": file_resp.json()["mime_type"]}}, ], }) ``` ```python # Webhook receiver (Flask) import hmac, hashlib from flask import Flask, request, abort app = Flask(__name__) @app.post("/webhooks/aist") def aist_webhook(): secret = app.config.get("AIST_WEBHOOK_SECRET") if secret: expected = hmac.new(secret.encode(), request.get_data(), hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, request.headers.get("X-Signature", "")): abort(403) data = request.json # data["uuid"], data["status"], data["turn"], data["error"] # Output: data["turn"]["output"][0]["text"] # Messages for multi-turn: data["turn"]["messages"] return "", 204 ``` ### Node.js / TypeScript ```typescript const BASE = "https://agent-api.ai.neobash.ru"; const headers = { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json", Accept: "application/json", }; // Create task const { uuid } = await fetch(`${BASE}/api/tasks`, { method: "POST", headers, body: JSON.stringify({ assistant_id: 1, input: [{ type: "text", text: "Hello, AI!" }], }), }).then((r) => r.json()); // Poll let task: any; do { await new Promise((r) => setTimeout(r, 2000)); task = await fetch(`${BASE}/api/tasks/${uuid}`, { headers }).then((r) => r.json()); } while (task.status === "pending" || task.status === "processing"); // Read result if (task.status === "completed") { for (const item of task.turn.output) { switch (item.type) { case "text": console.log(item.text); break; case "file": console.log(`File: ${item.file.name} (${item.file.mime_type})`); break; case "tool_call": console.log(`Tool: ${item.tool_call.name}`); break; case "search": console.log(`Search: ${item.search.results.length} results`); break; } } } else { console.error(task.error); } ``` ```typescript // Multi-turn conversation const history: any[] = []; for (const question of ["What is Laravel?", "How does routing work?", "Show an example"]) { const { uuid } = await fetch(`${BASE}/api/tasks`, { method: "POST", headers, body: JSON.stringify({ assistant_id: 1, input: [{ type: "text", text: question }], turns: history, }), }).then((r) => r.json()); let task: any; do { await new Promise((r) => setTimeout(r, 2000)); task = await fetch(`${BASE}/api/tasks/${uuid}`, { headers }).then((r) => r.json()); } while (task.status === "pending" || task.status === "processing"); if (task.status === "completed") { history.push(task.turn); // save for next request console.log(task.turn.output[0].text); } } ``` ```typescript // Webhook receiver (Express) import crypto from "crypto"; import express from "express"; const app = express(); app.use(express.raw({ type: "application/json" })); app.post("/webhooks/aist", (req, res) => { const secret = process.env.AIST_WEBHOOK_SECRET; if (secret) { const expected = crypto.createHmac("sha256", secret).update(req.body).digest("hex"); const received = req.headers["x-signature"] as string; if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received ?? ""))) { return res.sendStatus(403); } } const data = JSON.parse(req.body.toString()); // data.uuid, data.status, data.turn, data.error // Output: data.turn.output[0].text // Messages for multi-turn: data.turn.messages res.sendStatus(204); }); ``` ## Task Pruning Completed and failed tasks are automatically deleted after 30 days.