Handle pagination

Many ICEYE API endpoints return paginated results. This guide explains how cursor-based pagination works and how to implement it in your application.

How pagination works

Think of it like reading a book: the API gives you one page at a time, along with a bookmark (the cursor) to find the next page. You keep requesting pages using each bookmark until you’ve read the whole book.

Each API response returns one "page" of results. If more results exist, the response includes a cursor value. You pass this cursor in your next request to retrieve the next page, and repeat until no cursor is returned.

With cursor-based pagination, you can only move forward one page at a time. You cannot jump directly to a specific page (like "page 5").

Step 1: First request (no cursor)

Call the endpoint without a cursor to get the first page:

GET /tasking/v2/tasks?limit=20
Authorization: Bearer {token}

Step 2: Check for cursor in response

If more results exist, the response includes a cursor:

{
  "cursor": "eyJsYXN0SWQiOiJhYmMxMjM...",
  "data": [
    { "id": "task-001", "status": "DONE", ... },
    { "id": "task-002", "status": "ACTIVE", ... }
  ]
}

Step 3: Request next page with cursor

Pass the cursor from the previous response to get the next page:

GET /tasking/v2/tasks?limit=20&cursor=eyJsYXN0SWQiOiJhYmMxMjM...
Authorization: Bearer {token}

Step 4: Repeat until no cursor

When there are no more results, the response has no cursor field:

{
  "data": [
    { "id": "task-098", "status": "RECEIVED", ... },
    { "id": "task-099", "status": "DONE", ... }
  ]
}

No cursor in the response means you’ve reached the last page.

Query parameters

Parameter Description Default

limit

Number of items per page

10

cursor

Cursor from previous response to get next page

(none)

Maximum limit values

Different endpoints have different maximum values for the limit parameter:

API Maximum limit

Catalog API

200

Delivery API

100

If you request a limit higher than the maximum, the API will return the maximum allowed.

Implementation examples

Fetching all pages

If you need all results at once, loop through pages until no cursor is returned:

  • JavaScript

  • Python

async function fetchAllTasks() {
  let allTasks = []
  let cursor = null

  do {
    // Build URL with cursor if we have one
    let url = '/tasking/v2/tasks?limit=50'
    if (cursor) {
      url += '&cursor=' + encodeURIComponent(cursor)
    }

    // Make API call
    const response = await fetch(url, {
      headers: { 'Authorization': 'Bearer ' + token }
    })
    const result = await response.json()

    // Add this page's results to our collection
    allTasks = allTasks.concat(result.data)

    // Get cursor for next page (undefined if no more pages)
    cursor = result.cursor

  } while (cursor)  // Keep going while there are more pages

  return allTasks
}
def fetch_all_tasks():
    all_tasks = []
    cursor = None

    while True:
        # Build request params
        params = {'limit': 50}
        if cursor:
            params['cursor'] = cursor

        # Make API call
        response = requests.get(
            f'{API_BASE_URL}/tasking/v2/tasks',
            headers={'Authorization': f'Bearer {token}'},
            params=params
        )
        result = response.json()

        # Add this page's results to our collection
        all_tasks.extend(result.get('data', []))

        # Get cursor for next page
        cursor = result.get('cursor')

        # Stop if no more pages
        if not cursor:
            break

    return all_tasks

Loading pages on demand

If you want users to load pages incrementally (e.g., with a "Load More" button), store the cursor between requests:

  • JavaScript

  • Python

let currentCursor = null

async function loadNextPage() {
  let url = '/tasking/v2/tasks?limit=20'
  if (currentCursor) {
    url += '&cursor=' + encodeURIComponent(currentCursor)
  }

  const response = await fetch(url, {
    headers: { 'Authorization': 'Bearer ' + token }
  })
  const result = await response.json()

  // Store cursor for next time
  currentCursor = result.cursor || null

  // Return this page's tasks
  // Your UI would append these to the existing list
  return {
    tasks: result.data,
    hasMore: currentCursor !== null
  }
}
current_cursor = None

def load_next_page():
    global current_cursor

    params = {'limit': 20}
    if current_cursor:
        params['cursor'] = current_cursor

    response = requests.get(
        f'{API_BASE_URL}/tasking/v2/tasks',
        headers={'Authorization': f'Bearer {token}'},
        params=params
    )
    result = response.json()

    # Store cursor for next time
    current_cursor = result.get('cursor')

    return {
        'tasks': result.get('data', []),
        'has_more': current_cursor is not None
    }

The hasMore / has_more flag tells your application whether more pages are available.

Important considerations

Cursors are tied to query parameters

A cursor is only valid for the same query parameters used when it was generated. If you change filters (like contractID), you must start from the beginning without a cursor.

function handleFilterChange(newContractId) {
  currentContractId = newContractId
  currentCursor = null  // Reset cursor when filters change
  loadTasks()           // Fresh request without cursor
}

Cursors are opaque

Treat cursors as opaque strings. Don’t try to parse or modify them - always use the exact value returned by the API.

Endpoints with pagination

The following ICEYE API endpoints support cursor-based pagination:

Endpoint Reference

GET /tasking/v2/tasks

List Tasks

GET /tasking/v2/tasks/{taskID}/products

Task Products

GET /catalog/v2/items

List Catalog Items

POST /catalog/v2/search

Search Catalog Items

GET /catalog/v2/purchases

List Purchases

GET /catalog/v2/purchases/{purchaseID}/products

List Purchase Products

GET /delivery/v1/deliveries

List Deliveries

GET /notifications/v1/webhooks

List Webhooks

GET /company/v1/contracts

List Contracts