A small, Bearer-authenticated JSON API for programmatic access to this app.
Pointing an AI agent at this API?
Hand it the LLM-ready Markdown version — self-contained instructions an agent can follow with just a URL and an API key.
All endpoints live under `https://iriwdve0.vibecode.cloud/api/v1`. `https://iriwdve0.vibecode.cloud` is the origin you were given (scheme + host, e.g. `https://example.com`). Do not add a trailing slash.
Every endpoint requires an API key sent as a Bearer token: `Authorization: Bearer sk_your_key_here`. Keys always start with `sk_`. A missing or invalid key returns `401 { "error": "Invalid or missing API key" }`. Create keys in the app under Profile → API Keys.
Responses are JSON unless noted (file download returns raw bytes). Request bodies are JSON (`Content-Type: application/json`) except file upload, which is `multipart/form-data`.
The `/api/v1/tracetap/*` endpoints are called by installed TraceTap probes, not by users, and use different credentials: `Authorization: Bearer pt_...` (the probe key) together with `X-TraceTap-Project: pk_...` (the project key). An `sk_` key does not work on them, and a probe key does not work anywhere else. Request bodies may additionally be sealed with the project communication key (`ck_...`) using AES-256-GCM. Bodies are capped at 8 MB (sealed size): a larger one is refused with `413` before it is parsed.
Requests are rate limited per API key. When you exceed a limit you get `429` (or `403` if the limit is configured to block) with an `error` message and, when applicable, a `Retry-After` header (seconds). Back off and retry.
Errors are JSON with an `error` string and a matching HTTP status (`400` bad input, `401` unauthenticated, `403` forbidden, `404` not found, `413` payload too large, `429` rate limited, `500` server error).
Your base URL is https://iriwdve0.vibecode.cloud. Create API keys under Profile → API Keys.
/api/v1/healthConfirms the API is up and your key is valid. Handy as a first call to verify credentials and connectivity.
Request
curl https://iriwdve0.vibecode.cloud/api/v1/health \
-H "Authorization: Bearer sk_your_key_here"Response
{
"status": "healthy",
"timestamp": "2026-07-19T12:00:00.000Z",
"uptime": 1234.56,
"version": "1.0.0",
"apiKey": "My key",
"userId": "usr_...",
"message": "API is running successfully"
}/api/v1/statsReturns the calling user together with API-usage counters (requests today / this week / this month, error rate, API-key count).
Request
curl https://iriwdve0.vibecode.cloud/api/v1/stats \
-H "Authorization: Bearer sk_your_key_here"Response
{
"user": { "id": "usr_...", "email": "you@example.com", "name": "You", "role": "user", "createdAt": "..." },
"apiStats": {
"totalApiKeys": 2,
"requestsToday": 14,
"requestsThisWeek": 98,
"requestsThisMonth": 412,
"errorRate": "1.20%",
"errorCount": 5
},
"meta": { "timestamp": "...", "apiKey": "My key" }
}/api/v1/usersLists users. A regular key returns only its own user record; an admin key returns all users with pagination.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| limit | query | integer | no | Page size, 1–100 (default 10). Admin only; ignored for non-admins. |
| offset | query | integer | no | Rows to skip (default 0). Admin only. |
Request
curl "https://iriwdve0.vibecode.cloud/api/v1/users?limit=20&offset=0" \
-H "Authorization: Bearer sk_your_key_here"Response
{
"users": [
{ "id": "usr_...", "email": "you@example.com", "name": "You", "role": "user", "emailVerified": null, "createdAt": "..." }
],
"meta": { "limit": 20, "offset": 0, "total": 1, "apiKey": "My key" }
}/api/v1/usersAdmin-only endpoint scaffold for creating a user. Ships as a stub in this starter — it validates input and echoes it back rather than persisting. Fill in real creation logic before relying on it.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| body | string | yes | New user email. | |
| name | body | string | yes | New user display name. |
| role | body | string | no | 'user' (default) or 'admin'. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/users \
-H "Authorization: Bearer sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"email":"new@example.com","name":"New User","role":"user"}'Response
{
"message": "User creation endpoint - implementation needed",
"requestedData": { "email": "new@example.com", "name": "New User", "role": "user" },
"apiKey": "My key"
}/api/v1/filesUploads a file and stores its raw bytes. Use this instead of a form/Server Action for any real upload (Server Actions cap the body at ~1MB; this endpoint does not). Send `multipart/form-data` with a single `file` field.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| file | form | file | yes | The file to upload (multipart field name must be "file"). |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/files \
-H "Authorization: Bearer sk_your_key_here" \
-F "file=@./photo.png"Response
{
"id": "fil_...",
"filename": "photo.png",
"url": "/api/v1/files/fil_..."
}/api/v1/files/:idStreams the raw file bytes with the stored Content-Type (`Content-Length` is set when the backend reports it). Because it is Bearer-gated you cannot put it directly in an `<img src>`; fetch it with the token and build an object URL client-side.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | File id returned by the upload endpoint. |
Request
curl https://iriwdve0.vibecode.cloud/api/v1/files/fil_your_file_id \
-H "Authorization: Bearer sk_your_key_here" \
--output downloaded-fileResponse
Raw binary body with the stored `Content-Type` and `Content-Disposition: inline; filename="..."`. Returns `404 { "error": "File not found" }` if unknown./api/v1/files/:idDeletes a file owned by the calling key.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | File id to delete. |
Request
curl -X DELETE https://iriwdve0.vibecode.cloud/api/v1/files/fil_your_file_id \
-H "Authorization: Bearer sk_your_key_here"Response
{ "deleted": true } // { "deleted": false } with status 404 if not found / not owned/api/v1/tracetap/ingestWhere an installed probe posts the messages it sampled. Bodies have already been transformed by the project rules inside the probe, so nothing arrives here that the rules did not allow through. The body is either plain JSON or a sealed envelope encrypted with the project communication key.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| X-TraceTap-Project | header | string | yes | The project key (pk_...). |
| messages | body | array | yes | Captured messages. Each may carry direction, peer, method, path, statusCode, durationMs, protocol, operation, requestHeaders, responseHeaders, requestBody, responseBody, error and rulesVersion. A call that never got an answer has statusCode null and an error that starts with its class ("ECONNREFUSED: connect ECONNREFUSED 10.0.0.1:443"). |
| connectionId | body | string | no | Pins these messages to one connection on the topology. |
| probeVersion | body | string | no | Version of the probe, recorded for support. |
| stats | body | object | no | Traffic counters since the last report: { observed, sampled, withheld, errors, bytesObserved, since, peers? }. This is the only way TraceTap can know real traffic volume — stored samples are 1 in N by design, so counting them measures the sampling rate, not the link. `peers` (probe 1.2.0+) counts every outbound call per target host: { "sharefiles.eu": { calls, failures, classes: { "ECONNREFUSED": 12 } } } — dependency health and the dependency-failing alert are computed from it. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/ingest \
-H "X-TraceTap-Project: pk_your_project_key" \
-H "Authorization: Bearer pt_your_probe_key" \
-H "Content-Type: application/json" \
-d '{
"messages": [{
"method": "POST",
"path": "/api/customers",
"statusCode": 201,
"requestBody": "{\"email\":\"generated@example.com\"}",
"responseBody": "{\"id\":42}"
}]
}'Response
{
"accepted": 1,
"divergences": 1,
"rulesVersion": 3
}/api/v1/tracetap/rulesReturns the rules document a probe enforces locally: which fields are personal data or secrets, which generator replaces each one, and what to do with anything unclassified. Pass `since` with the version you already have to get 204 when nothing changed.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| X-TraceTap-Project | header | string | yes | The project key (pk_...). |
| since | query | integer | no | Rules version the probe already holds. Omit, or pass 0, to always receive the document. |
Request
curl "https://iriwdve0.vibecode.cloud/api/v1/tracetap/rules?since=0" \
-H "X-TraceTap-Project: pk_your_project_key" \
-H "Authorization: Bearer pt_your_probe_key"Response
{
"format": 1,
"projectKey": "pk_...",
"version": 3,
"publishedAt": "2026-08-24T10:00:00.000Z",
"sampleEveryN": 20,
"unknownFieldPolicy": "mask",
"generators": [
{ "id": "gen_...", "name": "email", "kind": "builtin", "builtinId": "email", "config": {} }
],
"fields": [
{ "endpoint": "POST /api/customers", "direction": "request", "section": "body",
"pointer": "$.email", "classification": "pii", "piiKind": "email",
"action": "generate", "generatorId": "gen_..." },
{ "endpoint": "GET /api/lists/:id/rows", "direction": "response", "section": "body",
"pointer": "$.data.content[].f{n}", "pattern": "$.data.content[].f{n}",
"classification": "internal", "action": "keep" },
{ "endpoint": "POST /api/events", "direction": "request", "section": "body",
"pointer": "$.metadata", "classification": "internal",
"action": "mask", "children": "mask-all" }
],
"redactHeaders": ["authorization", "cookie", "set-cookie", "x-api-key"]
}/api/v1/tracetap/heartbeatMarks the probe as alive and, optionally, reports a local problem it cannot fix itself. The response states which rules version the probe should be running and how often it should sample.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| X-TraceTap-Project | header | string | yes | The project key (pk_...). |
| version | body | string | no | Probe version string. |
| error | body | string | no | A local failure worth surfacing in the Pulse feed. |
| stats | body | object | no | The same traffic counters the ingest endpoint accepts. Sent here too because a probe sampling 1 in 1000 on a quiet link may go a long time without an ingest call, and its traffic volume still has to arrive. |
| meta | body | object | no | Free-form diagnostics stored with the event. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/heartbeat \
-H "X-TraceTap-Project: pk_your_project_key" \
-H "Authorization: Bearer pt_your_probe_key" \
-H "Content-Type: application/json" \
-d '{
"version": "1.0.0",
"stats": {
"observed": 4210, "sampled": 210, "withheld": 0,
"errors": 7, "bytesObserved": 8402331,
"since": "2026-08-24T12:00:00.000Z"
}
}'Response
{
"ok": true,
"rulesVersion": 3,
"sampleEveryN": 20
}/api/v1/tracetap/apisEvery outbound API (host) that any system in any TraceTap project you can see calls: which project, environment and system calls it, how often (total, last 24 h, hourly), which keys it used (masked, e.g. `sk-or-v1-c02…8ca` — computed inside the app, the key never reaches TraceTap), its status and the fleet rule that decides it. `q` matches host, system or project name and masked key fragments; learned endpoint paths that match are returned too. Empty `q` lists everything, what needs a decision first.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| q | query | string | no | e.g. `openrouter`, `VA5`, `8ca`. Do NOT put a full key here (query strings are logged) — use POST /api/v1/tracetap/apis/search. |
| status | query | string | no | Comma-separated: learned, new, approved, blocked, forbidden. |
| limit | query | number | no | Max API rows (default 300, max 1000). |
Request
curl "https://iriwdve0.vibecode.cloud/api/v1/tracetap/apis?q=openrouter" \
-H "Authorization: Bearer sk_your_api_key"Response
{
"query": "openrouter",
"credentialSearch": false,
"environmentsSearched": 16,
"enforcement": "record-only",
"apis": [
{
"id": "k3j…",
"host": "openrouter.ai",
"status": "learned",
"forbidden": true,
"rule": { "id": "r1…", "pattern": "openrouter.ai", "verdict": "deny", "note": "All AI via GluedEasy" },
"project": { "rootName": "AppSalad platform", "environment": "production", "apiMode": "learning" },
"system": { "id": "4ca8…", "name": "VA5" },
"calls": 11, "calls24h": 0, "failures": 0, "sampledOnly": false,
"hourly": [0, 0, 3, 8, 0],
"credentials": [{ "display": "sk-or-v1-c02…8ca", "calls": 11, "lastSeenAt": "2026-10-03T21:12:00.000Z" }],
"firstSeenAt": "2026-10-03T19:40:00.000Z",
"lastSeenAt": "2026-10-03T21:12:00.000Z"
}
],
"endpoints": [
{ "method": "POST", "pathPattern": "/api/v1/chat/completions", "connection": "VA5 → openrouter.ai" }
]
}/api/v1/tracetap/apis/searchThe same search as GET /api/v1/tracetap/apis, with the query in the JSON body. Paste a FULL key as `q` to find every API call made with exactly that key: it is matched by fingerprint (an HMAC per environment) and is never stored or logged.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| q | body | string | no | Search text, or a full key. |
| status | body | string[] | no | learned, new, approved, blocked, forbidden. |
| limit | body | number | no | Max API rows. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/apis/search \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"q": "sk-or-v1-…the full key…"}'Response
{ "query": "…", "credentialSearch": true, "apis": [ { "host": "openrouter.ai", "credentials": [ { "display": "sk-or-v1-c02…8ca", "matched": true } ] } ] }/api/v1/tracetap/apis/:id/decisionDecides on one API row (one system of one environment calling one host). approve resolves its open "new API" and "blocked" alerts; block marks it blocked — every further call raises an alert (enforcement integrations are coming soon); reset puts it back to learned.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | The API row id from the search. |
| decision | body | string | yes | approve | block | reset |
| note | body | string | no | Why — shown next to the status. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/apis/API_ID/decision \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"decision": "block", "note": "AI must go through GluedEasy"}'Response
{ "ok": true, "id": "k3j…", "status": "blocked", "enforcement": "record-only" }/api/v1/tracetap/api-rulesHosts forbidden (deny) or known-good (allow) across every project of their owner, with the projects each rule does not apply to.
Request
curl https://iriwdve0.vibecode.cloud/api/v1/tracetap/api-rules -H "Authorization: Bearer sk_your_api_key"Response
{
"rules": [
{ "id": "r1…", "pattern": "openrouter.ai", "verdict": "deny", "note": "All AI via GluedEasy",
"except": [{ "id": "p9…", "name": "viberun" }], "mine": true }
]
}/api/v1/tracetap/api-rulesdeny: any call to the host from any of your projects (except the listed ones) raises an `api-forbidden` alert at once, in learning and locked environments alike. allow: never flagged as a new API. `openrouter.ai` also covers its subdomains; `*.example.com` only subdomains. PATCH /api/v1/tracetap/api-rules/:id replaces a rule, DELETE removes it.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| pattern | body | string | yes | Host, host:port or *.domain. |
| verdict | body | string | yes | deny | allow |
| note | body | string | no | Why. |
| exceptRootIds | body | string[] | no | Root project ids the rule does not apply to. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/api-rules \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"pattern": "openrouter.ai", "verdict": "deny", "note": "All AI via GluedEasy"}'Response
{ "rule": { "id": "r1…", "pattern": "openrouter.ai", "verdict": "deny", "except": [] } }/api/v1/tracetap/projects/:projectId/field-patternsField patterns resolve data-driven keys with one decision: `$.data.content[].f{n}` stands for `f2350441`, `f2350451`, … A pointer a pattern matches raises no new-field divergence and gets no field of its own; its type is still checked. Syntax: the pointer text is literal except `*` (any characters within ONE key segment), `{n}` (one or more digits; `(\d+)` and `\d+` mean the same) and `**` (any number of segments, only in the middle). `re:<regex>` is a raw, anchored regex (max 200 chars; nested quantifiers, alternations inside quantified groups, backreferences and lookarounds are refused). Literal fields always win; then an endpoint pattern beats a project-wide one; then the most specific.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The environment (project) id. |
Request
curl https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/field-patterns -H "Authorization: Bearer sk_your_api_key"Response
{
"patterns": [
{ "id": "fp_…", "pattern": "$.data.content[].f{n}", "endpointId": "ep_…", "endpointLabel": "GET /api/lists/:id/rows",
"direction": "response", "section": "body", "jsonType": "number|string",
"classification": "internal", "action": "keep", "childrenMode": null, "generatorId": null, "status": "approved",
"source": "user", "matchCount": 812, "foldedFields": 3 }
]
}/api/v1/tracetap/projects/:projectId/field-patternsCreates the pattern, resolves every open/acknowledged new-field divergence in its scope whose pointer it matches (resolved by you), and folds the matching literal fields into it (retired; restored when the pattern is deleted). Literal fields that were already approved keep their own decision. Pass `"dryRun": true` to only get the matches. A pattern only reaches the probes (1.4.0+) once the rules are published — `POST /api/v1/tracetap/projects/:projectId/rules/publish` or the dashboard.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The environment (project) id. |
| pattern | body | string | yes | e.g. `$.data.content[].f{n}`, `$.byId.*`, `re:^\$\.data\.f\d+$`. |
| endpointId | body | string|null | no | Limit to one endpoint; omit/null for every endpoint (`*`). |
| direction | body | string | yes | request | response |
| section | body | string | no | body (default) | headers | query |
| jsonType | body | string | no | Expected type, may be a union (`number|string`); empty = any. Values of another type raise type-change. |
| classification | body | string | yes | unknown | public | internal | pii | secret |
| action | body | string | yes | keep | mask | generate | drop |
| childrenMode | body | string|null | no | Only for `keep` on a pattern that matches objects/arrays — what happens inside: `rules` (default: walk on, child rules apply, unknown leaves follow the unknown-field policy), `mask-all` (keep the structure, mask every leaf) or `keep-all` (send everything below as-is, including future fields). Ignored for other actions and for leaf types. The triage AI may never set `keep-all`. |
| generatorId | body | string | no | Generator of this project, required for `generate`. |
| label | body | string | no | Plain-language description. |
| dryRun | body | boolean | no | Only preview what it matches. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/field-patterns \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"pattern": "$.data.content[].f{n}", "endpointId": "ENDPOINT_ID", "direction": "response", "classification": "internal", "action": "keep", "jsonType": "number|string"}'Response
{
"pattern": { "id": "fp_…", "pattern": "$.data.content[].f{n}", "status": "approved", "foldedFields": 2 },
"resolvedDivergences": 14,
"foldedFields": 2,
"keptFields": 0
}/api/v1/tracetap/projects/:projectId/field-patterns/:idReplaces the pattern text, scope and decision (same body as POST, without dryRun) and re-applies it: newly matching divergences are resolved, newly matching fields folded, fields it no longer matches restored. Like every other field, an omitted `childrenMode` goes back to the default (`rules` for a container keep).
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The environment (project) id. |
| id | path | string | yes | The pattern id. |
Request
curl -X PATCH https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/field-patterns/PATTERN_ID \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"pattern": "$.data.content[].f{n}", "direction": "response", "classification": "pii", "action": "mask"}'Response
{ "pattern": { "id": "fp_…", "classification": "pii", "action": "mask" }, "resolvedDivergences": 0, "foldedFields": 0, "keptFields": 0 }/api/v1/tracetap/projects/:projectId/field-patterns/:idThe fields it folded come back; any other pointer it matched becomes learnable again from the next sample on (a new-field divergence when it is next seen). Nothing is raised for past data.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The environment (project) id. |
| id | path | string | yes | The pattern id. |
Request
curl -X DELETE https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/field-patterns/PATTERN_ID -H "Authorization: Bearer sk_your_api_key"Response
{ "ok": true, "restoredFields": 2 }/api/v1/tracetap/projects/:projectId/rulesThe latest published rules document of a project environment — the one its probes download — and whether the current decisions differ from it. Field patterns and field decisions (approved fields) only reach the probes after a publish: `unpublishedChanges: true` means something is waiting. `version: 0` means nothing was published yet (probes then mask every field).
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The environment (project) id. |
| doc | query | string | no | `1` to include the published document itself (`doc`). |
Request
curl "https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/rules?doc=1" -H "Authorization: Bearer sk_your_api_key"Response
{
"version": 12,
"publishedAt": "2026-10-29T09:12:00.000Z",
"publishedBy": "usr_…",
"notes": "Flexlists f-columns",
"checksum": "9f2c…",
"fieldCount": 214,
"generatorCount": 2,
"patternCount": 3,
"unpublishedChanges": false
}/api/v1/tracetap/projects/:projectId/rules/publishCompiles the approved field decisions and approved field patterns into a new rules version — exactly what the "Publish rules" button on the review page does — and probes pick it up on their next rules poll. Patterns and decisions made over the API do NOT reach the probes until this is called. Publishing an unchanged document is a no-op: `published: false` with a `reason`, and no version is used up. `"dryRun": true` writes nothing and answers the diff against the last published version: added, removed and changed field rules (endpoint, direction, section, pointer, action, childrenMode) and patterns, plus counts. The diff compares wire documents, so a container `keep` in `rules` mode (which is left out of the document) shows as a removed rule.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The environment (project) id. |
| notes | body | string | no | Why — stored with the version (max 500 chars). |
| dryRun | body | boolean | no | Only return what a publish would change. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/rules/publish \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"notes": "Flexlists f-columns"}'Response
{ "published": true, "version": 13, "fieldCount": 215, "generatorCount": 2, "patternCount": 4 }/api/v1/tracetap/rule-poolsA rule pool is a list of request paths internet scanners probe (`/.env`, `/wp-admin/**`, `/.git/**`). When a sample's method + RAW path (query dropped; its percent-decoded form and the normalised endpoint pattern also count) matches an entry of a pool that is ON for the project, ingest raises no new-endpoint / new-field / missing-field divergence: it bumps ONE `known-abuse` divergence (severity info, status `flagged`, never open) per environment × entry × method × path. Flagged divergences are never mailed, never triaged and never count as changes. Lists the system pool "Known scanner probes" (maintained by TraceTap, editable by admins and superadmins), your own pools, and the pools of the owners of projects shared with you (read-only). Every pool is ON for a project unless switched off for it; a user pool is on by default for its owner's projects.
Request
curl https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools -H "Authorization: Bearer sk_your_api_key"Response
{
"pools": [
{ "id": "rp_…", "name": "Known scanner probes", "kind": "abuse", "system": true, "ownerLabel": "TraceTap",
"mine": false, "canEdit": false, "defaultEnabled": true, "aiAutoApply": false,
"entryCount": 85, "suggestedCount": 0, "matchCount": 11667, "lastMatchedAt": "2026-10-29T09:12:00.000Z" }
]
}/api/v1/tracetap/rule-poolsCreates a pool of your own (scoped to your tenant), ON for all your projects by default, optionally with entries. Superadmins may pass `"system": true` for a pool that applies to everyone.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| name | body | string | yes | Up to 120 characters. |
| description | body | string | no | Free text. |
| defaultEnabled | body | boolean | no | ON for your projects without an explicit switch (default true). |
| aiAutoApply | body | boolean | no | Let AI triage add its suggested paths straight away instead of waiting for you (default false). |
| entries | body | array | no | Entries as in POST …/entries. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "Our scanners", "entries": [{"match": "glob", "path": "/**/backup.zip", "category": "other"}]}'Response
{ "pool": { "id": "rp_…", "name": "Our scanners", "system": false, "mine": true }, "entries": [ { "id": "rpe_…", "path": "/**/backup.zip" } ], "skipped": 0, "converted": 2 }/api/v1/tracetap/rule-pools/:idThe pool, its entries (active and AI-suggested) and its state on each project you can see.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | The pool id. |
Request
curl https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools/POOL_ID -H "Authorization: Bearer sk_your_api_key"Response
{
"pool": { "id": "rp_…", "name": "Known scanner probes", "system": true, "canEdit": false },
"entries": [ { "id": "rpe_…", "match": "glob", "method": "*", "path": "/**/.env", "category": "secrets", "note": "", "status": "active", "source": "system", "matchCount": 3277, "lastMatchedAt": "…" } ],
"projects": [ { "rootId": "prj_…", "name": "InferMux", "enabled": true, "explicit": false, "canToggle": true } ]
}/api/v1/tracetap/rule-pools/:idName, description, defaultEnabled, aiAutoApply (user pools only). Turning defaultEnabled on flags the backlog it now covers.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | The pool id. |
Request
curl -X PATCH https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools/POOL_ID \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"aiAutoApply": true}'Response
{ "pool": { "id": "rp_…", "aiAutoApply": true }, "converted": 0 }/api/v1/tracetap/rule-pools/:idTombstones the pool. What it already flagged stays flagged; nothing is re-opened.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | The pool id. |
Request
curl -X DELETE https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools/POOL_ID -H "Authorization: Bearer sk_your_api_key"Response
{ "ok": true }/api/v1/tracetap/rule-pools/:id/entriesOne entry in the body, or `{ "entries": [ … ] }` (at most 500). `match`: `prefix` (literal start, case-insensitive), `glob` (`*` within one segment, `**` any number of segments, `?` one character; anchored, case-insensitive — `/**/.env` matches `/.env` and `/app/.env`) or `regex` (anchored, max 200 chars, refused when it could backtrack catastrophically: nested or alternated quantified groups, backreferences, lookarounds — the field-pattern rules; case-sensitive unless it starts with `(?i)`). Duplicates are skipped. Open new-endpoint divergences the new entries match, on every project where the pool is ON, are converted to `known-abuse` / `flagged` at once.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | The pool id. |
| match | body | string | no | prefix | glob (default) | regex |
| method | body | string | no | HTTP method, or `*` (default) for any. |
| path | body | string | yes | e.g. `/**/.env`, `/wp-admin/**`, `/HNAP1`. |
| category | body | string | no | secrets | wordpress | php | vcs | cloud-creds | admin-panels | other (default) |
| note | body | string | no | Why it is abuse. |
| entries | body | array | no | Several entries at once, each with the fields above. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools/POOL_ID/entries \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"entries": [{"match": "glob", "path": "/**/.env", "category": "secrets"}, {"match": "prefix", "path": "/HNAP1", "category": "admin-panels"}]}'Response
{ "entries": [ { "id": "rpe_…", "match": "glob", "path": "/**/.env" } ], "skipped": 1, "converted": 37 }/api/v1/tracetap/rule-pools/:id/entries/:entryIdSame fields as when adding. `"status": "active"` accepts an entry AI triage suggested (it then starts matching and flags the backlog).
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | The pool id. |
| entryId | path | string | yes | The entry id. |
| status | body | string | no | `active` to accept a suggestion. |
Request
curl -X PATCH https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools/POOL_ID/entries/ENTRY_ID \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"status": "active"}'Response
{ "entry": { "id": "rpe_…", "status": "active" }, "converted": 4 }/api/v1/tracetap/rule-pools/:id/entries/:entryIdTombstones the entry (a seeded system entry is never re-added). Nothing already flagged is re-opened.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | The pool id. |
| entryId | path | string | yes | The entry id. |
Request
curl -X DELETE https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools/POOL_ID/entries/ENTRY_ID -H "Authorization: Bearer sk_your_api_key"Response
{ "ok": true }/api/v1/tracetap/projects/:projectId/rule-poolsEvery pool that applies to the project — the system pools, the owner's pools, yours, and any switched on for it — with whether it is ON (`explicit`: a switch was set; otherwise the pool's default) and whether you may switch it. Switches live on the root project and cover every environment; any environment id works.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The project (any environment) id. |
Request
curl https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/rule-pools -H "Authorization: Bearer sk_your_api_key"Response
{ "rootId": "prj_…", "canWrite": true, "pools": [ { "pool": { "id": "rp_…", "name": "Known scanner probes", "system": true }, "enabled": true, "explicit": false, "canToggle": true } ] }/api/v1/tracetap/projects/:projectId/rule-pools/:poolIdSets the pool's switch on the project (every environment). Switching ON converts the matching open new-endpoint divergences to known-abuse; switching OFF re-opens nothing — later hits raise normal new-endpoint divergences again. You can switch the system pools, your own pools and the project owner's pools.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The project (any environment) id. |
| poolId | path | string | yes | The pool id. |
| enabled | body | boolean | yes | true = ON, false = OFF. |
Request
curl -X PUT https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/rule-pools/POOL_ID \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'Response
{ "enabled": false, "converted": 0 }/api/v1/tracetap/projects/:projectId/mark-abuseAdds an entry to one of your pools (`poolId`) or to a new pool of yours (`newPoolName`), switches that pool ON for the project if it was not, and converts every matching open new-endpoint divergence to `known-abuse` / `flagged` — the way "resolve with pattern" resolves fields. `dryRun: true` only lists what it would flag.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The project (any environment) id. |
| path | body | string | yes | e.g. `/wp-admin/**`. |
| match | body | string | no | prefix | glob (default) | regex |
| method | body | string | no | HTTP method or `*` (default). |
| category | body | string | no | As for entries. |
| note | body | string | no | Why. |
| poolId | body | string | no | One of your pools… |
| newPoolName | body | string | no | …or the name of a new one. |
| divergenceId | body | string | no | The new-endpoint divergence you started from; the answer says whether it matched. |
| dryRun | body | boolean | no | Only preview. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/mark-abuse \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"path": "/wp-admin/**", "category": "wordpress", "newPoolName": "Our scanners", "divergenceId": "DIVERGENCE_ID"}'Response
{ "pool": { "id": "rp_…", "name": "Our scanners" }, "entry": { "id": "rpe_…", "path": "/wp-admin/**" }, "converted": 3, "matchedDivergence": true }/api/v1/tracetap/projects/:projectId/abuseThe flagged known-abuse divergences, grouped by pool → entry → path, with hit counts, first/last seen, status codes and caller IPs when a proxy header got through the probe. A root project id covers all its environments. `last24h` counts hits in the last 24 hours.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The project or environment id. |
Request
curl https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/abuse -H "Authorization: Bearer sk_your_api_key"Response
{
"total": 312, "last24h": 41,
"pools": [ { "poolId": "rp_…", "poolName": "Known scanner probes", "system": true, "total": 312, "lastSeenAt": "…",
"entries": [ { "entryId": "rpe_…", "entryPath": "/**/.env", "match": "glob", "category": "secrets", "total": 120,
"paths": [ { "divergenceId": "…", "environment": "Production", "method": "GET", "path": "/.env", "occurrences": 97,
"lastSeenAt": "…", "samplePaths": ["/.env", "/%2eenv"], "sourceIps": ["203.0.113.7"], "statusCodes": { "404": 97 } } ] } ] } ]
}/api/v1/tracetap/fluxwallWhether you connected FluxWall (through an AuthLock API grant, provider `fluxwall`, scopes domains:read waf:read waf:write), as which FluxWall account, and its state: `active`, or `error` when FluxWall answered 401/403 (grant revoked or expired) — syncing through it then stops until you reconnect. Connecting needs a browser for the AuthLock consent (dashboard → Integrations → FluxWall → Connect); there is no API for it. `?zones=1` also lists your FluxWall zones (apex rows only).
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| zones | query | string | no | `1` to include your zones. |
Request
curl "https://iriwdve0.vibecode.cloud/api/v1/tracetap/fluxwall?zones=1" -H "Authorization: Bearer sk_your_api_key"Response
{
"connection": { "authlockConfigured": true, "connected": true, "status": "active", "email": "you@example.com",
"scopes": ["domains:read", "waf:read", "waf:write"], "expiresAt": "2027-11-03T10:00:00.000Z", "lastError": null,
"connectedAt": "2026-11-03T10:00:00.000Z", "lastUsedAt": "2026-11-03T10:05:00.000Z", "links": 2 },
"zones": [ { "id": "dom_…", "domain": "example.com", "status": "active" } ],
"zonesError": null
}/api/v1/tracetap/projects/:projectId/fluxwallWhat TraceTap pushes to FluxWall for this project (root project; any environment id works): `mode` off | log | enforce, the action per rule-pool category (enforce defaults: secrets, cloud-creds, vcs → honeypot; wordpress, php, admin-panels, other → block; each can be honeypot | block | log | skip; in log mode every pushed rule is `log`), `ruleCount` (rules the current settings push per zone), the linked zones with their last sync (time, `pushedCount`, `deletedCount`, `rejected` entries with FluxWall's reason, `lastSyncError`), and — when you are connected and may write — your zones and `suggestions`: zones whose apex is, or is a parent of, one of the project's own hosts (its non-external systems and its linked vibecode projects' domains). The pushed set is the active entries of every rule pool enabled for the project; `externalId` = the TraceTap entry id, `label` = "<path> · <pool name>", match/method/path copied 1:1.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The project (any environment) id. |
Request
curl https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/fluxwall -H "Authorization: Bearer sk_your_api_key"Response
{
"rootId": "prj_…", "projectName": "Shop", "canWrite": true, "mode": "enforce", "ruleCount": 512,
"categories": [ { "category": "secrets", "label": "Secrets & dotfiles", "action": "honeypot", "defaultAction": "honeypot" } ],
"links": [ { "id": "fzl_…", "zone": "example.com", "zoneDomainId": "dom_…", "connectedBy": "Ann", "mine": true,
"connectionStatus": "active", "syncStatus": "ok", "lastSyncAt": "2026-11-03T10:05:00.000Z", "lastSyncError": null,
"pushedCount": 511, "deletedCount": 0,
"rejected": [ { "externalId": "rpe_…", "path": "(?i)/x(a|b)+", "label": "(?i)/x(a|b)+ · Ours", "error": "quantified alternation" } ] } ],
"connection": { "connected": true, "status": "active", "email": "you@example.com" },
"zones": [ { "id": "dom_…", "domain": "example.com", "status": "active" } ], "zonesError": null,
"suggestions": [ { "zoneDomainId": "dom_…", "zone": "example.net", "hosts": ["api.example.net"] } ]
}/api/v1/tracetap/projects/:projectId/fluxwallChanges what is pushed and starts a sync of every linked zone a few seconds later. `mode: "off"` never pushes rules; switching to off syncs an EMPTY set once, which removes TraceTap's rules from FluxWall. `categoryActions` changes only the categories you send; `null` or the default action resets one.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The project (any environment) id. |
| mode | body | string | no | off | log | enforce |
| categoryActions | body | object | no | `{ "<category>": "honeypot" | "block" | "log" | "skip" | null }` for secrets, wordpress, php, vcs, cloud-creds, admin-panels, other. |
Request
curl -X PATCH https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/fluxwall \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"mode": "enforce", "categoryActions": {"wordpress": "honeypot", "other": "log"}}'Response
{ "mode": "enforce", "categories": { "secrets": "honeypot", "wordpress": "honeypot", "php": "block", "vcs": "honeypot", "cloud-creds": "honeypot", "admin-panels": "block", "other": "log" } }/api/v1/tracetap/projects/:projectId/fluxwall/linksLinks zones of YOUR FluxWall account (from `GET /tracetap/fluxwall?zones=1`) to the project; they sync through your grant. Linking a zone already linked through someone else's connection moves it onto yours. Unknown zone ids are returned in `skipped`. At most 50 zones per project.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The project (any environment) id. |
| zoneDomainIds | body | string[] | yes | FluxWall zone (domain) ids. `zoneDomainId` (one string) also works. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/fluxwall/links \
-H "Authorization: Bearer sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"zoneDomainIds": ["dom_…"]}'Response
{ "linked": [ { "id": "fzl_…", "zone": "example.com", "syncStatus": "pending" } ], "skipped": [] }/api/v1/tracetap/projects/:projectId/fluxwall/links/:linkIdRemoves TraceTap's rules from the zone (an empty sync; best effort, `cleared` says whether it worked), then the link.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The project (any environment) id. |
| linkId | path | string | yes | The link id. |
Request
curl -X DELETE https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/fluxwall/links/LINK_ID -H "Authorization: Bearer sk_your_api_key"Response
{ "removed": true, "cleared": true }/api/v1/tracetap/projects/:projectId/fluxwall/syncPushes the project's set to every linked zone now — even when unchanged — and waits for FluxWall's answers (one zone at a time; retried in place on 429/5xx). Otherwise syncing is automatic: a few seconds after any entry is approved, edited, deleted or rejected, a pool is switched on/off for the project, or the mode / category actions change; due retries every minute; and a full pass every hour. `outcome` per zone: synced | unchanged | skipped (backing off) | error. A rule FluxWall rejects is listed with its reason and is not sent again until the entry (or its action) changes.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The project (any environment) id. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/fluxwall/sync -H "Authorization: Bearer sk_your_api_key"Response
{ "results": [ { "linkId": "fzl_…", "zone": "example.com", "outcome": "synced", "pushed": 511, "deleted": 0, "rejected": 1 } ] }/api/v1/tracetap/projects/:projectId/fluxwall/statsFluxWall's side of the project's abuse entries: hits per entry id in the last 24 h (`byEntry`, from FluxWall's `/waf/patterns/stats`), summed over the linked zones, and the size of each zone's IP deny-list (a honeypot hit denies the caller's IP for 24 h). Each zone's numbers are cached for 5 minutes.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| projectId | path | string | yes | The project (any environment) id. |
Request
curl https://iriwdve0.vibecode.cloud/api/v1/tracetap/projects/PROJECT_ID/fluxwall/stats -H "Authorization: Bearer sk_your_api_key"Response
{ "byEntry": { "rpe_…": 97 }, "totalHits": 140, "denies": 12, "zones": [ { "zone": "example.com", "hits": 140, "denies": 12, "error": null } ] }/api/v1/tracetap/rule-pools/:id/proposalsEvery hour TraceTap looks at the open new-endpoint divergences of the last 7 days that no enabled pool matched, groups them by path shape, keeps the probe-like groups (every sample answered non-2xx, or the shape was hit on ≥ 3 projects — and never a shape any endpoint anywhere answered 2xx on), and asks the AI which are scanner probes, in batches: 12 groups per call (TRACETAP_PROPOSALS_BATCH), at most 6 calls per run (TRACETAP_PROPOSALS_MAX_CALLS), most widespread first — the rest wait for the next run. Each proposal is validated in code (compiles under the ReDoS rules; not broad; matches none of /login, /admin, /api, /graphql, /robots.txt, /sitemap.xml, /manifest.json, /openapi.json, /.well-known/* token paths, /feed, /dashboard, /:id, /, /_next/**, /api/**; no route with a 2xx sample; matches what it claims) and stored with `status: "proposed"`. Proposed entries never match in ingest until approved. Lists them with the evidence and `wouldFlag`, the open divergences each would flag right now. `?status=rejected` lists rejected ones. `run` is the pool's latest proposal run (see `GET …/propose/:runId`), or null when none ran since the server started.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | The pool id. |
| status | query | string | no | `proposed` (default) or `rejected`. |
Request
curl https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools/POOL_ID/proposals -H "Authorization: Bearer sk_your_api_key"Response
{
"poolId": "rp_…", "status": "proposed",
"run": { "runId": "prr_…", "status": "running", "candidates": 30, "batch": 2, "batches": 3, "progress": "batch 2/3", "startedAt": "2026-10-29T15:00:00.000Z", "finishedAt": null, "result": null, "error": null },
"proposals": [ { "id": "rpe_…", "match": "glob", "method": "*", "path": "/**/.git-askpass.sh", "category": "vcs",
"status": "proposed", "source": "ai", "rationale": "Git credential helper; scanners look for leaked deploy helpers.",
"evidence": { "shapes": ["/.git-askpass.sh"], "paths": ["/.git-askpass.sh"], "methods": ["GET"], "projects": 2,
"rootIds": ["prj_…"], "statusCodes": { "404": 3 }, "divergences": 3, "hits": 3 },
"projectNames": ["batteringram", "InferMux"], "proposedAt": "2026-10-29T15:00:00.000Z", "wouldFlag": 3 } ]
}/api/v1/tracetap/rule-pools/:id/proposals/:entryId/approveThe proposal becomes `active` and the open new-endpoint divergences it matches (where the pool is ON) are converted to `known-abuse` / `flagged` at once. Optionally edit it first: an edited `path` / `match` must pass the same safety checks as a proposal (400 otherwise).
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | The pool id. |
| entryId | path | string | yes | The proposed entry id. |
| path | body | string | no | Edited path. |
| match | body | string | no | Edited match: prefix | glob | regex. |
| category | body | string | no | Edited category. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools/POOL_ID/proposals/ENTRY_ID/approve -H "Authorization: Bearer sk_your_api_key" -H "Content-Type: application/json" -d '{"category": "vcs"}'Response
{ "entry": { "id": "rpe_…", "path": "/**/.git-askpass.sh", "status": "active" }, "converted": 3 }/api/v1/tracetap/rule-pools/:id/proposals/:entryId/rejectThe proposal becomes `rejected`: it never matches, is never proposed again (nor is anything covering the paths it was proposed for), and later AI runs see it as "not abuse".
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | The pool id. |
| entryId | path | string | yes | The proposed entry id. |
| reason | body | string | no | Why (optional, max 500 chars). |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools/POOL_ID/proposals/ENTRY_ID/reject -H "Authorization: Bearer sk_your_api_key" -H "Content-Type: application/json" -d '{"reason": "Our React server actions live there"}'Response
{ "entry": { "id": "rpe_…", "path": "/_react/action/*", "status": "rejected" } }/api/v1/tracetap/rule-pools/:id/proposeStarts one proposal pass: the system pool over every project, a user pool over its owner's projects only. The candidate groups go to the AI in batches, one call at a time — 12 groups per call (TRACETAP_PROPOSALS_BATCH), at most 6 calls (TRACETAP_PROPOSALS_MAX_CALLS); the rest wait for the next run. Each call takes a minute or two with a reasoning model (timeout TRACETAP_AI_TIMEOUT_MS, 10 min), so this answers **202 at once** with the run — `{ runId, status: "running" }` — and the pass goes on in the background. Poll `GET …/propose/:runId` (every few seconds) until `status` is no longer `running`; `GET …/proposals` also returns the pool's latest run as `run`. One run per pool at a time: while one is going (started here, in the dashboard or by the hourly job) this returns that run with `alreadyRunning: true` and starts nothing. New proposals are stored for review (GET …/proposals); for the system pool the admins get at most one mail per 24 h.
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | The pool id. |
Request
curl -X POST https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools/POOL_ID/propose -H "Authorization: Bearer sk_your_api_key"Response
{
"runId": "prr_…", "poolId": "rp_…", "status": "running", "candidates": null, "batch": null, "batches": null, "progress": null,
"startedAt": "2026-10-29T15:00:00.000Z", "finishedAt": null, "result": null, "error": null,
"alreadyRunning": false
}/api/v1/tracetap/rule-pools/:id/propose/:runIdWhere a run started by `POST …/propose` is. `status`: `running` (`progress`: `batch 2/6`); `done` (at least one batch answered — `result.proposed` holds the new proposals, `result.refused` what the validator threw out and why, `result.batches` one row per AI call — status, groups, proposed, refused, finishReason, ms, error — and `result.totals`; a failed batch — timeout, 5xx, or cut at max_tokens with no parseable answer — is asked again next run, and `result.error` says so); `skipped` (`result.skipped`: `no-candidates` — nothing probe-like in the open backlog — or `ai-unavailable` — no LLM configured, prod/TEST need GLUEDEASY_BASE_URL + GLUEDEASY_API_KEY); `error` (every batch's GluedEasy call failed: 402 no credits, 403 model/profile, 429, 5xx or timeout — or the run itself failed; the reason is in `error`). Runs live in the server's memory: after a restart the id answers 404 (the proposals a finished run stored stay, of course).
| Name | In | Type | Req. | Description |
|---|---|---|---|---|
| id | path | string | yes | The pool id. |
| runId | path | string | yes | The `runId` POST …/propose returned. |
Request
curl https://iriwdve0.vibecode.cloud/api/v1/tracetap/rule-pools/POOL_ID/propose/RUN_ID -H "Authorization: Bearer sk_your_api_key"Response
{
"runId": "prr_…", "poolId": "rp_…", "status": "done", "candidates": 30, "batch": 3, "batches": 3, "progress": null,
"startedAt": "2026-10-29T15:00:00.000Z", "finishedAt": "2026-10-29T15:01:52.000Z", "error": null,
"result": {
"candidates": 30,
"proposed": [ { "id": "rpe_…", "path": "/**/.git-askpass.sh", "status": "proposed" } ],
"refused": [ { "path": "/api/**", "reason": "never-match", "error": "/api/** matches /api, which real apps serve" } ],
"dropped": { "notProbeLike": 0, "realRoute": 1, "covered": 0, "overLimit": 0 },
"batches": [
{ "batch": 1, "groups": 12, "status": "done", "proposed": 1, "refused": 1, "finishReason": "stop", "ms": 61234 },
{ "batch": 2, "groups": 12, "status": "error", "proposed": 0, "refused": 0, "finishReason": "length", "ms": 98012, "error": "The AI ran out of tokens (max_tokens 8000) before it answered." },
{ "batch": 3, "groups": 6, "status": "done", "proposed": 0, "refused": 0, "finishReason": "stop", "ms": 40211 }
],
"totals": { "batches": 3, "done": 2, "failed": 1, "groupsAnswered": 18, "groupsFailed": 12, "proposed": 1, "refused": 1, "ms": 199457 },
"error": "1 of 3 batches failed (their groups are asked again next run): The AI ran out of tokens (max_tokens 8000) before it answered."
}
}