> ## Documentation Index
> Fetch the complete documentation index at: https://docs.epode.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Send Telemetry Batches — POST /api/v2/telemetry/batches

> Reference for the SDK telemetry batch endpoint. Ships interaction opportunities asynchronously with your af_live_ product key without blocking responses.

The telemetry batches endpoint receives interaction opportunity metadata that the SDK enqueues locally and ships in the background. Your product responses never wait for this call — the SDK drains its queue asynchronously at a 500 ms interval, or sooner when 50 events accumulate. You do not need to call this endpoint yourself: the SDK handles everything automatically.

<Note>
  The SDK handles batching automatically. You only need this endpoint if you are implementing a custom SDK or porting to a new language.
</Note>

## Endpoint

```
POST /api/v2/telemetry/batches
```

**Base URL:** `https://agent-feedback-api-production.up.railway.app`

## Authorization

Use your company product key. This key stays on your backend and is never passed to customer agents.

```http theme={null}
Authorization: Bearer af_live_<your_product_key>
```

## Queue behavior

The SDK maintains a bounded in-memory queue (default: 1,000 events). When the queue is full, the oldest event is dropped to make room for the new one — product responses are never delayed or failed as a result. Batches are sent with a 3-second HTTP timeout; if delivery fails, the SDK retries each event up to three times before discarding it. A single warning is logged the first time delivery fails.

## Request body

Send a JSON object with a single `events` array. Each batch may contain between 1 and 100 events.

<ParamField body="events" type="array" required>
  An array of interaction telemetry event objects. Minimum 1, maximum 100 per batch.

  <Expandable title="Event object fields">
    <ParamField body="events[].interactionId" type="string" required>
      UUID v4 identifier for the interaction. Generated by the SDK when it prepares the `_agentFeedback` envelope. Used to correlate telemetry with a later outcome review.
    </ParamField>

    <ParamField body="events[].surface" type="string" required>
      The surface through which the agent consumed the product response.

      Allowed values:

      * `"http_json"` — a JSON API response
      * `"http_html"` — an HTML page response
      * `"http_headers"` — a response where the envelope was delivered via headers (arrays, scalars, binary bodies)
      * `"mcp"` — an MCP tool call response
    </ParamField>

    <ParamField body="events[].operation" type="string" required>
      The normalized route path, with dynamic segments replaced by `:id`. For example, `/orders/:id` or `/search`. Must be between 1 and 160 characters.

      The SDK derives this automatically by replacing UUIDs and numeric path segments. If you are building a custom SDK, apply the same normalization before sending.
    </ParamField>

    <ParamField body="events[].classification" type="string" required>
      The initial interaction classification.

      * `"unclassified"` — HTTP surfaces start here; they are promoted to `"confirmed"` when an outcome review arrives.
      * `"confirmed"` — MCP tool calls are confirmed immediately at the time of use.
    </ParamField>

    <ParamField body="events[].occurredAt" type="string" required>
      ISO 8601 timestamp (with timezone) for when the interaction occurred, e.g. `"2026-07-28T04:00:00.000Z"`.
    </ParamField>

    <ParamField body="events[].statusCode" type="number">
      The HTTP status code of the product response, e.g. `200`. Must be between 100 and 599.
    </ParamField>

    <ParamField body="events[].durationMs" type="number">
      How long the product API took to respond, in milliseconds. Must be ≥ 0.
    </ParamField>

    <ParamField body="events[].customerRef" type="string">
      An opaque reference to the customer in your own system, for example a hashed user ID. Must be between 1 and 160 characters. Never send an email address, name, or any other PII.
    </ParamField>

    <ParamField body="events[].runtimeHint" type="string">
      An observed agent runtime identifier, such as a `User-Agent` header value or MCP client name hint. This is explicitly unverified and is never presented as agent identity. Must be between 1 and 200 characters.
    </ParamField>

    <ParamField body="events[].runtimeHintSource" type="string">
      How the runtime hint was observed. Allowed values: `"http"`, `"mcp"`.
    </ParamField>

    <ParamField body="events[].sessionRef" type="string">
      An opaque reference linking this interaction to a broader agent session in your system. Must be between 1 and 160 characters.
    </ParamField>

    <ParamField body="events[].sessionSource" type="string">
      How the session reference was established. Allowed values: `"customer"`, `"mcp"`, `"continuation"`.
    </ParamField>

    <ParamField body="events[].confirmationMethod" type="string">
      How the interaction was confirmed. Currently the only accepted value is `"mcp"`.
    </ParamField>
  </Expandable>
</ParamField>

## Example request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://agent-feedback-api-production.up.railway.app/api/v2/telemetry/batches \
    -H "Authorization: Bearer af_live_<your_product_key>" \
    -H "Content-Type: application/json" \
    -d '{
      "events": [
        {
          "interactionId": "b3e2f1a0-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
          "surface": "http_json",
          "operation": "/search",
          "statusCode": 200,
          "durationMs": 142,
          "classification": "unclassified",
          "occurredAt": "2026-07-28T04:00:00.000Z"
        }
      ]
    }'
  ```

  ```json Request body theme={null}
  {
    "events": [
      {
        "interactionId": "b3e2f1a0-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
        "surface": "http_json",
        "operation": "/search",
        "statusCode": 200,
        "durationMs": 142,
        "classification": "unclassified",
        "occurredAt": "2026-07-28T04:00:00.000Z"
      }
    ]
  }
  ```
</CodeGroup>

## Response

The server returns HTTP `202 Accepted` with a count of how many events were stored.

```json theme={null}
{
  "accepted": 1,
  "dropped": 0
}
```

<ResponseField name="accepted" type="number">
  The number of events from this batch that were successfully ingested.
</ResponseField>

<ResponseField name="dropped" type="number">
  The number of events from this batch that were discarded (for example, because they failed internal validation). Currently always `0` for valid batches.
</ResponseField>

## Error responses

| Status                     | Cause                                                                                                           |
| -------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`          | Malformed JSON, unknown fields at the batch or event level, or a field value outside the allowed schema.        |
| `401 Unauthorized`         | The `Authorization` header is missing, malformed, or the product key has been revoked.                          |
| `422 Unprocessable Entity` | The request is valid JSON but fails schema validation — for example, a `surface` value not in the allowed enum. |
| `503 Service Unavailable`  | Transient server issue. The SDK will retry automatically.                                                       |

## Implementing a custom SDK

If you are porting Agent Feedback to a new language, your implementation must satisfy these requirements for the telemetry queue:

1. **Non-blocking** — enqueue the event synchronously; flush asynchronously. Never `await` the HTTP call inside a request handler.
2. **Bounded** — cap the queue at a fixed size (recommended: 1,000 events). Drop the oldest event when the queue is full rather than growing unbounded.
3. **Batched** — accumulate events and flush up to 50 at a time. Flush when 50 events accumulate, or after at most 500 ms, whichever comes first.
4. **Retry-limited** — retry failed batches up to 3 times. Discard after that to avoid memory growth.
5. **Schema-conformant** — normalize operation paths (replace UUIDs and bare numeric segments with `:id`) and only send the fields defined in `telemetry-batch.schema.json`.

See the [protocol conformance requirements](https://agent-feedback-api-production.up.railway.app/.well-known/agent-feedback-v1.json) for the full list.
