KubeadaptDocsBack to site
Sign inStart free
DocsAPI ReferenceCLI
    • Pagination & Filtering
    • Error Handling
    • Cost Modes
Docs homev1Api ReferencePagination

API Reference

Pagination

Cursor model, page size, 24-hour TTL, and the full meta.pagination shape for every list endpoint in the Kubeadapt /v1 API.


List endpoints in /v1 use opaque cursor pagination. Each response carries a cursor in meta.pagination.next_cursor; pass it back as ?cursor=<token> on the next request. The walk terminates when the cursor is empty. There is no ?page=2, ?skip=100, or ?sort= parameter — pages are sorted by cost descending on cost endpoints and by time descending on time-ordered endpoints. Modifying the cursor returns 400 INVALID_CURSOR; a cursor older than 24 hours returns 410. Cost Explorer is the one exception — it uses offset pagination (page/per_page).

Default and maximum page size

The ?limit= query parameter controls page size on every list endpoint:

BoundValue
Default (when ?limit= is absent)100
Minimum1
Maximum500

Out-of-range values (?limit=0, ?limit=501, ?limit=-5, ?limit=abc) return a 400 BAD_REQUEST with details[0].field = "limit". The maximum is 500.

Cursors expire after 24 hours

Cursors older than 24 hours return 410 CURSOR_EXPIRED with details[0].reason = "expired". Restart the walk from page 1 (no cursor) to recover.

json
1{
2  "data": null,
3  "meta": {
4    "request_id": "req_01J5K3V0Q7Y4XR8A2B3C5D7E9F",
5    "applied_at": "2026-05-22T11:14:02Z"
6  },
7  "error": {
8    "code": "CURSOR_EXPIRED",
9    "message": "cursor older than 24h, restart pagination",
10    "details": [
11      { "reason": "expired" }
12    ]
13  }
14}

Cursors are bound to the query that produced them

Warning

Changing any filter, cost_mode, or limit between pages invalidates the cursor and returns 410 CURSOR_EXPIRED. Fix the filter set before the first page.

A cursor is bound to the query parameters that produced it (filters, cost_mode, limit). Changing any of these between pages returns CURSOR_EXPIRED with details[0].reason = "query_changed".

json
1{
2  "data": null,
3  "meta": {
4    "request_id": "req_01J5K3V0Q7Y4XR8A2B3C5D7E9G",
5    "applied_at": "2026-05-21T10:31:15Z"
6  },
7  "error": {
8    "code": "CURSOR_EXPIRED",
9    "message": "cursor query parameters changed between pages",
10    "details": [
11      { "reason": "query_changed" }
12    ]
13  }
14}

Which parameters affect cursor validity

ParameterCan change between pages?
cursorYes — it is the page selector.
include_totalYes — toggling the count does not invalidate the cursor.
limitNo.
Any filter (provider, region, status, kind, …)No.
cost_modeNo (on endpoints that accept it).

Every parameter other than cursor and include_total must be byte-identical across all pages of a single walk.

The meta.pagination shape

Every paginated list response carries this block under meta:

json
1{
2  "meta": {
3    "request_id": "req_01J5K3V0Q7Y4XR8A2B3C5D7E9F",
4    "applied_at": "2026-05-21T10:30:00Z",
5    "pagination": {
6      "next_cursor": "eyJ2IjoxLCJhZnRlcl9pZCI6ImMxYTJiM2M0LWQ1ZTYtNzg5MC1hYmNkLWVmMTIzNDU2Nzg5MCIsImFmdGVyX3ZhbHVlIjoiMTQ3LjI0MDAiLCJxdWVyeV9oYXNoIjoiYTNiNGM1ZDZlN2Y4ZzlhMGIxYzJkM2U0ZjVnNiIsImlzc3VlZF9hdCI6MTcxNjI4NTAwMH0",
7      "has_more": true,
8      "limit": 100,
9      "total_count": 386
10    }
11  }
12}
FieldTypeNotes
next_cursorstringOpaque token. Empty string when no more pages exist (the stop signal for a walk).
has_morebooleanSame stop signal in cleaner form. true while pages remain, false on the final page.
limitintegerEcho of the resolved page size (100 by default).
total_countinteger (optional)Total rows matching the filter, ignoring limit and cursor. Present only when the request supplied ?include_total=true.

next_cursor == "" and has_more == false are always populated together; either is sufficient to terminate the loop.

include_total is opt-in

meta.pagination.total_count is the total row count matching the filter (ignoring cursor and limit). Default off; opt in with ?include_total=true when you need a total count for progress indicators.

bash
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  "https://public-api.kubeadapt.io/v1/workloads?limit=50&include_total=true"

Sort policy

List endpoints sort by their natural key in descending order. There is no ?sort= or ?order= parameter.

