Control API

aimock exposes a small HTTP control surface under the /__aimock/* prefix for inspecting recorded traffic and resetting server state between test runs — no restart required.

All control routes are exact-match and live alongside your mocked LLM endpoints on the same port. They are intended for use from test harnesses (directly via fetch / curl, or through the aimock-pytest client).

Route Overview

Method Path Description
GET /__aimock/health Liveness probe
GET /__aimock/journal Read-only snapshot of recorded requests, optionally filtered and paginated
GET /__aimock/fixtures How many fixtures are loaded, and optionally what they match on
POST /__aimock/fixtures Add fixtures at runtime
DELETE /__aimock/fixtures Clear all fixtures
POST /__aimock/reset Full reset: fixtures, journal entries, fixture match-counts (sequence position), video + fal.ai job state, the Files store, the Gemini interaction / event-id counters, every runtime chaos override and the chaos warning latch
POST /__aimock/reset/journal Clear only the request journal entries
POST /__aimock/reset/fixtures Deprecated. Alias for /reset
POST /__aimock/error Queue a one-shot error injection
GET /__aimock/chaos Read the chaos config in effect for your test
POST /__aimock/chaos Replace the chaos config at runtime, scoped to your test
DELETE /__aimock/chaos Drop the chaos override

Reset Routes

aimock keeps several kinds of in-memory state between requests: the loaded fixtures, the per-provider generation state (video and fal.ai jobs, plus the Gemini interaction and event-id counters), the fixture match-counts (sequence position), and the request journal (recorded requests). Two routes clear it: POST /__aimock/reset clears all of it — including every runtime chaos override, per-test and untagged alike, which reverts the server to the chaos config it was started with, and the latch on [chaos] … rejected warnings for invalid fixture or server chaos values, so a bad static value is reported again after the reset — and POST /__aimock/reset/journal clears only the recorded requests and leaves everything else intact, chaos included. A third, POST /__aimock/reset/fixtures, is a deprecated alias for the full reset.

Pick the narrower routePOST /__aimock/reset wipes the loaded fixtures along with everything else, and what happens to the next request then depends on the mode. In replay mode it fails with 404, and in strict mode with 503 — both carry code: "no_fixture_match". But in record mode, with a provider key configured, an unmatched request is proxied to the real provider — so a full reset mid-recording means live upstream calls and real spend, not an error. If all you want is a clean read between test runs, use POST /__aimock/reset/journal — it leaves your fixtures intact.

POST /__aimock/reset

Full reset. Returns the server to a pristine, fixture-free state. It clears the in-memory fixtures, the journal entries and the per-test fixture match-counts (so sequenced fixtures rewind to their first response), the video and fal.ai job and queue state, the Gemini interaction and event-id counters, every runtime chaos override set via POST /__aimock/chaos (every X-Test-Id scope and the untagged baseline, reverting to the construction-time chaos config), and this server's latch of once-per-value [chaos] rejection warnings so the next request reports them again. It also clears the Files API store (clearFileStore).

Full reset shell
$ curl -X POST http://localhost:4010/__aimock/reset
Response json
{ "reset": true }

POST /__aimock/reset/journal

Journal only. Clears only the request journal entries. Your loaded fixtures, generation state, and fixture match-counts (sequence position) are preserved, so the next request still matches. This is the recommended call for a clean read between test runs.

Journal-only reset shell
$ curl -X POST http://localhost:4010/__aimock/reset/journal
Response json
{ "reset": true }

POST /__aimock/reset/fixtures (Deprecated)

Deprecated alias for /__aimock/reset. The name promises a fixtures-only reset, but it performs the same full reset — journal, match-counts, job state and counters all go with it. It additionally sets a Deprecation: true response header and adds deprecated / deprecation fields to the body. Use /reset for a full reset, or /reset/journal for a journal-only one; to clear fixtures and nothing else, use DELETE /__aimock/fixtures.

Deprecated reset shell
$ curl -i -X POST http://localhost:4010/__aimock/reset/fixtures
Response json
// Deprecation: true   (response header)
{
  "reset": true,
  "deprecated": true,
  "deprecation": "POST /__aimock/reset/fixtures is deprecated; use POST /__aimock/reset (full reset) or POST /__aimock/reset/journal (journal only)"
}

Inspection

GET /__aimock/health

A simple liveness probe. Returns 200 once the server is accepting requests.

Health check shell
$ curl http://localhost:4010/__aimock/health
Response json
{ "status": "ok" }

GET /__aimock/journal

Returns a read-only snapshot of the recorded request entries as a JSON array. The journal records each incoming request so tests can assert on what was sent. Clearing it does not affect fixtures — see /__aimock/reset/journal above.

Read the journal shell
$ curl http://localhost:4010/__aimock/journal

With no query parameters the full array is returned. These parameters filter and paginate it; any other parameter is rejected with 400, so a typo can never quietly return the whole journal:

Param Matching Notes
path Substring ?path=chat matches a chat completions path
method Exact, case-insensitive ?method=post
status Exact integer Response status code
service Exact Not a substring: ?service=sear matches nothing
testId Exact Resolved per entry as the server resolves it: the X-Test-Id header, else ?testId= in the recorded path
requestId Exact Matches the entry's recorded x-request-id: the caller's X-Request-Id when it was well-formed, else the id aimock minted and echoed in that response's X-Request-Id header
limit, offset Integers ≥ 0 Applied last, after filtering

Every response carries an X-Total-Count header with the number of entries that matched the filters, before limit / offset were applied — so a paging caller can tell when it is done. The body stays a bare array.

Filter and paginate shell
$ curl -i "http://localhost:4010/__aimock/journal?path=chat&status=200&limit=10"
// X-Total-Count: 42   (response header)
Response json
[
  /* recorded request entries */
]

