---
title: Read API
description: Pull your runs, events, evaluations, and agents back out with a read-scoped API key.
section: api
order: 8
---

# Read API

The read API hands your data back. Every run, event, evaluation result,
and agent on your account is reachable with a read-scoped key, with
cursor pagination and no per-call charge.

It is served by the control plane, not the ingest tier:

```
https://agentping.io/v1/...
```

## Authentication and scope

Read endpoints require an API key with the `read` scope. Mint one in
**Settings, API keys** and grant it `read`; the ingest scope and the
read scope are separate, so a key your SDK uses to send data does not
automatically read it back, and vice versa. Keys created before scopes
existed keep working for ingest only.

```bash
curl https://agentping.io/v1/runs \
  -H "Authorization: Bearer apk_eu_018f3a2b9c1d7e8fa4b9c2d7e8f1a3b6"
```

A key without the `read` scope returns `403 insufficient_scope`.

## Rate limit

Read requests are limited to **60 per minute per key**. Over the limit
returns `429` with a `Retry-After` header in seconds. This is generous
for backfills and automations; for a full account dump prefer a
[bulk export](/docs/guides/your-data).

## Retention

Reads are bounded by your plan's run-history horizon. A `from`/`to`
window that reaches past it is silently clamped, never an error: list
responses carry a `retention_horizon` field with the oldest timestamp
you can read, and a window entirely before it returns an empty `data`
array. See [data retention](/docs/billing/retention).

## Pagination

List endpoints use opaque cursor pagination. Pass `per_page` (max 100,
default 50) and follow `next_cursor` until it is `null`:

```json
{
  "data": [ ... ],
  "next_cursor": "eyJpZCI6...",
  "retention_horizon": "2026-03-15T00:00:00+00:00"
}
```

```bash
curl "https://agentping.io/v1/runs?per_page=100&cursor=eyJpZCI6..." \
  -H "Authorization: Bearer apk_eu_..."
```

## GET /v1/runs

List runs newest-first.

| Query | Description |
|-------|-------------|
| `from`, `to` | ISO 8601 bounds on `received_at`. Clamped to the retention horizon. |
| `status` | `success`, `failed`, `timeout`, or `cancelled`. |
| `agent_id` | Agent external id or slug. |
| `per_page` | 1 to 100, default 50. |
| `cursor` | From a prior `next_cursor`. |

Each item is a run summary:

```json
{
  "id": "run_eu_018f3a2b9c1d7e8fa4b9c2d7e8f1a3b6",
  "agent": "support-triage",
  "status": "success",
  "goal": "Route the ticket to the right department",
  "customer_id": "acme-corp",
  "feature": "ticket-routing",
  "started_at": "2026-05-15T14:32:01+00:00",
  "finished_at": "2026-05-15T14:32:05+00:00",
  "duration_ms": 4666,
  "cost_usd": 0.0142,
  "total_input_tokens": 1200,
  "total_output_tokens": 340,
  "step_count": 3,
  "error_count": 0
}
```

## GET /v1/runs/{id}

One run, in full. Returns the same summary plus `metadata`, `tools`, and
`output`. A run id from another team returns `404` (not `403`), so a
caller can't probe which ids exist.

## GET /v1/runs/{id}/events

The run's events in timestamp order (up to 1000). When an event has an
offloaded payload, `data` is the slimmed inline copy and `payload_url`
points at the full body:

```json
{
  "data": [
    {
      "id": "evt_eu_018f3a2b9c1d7e8fa4b9c2d7e8f1a3b7",
      "type": "llm_call",
      "ts": "2026-05-15T14:32:02.456Z",
      "data": { "provider": "anthropic", "model": "claude-opus-4-7" },
      "payload_url": "https://agentping.io/v1/runs/run_eu_.../events/evt_eu_.../payload"
    }
  ]
}
```

## GET /v1/runs/{id}/events/{eventId}/payload

The full event payload, fetched from the payload store when one exists,
otherwise the inline copy. Payloads are only available within your
plan's step-detail horizon.

## GET /v1/runs/{id}/evaluation

The run's evaluation in the public shape, with internal stages collapsed
to a single `evaluation` type:

```json
{
  "data": {
    "type": "evaluation",
    "status": "complete",
    "result": "goal_achieved",
    "quality": 4,
    "issues": [],
    "reasoning": "The ticket was routed to billing, matching the goal.",
    "evaluated_at": "2026-05-15T15:02:11+00:00"
  }
}
```

`status` is one of `complete`, `pending`,
`covered_by_routine_baseline`, or `returned_to_allowance`. `quality` is
0 to 5 when present. A run that was never evaluated returns `404`.

## GET /v1/agents

Every agent on the team with rolled-up health:

```json
{
  "data": [
    {
      "id": "agt_eu_018f3a2b9c1d7e8fa4b9c2d7e8f1a3b9",
      "slug": "support-triage",
      "first_seen_at": "2026-02-01T09:14:00+00:00",
      "last_seen_at": "2026-05-15T14:32:01.156Z",
      "health_pass_rate_24h": 0.9821,
      "quality_7d": 88
    }
  ]
}
```

`last_seen_at` is the most recent run received for the agent;
`health_pass_rate_24h` and `quality_7d` are `null` until there is data.