The natural sort key per list family:

Endpoint familySort keyDirection
/v1/clusterscost.current_run_rate_hourlyDESC
/v1/clusters/{id}/namespaces, /v1/namespacescost.current_run_rate_hourlyDESC
/v1/clusters/{id}/workloads, /v1/workloadscost.current_run_rate_hourlyDESC
/v1/workloads/{uid}/podscost.current_run_rate_hourlyDESC
/v1/clusters/{id}/nodescost.current_run_rate_hourlyDESC
/v1/recommendationssavings.estimated_monthlyDESC
/v1/teams, /v1/departmentscost.current_run_rate_hourlyDESC
/v1/teams/{id}/assignmentsmetadata.created_atDESC

Endpoints that do NOT paginate

Three families return their full result set inline with no meta.pagination block:

  • GET /v1/node-groups and GET /v1/clusters/{cluster_id}/node-groups. Node groups are bounded in cardinality (a customer with 50 clusters has a few hundred node groups, not millions). The response is the complete list; no next_cursor, no has_more.
  • Single-resource GETs. GET /v1/clusters/{cluster_id}, GET /v1/workloads/{workload_uid}, GET /v1/teams/{team_id}, and the rest of the by-ID endpoints return one resource. data is a single object, not an array; meta.pagination is absent.
  • GET /v1/organization and GET /v1/organization/dashboard. These are tenant-level snapshots, not collections. One object in, one object out.

meta.pagination is absent whenever data is a single object, and also absent on node-groups responses where data is an array. Branch on the endpoint family, not on field presence.

A full pagination walk

Three back-to-back requests pulling pages 1, 2, 3 of /v1/workloads. The response is abbreviated to one workload per page so the pagination shape stays visible; in real responses, data is an array of limit (here 100) items.

Page 1: no cursor

bash
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  "https://public-api.kubeadapt.io/v1/workloads?limit=100"
json
1{
2  "data": [
3    {
4      "id": "b9f1a342-7c5e-4d8a-9e2b-1234567890ab",
5      "kind": "Workload",
6      "metadata": {
7        "name": "api-gateway",
8        "workload_kind": "Deployment",
9        "namespace": "checkout",
10        "cluster": { "id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890", "name": "prod-eu-west-1" }
11      },
12      "cost": {
13        "current_run_rate_hourly": { "amount": "12.4800", "currency": "USD" },
14        "cost_mode": "fully_loaded",
15        "last_updated_at": "2026-05-21T10:29:00Z"
16      }
17    }
18  ],
19  "meta": {
20    "request_id": "req_01J5K3V0Q7Y4XR8A2B3C5D7E9F",
21    "applied_at": "2026-05-21T10:30:00Z",
22    "pagination": {
23      "next_cursor": "eyJ2IjoxLCJhZnRlcl9pZCI6ImI5ZjFhMzQyLTdjNWUtNGQ4YS05ZTJiLTEyMzQ1Njc4OTBhYiIsImFmdGVyX3ZhbHVlIjoiMTIuNDgwMCIsInF1ZXJ5X2hhc2giOiJhM2I0YzVkNmU3ZjhnOWEwYjFjMmQzZTRmNWc2IiwiaXNzdWVkX2F0IjoxNzE2Mjg1MDAwfQ",
24      "has_more": true,
25      "limit": 100
26    },
27    "cost_mode": "fully_loaded"
28  }
29}

Page 2: pass the cursor back

bash
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  "https://public-api.kubeadapt.io/v1/workloads?limit=100&cursor=eyJ2IjoxLCJhZnRlcl9pZCI6ImI5ZjFhMzQyLTdjNWUtNGQ4YS05ZTJiLTEyMzQ1Njc4OTBhYiIsImFmdGVyX3ZhbHVlIjoiMTIuNDgwMCIsInF1ZXJ5X2hhc2giOiJhM2I0YzVkNmU3ZjhnOWEwYjFjMmQzZTRmNWc2IiwiaXNzdWVkX2F0IjoxNzE2Mjg1MDAwfQ"
json
1{
2  "data": [
3    {
4      "id": "a4d20f51-9b3c-4a17-8e6d-2345678901bc",
5      "kind": "Workload",
6      "metadata": {
7        "name": "cart-service",
8        "workload_kind": "Deployment",
9        "namespace": "checkout",
10        "cluster": { "id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890", "name": "prod-eu-west-1" }
11      },
12      "cost": {
13        "current_run_rate_hourly": { "amount": "4.7100", "currency": "USD" },
14        "cost_mode": "fully_loaded",
15        "last_updated_at": "2026-05-21T10:29:00Z"
16      }
17    }
18  ],
19  "meta": {
20    "request_id": "req_01J5K3V0Q7Y4XR8A2B3C5D7E9G",
21    "applied_at": "2026-05-21T10:30:08Z",
22    "pagination": {
23      "next_cursor": "eyJ2IjoxLCJhZnRlcl9pZCI6ImE0ZDIwZjUxLTliM2MtNGExNy04ZTZkLTIzNDU2Nzg5MDFiYyIsImFmdGVyX3ZhbHVlIjoiNC43MTAwIiwicXVlcnlfaGFzaCI6ImEzYjRjNWQ2ZTdmOGc5YTBiMWMyZDNlNGY1ZzYiLCJpc3N1ZWRfYXQiOjE3MTYyODUwMDh9",
24      "has_more": true,
25      "limit": 100
26    },
27    "cost_mode": "fully_loaded"
28  }
29}

