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://tracetap.net/api/v1`. `https://tracetap.net` 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.
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://tracetap.net. 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://tracetap.net/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://tracetap.net/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://tracetap.net/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://tracetap.net/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://tracetap.net/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. 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://tracetap.net/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://tracetap.net/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://tracetap.net/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://tracetap.net/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" }
],
"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://tracetap.net/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://tracetap.net/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://tracetap.net/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://tracetap.net/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://tracetap.net/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://tracetap.net/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://tracetap.net/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", "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. Publish the rules afterwards (dashboard) so probes 1.4.0+ apply it.
| 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 |
| 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://tracetap.net/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.
| 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://tracetap.net/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://tracetap.net/api/v1/tracetap/projects/PROJECT_ID/field-patterns/PATTERN_ID -H "Authorization: Bearer sk_your_api_key"Response
{ "ok": true, "restoredFields": 2 }