API reference
The MAIA Plus API is what makes the product reachable from outside: a page script posts a ticket, a ServiceDesk Plus trigger fires an automation, a workflow reads or writes. This page documents the contract of the endpoints external callers use, in the shape they use them.
Credentials
Section titled “Credentials”Four families exist and they are not interchangeable.
| Family | How it is presented | Lifetime | Used by |
|---|---|---|---|
| WebUI session | maia_webui_token cookie from the sign-in page | 12 hours | The browser UI |
| MAIA token | Authorization: Bearer <token>, or MAIA_Token, or the maia_token cookie | 8 hours | Technician and requester Copilot, minted from an SDP session |
| Webhook API key | x-api-key: whk_… | Until revoked | Automations, flows, workflow webhooks, model predict/train |
| Ticket-search scope | The same session or token, plus a resolved portal scope | — | /api/v1/ticket-search/search |
A webhook key is bound to one target. A key minted for assign_technician is rejected on generate_solution_for_closed_request; that binding, not secrecy, is the main defence against a leaked key being reused elsewhere. A webhook key must also be sent as a header — never in a URL.
The error envelope
Section titled “The error envelope”The /api/v1 family answers a failure with one envelope:
{ "error": { "code": "WEBHOOK_API_KEY_INVALID", "message": "Webhook API key is invalid or revoked" } }code is stable and safe to branch on; message is translated and must not be parsed.
Status codes
Section titled “Status codes”| Status | Codes you will actually see | Meaning |
|---|---|---|
| 400 | PORTAL_CONTEXT_REQUIRED, INVALID_TICKET_ID, ACTION_ID_REQUIRED | Arrived and rejected — a required field is missing or malformed |
| 401 | WEBHOOK_API_KEY_REQUIRED, WEBHOOK_API_KEY_INVALID, WEBHOOK_TARGET_FORBIDDEN, WEBHOOK_POLICY_NOT_ALLOWED, MAIA_TOKEN_REQUIRED, MAIA_TOKEN_INVALID, MAIA_SESSION_REVOKED | Credential absent, revoked, expired, or bound to another target |
| 403 | TECHNICIAN_ROLE_REQUIRED, REQUESTER_ROLE_REQUIRED, FORBIDDEN | Authenticated; this identity may not use this surface or record |
| 404 | NOT_FOUND, CONVERSATION_NOT_FOUND, RUN_NOT_FOUND | Unknown route, or a resource outside the caller’s scope |
| 409 | RUN_ALREADY_ACTIVE, WEBHOOK_DELIVERY_KEY_MISMATCH, WEBHOOK_DELIVERY_PAYLOAD_MISMATCH, ACTION_ALREADY_DECIDED | A conflicting state exists — not a retryable condition |
| 422 | A validation code from the payload validator | Body parsed but does not satisfy the target’s schema |
| 429 | — | A rate-limited mount; back off instead of retrying immediately |
| 503 | SDP_SCOPE_UNAVAILABLE | The portal scope could not be resolved — a configuration problem |
Two shapes, not one
Section titled “Two shapes, not one”Two exceptions are worth knowing, because they break a strict parser:
- The legacy flow handlers (for example
/api/v1/rewriteMessageFromTechnician) return{ "error": "<string>" }rather than a code object. - A few internal admin handlers return
{ "error": "Internal Server Error" }on an unhandled failure.
Treat the envelope as the contract for the routes documented on this page, and tolerate a string error from legacy paths.
Asynchronous calls
Section titled “Asynchronous calls”An automation call validates the request, enqueues the run and answers 202 before the work is done:
{ "runId": "webhook-run-3f0c…", "status": "accepted" }The runId is the handle for everything afterwards: use it in Enterprise Audit to read the tool calls, the business result and any approval. A 202 proves acceptance, not effect — confirm the change in ServiceDesk Plus or in the audit views.
Idempotency is a delivery guarantee, not a tool guarantee
Section titled “Idempotency is a delivery guarantee, not a tool guarantee”Send an Idempotency-Key header and the same key plus the same payload is delivered once:
| Situation | Response |
|---|---|
| First delivery | 202 with runId |
| Same key, same payload, run still going | 202 with the original runId |
| Same key, same payload, run finished | 200 with the stored result |
| Same key, different payload | 409 WEBHOOK_DELIVERY_PAYLOAD_MISMATCH |
| Same key, different key id | 409 WEBHOOK_DELIVERY_KEY_MISMATCH |
Without that header, every delivery becomes a new run — which is why a ServiceDesk Plus trigger that retries a webhook can write twice. Deduplication protects the delivery; it does not deduplicate the side effect of the tool the run calls.
Endpoint families
Section titled “Endpoint families”| Family | Base path | Auth | Purpose |
|---|---|---|---|
| Agent action | POST /api/v1/agent-actions/execute | Webhook key, target agent_action | Run a registered agent action against a ticket event |
| Canonical flows | POST /api/v1/flows/<flow-name> | Webhook key, one target per flow | Run one fixed flow |
| Workflow webhooks | POST /api/v1/workflows/<workflowKey>/webhook[/<bindingPath>] | Webhook key, target workflow_execute | Start a workflow from an external event |
| Workflow invocation | POST /api/v1/workflows/<workflowKey>/invoke | Session or token, audience-checked | Start a workflow for a person |
| Assignment | POST /api/v1/assignTechnician, /assignGroup, …/noteOnly | Webhook key per target | Apply an assignment-model prediction |
| Models | POST /api/v1/models/<modelId>/predict, /train | Webhook key per target | Serve or retrain a model |
| Technician Copilot | /api/v1/sdp-copilot/* | MAIA token, Technician | Conversational assistance from a ticket |
| Requester Copilot | /api/v1/requester-copilot/* | MAIA token, Requester | Conversational assistance in the requester portal |
| Knowledge | /api/v1/knowledge/*, /api/v1/addToRAG | Session; admin for writes | Search, add and maintain documents |
| Ticket search | /api/v1/ticket-search/search | Session or token, plus scope | Hybrid search over indexed tickets |
| Workflows (admin) | /api/v1/workflows/* | Session, administrator | Definitions, revisions, bindings, executions |
| Admin | /api/v1/admin/*, /api/v1/agents, /api/v1/mcp-servers | Session, administrator | Configuration |
Legacy routes that are still mounted
Section titled “Legacy routes that are still mounted”These paths still work and are still called by page scripts. They predate the canonical /flows/* family and keep generic authentication; each accepts only a key bound to its own target.
| Path | Target |
|---|---|
/parseClosedRequest | parse_closed_request |
/generateCategoryForRequest | generate_category_for_request |
/generateSolutionForClosedRequest | generate_solution_for_closed_request |
/generateMessagesForRequest[/techNote] | generate_messages_for_request |
/generateMessagesForRequest[/deeperResponse…] | four deeper-variant targets |
/assignTechnician[/noteOnly], /assignGroup[/noteOnly] | four assignment targets |
/rewriteMessageFromTechnician, /addToRAG, /context-search/custom | Generic auth only — no webhook target |
New integrations should use the canonical route. The legacy ones remain because retiring a route before its callers migrate would break working installations.
Webhook targets
Section titled “Webhook targets”Every webhook key names exactly one of these targets. The list is the authority for what a key can do; a target not listed here cannot be authorised by a key.
| Target | Route template | Kind |
|---|---|---|
agent_action | /agent-actions/execute | agent |
simple_prompt | /flows/simple-prompt | flow |
ticket_summary_for_overseer | /flows/ticket-summary-for-overseer | flow |
flow_ticket_summary_for_technician | /flows/ticket-summary-for-technician | flow |
flow_auto_reply_to_requester | /flows/auto-reply-to-requester | flow |
flow_category_prediction | /flows/category-prediction | flow |
flow_kb_solution_from_closed_ticket | /flows/kb-solution-from-closed-ticket | flow |
flow_deeper_response | /flows/deeper-response | flow |
flow_deeper_technician_note | /flows/deeper-technician-note | flow |
flow_deeper_requester_response | /flows/deeper-requester-response | flow |
flow_deeper_requester_note | /flows/deeper-requester-note | flow |
workflow_execute | /workflows/:workflowKey/webhook[/:bindingPath] | workflow |
model_predict | /models/:modelId/predict | model |
model_train | /models/:modelId/train | model |
assign_technician, assign_technician_note_only | /assignTechnician[/noteOnly] | assignment |
assign_group, assign_group_note_only | /assignGroup[/noteOnly] | assignment |
parse_closed_request, generate_category_for_request, generate_solution_for_closed_request, generate_messages_for_request and its deeper variants | see the legacy table | legacy flow |
Binding a key to a model or workflow
Section titled “Binding a key to a model or workflow”A key can also carry a model or workflow binding: model_predict compares policy.modelId with the modelId in the path, workflow_execute compares policy.workflowKey with the one in the path, and both answer 401 on a mismatch.
Example: agent action
Section titled “Example: agent action”POST /api/v1/agent-actions/executex-api-key: whk_…Content-Type: application/jsonIdempotency-Key: sdp-48213-run1
{ "id": "48213", "portalID": "1" }{ "runId": "webhook-run-3f0c…", "status": "accepted" }The body may be MAIA’s agent envelope or ServiceDesk Plus’s native ticket envelope — the handler validates the agent shape first and falls back to the native one, so a trigger can send the ticket object unchanged.
Example: workflow webhook
Section titled “Example: workflow webhook”POST /api/v1/workflows/diagnose_vpn/webhook/createdx-api-key: whk_… (target workflow_execute, bound to workflow diagnose_vpn)Content-Type: application/jsonThe URL selects the workflow and the binding; the body never chooses the entry node.
Assignment
Section titled “Assignment”POST /api/v1/assignTechnicianx-api-key: whk_… (target assign_technician)Content-Type: application/json
{ "id": "8000000123", "portalID": "1", "modelId": "technician_assignment" }| Field | Required | Notes |
|---|---|---|
id | yes | ServiceDesk Plus request id |
portalID | yes | Must be registered and inside the caller’s scope |
modelId | no | Defaults to the technician assignment model; the group endpoints use the group model |
The portal is resolved before any background write. An unregistered or out-of-scope portal returns 400 and never reaches the model.
Copilot surfaces
Section titled “Copilot surfaces”Routes
Section titled “Routes”| Route | Purpose |
|---|---|
GET /api/v1/sdp-copilot/conversations | List the technician’s conversations |
POST /api/v1/sdp-copilot/conversations | Start one |
POST /api/v1/sdp-copilot/conversations/:id/messages | Send a turn |
GET /api/v1/sdp-copilot/runs/:runId/progress | Poll progress of a run |
POST /api/v1/sdp-copilot/approve | Decide a paused write |
POST /api/v1/sdp-copilot/runs/:runId/cancel | Cancel |
GET /api/v1/sdp-copilot/tickets/:ticketId/outputs | Delivered outputs for a ticket |
PUT /api/v1/sdp-copilot/outputs/:outputId/feedback | Rate a delivered output |
What the two surfaces share
Section titled “What the two surfaces share”The requester counterparts sit under /api/v1/requester-copilot/* and accept only a Requester token: the portal and the ticket come from the signed token, never from the request body.
A WebUI session cookie is not accepted on either Copilot surface. The token must be minted by POST /api/v1/auth/login-via-sdp.
Knowledge
Section titled “Knowledge”| Route | Purpose |
|---|---|
GET /api/v1/knowledge/documents, /documents/:id | Read documents and their status |
GET /api/v1/knowledge/search | Text or semantic search |
GET /api/v1/knowledge/documents/:id/chunks | Inspect how a document was split |
POST /api/v1/knowledge/upload, /parse | Add a document (administrator) |
POST /api/v1/knowledge/documents/:id/reindex, /unindex | Rebuild or drop the vectors |
GET /api/v1/knowledge/approvals/pending, POST …/:queueId/decision | The approval queue |
POST /api/v1/addToRAG | Legacy ingestion path used by page scripts |
A document write returns the document id; indexing is asynchronous, so status may still read pending immediately after the response.
Ticket search
Section titled “Ticket search”GET /api/v1/ticket-search/search returns ranked tickets with a relevance score. When semantic scoring was unavailable the response names the reason — treat a present reason as a degraded answer, not an empty one.
GET /api/v1/ticket-search/status, /scopes report index health and the scope the caller is limited to.
Workflows
Section titled “Workflows”| Route | Auth | Purpose |
|---|---|---|
GET /api/v1/workflows | admin | List definitions with status and revision |
POST /api/v1/workflows | admin | Create |
PUT /api/v1/workflows/:id | admin | Save a new revision |
GET /api/v1/workflows/:id/revisions | admin | History, and POST …/:revision/restore |
POST /api/v1/workflows/:id/preflight | admin | Validate before activating |
POST /api/v1/workflows/:id/test, /:id/test-step | admin | Test run, or run to one node |
GET /api/v1/workflows/:id/executions, /:id/executions/:executionId | admin | Executions and their steps |
GET /api/v1/workflows/:workflowKey/invoke… | session or token, audience-checked | Start a workflow for a person |
POST/DELETE /api/v1/workflows/:id/webhook-bindings | admin | Manage bindings |
…/:bindingId/keys, /enable, /disable | admin | Mint or manage the key for a binding |
Revisions and runs
Section titled “Revisions and runs”Saving increments revision; every run records the revision it used, which is how an audit row is tied to the configuration that produced it.
Which page scripts call what
Section titled “Which page scripts call what”| Page script | Endpoint family |
|---|---|
| Technician chat | /api/v1/sdp-copilot/* |
| Requester chat | /api/v1/requester-copilot/* |
| AI actions | /api/v1/agent-actions/execute |
| Assignment buttons | /api/v1/assignTechnician, /api/v1/assignGroup |
| Solution generation on close | /api/v1/generateSolutionForClosedRequest |
| Silent sign-in | POST /api/v1/auth/login-via-sdp |
See Page scripts for where those scripts are installed.
Limits and failure modes
Section titled “Limits and failure modes”- Key bound to another target →
401 WEBHOOK_TARGET_FORBIDDEN, even when the key is valid elsewhere. - Event override → the event is bound to the key; sending
event-type,x-event-typeor aneventTypefield is rejected with401. - Portal outside scope →
400on assignment endpoints,403elsewhere. 202→ acceptance, not completion.- Retries without
Idempotency-Key→ duplicate runs, and possibly duplicate notes in the ticket. - Rate limiting →
429on the token-heavy mounts; back off rather than retrying immediately. - Stale run → a run that never finishes appears in audit as Waiting for input: it is blocked on an approval, not dead.
Related topics
Section titled “Related topics”Authentication · Errors and permissions · Page scripts · Custom triggers