REST API

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.

Open /docs/llm/api.md

Getting started

Base URL

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.

Authentication

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.

Content type

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`.

Probe endpoints

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.

Rate limiting

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

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.

Endpoints

GET
/api/v1/health

Health check

Confirms the API is up and your key is valid. Handy as a first call to verify credentials and connectivity.

Auth: Bearer tokenAccess: Any valid API key.

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"
}
GET
/api/v1/stats

Account & API usage stats

Returns the calling user together with API-usage counters (requests today / this week / this month, error rate, API-key count).

Auth: Bearer tokenAccess: Any valid API key (scoped to the key owner).

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" }
}
GET
/api/v1/users

List users

Lists users. A regular key returns only its own user record; an admin key returns all users with pagination.

Auth: Bearer tokenAccess: Any valid API key (admin keys see all users; others see themselves).
NameInTypeReq.Description
limitqueryintegernoPage size, 1–100 (default 10). Admin only; ignored for non-admins.
offsetqueryintegernoRows 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" }
}
POST
/api/v1/users

Create user (scaffold)

Admin-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.

Auth: Bearer tokenAccess: Admin API keys only (others get 403).
NameInTypeReq.Description
emailbodystringyesNew user email.
namebodystringyesNew user display name.
rolebodystringno'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"
}
  • This is a template stub — no user is actually created yet.
POST
/api/v1/files

Upload a file

Uploads 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.

Auth: Bearer tokenAccess: Any valid API key (the file is owned by the key owner).
NameInTypeReq.Description
fileformfileyesThe 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_..."
}
  • Default max size is 100MB (configurable via MAX_FILE_SIZE). Oversized uploads return 413 — at once when Content-Length is already over the limit, otherwise as soon as the limit is crossed.
  • The upload is streamed to the storage backend as it arrives, never buffered whole; only the first `file` part is stored.
  • At most 4 uploads are processed at once; past that the endpoint returns 503 with `Retry-After`.
  • The returned `url` is the Bearer-gated download endpoint below.
GET
/api/v1/files/:id

Download / preview a file

Streams 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.

Auth: Bearer tokenAccess: Any valid API key.
NameInTypeReq.Description
idpathstringyesFile 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-file

Response

Raw binary body with the stored `Content-Type` and `Content-Disposition: inline; filename="..."`. Returns `404 { "error": "File not found" }` if unknown.
DELETE
/api/v1/files/:id

Delete a file

Deletes a file owned by the calling key.

Auth: Bearer tokenAccess: Any valid API key (only the owner may delete).
NameInTypeReq.Description
idpathstringyesFile 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
POST
/api/v1/tracetap/ingest

Submit sampled traffic (probe)

Where 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.

Auth: Probe key + project headerAccess: A probe key belonging to the project named in X-TraceTap-Project.
NameInTypeReq.Description
X-TraceTap-ProjectheaderstringyesThe project key (pk_...).
messagesbodyarrayyesCaptured 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").
connectionIdbodystringnoPins these messages to one connection on the topology.
probeVersionbodystringnoVersion of the probe, recorded for support.
statsbodyobjectnoTraffic 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
}
  • To encrypt, send { "v": 1, "iv": "...", "ct": "...", "tag": "..." } — AES-256-GCM under the project communication key, wrapping the JSON body above.
  • At most 100 messages are processed per request; the rest are ignored.
  • The request body is capped at 8 MB; a larger one returns 413 without being read further.
  • `rulesVersion` in the response tells the probe whether a newer rules document exists.
  • `stats` counters are deltas since the probe last reported, not running totals. Send them again if a request fails, or that window is lost.
  • The counters are plain numbers — no payload content — so reporting them does not weaken the guarantee that nothing sensitive leaves the probe.
GET
/api/v1/tracetap/rules

Fetch the deterministic generation rules (probe)

Returns 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.

Auth: Probe key + project headerAccess: A probe key belonging to the project named in X-TraceTap-Project.
NameInTypeReq.Description
X-TraceTap-ProjectheaderstringyesThe project key (pk_...).
sincequeryintegernoRules 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"]
}
  • Returns 204 with no body when `since` is 1 or higher and already matches the current version.
  • A field carrying `pattern` is a field pattern: it applies to every pointer the pattern matches (`{n}` = digits, `*` = any characters within one key segment, `**` = any number of segments, `re:` = an anchored regex). An exact `pointer` rule always wins over a pattern; then an endpoint rule beats a `*` one; then the most specific pattern. Probes older than 1.4.0 ignore pattern rules (`pointer` repeats the pattern text, which no real pointer equals), so those fields fall to `unknownFieldPolicy` — masked by default.
  • `unknownFieldPolicy` is what a probe applies to any field not listed: the default, `mask`, is why an unreviewed field never leaves in the clear.
  • `children` (probe 1.5.0+) is the children mode of a `keep` on an object/array. `mask-all` (sent as `"action": "mask"`) keeps the structure and masks every leaf below, whatever `unknownFieldPolicy` says; explicit rules below still apply and a child `keep` un-masks that one leaf. `keep-all` (sent as `"action": "keep"`) passes the subtree as-is. The default mode, `rules`, is never sent: the container simply has no rule, so it is walked like any other. A 1.5.0 probe treats a container `keep` without `children` as `rules`; an older probe masks a `mask-all` container whole (fail closed) and only ever sees a bare container `keep` for `keep-all`.
POST
/api/v1/tracetap/heartbeat

Probe liveness and error reporting (probe)

Marks 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.

Auth: Probe key + project headerAccess: A probe key belonging to the project named in X-TraceTap-Project.
NameInTypeReq.Description
X-TraceTap-ProjectheaderstringyesThe project key (pk_...).
versionbodystringnoProbe version string.
errorbodystringnoA local failure worth surfacing in the Pulse feed.
statsbodyobjectnoThe 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.
metabodyobjectnoFree-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
}
GET
/api/v1/tracetap/apis

Search the API inventory across all your projects

Every 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.

Auth: Bearer tokenAccess: Projects you own, projects shared with you (every environment), everything for admins.
NameInTypeReq.Description
qquerystringnoe.g. `openrouter`, `VA5`, `8ca`. Do NOT put a full key here (query strings are logged) — use POST /api/v1/tracetap/apis/search.
statusquerystringnoComma-separated: learned, new, approved, blocked, forbidden.
limitquerynumbernoMax 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" }
  ]
}
  • status: learned (seen while the environment was learning), new (first seen after it was locked), approved, blocked. `forbidden` is true when a fleet deny rule matches.
  • "Blocked" is recorded and alerted on every call; enforcing it at the network edge is coming soon (`enforcement: "record-only"`).
  • Counts with `sampledOnly: true` come from a probe older than 1.2.0, which only reports sampled calls.
POST
/api/v1/tracetap/apis/search

Search the API inventory (query in the body; find where a key is used)

The 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.

Auth: Bearer tokenAccess: As GET /api/v1/tracetap/apis.
NameInTypeReq.Description
qbodystringnoSearch text, or a full key.
statusbodystring[]nolearned, new, approved, blocked, forbidden.
limitbodynumbernoMax 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 } ] } ] }
POST
/api/v1/tracetap/apis/:id/decision

Approve, block or reset one API

Decides 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.

Auth: Bearer tokenAccess: Owner, admin or editor of the project.
NameInTypeReq.Description
idpathstringyesThe API row id from the search.
decisionbodystringyesapprove | block | reset
notebodystringnoWhy — 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" }
GET
/api/v1/tracetap/api-rules

List fleet API rules

Hosts forbidden (deny) or known-good (allow) across every project of their owner, with the projects each rule does not apply to.

Auth: Bearer tokenAccess: Your own rules, plus those of the owners of projects shared with you.

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 }
  ]
}
POST
/api/v1/tracetap/api-rules

Forbid or allow a host across all your projects

deny: 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.

Auth: Bearer tokenAccess: Any user; the rule applies to the projects you own.
NameInTypeReq.Description
patternbodystringyesHost, host:port or *.domain.
verdictbodystringyesdeny | allow
notebodystringnoWhy.
exceptRootIdsbodystring[]noRoot 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": [] } }
  • MCP: the same search, decisions and rules are tools on POST /api/mcp (streamable HTTP, Bearer sk_ key): search_apis, list_flagged_apis, decide_api, list_api_rules, add_api_rule, delete_api_rule (plus get_rules and publish_rules for a project's field rules).
GET
/api/v1/tracetap/projects/:projectId/field-patterns

List the field patterns of a project environment

Field 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.

Auth: Bearer tokenAccess: Anyone who can see the project (owner, members, admins).
NameInTypeReq.Description
projectIdpathstringyesThe 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 }
  ]
}
POST
/api/v1/tracetap/projects/:projectId/field-patterns

Create a field pattern ("resolve with pattern")

Creates 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.

Auth: Bearer tokenAccess: Owner, admin or editor of the project.
NameInTypeReq.Description
projectIdpathstringyesThe environment (project) id.
patternbodystringyese.g. `$.data.content[].f{n}`, `$.byId.*`, `re:^\$\.data\.f\d+$`.
endpointIdbodystring|nullnoLimit to one endpoint; omit/null for every endpoint (`*`).
directionbodystringyesrequest | response
sectionbodystringnobody (default) | headers | query
jsonTypebodystringnoExpected type, may be a union (`number|string`); empty = any. Values of another type raise type-change.
classificationbodystringyesunknown | public | internal | pii | secret
actionbodystringyeskeep | mask | generate | drop
childrenModebodystring|nullnoOnly 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`.
generatorIdbodystringnoGenerator of this project, required for `generate`.
labelbodystringnoPlain-language description.
dryRunbodybooleannoOnly 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
}
  • dryRun answers `{ "preview": { "error": null, "divergenceCount": 14, "divergences": […], "fieldCount": 2, "fields": […], "suggestedType": "number|string" } }`.
  • Errors (bad syntax, unsafe regex, unknown endpoint/generator, duplicate) answer 400 with `{ "error": "…" }`.
