# REST API — LLM Integration Guide

You are an autonomous agent that has been given a **base URL** and an **API key** for the service documented below. This document is self-contained: it tells you everything needed to call the API. Follow it literally.

- **Base URL:** `http://tracetap.net`
- **API key:** provided to you separately; it starts with `sk_`.
- **All paths below are relative to the base URL** (e.g. `http://tracetap.net/api/v1/health`).

> Generated from the app's single source of truth. If an endpoint
> behaves differently from what you read here, trust the live response
> and report the mismatch — the docs are meant to be authoritative.

## Essentials

### Base URL

All endpoints live under `http://tracetap.net/api/v1`. `http://tracetap.net` 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.

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

## Quick start

```bash
# 1. Verify your key works
curl http://tracetap.net/api/v1/health -H "Authorization: Bearer sk_your_key_here"
# A 200 with {"status":"healthy",...} means you are authenticated.
```

## 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:** `Authorization: Bearer sk_...` required
- **Access:** Any valid API key.

**Request**

```bash
curl http://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"
}
```

---

### 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:** `Authorization: Bearer sk_...` required
- **Access:** Any valid API key (scoped to the key owner).

**Request**

```bash
curl http://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" }
}
```

---

### 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:** `Authorization: Bearer sk_...` required
- **Access:** Any valid API key (admin keys see all users; others see themselves).

**Parameters**

| Name | In | Type | Required | 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**

```bash
curl "http://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" }
}
```

---

### 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:** `Authorization: Bearer sk_...` required
- **Access:** Admin API keys only (others get 403).

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `email` | body | string | yes | New user email. |
| `name` | body | string | yes | New user display name. |
| `role` | body | string | no | 'user' (default) or 'admin'. |

**Request**

```bash
curl -X POST http://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"
}
```

> 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:** `Authorization: Bearer sk_...` required
- **Access:** Any valid API key (the file is owned by the key owner).

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `file` | form | file | yes | The file to upload (multipart field name must be "file"). |

**Request**

```bash
curl -X POST http://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_..."
}
```

> Default max size is 100MB (configurable via MAX_FILE_SIZE). Oversized uploads return 413.
>
> 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. 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:** `Authorization: Bearer sk_...` required
- **Access:** Any valid API key.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | File id returned by the upload endpoint. |

**Request**

```bash
curl http://tracetap.net/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:** `Authorization: Bearer sk_...` required
- **Access:** Any valid API key (only the owner may delete).

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | File id to delete. |

**Request**

```bash
curl -X DELETE http://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
```

---

### 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:** `Authorization: Bearer pt_...` **and** `X-TraceTap-Project: pk_...` required (probe credentials, not a user API key)
- **Access:** A probe key belonging to the project named in X-TraceTap-Project.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-TraceTap-Project` | header | string | yes | The project key (pk_...). |
| `messages` | body | array | yes | Captured messages. Each may carry method, path, statusCode, durationMs, protocol, operation, requestHeaders, responseHeaders, requestBody, responseBody, error and rulesVersion. |
| `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 }. 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. |

**Request**

```bash
curl -X POST http://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
}
```

> 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.
>
> `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:** `Authorization: Bearer pt_...` **and** `X-TraceTap-Project: pk_...` required (probe credentials, not a user API key)
- **Access:** A probe key belonging to the project named in X-TraceTap-Project.

**Parameters**

| Name | In | Type | Required | 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**

```bash
curl "http://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_..." }
  ],
  "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.
>
> `unknownFieldPolicy` is what a probe applies to any field not listed: the default, `mask`, is why an unreviewed field never leaves in the clear.

---

### 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:** `Authorization: Bearer pt_...` **and** `X-TraceTap-Project: pk_...` required (probe credentials, not a user API key)
- **Access:** A probe key belonging to the project named in X-TraceTap-Project.

**Parameters**

| Name | In | Type | Required | 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**

```bash
curl -X POST http://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
}
```

---

## Notes for automated callers

- Always send the `Authorization: Bearer sk_...` header; there is no cookie/session auth here.
- On `429`/`403` with a `Retry-After` header, wait that many seconds before retrying.
- Treat any non-2xx JSON `error` field as the human-readable failure reason.
- File downloads (`GET /api/v1/files/:id`) return raw bytes, not JSON.
