# Spans API

> Aggregate the database, cache, outbound HTTP and queue operations inside requests, chart one over time, drill back to its requests and look up fingerprints.

Source: https://www.apitraffic.io/docs/api-reference/spans

A span is one unit of work inside a request: a database query, a cache call, an outbound HTTP request, a queue publish. Every run of the same operation shares a fingerprint (`fpr_…`), so you can ask how often a query runs, how slow it is at the 95th percentile, and which endpoints run it most. A high `runsPerRequest` usually means an N+1 query. For request-level time, use the [APM API](https://www.apitraffic.io/docs/api-reference/apm).

**Plan:** needs a plan that includes **APM**; otherwise every operation answers `403` with code `2221`. **Permissions:** any member, or a `user` or `administrator` API token.

Times are ISO 8601 in UTC and must end in `Z`. Span durations are in microseconds (`_us`).

## Operation report

`GET https://api.apitraffic.io/v1/accounts/{accountSid}/spans/reports`

Groups spans by a field you choose and returns run counts, errors and latency percentiles for each group. Group by `fingerprint` for one row per operation, or by `endpoint`, `host`, `type` and others to see where the work happens.

### Path parameters

- **accountSid (path, string, required)**:
  The account identifier.

### Query parameters

- **groupBy (query, string, required)**:
  The field to group by: `fingerprint`, `type`, `endpoint`, `name`, `system`, `host`, `db`, `call_site`, `out_endpoint`, `out_target`, `out_status_class`, `bucket_sid` or `environment_sid`.

- **startTime (query, string, required)**:
  Start of the range.

- **endTime (query, string, required)**:
  End of the range.

- **environmentSid (query, string)**:
  Only this environment.

- **bucketSid (query, string)**:
  Only this bucket.

- **endpoint (query, string)**:
  Only spans inside this endpoint of your application, for example `/v1/orders/{order_id}`.

- **type (query, string)**:
  Only this span type: `db.query`, `redis`, `cache`, `http.out`, `queue`, `rpc`, `graphql` or `custom`.

- **fingerprint (query, string)**:
  Only this operation.

- **host (query, string)**:
  Only operations against this peer host, for example `payments.acme-pay.example`.

- **outEndpoint (query, string)**:
  Only outbound HTTP calls to this endpoint on the other service.

- **outStatusClass (query, string)**:
  Only outbound calls with this outcome: `2xx`, `3xx`, `4xx`, `5xx`, or `none` (no response, for example a timeout).

- **outStatusCode (query, integer)**:
  Only outbound calls that returned exactly this status, 0 to 599.

- **orderBy (query, string, default total)**:
  How groups are ranked before `limit` is applied: `total` (time), `p95`, `p99`, `avg`, `runs`, `errors` or `runs_per_request`. Ranking happens on the server, so slow but rare groups stay in the page when you sort by a percentile.

- **limit (query, integer, default 50)**:
  Groups to return, 1 to 1000.

### Response

- **groupBy (string, required)**:
  The grouping used.

- **total (number, required)**:
  The sum of `runs` across the groups returned.

- **sourceState (string, required)**:
  `ready`, or `absent` when the account has no span data yet, so an empty result is expected rather than a quiet period.

- **points (object[], required)**:
  One entry per group.

**point**

- **key (string)**: The group's value, for example the fingerprint.

- **runs (number)**: Spans in the group.

- **requests (number)**: Distinct requests they ran in.

- **runsPerRequest (number)**: `runs` divided by `requests`. High values point to an N+1.

- **errorCount (number)**: Spans that failed.

- **avg_us (number)**: Average duration.

- **p50_us (number)**: Median duration.

- **p95_us (number)**: 95th percentile duration.

- **p99_us (number)**: 99th percentile duration.

- **total_us (number)**: Total time.

- **lastSeen (string | null)**: The latest run.

- **sampleType (string)**: A span type in the group. The other `sample…` fields describe a representative span the same way: `sampleName`, `sampleSystem`, `sampleHost`, `sampleDb`, `sampleEndpoint` (your endpoint), `sampleOutEndpoint`, `sampleOutMethod`, `sampleOutStatusCode` (0 when no call got a response) and `sampleStatement`.

- **sampleCallSite (string)**: `file:line:col` in your code that issued the operation; empty unless call-site capture is on. `sampleCallSiteFn` is the enclosing function and `sampleCallStack` the caller chain, one `file:line:col|function` per line.

- **outCalls (number)**: Outbound HTTP calls in the group. `outClientErrorCount` (4xx), `outServerErrorCount` (5xx) and `outNoResponseCount` break down the failures.

```bash cURL
curl -G https://api.apitraffic.io/v1/accounts/acc_2mXq7RbT4kPz9WvN3cHd8LsYfQa/spans/reports \
  -H "Authorization: Bearer $APITRAFFIC_TOKEN" \
  --data-urlencode 'groupBy=fingerprint' \
  --data-urlencode 'type=db.query' \
  --data-urlencode 'endpoint=/v1/orders/{order_id}' \
  --data-urlencode 'orderBy=runs_per_request' \
  --data-urlencode 'startTime=2026-10-19T00:00:00.000Z' \
  --data-urlencode 'endTime=2026-10-20T00:00:00.000Z'
```

```js Node.js
const accountSid = 'acc_2mXq7RbT4kPz9WvN3cHd8LsYfQa';
const params = new URLSearchParams({
  groupBy: 'fingerprint',
  type: 'db.query',
  endpoint: '/v1/orders/{order_id}',
  orderBy: 'runs_per_request',
  startTime: '2026-10-19T00:00:00.000Z',
  endTime: '2026-10-20T00:00:00.000Z',
});
const response = await fetch(`https://api.apitraffic.io/v1/accounts/${accountSid}/spans/reports?${params}`, {
  headers: { Authorization: `Bearer ${process.env.APITRAFFIC_TOKEN}` },
});
const { points } = await response.json();
const nPlusOne = points.filter((point) => point.runsPerRequest > 5);
```

```json 200
{
  "groupBy": "fingerprint",
  "total": 21960,
  "sourceState": "ready",
  "points": [
    {
      "key": "fpr_5b8e2d7c1a9f4e3b",
      "runs": 21960,
      "requests": 1830,
      "runsPerRequest": 12,
      "errorCount": 0,
      "avg_us": 1840,
      "p50_us": 1500,
      "p95_us": 4200,
      "p99_us": 7900,
      "total_us": 40406400,
      "lastSeen": "2026-10-19T23:59:12.000Z",
      "sampleType": "db.query",
      "sampleName": "SELECT order_items",
      "sampleSystem": "postgresql",
      "sampleHost": "db.acme.internal",
      "sampleDb": "acme_store",
      "sampleEndpoint": "/v1/orders/{order_id}",
      "sampleOutEndpoint": "",
      "sampleOutMethod": "",
      "sampleOutStatusCode": 0,
      "outCalls": 0,
      "outClientErrorCount": 0,
      "outServerErrorCount": 0,
      "outNoResponseCount": 0,
      "sampleStatement": "SELECT * FROM order_items WHERE order_id = $1 AND line = $2",
      "sampleCallSite": "src/repositories/orderRepository.js:48:22",
      "sampleCallSiteFn": "loadLineItems",
      "sampleCallStack": "src/repositories/orderRepository.js:48:22|loadLineItems\nsrc/routes/orders.js:31:17|getOrder"
    }
  ]
}
```

## Operation trend

`GET https://api.apitraffic.io/v1/accounts/{accountSid}/spans/timeseries`

