Skip to content

Request capture

Included free on every install, replay and diff included, with every request kept for 24 hours. Capture rules, retention up to 30 days and saved replay sets need a license with the request-capture-pro feature. See pricing.

Request capture records each request to the Admin API and the Content API, with its response, for 24 hours. You can see exactly what a client sent and what it got back, compare a failing call with a working one, and send a call again after a fix.

KeptLeft out or masked
Method, URL, status, duration in milliseconds, tenant.Credential headers such as Authorization and X-Api-Key, stored as [REDACTED].
Request and response headers.Sensitive values in JSON, XML, form and plain-text bodies, redacted before they are stored.
Request and response bodies, up to 64 KiB each.The bodies of routes that carry a credential, such as a password or a secret shown once.

Captures expire after 24 hours, or after the retention of the capture rule that kept them, and are deleted within ten minutes of expiring. A replay keeps the lifetime of the capture it replays. Capturing never slows a request down. Under heavy load some captures are dropped rather than queued, and the server log counts them.

Deleting a tenant deletes its captures. A privacy erasure request for a person deletes the captures that name them.

You need an admin token in TOKEN. The quickstart shows how to get one.

  1. List the latest GET captures:

    Terminal window
    curl "http://localhost:3001/api/admin/request-captures?method=GET&limit=2" \
    -H "Authorization: Bearer $TOKEN"
    {
    "data": [
    {
    "id": "56cea807-47e3-413d-beca-28f6128a172e",
    "captured_at": "2026-10-01T09:14:36Z",
    "method": "GET",
    "url": "/api/admin/logging/levels",
    "request_headers": { "Authorization": ["[REDACTED]"], "User-Agent": ["curl/8.18.0"] },
    "status_code": 200,
    "response_headers": { "Content-Type": ["application/json"] },
    "response_body": "{\"default_level\":\"INFO\",\"plugins\":{},\"tenants\":{}}",
    "duration_ms": 1.548,
    "tenant_id": "default",
    "ttl_seconds": 86400
    }
    ],
    "total_count": 247,
    "limit": 2,
    "offset": 0
    }

    Copy an id into CAPTURE_ID.

  2. Fetch that one capture in full:

    Terminal window
    curl http://localhost:3001/api/admin/request-captures/$CAPTURE_ID \
    -H "Authorization: Bearer $TOKEN"
  3. Replay it. A GET needs only confirm_replay:

    Terminal window
    curl -X POST http://localhost:3001/api/admin/request-captures/$CAPTURE_ID/replay \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"confirm_replay": true}'

    The answer holds original, replay and diff. The replay is stored as a new capture, and its id is replay.id. Copy it into REPLAY_ID.

  4. Compare the two:

    Terminal window
    curl http://localhost:3001/api/admin/request-captures/$CAPTURE_ID/diff/$REPLAY_ID \
    -H "Authorization: Bearer $TOKEN"
    { "id1": "56cea807-47e3-413d-beca-28f6128a172e", "id2": "aa51f554-ab7a-4aca-8e89-a762fb626029", "status_match": true, "status_code1": 200, "status_code2": 200, "duration1_ms": 1.548, "duration2_ms": 1.344 }

    The real answer also carries headers_diff, described below.

  5. Delete the replay when you are done. The answer is 204:

    Terminal window
    curl -X DELETE http://localhost:3001/api/admin/request-captures/$REPLAY_ID \
    -H "Authorization: Bearer $TOKEN"

The admin console has no page for captures, so you work with them over the API.

A replay sends the stored request again, with your own Authorization and X-Api-Key in place of the redacted ones. You confirm that with "confirm_replay": true. Replaying a POST, PUT, PATCH or DELETE changes data again, so it also needs "confirm_mutating": true.

override_url sends the request to a different path. It must keep the original scheme and host.

status_match says whether the two statuses agree. body_diff is a line diff of the response bodies, and headers_diff lists the response headers that differ, ignoring Date, X-Request-Id, X-Response-Time and the rate limit counters. Each appears only when the two captures differ. Content-Security-Policy and X-Csp-Nonce carry a fresh value on every answer, so they always show in headers_diff.

Capture rules need request-capture-pro. Without rules, every request is captured and kept 24 hours. Once a tenant has at least one enabled rule, it captures only the requests a rule matches, and the first matching rule, oldest first, decides how long each is kept.

