REST API Reference
This page enumerates every HTTP route exposed by the Polyant engine (@polyant/engine). Routes are grouped by domain (one section per controller). Paths are listed with their HTTP verb, authentication requirement, expected body / query parameters, and the response shape. The engine is built on NestJS 11; controllers live under packages/engine/src/server/ and a few cross-cutting domains (users, skills, activity-stream) under packages/engine/src/.
Conventions
Path prefixes
| Prefix | Audience | Auth |
|---|---|---|
/api/... | Admin panel and internal automation | Session JWT (cookie or Bearer) |
/v1/... | OpenAI-compatible chat surface | Public when authEnabled=false; per-instance API key otherwise |
/memories | Legacy admin endpoint (mounted at root) | Session JWT |
/webhooks/... | External event ingestion | Public (token in path) |
/health | Probe endpoint | Public |
All other routes default to authenticated; only the ones explicitly decorated with @Public() in the source skip auth. The current @Public() set is:
GET /healthPOST /v1/chat/completions(per-instance API key enforced at the controller whenauthEnabled=true)POST /webhooks/:webhookTokenandPOST /webhooks/twilio/:instanceSlug/whatsapp(token / Twilio-signature gated)POST /api/auth/credentials/verify(shared-secret gated viax-internal-auth)
GET /v1/models is not @Public() — it requires session auth like the rest of the management API (or a per-instance API key via @AllowInstanceApiKey()). Calling it without credentials returns HTTP 401.
Authentication
Three independent mechanisms cohabit the engine:
-
Session JWT (admin routes). The NestJS global
AuthGuard(packages/engine/src/auth/auth.guard.ts) accepts either:Authorization: Bearer <token>— an Auth.js JWE produced bypackages/web.- The
authjs.session-tokencookie set by the same Auth.js session.
The guard decrypts the JWE locally with
AUTH_SECRET(see Environment Variables). No per-request DB lookup is performed. -
Per-instance API key (
/v1/chat/completionsonly). Used when the instance hasauthEnabled = true. The key is stored as theauth_api_keyrow ininstance_secretsand submitted viaAuthorization: Bearer sk-.... WhenauthEnabled = falsethe endpoint is fully public for that instance. -
Webhook token (
/webhooks/:webhookToken). A per-event-source opaque token issued at event-source creation. No other auth.
Response shape and errors
Successful responses are JSON envelopes specific to each endpoint — there is no global wrapping (no data / error discriminator). Errors are returned with a standard NestJS exception body:
{
"statusCode": 404,
"message": "Instance \"acme\" not found",
"error": "Not Found"
}The HTTP status code is the only structured error signal. There is no OpenAI-style {message, type, code} envelope. Stack traces and SQL queries are never leaked in production responses.
CORS
CORS is governed by the CORS_ORIGINS env variable (see Environment Variables). In NODE_ENV=production with CORS_ORIGINS unset, all cross-origin requests are rejected.
Rate limiting
NestJS @nestjs/throttler is wired globally; specific endpoints override the defaults:
| Endpoint | Limit |
|---|---|
POST /v1/chat/completions | 20 req / 60 s / IP |
POST /webhooks/:webhookToken | 60 req / 60 s / IP |
POST /webhooks/twilio/:instanceSlug/whatsapp | 60 req / 60 s / IP |
POST /api/auth/credentials/verify | 5 req / 60 s / IP |
GET /api/activity-stream/live (SSE) | Skipped (@SkipThrottle()) |
1. Authentication & users
Controller files: packages/engine/src/users/users.controller.ts, me.controller.ts, credentials.controller.ts.
GET /api/users
List all users. Requires the caller to have user-management permissions (Superadmin / Admin).
| Field | Type | Notes |
|---|---|---|
users[] | array | { id, email, name, role, ... } |
GET /api/users/:id
Fetch a single user. Returns 404 if not found.
POST /api/users
Create a user.
| Body field | Type | Required | Notes |
|---|---|---|---|
email | string | yes | Lowercased and trimmed server-side. |
name | string | no | Display name. |
role | string | no | Defaults to viewer. See Roles. |
password | string | no | Optional — only used for credentials login. |
PATCH /api/users/:id
Partial update of name / email / role. Cannot demote the last Superadmin.
DELETE /api/users/:id
Delete a user. Idempotent — returns 404 if the user does not exist.
POST /api/users/:id/reset-password
Issue a new password for the given user. Returns the plain-text password once; never stored in clear.
POST /api/me/password
Self-service password change for the authenticated user. Body: { currentPassword, newPassword }.
POST /api/auth/credentials/verify (public, throttled)
Used internally by packages/web (Auth.js Credentials provider) to verify an email/password pair against the engine. Returns the user record (without the hashed password) on success, 401 otherwise. Protected by a 5 req/min/IP throttle and a shared secret carried in the x-internal-auth header (whose value must match the AUTH_INTERNAL_SECRET env var on both web and engine).
2. Instances
Controller: packages/engine/src/server/instances/instances.controller.ts.
GET /api/instances
List all instances. Response: { instances: InstanceDTO[] }.
GET /api/instances/models
List available AI providers and models with their tier mapping. Response shape:
{
"providers": {
"openai": {
"models": [
{ "id": "gpt-4.1-mini", "tier": "fast", "costInput": 0.4, "costOutput": 1.6, "supportsThinking": false }
]
},
"anthropic": { "models": [ ... ] }
}
}Costs are USD / 1M tokens. supportsThinking is computed from the gateway config — drives the toggle visibility in the admin UI.
GET /api/instances/:slug
Fetch a single instance by slug. 404 if not found.
GET /api/instances/:slug/icon
Serve the icon binary. The icon is stored as a data URI in the DB; this endpoint decodes it and serves the binary with Cache-Control: private, max-age=3600. Returns 404 if no icon is set or the data URI is malformed.
POST /api/instances
Create an instance. Triggers seeding of prompts / tools / skills from defaults.
| Body field | Type | Required | Notes |
|---|---|---|---|
slug | string | yes | [a-z0-9]([a-z0-9_-]*[a-z0-9])?, ≤100. |
name | string | yes | Display name. |
description | string | no | Free text. |
provider | string | no | One of the configured providers. |
model | string | no | Validated against the provider’s catalog. |
409 Conflict if the slug already exists (DB unique constraint).
PATCH /api/instances/:slug
Partial update. Recognised fields: name, description, status, provider, model, memoryEnabled, knowledgeEnabled, langsmithEnabled, langsmithProject, authEnabled, thinkingEnabled, sttProvider. Invalidates the per-instance config resolver cache on success.
DELETE /api/instances/:slug
Delete the instance. ON DELETE CASCADE removes prompts, tools, skills, secrets, channels, conversations, memories, and room state.
PUT /api/instances/:slug/icon
Set the icon. Body: { icon: "data:image/png;base64,..." }. Validates the data URI before persistence.
DELETE /api/instances/:slug/icon
Clear the icon.
3. Instance prompts
Controller: packages/engine/src/server/instances/instance-prompts.controller.ts.
GET /api/instances/:slug/prompts
Returns the eight prompt sections for the instance.
{ "prompts": { "00-identity": "...", "01-...": "...", ... } }PATCH /api/instances/:slug/prompts
Partial update. Body is a map from section id → markdown content. Unknown section ids are rejected with 400.
4. Instance skills
Two controllers handle skill management:
packages/engine/src/skills/instance-skills.controller.ts— bulk toggle and per-skill enable/disable.packages/engine/src/server/instances/instance-skills.controller.ts— upgrade / rollback / env vars.
GET /api/instances/:slug/skills
List all skills with their per-instance state: { skills: [{ slug, name, enabled, pinnedVersion, autoLoad, ... }] }.
POST /api/instances/:slug/skills/:name
Enable a skill on the instance. Body: { pinnedVersion?: string } to lock to a specific version.
DELETE /api/instances/:slug/skills/:name
Disable a skill on the instance.
PATCH /api/instances/:slug/skills
Bulk toggle. Body: { enabled: string[] } — list of slugs to enable; everything else is disabled.
POST /api/instances/:slug/skills/:skillSlug/upgrade
Move the instance to the latest version of the skill. Removes any pin.
POST /api/instances/:slug/skills/:skillSlug/auto-load
Toggle auto-load. Body: { autoLoad: boolean }. Auto-loaded skills are injected into the supervisor system prompt without an explicit user request.
POST /api/instances/:slug/skills/:skillSlug/rollback
Pin the instance to the previous version. 400 if no prior version exists.
GET /api/instances/:slug/skills/:skillSlug/env
List the per-skill env vars set for this instance. Sensitive values are masked.
PUT /api/instances/:slug/skills/:skillSlug/env
Set env vars for the skill. Body: { env: { KEY: "value" } }. Stored encrypted (AES-256-GCM).
DELETE /api/instances/:slug/skills/:skillSlug/env/:key
Remove a single env var.
5. Instance tools
Controller: packages/engine/src/server/instances/instance-tools.controller.ts.
GET /api/instances/:slug/tools
List all registered tools with their per-instance enabled flag.
PATCH /api/instances/:slug/tools
Bulk update. Body: { enabled: string[] } — list of tool names to enable. Tools not in the list become disabled.
GET /api/instances/:slug/tools/required-secrets
Compute the union of requiredSecrets across all enabled tools — useful to display the operator’s “missing configuration” warning in the admin UI. The response contains only the requiredSecrets array (de-duplicated by key, sorted); the admin UI compares it against GET /api/instances/:slug/secrets to compute what is missing.
{
"requiredSecrets": [
{ "key": "hubspot_api_key", "type": "text", "label": "HubSpot API key" },
{ "key": "search_provider", "type": "select", "choices": ["tavily", "serpapi", "duckduckgo"], "currentValue": "tavily" }
]
}For non-sensitive select specs, the current value is surfaced as currentValue (so the UI can preselect it). Sensitive text specs (API keys) are never echoed back.
6. Instance secrets
Controller: packages/engine/src/server/instances/instance-secrets.controller.ts.
Secrets are encrypted with AES-256-GCM at rest (ENCRYPTION_KEY).
GET /api/instances/:slug/secrets
List the keys (not the values) currently set for the instance.
PUT /api/instances/:slug/secrets
Upsert one or more secrets. Body: { secrets: { key: value } }. Empty string clears the key.
DELETE /api/instances/:slug/secrets/:key
Remove a single secret. Idempotent.
7. Instance channels
Controller: packages/engine/src/server/instances/instance-channels.controller.ts.
GET /api/instances/:slug/channels
List configured channels (Telegram / Slack / WhatsApp) with their status. Credentials are masked.
PUT /api/instances/:slug/channels/:type
Configure a channel. :type is one of telegram | slack | whatsapp. Body is channel-specific:
- Telegram:
{ botToken, allowedChatIds?: string[] } - Slack:
{ botToken, appToken, signingSecret? } - WhatsApp:
{ accountSid, authToken, fromNumber, twilioMessagingServiceSid? }
The channel adapter is started in the background after a successful save (fire-and-forget — errors are logged, not propagated).
DELETE /api/instances/:slug/channels/:type
Stop and remove the channel configuration.
8. Instance knowledge
Controller: packages/engine/src/server/instances/instance-knowledge.controller.ts. Knowledge documents are stored in the DB (instance_knowledge) and embedded for retrieval.
GET /api/instances/:slug/knowledge
List all knowledge docs for the instance. Query params: ?q=<text> for FTS, ?limit=<n>, ?offset=<n>.
GET /api/instances/:slug/knowledge/:docId
Fetch a single document with its full body.
POST /api/instances/:slug/knowledge
Create or update a document. Body: { filename: string, content: string, title?: string }. Re-embedding is triggered after persistence.
DELETE /api/instances/:slug/knowledge/:docId
Delete a single document.
9. Instance analytics
Controller: packages/engine/src/server/analytics/analytics.controller.ts.
GET /api/analytics
Aggregate metrics across all instances. Query: ?from=<iso>&to=<iso>&groupBy=<day|hour>.
GET /api/instances/:slug/analytics
Per-instance KPI snapshot: message counts, token usage, average latency, tool calls, channel distribution. Same query params as the global endpoint.
10. Room, event sources, backlog, activity log
The Room is the proactive (event-driven) workspace per instance. Three controllers cover it.
Room config — packages/engine/src/server/room/room.controller.ts
GET /api/instances/:slug/room
When no room is configured for the instance: { "configured": false }. When configured: { "configured": true, id, instanceId, prompt, outboundChannel, ..., pendingEventCount } — all room columns flattened at the top level plus the live pending-event count for the backlog badge.
PUT /api/instances/:slug/room
Create or update the room. Body: { prompt, outboundChannel: "telegram" | "slack" | "whatsapp" }.
DELETE /api/instances/:slug/room
Disable the room and clear configuration.
Event sources — packages/engine/src/server/webhooks/webhook-sources.controller.ts
Mounted at /api/instances/:slug/event-sources.
| Method | Path | Description |
|---|---|---|
| GET | / | List event sources for the instance. |
| POST | / | Create an event source (returns the webhook token once). |
| PUT | /:id | Update name / description / config (AES-256-GCM-encrypted). |
| DELETE | /:id | Delete the event source and all its definitions. |
| POST | /:id/rotate-token | Issue a new webhook token; the old one stops accepting events. |
| GET | /:id/definitions | List event definitions (priority-ordered). |
| POST | /:id/definitions | Create an event definition. |
| PUT | /:id/definitions/:defId | Update an event definition. |
| DELETE | /:id/definitions/:defId | Delete an event definition. |
Backlog & activity — packages/engine/src/server/webhooks/webhook-backlog.controller.ts
Mounted at /api/instances/:slug/room.
GET /api/instances/:slug/room/backlog
Query the pending/processing/completed event queue. Query: ?status=pending|processing|completed&limit=<n>.
GET /api/instances/:slug/room/activity
Auto-compacted activity log (7d daily → weekly → monthly). Query: ?from=<iso>&to=<iso>.
11. Scheduled tasks
Controller: packages/engine/src/server/instances/instance-scheduled-tasks.controller.ts. Seven routes for per-instance cron / one-shot tasks.
GET /api/instances/:slug/scheduled-tasks
List tasks. Optional ?status=active|paused|completed.
GET /api/instances/:slug/scheduled-tasks/runs
List recent task runs across all tasks. Useful for the admin “task history” panel.
GET /api/instances/:slug/scheduled-tasks/:id
Fetch a single task including the last N runs.
POST /api/instances/:slug/scheduled-tasks
Create a task.
| Body field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Display name. |
prompt | string | yes | Prompt injected when the task fires. |
scheduleType | enum | yes | cron or one-shot. |
cronExpression | string | alt | Required when scheduleType=cron (standard 5-field). |
runAt | string | alt | ISO timestamp or relative (+20m, +1h). |
deleteAfterRun | boolean | no | Default false. Useful for one-shot reminders. |
outboundChannel | string | no | Channel for proactive messages emitted by the task. |
PATCH /api/instances/:slug/scheduled-tasks/:id
Partial update. Same fields as POST plus status (active|paused|completed).
DELETE /api/instances/:slug/scheduled-tasks/:id
Delete a task and its run history.
POST /api/instances/:slug/scheduled-tasks/:id/run
Trigger an out-of-schedule run. Useful for testing.
12. Instance export / import
Controller: packages/engine/src/server/instances/instance-export.controller.ts.
GET /api/instances/:slug/export
Download a portable JSON snapshot of the instance (prompts, skills, tool enablement, room, scheduled tasks). Secrets and channel credentials are not exported. Sets Content-Disposition: attachment.
POST /api/instances/import
Create a new instance from an exported snapshot. Body: the snapshot JSON. Optional ?slug=<new-slug> to rename.
POST /api/instances/:slug/import
Apply a snapshot to an existing instance. Replaces prompts and tool enablement; skills are reconciled by slug.
13. Conversations
Controller: packages/engine/src/server/conversations/conversations.controller.ts. Mounted at /api/conversations.
GET /api/conversations
List recent conversations. Query: ?instanceId=<id>&search=<fts>&source=<channel>&limit=<n>&offset=<n>. instanceId accepts the instance UUID — not the slug — and is required by every other endpoint on this controller for IDOR scoping.
GET /api/conversations/:conversationId
Fetch a single conversation metadata row.
GET /api/conversations/:conversationId/messages
List messages with pagination. Query: ?instanceId=<id>&limit=<n>&offset=<n>&order=asc|desc (default asc). Response: { messages, total, limit, offset, order } where each message is augmented with promptTokens / completionTokens pulled from the ai-logs join.
DELETE /api/conversations/:conversationId
Delete a conversation and all its messages (and attachments).
14. Skills catalog (global library)
Controller: packages/engine/src/skills/skills.controller.ts. Mounted at /api/skills.
| Method | Path | Description |
|---|---|---|
| GET | / | List skills in the global catalog. |
| GET | /catalog/export | Export the full catalog as a portable JSON archive (skills + versions). |
| POST | /catalog/import | Import a previously exported catalog. Skills are reconciled by slug. |
| GET | /:name | Fetch one skill by slug, including the current version’s content. |
| POST | / | Create a new skill (creates version 1). |
| PUT | /:name | Update a skill — bumps the version automatically. |
| DELETE | /:name | Delete a skill (cascades to versions, instance assignments, env vars). |
| GET | /:name/export | Export a single skill as JSON (all versions). |
| GET | /:name/versions | List versions for a skill. |
| GET | /:name/versions/:version | Fetch a specific version’s body and metadata. |
POST /api/skills body shape:
{
"name": "issue-workflow",
"description": "Standard issue triage flow",
"category": "operations",
"content": "...markdown body...",
"requiredEnv": [{ "name": "GITHUB_TOKEN", "sensitive": true }],
"requiredTools": ["ghIssue", "ghPR"],
"scripts": [{ "file": "extract.py", "description": "Extract metadata", "content": "..." }]
}See Skill Frontmatter for the exact field reference.
15. Tool catalog
Controller: packages/engine/src/server/tools/tools.controller.ts. Mounted at /api/tools.
GET /api/tools
Returns the registry snapshot — the ToolInfo shape from packages/engine/src/agents/tools/registry.ts:
{
"tools": [
{
"name": "webSearch",
"description": "Search the web for current information",
"category": "research",
"requiredSecrets": [{ "key": "search_provider", "type": "select", "choices": ["tavily", "serpapi", "duckduckgo"] }]
}
]
}Each entry has name, description, and category; requiredSecrets and inputExamples are included only when the tool declares them. The endpoint filters out harness tools (harness: true), so the harness and metaTool flags are never present in the response. Read-only — tools are self-registered at engine boot from packages/engine/src/agents/tools/*.tool.ts. See Tool Catalog for the full list.
16. Memories
Controller: packages/engine/src/server/memories/memories.controller.ts. Mounted at /memories (not under /api).
GET /memories
List memories with pagination and optional FTS. Query: ?instanceId=<id>&search=<text>&category=<cat>&limit=<n>&offset=<n>. instanceId is required (HTTP 400 otherwise) and refers to the instance UUID — not the slug.
POST /memories
Create a memory. Body: { instanceId, content, category?, importance? }. instanceId is the instance UUID. The content is embedded and deduplicated against existing memories (cosine threshold DEDUP_SIMILARITY_THRESHOLD).
DELETE /memories/:id
Delete a single memory. Query: ?instanceId=<id> (required for IDOR scoping).
DELETE /memories
Delete every memory for an instance. Query: ?instanceId=<id> (required, UUID). There is no body — this is an all-or-nothing wipe; per-id selection is done via DELETE /memories/:id instead.
17. Audit logs
Controller: packages/engine/src/server/audit/audit.controller.ts. Mounted at /api/audit-logs.
GET /api/audit-logs
Paginated list. Query params: ?instanceId=<id>&toolName=<name>&action=<prefix>&search=<text>&from=<iso>&to=<iso>&limit=<n>&offset=<n>. instanceId is optional — audit logs are a cross-instance view by design (see the controller note); omit it for the admin “all agents” view. Offset-based pagination; response shape: { items, limit, offset, total }.
GET /api/audit-logs/stats
Aggregate counts grouped by action prefix (e.g. tool.*, prompt.*, auth.*). Same time-window filters as the list endpoint.
18. Analytics (global)
See section 9 above — the same controller serves both global (/api/analytics) and per-instance (/api/instances/:slug/analytics) routes.
19. Activity stream (SSE)
Controller: packages/engine/src/activity-stream/activity-stream.controller.ts. Mounted at /api/activity-stream.
GET /api/activity-stream/live
Server-Sent Events stream of pipeline events (message-received, tool-call, agent-handoff, response-sent). Bypasses throttling via @SkipThrottle(). Query: ?instanceSlug=<slug> to filter by instance.
Each event is encoded as data: <json>\n\n with a type discriminator. The connection stays open until the client disconnects.
20. Attachments
Controller: packages/engine/src/server/attachments/attachments.controller.ts. Mounted at /api/attachments.
GET /api/attachments/*key
Proxy a stored attachment by its key. The wildcard parameter (*key) preserves slashes — keys are typed like <conversationId>/<filename>. Returns the binary with the original Content-Type. 404 if not found, 403 if the caller lacks access to the underlying conversation.
21. Health
Controller: packages/engine/src/server/health/health.controller.ts. Public.
GET /health
Liveness probe. Returns { status: "ok", timestamp: "<iso-8601>", service: "polyant" }. Used by Render / Docker / k8s readiness checks.
22. OpenAI-compatible API
Controller: packages/engine/src/server/openai/openai.controller.ts. Mounted at /v1. POST /v1/chat/completions is @Public() (auth is enforced per-instance when authEnabled=true). GET /v1/models is not public — it requires session auth or a per-instance API key (@AllowInstanceApiKey()).
GET /v1/models
OpenAI-compatible model listing. Each instance is exposed as a model row:
{
"object": "list",
"data": [
{ "id": "acme", "object": "model", "owned_by": "polyant", "created": 1700000000 }
]
}POST /v1/chat/completions
Throttled at 20 req/min/IP. Accepts the standard OpenAI request body:
{
"model": "acme",
"messages": [{ "role": "user", "content": "Hello" }],
"stream": false,
"temperature": 0.7
}The model field selects the instance by slug. When the instance has authEnabled=true, the request must include Authorization: Bearer <auth_api_key> matching the instance’s auth_api_key secret. Streaming responses use the OpenAI SSE format (data: {...}\n\n, terminated by data: [DONE]\n\n).
23. Webhooks
Two controllers handle external ingestion.
POST /webhooks/:webhookToken — generic event ingestion
Controller: packages/engine/src/server/webhooks/webhook.controller.ts. Public, throttled at 60 req/min/IP.
Body: arbitrary JSON, up to 64 KB. The token resolves to an event source; the engine matches the payload against the source’s event definitions (LLM-based, tier fast) and enqueues a backlog row. The endpoint always returns 200 OK, even on payload validation failure or matcher errors — processing is fire-and-forget. Events are dropped (not queued) when the per-instance backlog reaches 100 pending rows.
POST /webhooks/twilio/:instanceSlug/whatsapp — Twilio WhatsApp inbound
Controller: packages/engine/src/server/channels/twilio-webhook.controller.ts. Public, throttled at 60 req/min/IP.
Twilio webhook delivery for inbound WhatsApp messages. Twilio signs requests with the auth-token-derived X-Twilio-Signature header; the controller verifies the signature before dispatching to the message coordinator. Returns an empty <Response/> TwiML envelope.
DTO references
All DTOs are TypeScript types exported by the engine. The canonical definitions live next to their controllers:
InstanceDTO—packages/engine/src/server/instances/instances.controller.tsSkillDTO—packages/engine/src/skills/skills.service.tsToolInfo—packages/engine/src/agents/tools/registry.tsConversationDTO,MessageDTO—packages/engine/src/server/conversations/conversations.controller.ts
The web admin panel consumes these types directly through path aliases (packages/web/src/lib/api.ts).