REST API Reference Generated by AI
Alongside the MCP server, MockMotor exposes the same environment/service/reaction/account management as a plain REST API under /console/api/ — JSON over HTTP, no JSON-RPC envelope required, for scripts, CI pipelines, or any HTTP client that doesn't speak MCP. Every endpoint here calls the exact same server-side logic as its MCP tool counterpart (the same *Handler classes), so the two stay behaviourally identical — only the transport differs.
This page pulls its field-level documentation live from the MCP server's own tools/list response (see MCP Tools Reference), so parameter and result descriptions always match what the server actually validates — only the HTTP method/path framing below is REST-specific.
- Auth: read-only endpoints (
GET) work without authentication. Endpoints that change something require an API key, sent as either theAuthorization: Bearer <key>orX-MockMotor-API-Key: <key>header — find your key on your account page. GET/DELETEparameters go in the query string, e.g.?envid=XXX&varid=YYY.POSTparameters go in a JSON request body (Content-Type: application/json) — one flat JSON object with the fields listed under "Body" below, same field names as the equivalent MCP tool's arguments.- Create vs. update share one endpoint for several resources (environment, service, reaction, property, account):
POSTwithout the resource's id field creates a new one;POSTwith it updates the existing one. Both variants are documented below where this applies. - Errors always come back as HTTP
200with a{"error": "message"}body (matching the rest of the MockMotor console) — check for anerrorkey rather than the HTTP status code.
Lessons learned
Field notes from real agent sessions against this server — things that aren't obvious from the schema alone. These apply the same whether you're calling the REST endpoints below or the equivalent MCP tools, since both go through the same server-side logic.
"Authentication required. Pass the X-MockMotor-API-Key header."means the key itself is missing/invalid — a header-format problem is not the likely cause if you already sentX-MockMotor-API-Key:orAuthorization: Bearer; the key value itself is wrong or stale."Access denied: you are not an owner of this environment."means the key is valid and authenticates correctly, but that user isn't in the environment's owner list. CheckGET /console/api/owners.- Read-only endpoints (
GET) succeed even fully unauthenticated, so a successful read does not confirm a key works — only a write call (e.g.POST /console/api/reaction) proves authentication. - User accounts can be recreated with a new internal ID over time, silently dropping them from an environment's owner list even though the username is unchanged. If a previously-working key suddenly gets "Access denied," re-check the owners list before assuming the key is wrong.
For SOAP/XML payloads, use XQuery instead: $input//*:fieldName (wildcard-namespace match) reliably extracts fields regardless of the request's namespace prefixes, e.g. $input//*:ban[starts-with(.,'6')].
$accounts variable is a single wrapper element, not a sequenceIn a payload/response script it has the shape <accounts><account>...</account><account>...</account></accounts>. Iterate with for $a in $accounts/account, not for $a in $accounts (the latter silently loops exactly once, over the wrapper). Each <account> exposes its properties as direct child elements ($a/BAN, $a/SUB, ...) plus an <ID> element. This differs from the singular $account bound per-candidate inside accountsSelectorScript during selection.
maxAccounts defaults to 1If a reaction needs to bind more than one matching account (e.g. multiple subscriber rows per key), explicitly raise maxAccounts, or only the first match is ever used regardless of how many accounts actually match.
matchByScript whitelist...when a reaction's response is generated from mock account data. A hardcoded OR-chain of known keys requires editing the reaction every time mock data changes, and silently drifts out of sync (new accounts added via create_account won't be served until the reaction is manually updated). Instead:
- Leave
matchByScriptempty; rely onmatchByFirstElement/matchByRelativeURIfor base routing. - Use
accountsSelectorScriptto select by key, e.g.$account/BAN = $input//*:ban. - Set
ifAccountNotFound: skipso the reaction only fires when a matching account exists, falling through to the next reaction (e.g. a default stub) otherwise. - For a "known key, legitimately zero results" case, use a real sentinel account (key present, other fields blank) plus a payload filter (e.g.
$accounts/account[normalize-space(SUB) != '']), rather than encoding that case into the match script.
POST /console/api/reactionDebug is the primary diagnostic toolIt returns a step-by-step trace (Match by HTTP Method, Match by FirstPayloadElement, Script XQ/Script JS, Match by Script, Executed/Skipped reaction) that pinpoints exactly which stage failed and why. When a variable's structure is unclear, dump it directly as a temporary diagnostic payload (e.g. <Debug>{ for $a in $accounts return <acc>{$a}</acc> }</Debug>) rather than guessing.
Creating a reaction places it before all existing reactions. This is useful for overriding a generated default, but means an overly broad matcher will shadow every request matching its base criteria — scope it precisely with matchByScript/accountsSelectorScript rather than relying on order alone.
/console/api/mcp…