Terminal window
curl -X POST http://localhost:3001/api/admin/request-captures/rules \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"route_pattern": "/api/v1/content/orders/**", "method": "POST", "sample_rate": 0.5, "retention_seconds": 604800}'
FieldMeaningDefault
route_patternAn absolute path. A segment is literal, a glob such as report-*, * or {name} for any one segment, or ** as the last segment for everything below it. /api/v1/** also matches /api/v1. Required.
methodGET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS or *.*
sample_rateThe share of matching requests to keep, from 0 to 1.1
retention_secondsHow long a capture is kept, from 3600 (one hour) to 2592000 (30 days).86400
enabledWhether the rule applies.true

The answer is 201 with the rule. PATCH /api/admin/request-captures/rules/{id} changes the fields you send, and DELETE removes the rule. A change reaches every replica within 30 seconds.

If the license lapses, enabled rules keep applying with their retention. Turning a rule off, editing a rule that is off and deleting a rule stay free. Creating a rule, turning one on and changing one that is on answer 402.

Creating a set needs request-capture-pro. A replay set is a named list of up to 50 of your captures, replayed together in order with each replay compared against its original:

Terminal window
curl -X POST http://localhost:3001/api/admin/request-captures/sets \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "checkout smoke", "capture_ids": ["'"$CAPTURE_ID"'"]}'
curl -X POST http://localhost:3001/api/admin/request-captures/sets/$SET_ID/replay \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"confirm_replay": true}'

The replay answers set_id and results, one per capture with its capture_id, the captured method and path, and either the replay_id and diff, or an error such as capture not found, it may have expired. A set that holds a write needs "confirm_mutating": true as well. A capture that expired since is reported and the rest still run, so keep the captures a set depends on with a rule whose retention outlasts it.

A set's name is unique in its tenant. Listing, reading, replaying and deleting sets stay free after a lapse, and deleting a set leaves its captures.

Every route needs an admin or super admin. An admin sees only their own tenant's captures. A super admin may pass ?tenant_id=<tenant> to the list to read one tenant, or ?tenant_id=* to read every tenant. An admin who names another tenant gets 403.

The capture list, the rule list and the set list each carry licensed, which says whether this install may create rules and sets.

There is nothing to configure. Request capture runs on every install. If you set LYEVE_PLUGINS to choose which features start, include request-capture in it. See licensing and tiers.

MethodPathPurpose
GET/api/admin/request-capturesList captures, newest first, with limit and offset. Filter with ?method=.
GET/api/admin/request-captures/{id}Fetch one capture.
DELETE/api/admin/request-captures/{id}Delete one capture. Answers 204.
POST/api/admin/request-captures/{id}/replaySend the captured request again and compare the result.
GET/api/admin/request-captures/{id1}/diff/{id2}Compare two captures.
POST/api/admin/request-captures/pruneDelete every expired capture now. Super admin only.
GET/api/admin/request-captures/rulesYour tenant's capture rules, oldest first, and licensed.
POST/api/admin/request-captures/rulesCreate a rule. 201. Needs request-capture-pro.
PATCH/api/admin/request-captures/rules/{id}Change a rule. Needs request-capture-pro unless the rule ends up off.
DELETE/api/admin/request-captures/rules/{id}Delete a rule. 204.
GET/api/admin/request-captures/setsYour tenant's replay sets, newest first, and licensed.
POST/api/admin/request-captures/setsCreate a set: name and capture_ids. 201. Needs request-capture-pro.
GET/api/admin/request-captures/sets/{id}One set.
DELETE/api/admin/request-captures/sets/{id}Delete a set and keep its captures. 204.
POST/api/admin/request-captures/sets/{id}/replayReplay every capture in the set.
StatusMessageCause
400override_url must have the same scheme and host as the original captureThe override points somewhere else.
403tenant_id mismatchAn admin named another tenant.
402payment_required, naming feature:request-capture-proCreating a rule or a set, or turning on or changing an enabled rule, without the license.
403super_admin requiredAn admin called prune.
404capture record not foundUnknown id, or another tenant's capture.
404capture rule not found, replay set not foundUnknown id, or another tenant's rule or set.
409failed to create replay setYour tenant already has a set with that name.
422route_pattern must be an absolute path such as /api/v1/orders/{id} or /api/v1/**The pattern does not parse. route_pattern may use ** only as its last segment is the same cause.
422sample_rate must be between 0 and 1, retention_seconds must be between 3600 (one hour) and 2592000 (30 days), method must be one of GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS or *A rule field is out of range.
422name must be 1 to 255 characters, capture_ids must hold 1 to 50 captures, every capture id must name a capture this tenant holdsA set is not valid.
428replay requires confirm_replay: true, which acknowledges auth header substitution, code request_capture.confirm_replayconfirm_replay is missing.
428replaying a POST request requires confirm_mutating: true, code request_capture.confirm_writesThe capture changes data and confirm_mutating is missing.
428the set holds a write, so replaying it requires confirm_mutating: true, code request_capture.confirm_writesA set replay without confirm_mutating.
502replay could not reach the targetThe replayed request failed to connect.