Page 3: the final page

bash
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  "https://public-api.kubeadapt.io/v1/workloads?limit=100&cursor=eyJ2IjoxLCJhZnRlcl9pZCI6ImE0ZDIwZjUxLTliM2MtNGExNy04ZTZkLTIzNDU2Nzg5MDFiYyIsImFmdGVyX3ZhbHVlIjoiNC43MTAwIiwicXVlcnlfaGFzaCI6ImEzYjRjNWQ2ZTdmOGc5YTBiMWMyZDNlNGY1ZzYiLCJpc3N1ZWRfYXQiOjE3MTYyODUwMDh9"
json
1{
2  "data": [
3    {
4      "id": "f8b6c7e3-1a92-4f5b-bc8e-3456789012cd",
5      "kind": "Workload",
6      "metadata": {
7        "name": "db-migrator",
8        "workload_kind": "Job",
9        "namespace": "payments",
10        "cluster": { "id": "d8e9f0a1-b2c3-4567-89ab-cdef01234567", "name": "staging-us-east-1" }
11      },
12      "cost": {
13        "current_run_rate_hourly": { "amount": "0.0900", "currency": "USD" },
14        "cost_mode": "fully_loaded",
15        "last_updated_at": "2026-05-21T10:29:00Z"
16      }
17    }
18  ],
19  "meta": {
20    "request_id": "req_01J5K3V0Q7Y4XR8A2B3C5D7E9H",
21    "applied_at": "2026-05-21T10:30:16Z",
22    "pagination": {
23      "next_cursor": "",
24      "has_more": false,
25      "limit": 100
26    },
27    "cost_mode": "fully_loaded"
28  }
29}

next_cursor is the empty string and has_more is false. Stop the loop.

A complete walk in pseudocode

python
1url = "https://public-api.kubeadapt.io/v1/workloads"
2params = {"limit": 500}
3all_rows = []
4
5while True:
6    response = http_get(url, params=params, headers=auth_headers).json()
7    all_rows.extend(response["data"])
8    page = response["meta"]["pagination"]
9    if not page["has_more"]:
10        break
11    params["cursor"] = page["next_cursor"]

Two constraints on the loop:

  • Cap iterations with a max-iterations guard and add a Retry-After-aware sleep on 429.
  • Keep every query parameter constant across iterations except cursor and include_total. Mutating cost_mode, limit, or any filter starts a different walk and invalidates the cursor.

See also

  • API Overview, the envelope shape and the resource family index.
  • Error Handling, the full error code catalog and the retry guide. INVALID_CURSOR and CURSOR_EXPIRED live here.
  • Cost Modes, which endpoints accept ?cost_mode= and which reject it. On accepting endpoints, cost_mode must not change between pages of a single walk.
  • Workloads, the canonical large list endpoint, the one most users walk first.
  • Recommendations, sorted by savings.estimated_monthly descending instead of cost.

Related

  • API Overview
  • Error Handling
  • Cost Modes
PreviousPermission ScopesAPI ReferenceNextError HandlingAPI Reference

On this page

  • Default and maximum page size
  • Cursors expire after 24 hours
  • Cursors are bound to the query that produced them
  • Which parameters affect cursor validity
  • The meta.pagination shape
  • includetotal is opt-in
  • Sort policy
  • Endpoints that do NOT paginate
  • A full pagination walk
  • Page 1: no cursor
  • Page 2: pass the cursor back
  • Page 3: the final page
  • A complete walk in pseudocode
  • See also
Edit this page
Kubeadapt

Kubernetes FinOps platform. Cost visibility, rightsizing, and capacity planning that pays for itself in 30 days.

Product

  • Cost Monitoring
  • Cost Attribution
  • Workload Rightsizing
  • Recommendations
  • Smart Alerting
  • Best Practices
  • Network Cross-AZ

Resources

  • Documentation
  • Status Page
  • Feature Requests

Company

  • About Us
  • Security
  • Careers
  • Contact

© 2026 Kubeadapt. All rights reserved.

PrivacyTermsSecurity