Skip to main content

API reference

Pagination, filtering and sorting

Working through large collections.

Beta. This endpoint is not final. Field names and response shapes can change before general availability.

Every list endpoint in the Finn API returns a page, not a full collection. This page covers the paging model, the filter parameters that are shared across resources, and sorting. Resource-specific filters are documented on the resource pages: calls, finns, deployments, audiences.

Cursor pagination

List endpoints use cursor pagination. You pass a limit, read the results, and pass the returned cursor back to get the next page.

curl "https://api.hirefinn.ai/v1/calls?limit=50" \
  -H "Authorization: Bearer $FINN_API_KEY"
{
  "object": "list",
  "data": [
    { "id": "call_01HZX8QP3M4K", "status": "completed" },
    { "id": "call_01HZX8QP3M4J", "status": "no_answer" }
  ],
  "has_more": true,
  "next_cursor": "eyJpZCI6ImNhbGxfMDFIWlg4UVAzTTRKIn0"
}
FieldTypeDescription
objectstringAlways list for a paged response.
dataarrayThe page of results, ordered by the active sort.
has_morebooleanWhether more results exist after this page.
next_cursorstring or nullOpaque cursor for the next page. Null when has_more is false.

Request the next page by passing cursor:

curl "https://api.hirefinn.ai/v1/calls?limit=50&cursor=eyJpZCI6ImNhbGxfMDFIWlg4UVAzTTRKIn0" \
  -H "Authorization: Bearer $FINN_API_KEY"

Cursors are opaque. Do not parse one, build one, or store one long term. A cursor encodes the sort and filter parameters of the request that produced it, so reusing a cursor with different filter or sort values returns 400 invalid_request. See error codes.

Whether the API also returns a total count is not yet settled. Do not write code that depends on a total field. Drive your loop off has_more.

ParameterDefaultNotes
limit25Maximum page size is not final. Requests above the cap are rejected rather than silently clamped, so read the error body instead of assuming you got what you asked for.
cursornoneOmit for the first page.

Iterating a full collection

async function listAllCalls(apiKey: string): Promise<unknown[]> {
  const results: unknown[] = [];
  let cursor: string | null = null;

  do {
    const url = new URL("https://api.hirefinn.ai/v1/calls");
    url.searchParams.set("limit", "50");
    if (cursor) url.searchParams.set("cursor", cursor);

    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    if (!res.ok) {
      throw new Error(`${res.status} ${await res.text()}`);
    }

    const page = await res.json();
    results.push(...page.data);
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);

  return results;
}
import requests

BASE = "https://api.hirefinn.ai/v1"

def list_all_calls(api_key):
    results = []
    cursor = None

    while True:
        params = {"limit": 50}
        if cursor:
            params["cursor"] = cursor

        res = requests.get(
            f"{BASE}/calls",
            headers={"Authorization": f"Bearer {api_key}"},
            params=params,
            timeout=30,
        )
        res.raise_for_status()
        page = res.json()

        results.append_all = None
        results.extend(page["data"])

        if not page["has_more"]:
            return results
        cursor = page["next_cursor"]

Two things go wrong with loops like this. First, paging a large collection burns rate limit quota fast, and a 429 mid-loop will drop you out with a partial result unless you retry. See rate limits. Second, the collection can change while you page it. New calls arriving during a descending-order walk are not picked up, and records that move can be seen twice or missed. For anything that has to be exact, filter to a closed time range instead of walking the live tail.

Filtering

Filters are query parameters. Unknown parameters are rejected with 400 invalid_request rather than ignored, so a typo fails loudly instead of returning an unfiltered page you mistake for a filtered one.

Timestamps are ISO 8601 in UTC. Range filters use _after and _before suffixes and are inclusive on the lower bound, exclusive on the upper.

curl -G "https://api.hirefinn.ai/v1/calls" \
  -H "Authorization: Bearer $FINN_API_KEY" \
  --data-urlencode "created_after=2026-07-01T00:00:00Z" \
  --data-urlencode "created_before=2026-07-08T00:00:00Z" \
  --data-urlencode "limit=100"
ParameterApplies toDescription
created_afterall list endpointsInclude records created at or after this timestamp.
created_beforeall list endpointsInclude records created strictly before this timestamp.
statuscalls, deploymentsExact match on the resource status value.

Repeating a parameter to mean OR (status=completed&status=failed) is not settled. Until it is, send one value per parameter and merge client side.

Sorting

sort takes a field name with a direction prefix. - is descending, no prefix is ascending. The default is -created_at, newest first.

curl "https://api.hirefinn.ai/v1/calls?sort=created_at&limit=50" \
  -H "Authorization: Bearer $FINN_API_KEY"

Only created_at is guaranteed sortable on every list endpoint. Other fields vary by resource and are listed on the resource pages. Sorting on an unsupported field returns 400 invalid_request.

Because cursors encode the sort, changing sort means starting over from the first page. There is no way to re-sort mid-walk.

See API conventions for the shared request and response rules, and errors for the full status code list.