API ReferencePagination and filtering
All endpoints

Filter and paginate collections

Collection endpoints use a limit and an opaque cursor. Process each page once, preserve every filter, and stop when pagination.has_more is false.

Filtering rules

The default limit is 50 and the maximum is 100. Repeating the same incident filter means OR: status=active&status=investigating accepts either status. Different filters combine with AND. Always URL-encode names and values.

Do not inspect, edit, or create cursor values. Send next_cursor exactly as returned, together with the same filters and limit. An empty items array is valid, including on the first page.

Filtered curl request

This request accepts either repeated workflow status and restricts results to incidents created at or after the encoded RFC 3339 timestamp.

Replace the company and token placeholders with your workspace slug and API key.

bash
curl --request GET \
  --url 'https://{company}.api.tellagen.com/api/v1/incidents' \
  --header 'Authorization: Bearer <token>' \
  --get \
  --data-urlencode 'limit=100' \
  --data-urlencode 'status=active' \
  --data-urlencode 'status=investigating' \
  --data-urlencode 'created_from=2026-08-31T12:00:00+03:00'

Page response shapes

Page response shapes
CaseResponse fields
Empty collection"incidents": [], "pagination": {"limit": 100, "has_more": false}
Final non-empty page"incidents": [{...}], "pagination": {"limit": 100, "has_more": false}. The final page can contain items; next_cursor is absent.
First or middle page"incidents": [...], "pagination": {"limit": 100, "has_more": true, "next_cursor": "opaque-server-cursor"}
Active and archived incident collectionsMay also include restricted_count and history_limited, which describe matching incidents excluded by the workspace history limit. They do not count arbitrary permission-denied results.

Run the pagination example

Set TELLAGEN_COMPANY to your workspace slug and TELLAGEN_TOKEN to an API key with incidents:read. For a local server, set TELLAGEN_API_ORIGIN to its address; you can then omit TELLAGEN_COMPANY.

Choose Node.js 22 or later, or Go 1.24 or later. Both examples use the standard library and need no extra packages.

Set TELLAGEN_STATUSES to comma-separated status keys. Set TELLAGEN_CREATED_FROM to an RFC 3339 timestamp when you need a start-date filter.

Both examples read every page, preserve filters, and limit retries after 429. They warn on stderr when history limits make the export incomplete.

Pagination example

Example

Save this code as pagination.mjs. Run it with node pagination.mjs.

import { setTimeout as sleep } from "node:timers/promises";

async function main() {
  const token = process.env.TELLAGEN_TOKEN;
  if (!token) throw new Error("Set TELLAGEN_TOKEN.");
  let origin = process.env.TELLAGEN_API_ORIGIN;
  if (!origin) {
    const company = process.env.TELLAGEN_COMPANY;
    if (!company) throw new Error("Set TELLAGEN_COMPANY or TELLAGEN_API_ORIGIN.");
    origin = "https://" + company + ".api.tellagen.com";
  }
  origin = origin.replace(/\/+$/, "");

  async function getPage(url) {
    for (let attempt = 0; attempt < 4; attempt++) {
      const response = await fetch(url, {
        headers: { Authorization: "Bearer " + token },
        signal: AbortSignal.timeout(30_000),
      });
      const body = await response.text();
      if (response.status === 429 && attempt < 3) {
        const seconds = Number(response.headers.get("Retry-After") ?? "1");
        const delay = Number.isFinite(seconds) && seconds >= 0 ? seconds : 1;
        await sleep(delay * 1_000);
        continue;
      }
      if (!response.ok) throw new Error("HTTP " + response.status + ": " + body);
      return JSON.parse(body);
    }
  }

  const filters = new URLSearchParams({ limit: "100" });
  const statuses = (process.env.TELLAGEN_STATUSES ?? "").split(",");
  for (const status of statuses.map((value) => value.trim()).filter(Boolean)) {
    filters.append("status", status);
  }
  if (process.env.TELLAGEN_CREATED_FROM) {
    filters.set("created_from", process.env.TELLAGEN_CREATED_FROM);
  }
  const incidents = [];
  let cursor;
  let historyWarningShown = false;
  for (let pageNumber = 0; pageNumber < 1_000; pageNumber++) {
    const params = new URLSearchParams(filters);
    if (cursor) params.set("cursor", cursor);
    const page = await getPage(origin + "/api/v1/incidents?" + params);
    if (!historyWarningShown && (page.history_limited || page.restricted_count > 0)) {
      console.error(
        "Warning: workspace history limit applies; " +
        (page.restricted_count ?? 0) +
        " matching incidents excluded. This is not a complete export.",
      );
      historyWarningShown = true;
    }
    incidents.push(...(page.incidents ?? []));
    if (!page.pagination.has_more) {
      console.log(JSON.stringify(incidents));
      return;
    }
    cursor = page.pagination.next_cursor;
    if (!cursor) throw new Error("has_more was true without next_cursor");
  }
  throw new Error("Stopped after 1000 pages.");
}

main().catch((error) => {
  const token = process.env.TELLAGEN_TOKEN;
  const message = error instanceof Error ? error.message : String(error);
  console.error(token ? message.replaceAll(token, "[redacted]") : message);
  process.exitCode = 1;
});

Other collections

The same pagination object is used by active and archived incidents, incident timeline events, services, teams, team members, custom fields, and incident custom-field values. Use the item property documented by that endpoint.

Incident pages have a stable endpoint-defined ordering, with incident ID used to break ties; archived incidents order by archived_at and incident ID. Cursor traversal is not a frozen snapshot: concurrent creates or updates can affect later pages, so consumers should tolerate duplicates and re-read important resources by ID.