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

# Exporting traces

> Pull a full trace and its event timeline into tickets, data stores, and other systems

A common pattern is to react to a Mirador event — typically an [automation rule firing](/automations/rules) — by exporting the full trace into another system: an investigation ticket, a data warehouse, or an incident channel. This page shows how.

## There's no single "export" endpoint — combine two

A complete trace export is the **summary plus the event timeline**, assembled from two calls:

<Steps>
  <Step title="GET /v1/traces/{trace_id}">
    The trace summary — status, severity, duration, tags, attributes, and any rule matches.
  </Step>

  <Step title="GET /v1/traces/{trace_id}/events">
    The complete, ordered event timeline, returned in one response.
  </Step>
</Steps>

Merge the two into whatever document your downstream system needs:

```ts theme={null}
async function exportTrace(traceId: string) {
  const headers = { Authorization: `Bearer ${process.env.MIRADOR_SERVER_KEY}` };
  const base = "https://api.mirador.org";

  const [summaryRes, eventsRes] = await Promise.all([
    fetch(`${base}/v1/traces/${traceId}`, { headers }),
    fetch(`${base}/v1/traces/${traceId}/events`, { headers }),
  ]);

  if (!summaryRes.ok) throw new Error(`summary ${summaryRes.status}`);
  if (!eventsRes.ok) throw new Error(`events ${eventsRes.status}`);

  const { trace } = await summaryRes.json();
  const { events } = await eventsRes.json();

  return { ...trace, events }; // full trace JSON, including events
}
```

## Recipe: auto-create a ticket when a rule fires

This is the end-to-end flow for "open an investigation ticket whenever an automation rule matches, with the full trace attached."

```mermaid theme={null}
sequenceDiagram
  participant M as Mirador
  participant S as Your service
  participant T as Ticket system
  M->>S: Webhook POST (rule match)
  S->>M: GET /v1/traces/{id} + /events (server key)
  S->>T: Create ticket with full trace JSON
```

### 1. Receive the automation webhook

Add a [Webhook integration](/automations/integrations) to the rule. When the rule fires, Mirador POSTs this JSON to your URL:

```json theme={null}
{
  "schema_version": "1.0",
  "event_id": "5e2a9c14-7b3d-4f88-9a1e-6c0b2d4f8a31",
  "event_timestamp": "2026-06-18T16:12:34Z",
  "rule_id": "b7c4e90a-1f22-4d6b-8e33-9a0f1c2d3e44",
  "rule_name": "Failed Bridge Transfers",
  "match_reason": "trace.attributes[\"status\"] == \"failed\" && \"bridge\" in trace.tags",
  "project_name": "Production",
  "entities": [
    { "type": "trace", "id": "4bf92f3577b34da6a3ce929d0e0e4736" }
  ]
}
```

<Note>
  The webhook tells you a rule matched and which entities matched — but it does
  **not** contain the trace's data. Each entry in `entities` carries the matched
  object's `type` and `id`; read the `id` of the `trace` entity, then fetch the
  full trace from the REST API. See the
  [webhook payload reference](/automations/integrations#webhook-payload) for every
  field, including how `entities` may grow to cover other types over time.
</Note>

### 2. Read the `trace_id`, fetch the trace, open the ticket

The matched trace's id arrives directly on the entity as `id` — no parsing required. Find the `trace` entity, then fetch and file it:

````ts theme={null}
import express from "express";

const app = express();
app.use(express.json());

app.post("/webhooks/mirador", async (req, res) => {
  // Respond fast so Mirador's delivery succeeds; do the heavy work async.
  res.sendStatus(202);

  const payload = req.body;
  // entities is a typed list — match on type instead of assuming entities[0].
  const traceEntity = (payload.entities ?? []).find((e) => e.type === "trace");
  if (!traceEntity?.id) return;

  const trace = await exportTrace(traceEntity.id); // from the snippet above

  await createTicket({
    title: `[Mirador] ${payload.rule_name} — ${trace.name}`,
    body: [
      `Rule: ${payload.rule_name}`,
      `Why: ${payload.match_reason}`,
      `Trace: ${traceEntity.id}`,
      "",
      "```json",
      JSON.stringify(trace, null, 2),
      "```",
    ].join("\n"),
  });
});
````

### 3. Harden the consumer

<Tip>
  * **Authenticate the webhook.** Mirador lets you attach custom headers to a
    webhook integration — set a shared secret (e.g. `X-Webhook-Secret`) and
    verify it on every request. Reject anything that doesn't match.
  * **Acknowledge fast, work async.** Return `2xx` immediately, then fetch and
    file the ticket out of band. Slow responses count as delivery failures.
  * **Be idempotent.** A webhook may be delivered more than once. Dedupe on the
    `event_id` — it's unique per delivery — so you don't open duplicate tickets.
  * **Handle retries from your side too.** If the REST fetch returns
    `UNAVAILABLE` or `DEADLINE_EXCEEDED`, retry with backoff before giving up.
</Tip>

## Polling instead of webhooks

If you'd rather pull than receive pushes, poll `GET /v1/traces` on a schedule with a `since` window and a filter, then export each match:

```bash theme={null}
GET /v1/traces?filter=severity="error"&since=2026-06-18T16:00:00Z
```

Track the latest `created_at` you've processed and advance `since` each run so you don't re-export the same traces.

## Next Steps

<CardGroup cols={2}>
  <Card title="Endpoints" icon="list" href="/api/endpoints">
    Filter syntax, pagination, and response shapes
  </Card>

  <Card title="Automation rules" icon="filter" href="/automations/rules">
    Define the conditions that trigger the webhook
  </Card>
</CardGroup>