Fixtures

GET /__aimock/fixtures

Read-only: how many fixtures are currently registered. Useful for asserting a CI job or test harness actually loaded its tape before it starts making requests, without having to send a probe request and infer the answer from the reply. Returns the count only by default. Any query parameter other than include is rejected with 400, so a typo can never quietly return the count-only body.

Count fixtures shell
$ curl http://localhost:4010/__aimock/fixtures
Response json
{ "count": 2 }

Add ?include=fixtures to also dump what each fixture matches on, so a harness can assert which fixture would serve a request without sending one. Any other include value is rejected with 400. Match criteria are JSON-safe: regexps are stringified and predicate functions become "[function]". Response bodies are never serialized — each fixture reports only a one-word responseKind (text, toolCalls, error, audio, image, factory, …).

Dump fixtures shell
$ curl "http://localhost:4010/__aimock/fixtures?include=fixtures"
Response json
{
  "count": 1,
  "fixtures": [
    {
      "index": 0,
      "match": { "userMessage": "hello" },
      "responseKind": "text"
    }
  ]
}

POST /__aimock/fixtures

Add fixtures at runtime without restarting the server. The body is an object with a "fixtures" array of fixtures to register. Returns the number of fixtures added.

Add fixtures shell
$ curl -X POST http://localhost:4010/__aimock/fixtures \
  -H "Content-Type: application/json" \
  -d '{ "fixtures": [{ "match": { ... }, "response": { ... } }] }'
Response json
{ "added": 1 }

DELETE /__aimock/fixtures

Clears all registered fixtures. Generation state and the journal are left untouched. To clear everything at once, use /__aimock/reset instead.

Clear fixtures shell
$ curl -X DELETE http://localhost:4010/__aimock/fixtures
Response json
{ "cleared": true }

Error Injection

POST /__aimock/error

Queues a one-shot error injection. The next matching request returns the queued error instead of a normal response, after which the injection is consumed. See Error Injection for the full request shape and options.

Queue an error shell
$ curl -X POST http://localhost:4010/__aimock/error \
  -H "Content-Type: application/json" \
  -d '{ "status": 429, "body": { "message": "rate limited", "type": "rate_limit_error" } }'
Response json
{ "queued": true }

The payload is validated, and an invalid one is rejected with 400 and { "error": "…" } without queueing anything:

Chaos

Send your X-Test-Id — chaos overrides are scoped to it, like fixture match-counts and video job state. An override installed with X-Test-Id: t1 applies only to traffic tagged t1, so one test turning chaos on cannot fail the tests running beside it on a shared aimock process. A call with no tag sets the server-wide baseline, which applies to every test that has no override of its own. Both sides — the control call and the traffic it affects — resolve the tag the same way the rest of the server does: the X-Test-Id header, else ?testId= in the query string. An X-Test-Id that is present but empty is rejected with 400 rather than silently treated as untagged. For chaos on a single request, the X-AIMock-Chaos-Drop, X-AIMock-Chaos-Malformed, X-AIMock-Chaos-Ratelimit, X-AIMock-Chaos-Disconnect and X-AIMock-Chaos-Latency request headers still override everything, field by field.

WebSocket connections are different. The WebSocket endpoints (the Responses, Realtime and Gemini Live upgrades) read X-Test-Id, X-AIMock-Strict and X-AIMock-Context from the upgrade request, once, and hold that copy along with the server defaults for the life of the socket. The WebSocket handlers do not evaluate chaos at all, so POST /__aimock/chaos and the X-AIMock-Chaos-* headers affect HTTP requests only; a socket that is already open keeps the strict and record settings it was opened with.

GET /__aimock/chaos

Reads the chaos config in effect for the caller's X-Test-Id: that test's override if one is installed, otherwise the server-wide baseline — an untagged override, or whatever the server was started with (--chaos-drop and friends).

Read chaos shell
$ curl http://localhost:4010/__aimock/chaos
Response json
{ "chaos": { "dropRate": 0.1 } }

POST /__aimock/chaos

Replaces the chaos config at runtime, no restart, for the caller's X-Test-Id. The body is a JSON object with any subset of the five chaos fields: dropRate, malformedRate, rateLimitRate and disconnectRate, each a number in [0, 1], and latencyMs, a whole number of milliseconds in [0, 30000]. Unknown fields and out-of-range values are rejected with 400. POST {} means “explicitly no chaos”.

The body replaces the config for that scope wholesale — it is not merged with the server baseline. A server started with --chaos-latency 500 that is sent { "dropRate": 1 } for X-Test-Id: t1 gives t1's traffic a drop and no latency, because latencyMs was not restated. Restate every field you want to keep. This is what makes POST {} (“explicitly no chaos for my test”) different from DELETE (“fall back to what I was shadowing”) — under a merge, POST {} would do nothing at all. The 200 body echoes the config now in effect for the scope, and any field that was in effect and is not restated is named in a server warning, so a dropped baseline rate is never silent.

Set chaos shell
$ curl -X POST http://localhost:4010/__aimock/chaos \
  -H "Content-Type: application/json" \
  -H "X-Test-Id: t1" \
  -d '{ "dropRate": 0.25 }'
Response json
{ "chaos": { "dropRate": 0.25 } }

DELETE /__aimock/chaos

Drops the override for the caller's X-Test-Id (or the server-wide baseline when untagged), falling back to whatever it was shadowing. Symmetric with POST: an untagged DELETE touches the baseline only and leaves every per-test override in place, so one test's cleanup cannot revoke another's. POST /__aimock/reset drops every override at once, restoring the config the server was started with.

Clear chaos shell
$ curl -X DELETE http://localhost:4010/__aimock/chaos \
  -H "X-Test-Id: t1"
Response json
{ "chaos": {} }