Skip to content

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.

Four families exist and they are not interchangeable.

FamilyHow it is presentedLifetimeUsed by
WebUI sessionmaia_webui_token cookie from the sign-in page12 hoursThe browser UI
MAIA tokenAuthorization: Bearer <token>, or MAIA_Token, or the maia_token cookie8 hoursTechnician and requester Copilot, minted from an SDP session
Webhook API keyx-api-key: whk_…Until revokedAutomations, flows, workflow webhooks, model predict/train
Ticket-search scopeThe 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 /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.

StatusCodes you will actually seeMeaning
400PORTAL_CONTEXT_REQUIRED, INVALID_TICKET_ID, ACTION_ID_REQUIREDArrived and rejected — a required field is missing or malformed
401WEBHOOK_API_KEY_REQUIRED, WEBHOOK_API_KEY_INVALID, WEBHOOK_TARGET_FORBIDDEN, WEBHOOK_POLICY_NOT_ALLOWED, MAIA_TOKEN_REQUIRED, MAIA_TOKEN_INVALID, MAIA_SESSION_REVOKEDCredential absent, revoked, expired, or bound to another target
403TECHNICIAN_ROLE_REQUIRED, REQUESTER_ROLE_REQUIRED, FORBIDDENAuthenticated; this identity may not use this surface or record
404NOT_FOUND, CONVERSATION_NOT_FOUND, RUN_NOT_FOUNDUnknown route, or a resource outside the caller’s scope
409RUN_ALREADY_ACTIVE, WEBHOOK_DELIVERY_KEY_MISMATCH, WEBHOOK_DELIVERY_PAYLOAD_MISMATCH, ACTION_ALREADY_DECIDEDA conflicting state exists — not a retryable condition
422A validation code from the payload validatorBody parsed but does not satisfy the target’s schema
429—A rate-limited mount; back off instead of retrying immediately
503SDP_SCOPE_UNAVAILABLEThe portal scope could not be resolved — a configuration problem

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.

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:

SituationResponse
First delivery202 with runId
Same key, same payload, run still going202 with the original runId
Same key, same payload, run finished200 with the stored result
Same key, different payload409 WEBHOOK_DELIVERY_PAYLOAD_MISMATCH
Same key, different key id409 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.

