Pagination

The Udacity Public API uses cursor-based pagination (Relay-style) for all list queries. This provides stable, efficient pagination even on large datasets.

Cursor-based pagination uses opaque cursor strings instead of page numbers, ensuring stable results even when data changes between requests.

How It Works

Every paginated query returns a connection with three key parts: totalCount, pageInfo, and edges.

Each edge contains a cursor (an opaque string identifying that item’s position) and a node (the actual data).

Connection Structure
{
  totalCount    # Total number of results
  pageInfo {
    hasNextPage   # Are there more results?
    endCursor     # Cursor to pass as "after" for the next page
  }
  edges {
    cursor        # This item's cursor
    node {
      ...         # The actual data
    }
  }
}

Basic Pagination

First page

Request the first N items using first. The response includes pageInfo telling you whether more results exist.

First Page Query
query FirstPage($input: ProgramProgressInput!) {
  programProgress(input: $input) {
    totalCount
    pageInfo {
      hasNextPage
      endCursor
    }
    edges {
      node {
        userId
        userEmail
      }
    }
  }
}

Next page

Use the endCursor from the previous response as the after argument to fetch the next page.

Next Page Query
query NextPage($input: ProgramProgressInput!) {
  programProgress(input: $input) {
    pageInfo {
      hasNextPage
      endCursor
    }
    edges {
      node {
        userId
        userEmail
      }
    }
  }
}
Variables
{
  "input": {
    "first": 50,
    "after": "eyJpZCI6IjUwIn0="
  }
}

Paginating Through All Results

Loop until hasNextPage is false, passing the endCursor from each response as after in the next request.

JavaScript
async function fetchAllProgress(apiKey) {
  let allRecords = [];
  let cursor = null;
  let hasNextPage = true;

  while (hasNextPage) {
    const response = await fetch('https://api.udacity.com/api/public/api/v1/program-progress/graphql', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Token ${apiKey}`,
      },
      body: JSON.stringify({
        query: `
          query GetProgramProgress($input: ProgramProgressInput!) {
            programProgress(input: $input) {
              pageInfo { hasNextPage endCursor }
              edges { node { userId userEmail userFirstName userLastName } }
            }
          }
        `,
        variables: { input: { first: 100, after: cursor } },
      }),
    });

    const { data } = await response.json();
    const { edges, pageInfo } = data.programProgress;

    allRecords.push(...edges.map(e => e.node));
    hasNextPage = pageInfo.hasNextPage;
    cursor = pageInfo.endCursor;
  }

  return allRecords;
}

Page Size Limits

programProgressmax first: 10,000

Maximum page size for program progress queries

assessmentProgressmax first: 10,000

Maximum page size for assessment progress queries

learningPlanProgressmax first: 10,000

Maximum page size for learning plan progress queries

Tip: For best performance, use page sizes of 100–500. Larger pages take longer to process and may time out on complex queries.

Tips

  • Always check hasNextPage before requesting the next page
  • Don’t store cursors long-term — they may become invalid if the underlying data changes
  • Use totalCount to show progress indicators or estimate pagination depth
  • Combine pagination with filters to reduce the total dataset before paginating

Tip: Combine pagination with orderBy and filter to efficiently retrieve exactly the data you need. See Filtering & Sorting for details.