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

# POST /v1/traces/batch — send a trace event batch

> Send a gzip-compressed batch of trace and event objects to Mortem. This is the primary write path used by the SDK on every flush cycle.

The batch endpoint is the main write path for agent trace data. Every time the SDK flushes its internal buffer — on a timer, when the buffer fills, or when `mortem.close()` is called — it serializes the queued trace and event objects, compresses them with gzip, and sends them here. You can call this endpoint directly if you are building your own instrumentation or sending traces from outside the SDK.

```
POST https://ingest.mortem.dev/v1/traces/batch
```

## Request headers

| Header             | Value                     | Required                |
| ------------------ | ------------------------- | ----------------------- |
| `Authorization`    | `Bearer <MORTEM_API_KEY>` | Yes                     |
| `Content-Type`     | `application/json`        | Yes                     |
| `Content-Encoding` | `gzip`                    | When body is compressed |

<Note>
  The SDK always sends gzip-compressed bodies and includes `Content-Encoding: gzip`. If you are calling the endpoint directly without compression, omit the `Content-Encoding` header and send plain JSON. Both are accepted.
</Note>

## Request body

The request body is a JSON object that matches `TransportBatchPayload`.

<ParamField body="batchId" type="string" required>
  A unique identifier for this batch, used for idempotency and deduplication. The SDK generates a ULID for each flush. If you are calling the API directly, generate a ULID or UUID per request.
</ParamField>

<ParamField body="items" type="BufferBatchItem[]" required>
  An array of trace-and-events bundles to ingest. Each element must contain a full `trace` object and the array of `events` captured since the last flush. The array must contain at least one item.
</ParamField>

<ParamField body="agentId" type="string">
  The agent ID the batch belongs to. Include this when you also include `verifyToken` — it lets the ingest service match the token to the correct agent record. In all other cases the agent is identified by the API key alone and this field is ignored.
</ParamField>

<ParamField body="verifyToken" type="string">
  A one-time token sent on the first ever flush from a new agent. The dashboard generates this token during onboarding and uses it to confirm your agent is live and the API key is correctly configured. After the dashboard marks the agent as verified, you can remove the token from your environment — it is consumed and cannot be reused.
</ParamField>

### `items[].trace`

Each `trace` object is a complete snapshot of the trace's current state at flush time. The ingest service upserts on `id`, so partial updates from intermediate flushes are safe — later flushes overwrite earlier ones for the same trace ID.

<ParamField body="id" type="string" required>
  The trace's unique identifier. The SDK generates a ULID when the session starts. This ID is stable across all flushes for the same session — it is the primary key used for upsert.
</ParamField>

<ParamField body="agentId" type="string" required>
  The agent that owns this trace. The ingest service overwrites this with the agent resolved from the API key, so the value you send is effectively ignored — but the field must be present and a non-empty string.
</ParamField>

<ParamField body="status" type="string" required>
  The trace's current lifecycle state. One of `"running"`, `"completed"`, `"errored"`, or `"timeout"`. In-progress flushes use `"running"`. The final flush from `session.complete()` or `session.fail()` sets this to `"completed"` or `"errored"` respectively.
</ParamField>

<ParamField body="startedAt" type="string" required>
  ISO 8601 datetime string for when the trace started. This is the timestamp of the `mortem.startSession()` call (or the `startedAt` value you passed in `SessionOptions`).
</ParamField>

<ParamField body="inputSummary" type="string" required>
  The human-readable description of what this agent run was attempting to do. This is the value you pass as `inputSummary` in `SessionOptions` and becomes the trace title in the dashboard.
</ParamField>

<ParamField body="endedAt" type="string">
  ISO 8601 datetime string for when the trace ended. `null` while the trace is still running.
</ParamField>

<ParamField body="durationMs" type="number">
  Wall-clock duration of the trace in milliseconds. `null` while still running.
</ParamField>

<ParamField body="outputSummary" type="string">
  The output summary written by `session.complete(outputSummary)`. `null` if not yet set.
</ParamField>

<ParamField body="errorMessage" type="string">
  The error message written by `session.fail(error)`. `null` unless the trace ended in an error.
</ParamField>

<ParamField body="tags" type="string[]" required>
  Labels attached to the trace. Pass an empty array if you did not specify tags in `SessionOptions`.
</ParamField>

<ParamField body="eventCount" type="number" required>
  Total number of events recorded on this trace so far.
</ParamField>

<ParamField body="totalTokens" type="number" required>
  Cumulative token count across all `llm_call` events on this trace.
</ParamField>

<ParamField body="totalCostUsd" type="string" required>
  Cumulative estimated LLM cost as a decimal string (for example `"0.0042"`).
</ParamField>

<ParamField body="totalLamports" type="string" required>
  Cumulative lamports transferred across all `solana_tx` events, serialized as a decimal string because JSON cannot represent `bigint` natively.
</ParamField>

<ParamField body="solanaTxCount" type="number" required>
  Number of `solana_tx` events on this trace.
</ParamField>

<ParamField body="toolsCalled" type="string[]" required>
  Deduplicated list of tool names called on this trace via `tool_call` events.
</ParamField>