PATCH
/api/v1/tracetap/projects/:projectId/field-patterns/:id

Change a field pattern

Replaces 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).

Auth: Bearer tokenAccess: Owner, admin or editor of the project.
NameInTypeReq.Description
projectIdpathstringyesThe environment (project) id.
idpathstringyesThe 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 }
DELETE
/api/v1/tracetap/projects/:projectId/field-patterns/:id

Delete a field pattern

The 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.

Auth: Bearer tokenAccess: Owner, admin or editor of the project.
NameInTypeReq.Description
projectIdpathstringyesThe environment (project) id.
idpathstringyesThe 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 }
GET
/api/v1/tracetap/projects/:projectId/rules

The rules version published to the probes

The 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).

Auth: Bearer tokenAccess: Anyone who can see the project (owner, members, admins).
NameInTypeReq.Description
projectIdpathstringyesThe environment (project) id.
docquerystringno`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
}
  • `fieldCount` counts every rule in the document, `patternCount` of them pattern rules.
  • With `?doc=1` the body also has `doc`: `{ format, projectKey, version, publishedAt, sampleEveryN, unknownFieldPolicy, generators, fields, redactHeaders }`.
POST
/api/v1/tracetap/projects/:projectId/rules/publish

Publish the rules to the probes (or dry-run the diff)

Compiles 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.

Auth: Bearer tokenAccess: Owner, admin or editor of the project (a dry run: anyone who can see it). View-only roles get 403; an unknown or foreign project 404.
NameInTypeReq.Description
projectIdpathstringyesThe environment (project) id.
notesbodystringnoWhy — stored with the version (max 500 chars).
dryRunbodybooleannoOnly 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 }
  • Unchanged: `{ "published": false, "version": 12, "fieldCount": 214, "generatorCount": 2, "patternCount": 3, "reason": "Nothing changed since the last publish." }`.
  • dryRun answers `{ "dryRun": true, "published": false, "wouldPublish": true, "version": 13, "fieldCount": 215, "generatorCount": 2, "patternCount": 4, "diff": { "baseVersion": 12, "fields": { "added": [], "removed": [{ "endpoint": "GET /api/lists/:id/rows", "direction": "response", "section": "body", "pointer": "$.data.title", "classification": "internal", "action": "keep", "childrenMode": null, "generatorId": null }], "changed": [{ "before": {…, "action": "keep" }, "after": {…, "action": "mask" }, "changed": ["action"] }] }, "patterns": { "added": [{ "endpoint": "*", "pointer": "$.data.content[].f{n}", "pattern": "$.data.content[].f{n}", … }], "removed": [], "changed": [] }, "generators": { "added": [], "removed": [], "changed": [] }, "settings": [], "counts": { "fieldsAdded": 0, "fieldsRemoved": 1, "fieldsChanged": 1, "patternsAdded": 1, "patternsRemoved": 0, "patternsChanged": 0, "generatorsChanged": 0, "settingsChanged": 0, "total": 3 }, "truncated": false } }`. Lists hold at most 500 entries each (`truncated: true`); counts are exact.
  • Rate limited (`tracetap.rules.publish`, dry runs included): 429 with `{ "error": "…" }`.
  • MCP: the same as tools `get_rules` and `publish_rules` (with `dry_run`) on POST /api/mcp.
GET
/api/v1/tracetap/rule-pools

List known-abuse rule pools

A 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.

Auth: Bearer tokenAccess: Any signed-in API key.

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" }
  ]
}
POST
/api/v1/tracetap/rule-pools

Create a rule pool

Creates 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.

Auth: Bearer tokenAccess: Any signed-in API key; `system` needs a superadmin.
NameInTypeReq.Description
namebodystringyesUp to 120 characters.
descriptionbodystringnoFree text.
defaultEnabledbodybooleannoON for your projects without an explicit switch (default true).
aiAutoApplybodybooleannoLet AI triage add its suggested paths straight away instead of waiting for you (default false).
entriesbodyarraynoEntries 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 }
  • Rate limit `tracetap.rule_pools` is warn-mode by default: past it a change still goes through, with an `X-RateLimit-Warning` header; an admin can make it block (429).
GET
/api/v1/tracetap/rule-pools/:id

Get a rule pool with its entries

The pool, its entries (active and AI-suggested) and its state on each project you can see.

Auth: Bearer tokenAccess: Anyone who can see the pool.
NameInTypeReq.Description
idpathstringyesThe 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 } ]
}
PATCH
/api/v1/tracetap/rule-pools/:id

Change a rule pool

Name, description, defaultEnabled, aiAutoApply (user pools only). Turning defaultEnabled on flags the backlog it now covers.

Auth: Bearer tokenAccess: The pool's owner; system pools: admins and superadmins (403 otherwise).
NameInTypeReq.Description
idpathstringyesThe 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 }
DELETE
/api/v1/tracetap/rule-pools/:id

Delete a rule pool

Tombstones the pool. What it already flagged stays flagged; nothing is re-opened.

Auth: Bearer tokenAccess: The pool's owner; system pools: admins and superadmins.
NameInTypeReq.Description
idpathstringyesThe 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 }
POST
/api/v1/tracetap/rule-pools/:id/entries

Add entries to a rule pool

One 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.

Auth: Bearer tokenAccess: The pool's owner; system pools: admins and superadmins.
NameInTypeReq.Description
idpathstringyesThe pool id.
matchbodystringnoprefix | glob (default) | regex
methodbodystringnoHTTP method, or `*` (default) for any.
pathbodystringyese.g. `/**/.env`, `/wp-admin/**`, `/HNAP1`.
categorybodystringnosecrets | wordpress | php | vcs | cloud-creds | admin-panels | other (default)
notebodystringnoWhy it is abuse.
entriesbodyarraynoSeveral 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 }
  • Errors (bad glob, unsafe regex, a pattern that would match everything) answer 400 with `{ "error": "Entry 2: …" }`.
PATCH
/api/v1/tracetap/rule-pools/:id/entries/:entryId

Change a rule pool entry

Same fields as when adding. `"status": "active"` accepts an entry AI triage suggested (it then starts matching and flags the backlog).

Auth: Bearer tokenAccess: The pool's owner; system pools: admins and superadmins.
NameInTypeReq.Description
idpathstringyesThe pool id.
entryIdpathstringyesThe entry id.
statusbodystringno`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 }
DELETE
/api/v1/tracetap/rule-pools/:id/entries/:entryId

Delete a rule pool entry

Tombstones the entry (a seeded system entry is never re-added). Nothing already flagged is re-opened.

Auth: Bearer tokenAccess: The pool's owner; system pools: admins and superadmins.
NameInTypeReq.Description
idpathstringyesThe pool id.
entryIdpathstringyesThe 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 }
GET
/api/v1/tracetap/projects/:projectId/rule-pools

Rule pools of a project, on or off

Every 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.

Auth: Bearer tokenAccess: Anyone who can see the project.
NameInTypeReq.Description
projectIdpathstringyesThe 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 } ] }
PUT
/api/v1/tracetap/projects/:projectId/rule-pools/:poolId

Switch a rule pool on or off for a project

Sets 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.

Auth: Bearer tokenAccess: Owner, admin or editor of the project (403 otherwise — also for an unknown project id).
NameInTypeReq.Description
projectIdpathstringyesThe project (any environment) id.
poolIdpathstringyesThe pool id.
enabledbodybooleanyestrue = 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 }
POST
/api/v1/tracetap/projects/:projectId/mark-abuse

Mark a path as known abuse ("Mark as abuse…")

Adds 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.

Auth: Bearer tokenAccess: Owner, admin or editor of the project; the pool must be yours (system pools: admins and superadmins).
NameInTypeReq.Description
projectIdpathstringyesThe project (any environment) id.
pathbodystringyese.g. `/wp-admin/**`.
matchbodystringnoprefix | glob (default) | regex
methodbodystringnoHTTP method or `*` (default).
categorybodystringnoAs for entries.
notebodystringnoWhy.
poolIdbodystringnoOne of your pools…
newPoolNamebodystringno…or the name of a new one.
divergenceIdbodystringnoThe new-endpoint divergence you started from; the answer says whether it matched.
dryRunbodybooleannoOnly 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 }
  • dryRun answers `{ "preview": { "error": null, "matches": [ { "id": "…", "environment": "Production", "title": "New endpoint GET /wp-admin/maint", "path": "/wp-admin/maint" } ], "matchesDivergence": true } }`.
GET
/api/v1/tracetap/projects/:projectId/abuse

Known-abuse attempts of a project

The 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.

Auth: Bearer tokenAccess: Anyone who can see the project.
NameInTypeReq.Description
projectIdpathstringyesThe 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 } } ] } ] } ]
}
GET
/api/v1/tracetap/fluxwall

Your FluxWall connection

Whether 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).

Auth: Bearer tokenAccess: Any signed-in user (your own connection only).
NameInTypeReq.Description
zonesquerystringno`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
}
GET
/api/v1/tracetap/projects/:projectId/fluxwall

FluxWall WAF settings of a project

What 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.

Auth: Bearer tokenAccess: Anyone who can see the project.
NameInTypeReq.Description
projectIdpathstringyesThe 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"] } ]
}
PATCH
/api/v1/tracetap/projects/:projectId/fluxwall

Set a project's FluxWall mode and category actions

Changes 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.

Auth: Bearer tokenAccess: Owner, admin or editor of the project (403 otherwise).
NameInTypeReq.Description
projectIdpathstringyesThe project (any environment) id.
modebodystringnooff | log | enforce
categoryActionsbodyobjectno`{ "<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" } }
  • Rate limited by `tracetap.fluxwall` (429 when exceeded).
POST
/api/v1/tracetap/projects/:projectId/fluxwall/links

Link FluxWall zones to a project

Links 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.

Auth: Bearer tokenAccess: Owner, admin or editor of the project, with a FluxWall connection (409 without one or when it needs reconnecting).
NameInTypeReq.Description
projectIdpathstringyesThe project (any environment) id.
zoneDomainIdsbodystring[]yesFluxWall 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": [] }
POST
/api/v1/tracetap/projects/:projectId/fluxwall/sync

Sync a project to FluxWall now

Pushes 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.

Auth: Bearer tokenAccess: Owner, admin or editor of the project.
NameInTypeReq.Description
projectIdpathstringyesThe 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 } ] }
  • Rate limited by `tracetap.fluxwall`.
GET
/api/v1/tracetap/projects/:projectId/fluxwall/stats

FluxWall hits and deny-list of a project

FluxWall'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.

Auth: Bearer tokenAccess: Anyone who can see the project.
NameInTypeReq.Description
projectIdpathstringyesThe 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 } ] }
GET
/api/v1/tracetap/rule-pools/:id/proposals

AI-proposed entries of a rule pool

Every 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.

Auth: Bearer tokenAccess: System pool: admins and superadmins; a user pool: its owner (403 otherwise).
NameInTypeReq.Description
idpathstringyesThe pool id.
statusquerystringno`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 } ]
}
POST
/api/v1/tracetap/rule-pools/:id/proposals/:entryId/approve

Approve an AI-proposed entry

The 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).

Auth: Bearer tokenAccess: System pool: admins and superadmins; a user pool: its owner (403 otherwise).
NameInTypeReq.Description
idpathstringyesThe pool id.
entryIdpathstringyesThe proposed entry id.
pathbodystringnoEdited path.
matchbodystringnoEdited match: prefix | glob | regex.
categorybodystringnoEdited 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 }
  • A body is optional. An entry that is not `proposed` (already approved or rejected) answers 400.
POST
/api/v1/tracetap/rule-pools/:id/proposals/:entryId/reject

Reject an AI-proposed entry

The 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".

Auth: Bearer tokenAccess: System pool: admins and superadmins; a user pool: its owner (403 otherwise).
NameInTypeReq.Description
idpathstringyesThe pool id.
entryIdpathstringyesThe proposed entry id.
reasonbodystringnoWhy (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" } }
POST
/api/v1/tracetap/rule-pools/:id/propose

Start the AI proposer for a pool now (async, 202)

Starts 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.

Auth: Bearer tokenAccess: System pool: admins and superadmins; a user pool: its owner (403 otherwise).
NameInTypeReq.Description
idpathstringyesThe 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
}
  • Status 202 (Accepted). `candidates` is null while the candidates are still being collected (well under a second), then the number of groups the AI is looking at. `progress` (`"batch 2/6"`), `batch` and `batches` say which AI call is going.
  • Rate limit `tracetap.rule_pools_ai` (warn by default: past it a run still goes, with an `X-RateLimit-Warning` header).
  • MCP: `propose_rule_pool_entries` (returns the run at once), `get_rule_pool_proposal_run`, `list_rule_pool_proposals`, `approve_rule_pool_proposal`, `reject_rule_pool_proposal`.
GET
/api/v1/tracetap/rule-pools/:id/propose/:runId

Status of a proposal run

Where 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).

Auth: Bearer tokenAccess: System pool: admins and superadmins; a user pool: its owner (403 otherwise).
NameInTypeReq.Description
idpathstringyesThe pool id.
runIdpathstringyesThe `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."
  }
}
  • 404 for an unknown run, or a run of another pool.