Charts runs, errors and latency of spans over time. Pass a `fingerprint` to follow one operation, for example before and after a deploy.

### Path parameters

- **accountSid (path, string, required)**:
  The account identifier.

### Query parameters

- **startTime (query, string, required)**:
  Start of the range.

- **endTime (query, string, required)**:
  End of the range.

- **fingerprint (query, string)**:
  The operation to chart. Strongly recommended: without it, the series covers every span in the range.

- **environmentSid (query, string)**:
  Only this environment.

- **bucketSid (query, string)**:
  Only this bucket.

- **endpoint (query, string)**:
  Only spans inside this endpoint.

- **type (query, string)**:
  Only this span type, as in [Operation report](#operation-report).

- **host (query, string)**:
  Only operations against this peer host.

- **outEndpoint (query, string)**:
  Only outbound calls to this endpoint.

- **outStatusClass (query, string)**:
  `2xx`, `3xx`, `4xx`, `5xx` or `none`.

- **outStatusCode (query, integer)**:
  Exact outbound status, 0 to 599.

### Response

- **points (object[], required)**:
  One entry per period, in time order: `bucket` (period start), `runs`, `requests`, `errorCount`, `avg_us`, `p95_us`, `p99_us`, `total_us`, `outCalls`, `outClientErrorCount`, `outServerErrorCount` and `outNoResponseCount`. The period width follows the length of the range.

```bash cURL
curl -G https://api.apitraffic.io/v1/accounts/acc_2mXq7RbT4kPz9WvN3cHd8LsYfQa/spans/timeseries \
  -H "Authorization: Bearer $APITRAFFIC_TOKEN" \
  --data-urlencode 'fingerprint=fpr_2a7e9c4b1d8f6e3a' \
  --data-urlencode 'startTime=2026-10-19T13:00:00.000Z' \
  --data-urlencode 'endTime=2026-10-19T16:00:00.000Z'
```

```js Node.js
const accountSid = 'acc_2mXq7RbT4kPz9WvN3cHd8LsYfQa';
const params = new URLSearchParams({
  fingerprint: 'fpr_2a7e9c4b1d8f6e3a',
  startTime: '2026-10-19T13:00:00.000Z',
  endTime: '2026-10-19T16:00:00.000Z',
});
const response = await fetch(`https://api.apitraffic.io/v1/accounts/${accountSid}/spans/timeseries?${params}`, {
  headers: { Authorization: `Bearer ${process.env.APITRAFFIC_TOKEN}` },
});
const { points } = await response.json();
```

```json 200
{
  "points": [
    {
      "bucket": "2026-10-19T14:00:00.000Z",
      "runs": 41,
      "requests": 41,
      "errorCount": 19,
      "avg_us": 2310000,
      "p95_us": 3990000,
      "p99_us": 4010000,
      "total_us": 94710000,
      "outCalls": 41,
      "outClientErrorCount": 0,
      "outServerErrorCount": 15,
      "outNoResponseCount": 4
    }
  ]
}
```

## Drill back to requests

`GET https://api.apitraffic.io/v1/accounts/{accountSid}/spans`