<ParamField body="anchorSignature" type="string">
  On-chain anchor signature if the trace was anchored. Reserved for future on-chain anchoring features. Pass `null`.
</ParamField>

<ParamField body="anchorSlot" type="string">
  Solana slot at which the trace was anchored, serialized as a decimal string. Reserved for future on-chain anchoring features. Pass `null`.
</ParamField>

<ParamField body="merkleProof" type="string">
  Merkle proof string for the trace hash. Reserved for future integrity verification features. Pass `null`.
</ParamField>

<ParamField body="traceHash" type="string">
  Deterministic hash of the trace content. Reserved for future integrity verification features. Pass `null`.
</ParamField>

<ParamField body="shareToken" type="string">
  Share token for the public trace URL if the trace has been shared from the dashboard. `null` if not shared.
</ParamField>

### `items[].events`

Each element in the `events` array represents a single instrumented step in the agent run. Pass an empty array when flushing a trace state update with no new events.

<ParamField body="id" type="string" required>
  Unique identifier for this event. The SDK generates a ULID.
</ParamField>

<ParamField body="traceId" type="string" required>
  The ID of the trace this event belongs to. Must match `items[].trace.id`.
</ParamField>

<ParamField body="sequence" type="number" required>
  Monotonically increasing integer that determines the display order of events in the trace timeline. The SDK assigns sequence numbers from a per-session counter starting at `0`.
</ParamField>

<ParamField body="type" type="string" required>
  The event category. One of `"llm_call"`, `"tool_call"`, `"solana_tx"`, `"x402_payment"`, `"mcp_call"`, or `"custom"`. The dashboard renders each type with a distinct icon and payload layout.
</ParamField>

<ParamField body="startedAt" type="string" required>
  ISO 8601 datetime string for when this event started.
</ParamField>

<ParamField body="payload" type="object" required>
  The event-specific payload object. The shape depends on `type`. See the [event types reference](/reference/event-types) for the full payload schema for each type.
</ParamField>

<ParamField body="status" type="string" required>
  The event's terminal status. Use `"ok"` for success and `"error"` for failure.
</ParamField>

<ParamField body="parentEventId" type="string">
  The ID of the parent event, if this event is nested inside another. `null` for top-level events.
</ParamField>

<ParamField body="endedAt" type="string">
  ISO 8601 datetime string for when this event completed. `null` for in-progress events.
</ParamField>

<ParamField body="durationMs" type="number">
  Duration of the event in milliseconds. `null` for in-progress events.
</ParamField>

<ParamField body="errorMessage" type="string">
  Error message if the event failed. `null` on success.
</ParamField>

<ParamField body="payloadEncrypted" type="boolean" required>
  `true` if the payload was encrypted with `MORTEM_MASTER_KEY` before transmission. The dashboard decrypts it transparently when you have the key configured.
</ParamField>

## Response

On success the endpoint returns `202 Accepted` with the ID of the first trace in the batch.

```json theme={null}
{ "traceId": "01J8XKZP4N0000000000000000" }
```

## Example

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://ingest.mortem.dev/v1/traces/batch \
    -H "Authorization: Bearer $MORTEM_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "batchId": "01J8XKZP4N0000000000000001",
      "items": [
        {
          "trace": {
            "id": "01J8XKZP4N0000000000000000",
            "agentId": "01J8AGENT00000000000000000",
            "status": "running",
            "startedAt": "2025-01-15T10:30:00.000Z",
            "endedAt": null,
            "durationMs": null,
            "inputSummary": "Evaluate whether to swap 1 SOL for JUP",
            "outputSummary": null,
            "errorMessage": null,
            "eventCount": 1,
            "totalTokens": 312,
            "totalCostUsd": "0.0004",
            "totalLamports": "0",
            "solanaTxCount": 0,
            "toolsCalled": [],
            "anchorSignature": null,
            "anchorSlot": null,
            "merkleProof": null,
            "traceHash": null,
            "shareToken": null,
            "tags": ["swap", "devnet"]
          },
          "events": [
            {
              "id": "01J8XKZP4N0000000000000002",
              "traceId": "01J8XKZP4N0000000000000000",
              "parentEventId": null,
              "sequence": 0,
              "type": "llm_call",
              "startedAt": "2025-01-15T10:30:00.100Z",
              "endedAt": "2025-01-15T10:30:01.400Z",
              "durationMs": 1300,
              "payload": {
                "provider": "openai",
                "model": "gpt-4o-mini",
                "input": {
                  "messages": [
                    { "role": "user", "content": "Should I swap 1 SOL for JUP right now?" }
                  ]
                },
                "output": {
                  "content": "Based on current market conditions...",
                  "finishReason": "stop"
                },
                "usage": { "inputTokens": 24, "outputTokens": 288, "totalTokens": 312 },
                "costUsd": 0.0004,
                "streamed": false
              },
              "payloadEncrypted": false,
              "status": "ok",
              "errorMessage": null
            }
          ]
        }
      ]
    }'
  ```

  ```json response theme={null}
  {
    "traceId": "01J8XKZP4N0000000000000000"
  }
  ```
</CodeGroup>