FamilyBase pathAuthPurpose
Agent actionPOST /api/v1/agent-actions/executeWebhook key, target agent_actionRun a registered agent action against a ticket event
Canonical flowsPOST /api/v1/flows/<flow-name>Webhook key, one target per flowRun one fixed flow
Workflow webhooksPOST /api/v1/workflows/<workflowKey>/webhook[/<bindingPath>]Webhook key, target workflow_executeStart a workflow from an external event
Workflow invocationPOST /api/v1/workflows/<workflowKey>/invokeSession or token, audience-checkedStart a workflow for a person
AssignmentPOST /api/v1/assignTechnician, /assignGroup, …/noteOnlyWebhook key per targetApply an assignment-model prediction
ModelsPOST /api/v1/models/<modelId>/predict, /trainWebhook key per targetServe or retrain a model
Technician Copilot/api/v1/sdp-copilot/*MAIA token, TechnicianConversational assistance from a ticket
Requester Copilot/api/v1/requester-copilot/*MAIA token, RequesterConversational assistance in the requester portal
Knowledge/api/v1/knowledge/*, /api/v1/addToRAGSession; admin for writesSearch, add and maintain documents
Ticket search/api/v1/ticket-search/searchSession or token, plus scopeHybrid search over indexed tickets
Workflows (admin)/api/v1/workflows/*Session, administratorDefinitions, revisions, bindings, executions
Admin/api/v1/admin/*, /api/v1/agents, /api/v1/mcp-serversSession, administratorConfiguration

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.

PathTarget
/parseClosedRequestparse_closed_request
/generateCategoryForRequestgenerate_category_for_request
/generateSolutionForClosedRequestgenerate_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/customGeneric 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.

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.

TargetRoute templateKind
agent_action/agent-actions/executeagent
simple_prompt/flows/simple-promptflow
ticket_summary_for_overseer/flows/ticket-summary-for-overseerflow
flow_ticket_summary_for_technician/flows/ticket-summary-for-technicianflow
flow_auto_reply_to_requester/flows/auto-reply-to-requesterflow
flow_category_prediction/flows/category-predictionflow
flow_kb_solution_from_closed_ticket/flows/kb-solution-from-closed-ticketflow
flow_deeper_response/flows/deeper-responseflow
flow_deeper_technician_note/flows/deeper-technician-noteflow
flow_deeper_requester_response/flows/deeper-requester-responseflow
flow_deeper_requester_note/flows/deeper-requester-noteflow
workflow_execute/workflows/:workflowKey/webhook[/:bindingPath]workflow
model_predict/models/:modelId/predictmodel
model_train/models/:modelId/trainmodel
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 variantssee the legacy tablelegacy flow

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.

POST /api/v1/agent-actions/execute
x-api-key: whk_…
Content-Type: application/json
Idempotency-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.

POST /api/v1/workflows/diagnose_vpn/webhook/created
x-api-key: whk_… (target workflow_execute, bound to workflow diagnose_vpn)
Content-Type: application/json

The URL selects the workflow and the binding; the body never chooses the entry node.

POST /api/v1/assignTechnician
x-api-key: whk_… (target assign_technician)
Content-Type: application/json
{ "id": "8000000123", "portalID": "1", "modelId": "technician_assignment" }
FieldRequiredNotes
idyesServiceDesk Plus request id
portalIDyesMust be registered and inside the caller’s scope
modelIdnoDefaults 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.

RoutePurpose
GET /api/v1/sdp-copilot/conversationsList the technician’s conversations
POST /api/v1/sdp-copilot/conversationsStart one
POST /api/v1/sdp-copilot/conversations/:id/messagesSend a turn
GET /api/v1/sdp-copilot/runs/:runId/progressPoll progress of a run
POST /api/v1/sdp-copilot/approveDecide a paused write
POST /api/v1/sdp-copilot/runs/:runId/cancelCancel
GET /api/v1/sdp-copilot/tickets/:ticketId/outputsDelivered outputs for a ticket
PUT /api/v1/sdp-copilot/outputs/:outputId/feedbackRate a delivered output

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.

RoutePurpose
GET /api/v1/knowledge/documents, /documents/:idRead documents and their status
GET /api/v1/knowledge/searchText or semantic search
GET /api/v1/knowledge/documents/:id/chunksInspect how a document was split
POST /api/v1/knowledge/upload, /parseAdd a document (administrator)
POST /api/v1/knowledge/documents/:id/reindex, /unindexRebuild or drop the vectors
GET /api/v1/knowledge/approvals/pending, POST …/:queueId/decisionThe approval queue
POST /api/v1/addToRAGLegacy 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.

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.

RouteAuthPurpose
GET /api/v1/workflowsadminList definitions with status and revision
POST /api/v1/workflowsadminCreate
PUT /api/v1/workflows/:idadminSave a new revision
GET /api/v1/workflows/:id/revisionsadminHistory, and POST …/:revision/restore
POST /api/v1/workflows/:id/preflightadminValidate before activating
POST /api/v1/workflows/:id/test, /:id/test-stepadminTest run, or run to one node
GET /api/v1/workflows/:id/executions, /:id/executions/:executionIdadminExecutions and their steps
GET /api/v1/workflows/:workflowKey/invoke…session or token, audience-checkedStart a workflow for a person
POST/DELETE /api/v1/workflows/:id/webhook-bindingsadminManage bindings
…/:bindingId/keys, /enable, /disableadminMint or manage the key for a binding

Saving increments revision; every run records the revision it used, which is how an audit row is tied to the configuration that produced it.

Page scriptEndpoint 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-inPOST /api/v1/auth/login-via-sdp

See Page scripts for where those scripts are installed.

  • 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-type or an eventType field is rejected with 401.
  • Portal outside scope → 400 on assignment endpoints, 403 elsewhere.
  • 202 → acceptance, not completion.
  • Retries without Idempotency-Key → duplicate runs, and possibly duplicate notes in the ticket.
  • Rate limiting → 429 on 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.

Authentication · Errors and permissions · Page scripts · Custom triggers