Lists the requests that ran an operation, so you can open real examples of a slow query or a failing call.

### Path parameters

- **accountSid (path, string, required)**:
  The account identifier.

### Query parameters

- **fingerprint (query, string, required)**:
  The operation.

- **startTime (query, string, required)**:
  Start of the range.

- **endTime (query, string, required)**:
  End of the range.

- **environmentSid (query, string)**:
  Only this environment.

- **bucketSid (query, string)**:
  Only this bucket.

- **endpoint (query, string)**:
  Only requests to this endpoint.

- **limit (query, integer, default 50)**:
  Requests to return, 1 to 1000.

### Response

- **fingerprint (string, required)**:
  The operation.

- **points (object[], required)**:
  One entry per request: `request_sid` (use it with [Get request](https://www.apitraffic.io/docs/api-reference/requests#get-request)), `endpoint`, `spanCount` (runs of the operation in that request), `total_us` and `lastSeen`.

```bash cURL
curl -G https://api.apitraffic.io/v1/accounts/acc_2mXq7RbT4kPz9WvN3cHd8LsYfQa/spans \
  -H "Authorization: Bearer $APITRAFFIC_TOKEN" \
  --data-urlencode 'fingerprint=fpr_5b8e2d7c1a9f4e3b' \
  --data-urlencode 'startTime=2026-10-19T00:00:00.000Z' \
  --data-urlencode 'endTime=2026-10-20T00:00:00.000Z' \
  --data-urlencode 'limit=10'
```

```js Node.js
const accountSid = 'acc_2mXq7RbT4kPz9WvN3cHd8LsYfQa';
const params = new URLSearchParams({
  fingerprint: 'fpr_5b8e2d7c1a9f4e3b',
  startTime: '2026-10-19T00:00:00.000Z',
  endTime: '2026-10-20T00:00:00.000Z',
  limit: '10',
});
const response = await fetch(`https://api.apitraffic.io/v1/accounts/${accountSid}/spans?${params}`, {
  headers: { Authorization: `Bearer ${process.env.APITRAFFIC_TOKEN}` },
});
const { points } = await response.json();
```

```json 200
{
  "fingerprint": "fpr_5b8e2d7c1a9f4e3b",
  "points": [
    {
      "request_sid": "req_2hJ6kL8mN1bV3cX5zA7sD9fG4qW",
      "endpoint": "/v1/orders/{order_id}",
      "spanCount": 14,
      "total_us": 31200,
      "lastSeen": "2026-10-19T23:59:12.000Z"
    }
  ]
}
```

## Look up fingerprints

`GET https://api.apitraffic.io/v1/accounts/{accountSid}/spans/fingerprints`

