Skip to Content
Polyant is open source under AGPL-3.0 — star us on GitHub.
ReferenceREST API

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

PrefixAudienceAuth
/api/...Admin panel and internal automationSession JWT (cookie or Bearer)
/v1/...OpenAI-compatible chat surfacePublic when authEnabled=false; per-instance API key otherwise
/memoriesLegacy admin endpoint (mounted at root)Session JWT
/webhooks/...External event ingestionPublic (token in path)
/healthProbe endpointPublic

All other routes default to authenticated; only the ones explicitly decorated with @Public() in the source skip auth. The current @Public() set is:

  • GET /health
  • POST /v1/chat/completions (per-instance API key enforced at the controller when authEnabled=true)
  • POST /webhooks/:webhookToken and POST /webhooks/twilio/:instanceSlug/whatsapp (token / Twilio-signature gated)
  • POST /api/auth/credentials/verify (shared-secret gated via x-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:

  1. 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 by packages/web.
    • The authjs.session-token cookie 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.

  2. Per-instance API key (/v1/chat/completions only). Used when the instance has authEnabled = true. The key is stored as the auth_api_key row in instance_secrets and submitted via Authorization: Bearer sk-.... When authEnabled = false the endpoint is fully public for that instance.

  3. 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:

EndpointLimit
POST /v1/chat/completions20 req / 60 s / IP
POST /webhooks/:webhookToken60 req / 60 s / IP
POST /webhooks/twilio/:instanceSlug/whatsapp60 req / 60 s / IP
POST /api/auth/credentials/verify5 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).

FieldTypeNotes
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 fieldTypeRequiredNotes
emailstringyesLowercased and trimmed server-side.
namestringnoDisplay name.
rolestringnoDefaults to viewer. See Roles.
passwordstringnoOptional — 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 fieldTypeRequiredNotes
slugstringyes[a-z0-9]([a-z0-9_-]*[a-z0-9])?, ≤100.
namestringyesDisplay name.
descriptionstringnoFree text.
providerstringnoOne of the configured providers.
modelstringnoValidated 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.

MethodPathDescription
GET/List event sources for the instance.
POST/Create an event source (returns the webhook token once).
PUT/:idUpdate name / description / config (AES-256-GCM-encrypted).
DELETE/:idDelete the event source and all its definitions.
POST/:id/rotate-tokenIssue a new webhook token; the old one stops accepting events.
GET/:id/definitionsList event definitions (priority-ordered).
POST/:id/definitionsCreate an event definition.
PUT/:id/definitions/:defIdUpdate an event definition.
DELETE/:id/definitions/:defIdDelete 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 fieldTypeRequiredNotes
namestringyesDisplay name.
promptstringyesPrompt injected when the task fires.
scheduleTypeenumyescron or one-shot.
cronExpressionstringaltRequired when scheduleType=cron (standard 5-field).
runAtstringaltISO timestamp or relative (+20m, +1h).
deleteAfterRunbooleannoDefault false. Useful for one-shot reminders.
outboundChannelstringnoChannel 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.

MethodPathDescription
GET/List skills in the global catalog.
GET/catalog/exportExport the full catalog as a portable JSON archive (skills + versions).
POST/catalog/importImport a previously exported catalog. Skills are reconciled by slug.
GET/:nameFetch one skill by slug, including the current version’s content.
POST/Create a new skill (creates version 1).
PUT/:nameUpdate a skill — bumps the version automatically.
DELETE/:nameDelete a skill (cascades to versions, instance assignments, env vars).
GET/:name/exportExport a single skill as JSON (all versions).
GET/:name/versionsList versions for a skill.
GET/:name/versions/:versionFetch 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:

  • InstanceDTOpackages/engine/src/server/instances/instances.controller.ts
  • SkillDTOpackages/engine/src/skills/skills.service.ts
  • ToolInfopackages/engine/src/agents/tools/registry.ts
  • ConversationDTO, MessageDTOpackages/engine/src/server/conversations/conversations.controller.ts

The web admin panel consumes these types directly through path aliases (packages/web/src/lib/api.ts).

Last updated on