HTTP reference
AI API
AI HTTP routes live in routes/ai.php: one token-authenticated webhook (no session) and an authenticated /ai/* group gated by can-use-ai.
Config: AI configuration. Product behaviour: AI assistant.
Webhook (token auth)
| Method | Path | Route name | Middleware |
|---|---|---|---|
POST | /ai/webhooks/collection-import | ai.webhooks.collection-import | throttle:30,1 |
CSRF is disabled for this path in bootstrap/app.php.
Authentication
Controller: CollectionImportWebhookController.
- Read configured secret:
config('ai.webhook_token')←AI_WEBHOOK_TOKEN. - Read provided token from either:
Authorization: Bearer {token}, or- header
X-AI-Webhook-Token
- Abort 403 if the configured token is empty or
hash_equalsfails.
Body (validated)
Expects a collection id, associative records array, and optional upsert_key. On success returns JSON with ok, collection_id, url, and import summary fields. Validation failures / import errors return 4xx JSON.
No user context
The webhook has no interactive user; AI mutation logging is skipped for this path. Protect the token like a production secret.
POST /ai/webhooks/collection-import HTTP/1.1
Host: app.example.com
Authorization: Bearer your-AI_WEBHOOK_TOKEN
Content-Type: application/json
Authenticated group
Middleware stack for all routes below:
auth + verified + permission:can-use-ai
Prefix: /ai, name prefix: ai..
Status and shell
| Method | Path | Route name | Notes |
|---|---|---|---|
GET | /ai | ai.index | Inertia AI page shell |
GET | /ai/status | ai.status | Provider / readiness JSON (AiStatusController) |
Conversations list / create / show / destroy / pin / truncate / bulk-destroy
| Method | Path | Route name | Purpose |
|---|---|---|---|
GET | /ai/conversations | ai.conversations.index | List conversations |
POST | /ai/conversations | ai.conversations.store | Create conversation |
POST | /ai/conversations/bulk-destroy | ai.conversations.bulk-destroy | Delete many |
GET | /ai/conversations/{conversation} | ai.conversations.show | Show conversation + messages |
DELETE | /ai/conversations/{conversation} | ai.conversations.destroy | Delete one |
POST | /ai/conversations/{conversation}/pin | ai.conversations.pin | Toggle pin |
POST | /ai/conversations/{conversation}/truncate | ai.conversations.truncate | Truncate message history |
Persistence tables: agent_conversations, agent_conversation_messages (AI configuration).
Controller: AiConversationController.
Attachments
| Method | Path | Route name | Throttle |
|---|---|---|---|
POST | /ai/attachments | ai.attachments.store | throttle:30,1 |
Uploads chat attachments (CSV/TXT/XLSX/PDF per assistant instructions). Expired files are cleaned by ai:cleanup-attachments.
Controller: AiChatAttachmentController.
Chat
| Method | Path | Route name | Throttle |
|---|---|---|---|
POST | /ai/chat | ai.chat | throttle:30,1 |
Invokes AppAssistant with tools filtered by the user’s effective permissions. Optional daily cap: AI_DAILY_PROMPT_LIMIT (0 = unlimited).
Controller: AiChatController.
Import job status
| Method | Path | Route name | Extra permission |
|---|---|---|---|
GET | /ai/import-jobs/{jobId} | ai.import-jobs.show | can-create-collections |
jobId must be a UUID. Used to poll async collection import jobs started from AI tools.
Controller: ImportJobStatusController.
Conversation deep link
Registered last so static /ai/* paths are not captured:
| Method | Path | Route name | Constraint |
|---|---|---|---|
GET | /ai/{conversation} | ai.show | {conversation} UUID |
Inertia page for opening a specific conversation (AiPageController@show).
Throttles summary
| Endpoint | Limit |
|---|---|
| Webhook import | 30 requests / minute |
| Attachments store | 30 / minute |
| Chat | 30 / minute |
Other authenticated AI routes rely on session auth + Fortify login throttles unless additional limiters are added.
Tool permissions (not HTTP middleware)
Even with can-use-ai, individual tools still call ChecksAiPermissions::requirePermission() for domain actions (files, collections, users, …). Users without matching effective permissions get English machine-error strings from tools (the model should translate them into the user’s language) instead of silent success. See Extending Externa.