Describes up to 100 fingerprints: the operation type, system, target and code location, and the normalized statement when the account stores statement text. Use it to label fingerprints you got from another operation.

### Path parameters

- **accountSid (path, string, required)**:
  The account identifier.

### Query parameters

- **fingerprints (query, string, required)**:
  Comma-separated fingerprints (`fpr_` followed by 16 to 64 hex digits), at most 100.

### Response

- **statementText (boolean, required)**:
  Whether the account stores statement text. When `false`, `statement` and `callStack` are `null`.

- **fingerprints (object[], required)**:
  One entry per known fingerprint: `fingerprint`, `fpVersion`, `type`, `system` (for example `postgresql`), `db`, `op` (for example `SELECT`), `target` (the table, collection, host or queue), `name`, `host`, `statement`, `callSite`, `callSiteFn`, `callStack`, `firstSeenVersion`, `firstSeenAt` and `lastSeenAt`.

```bash cURL
curl -G https://api.apitraffic.io/v1/accounts/acc_2mXq7RbT4kPz9WvN3cHd8LsYfQa/spans/fingerprints \
  -H "Authorization: Bearer $APITRAFFIC_TOKEN" \
  --data-urlencode 'fingerprints=fpr_5b8e2d7c1a9f4e3b,fpr_2a7e9c4b1d8f6e3a'
```

```js Node.js
const accountSid = 'acc_2mXq7RbT4kPz9WvN3cHd8LsYfQa';
const params = new URLSearchParams({ fingerprints: ['fpr_5b8e2d7c1a9f4e3b', 'fpr_2a7e9c4b1d8f6e3a'].join(',') });
const response = await fetch(`https://api.apitraffic.io/v1/accounts/${accountSid}/spans/fingerprints?${params}`, {
  headers: { Authorization: `Bearer ${process.env.APITRAFFIC_TOKEN}` },
});
const { fingerprints } = await response.json();
```

```json 200
{
  "statementText": true,
  "fingerprints": [
    {
      "fingerprint": "fpr_5b8e2d7c1a9f4e3b",
      "fpVersion": 1,
      "type": "db.query",
      "system": "postgresql",
      "db": "acme_store",
      "op": "SELECT",
      "target": "order_items",
      "name": "SELECT order_items",
      "host": "db.acme.internal",
      "statement": "SELECT * FROM order_items WHERE order_id = $1 AND line = $2",
      "callSite": "src/repositories/orderRepository.js:48:22",
      "callSiteFn": "loadLineItems",
      "callStack": "src/repositories/orderRepository.js:48:22|loadLineItems\nsrc/routes/orders.js:31:17|getOrder",
      "firstSeenVersion": "v2026.10.2",
      "firstSeenAt": "2026-10-02T10:04:51.000Z",
      "lastSeenAt": "2026-10-19T23:59:12.000Z"
    }
  ]
}
```

## Errors

| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `1000` | A parameter is missing or invalid, for example an unknown `groupBy` or more than 100 fingerprints. |
| `403` | `2221` | The plan does not include APM. |
| `502` | `5105` | The query failed. Retry. |
| `503` | `5102`, `5103`, `5104` | Analytics are temporarily unavailable. Retry after `Retry-After` seconds. |
| `504` | `5101` | The query took too long. Narrow the range, or pass a `fingerprint`. |

## Related

- [Performance analytics](https://www.apitraffic.io/docs/apm/analytics):
    The same reports in the app.

- [APM capture settings](https://www.apitraffic.io/docs/apm/capture-settings):
    Control statement text, Redis keys and code locations.
