Migrating from the Data API
The legacy Data API provided SQL-based access to learner data via POST /api/emc/reports/query with a SQL query body. The new GraphQL API replaces this with structured, filterable endpoints.
Column mapping — roster_v1
The most commonly used Data API table is roster_v1. Here is how its columns map to GraphQL fields:
roster_v1 column | GraphQL field | API | Notes |
|---|---|---|---|
email | userEmail | Program Progress | |
first_name | userFirstName | Program Progress | |
last_name | userLastName | Program Progress | |
user_key | userId | Program Progress | |
nd_title | — | Catalog | Query separately: catalog { programs { key title } } |
node_key | programKey | Program Progress | |
enrollment_state | enrollmentStatus | Program Progress | Enum: ENROLLED, UNENROLLED, GRADUATED, STATIC_ACCESS |
enrolled_at | enrolledAt | Program Progress | RFC 3339 timestamp |
graduated_at | graduatedAt / completedAt | Program Progress | Nullable — only set when graduated/completed |
last_seen | lastActiveAt | Program Progress | Nullable |
on_track_status | onTrackStatus | Program Progress | Enum: ON_TRACK, OFF_TRACK, MONITOR, COMPLETED, READY_TO_GRADUATE, GRADUATED |
program_type | programType | Program Progress | Enum: NANODEGREE, COURSE, PART |
core_projects_passed | projectsRequiredCompleted | Program Progress | Approximate mapping |
total_core_projects | projectsRequiredTotal | Program Progress | Approximate mapping |
SQL → GraphQL examples
Graduated learners since a date
Data API (SQL):
SELECT email, nd_title, graduated_at::DATE
FROM roster_v1
WHERE graduated_at IS NOT NULL
AND graduated_at::date > '2026-01-01'::DATEGraphQL:
query GraduatedLearners($input: ProgramProgressInput!) {
programProgress(input: $input) {
totalCount
pageInfo { hasNextPage endCursor }
edges {
node {
userEmail
programKey
enrollmentStatus
graduatedAt
}
}
}
}Variables:
{
"input": {
"filter": { "enrollmentStatus": ["GRADUATED"] },
"orderBy": "LAST_ACTIVE",
"order": "DESC",
"first": 100
}
}Note: The GraphQL API filters by enrollment status but does not support arbitrary date range filtering on graduatedAt. Filter to graduated learners server-side, then check graduatedAt values client-side if you need a specific date cutoff.
Currently enrolled learners, sorted by activity
Data API (SQL):
SELECT email, last_seen, enrollment_state
FROM roster_v1
WHERE enrollment_state = 'enrolled'
ORDER BY last_seen ASCGraphQL:
query EnrolledByActivity($input: ProgramProgressInput!) {
programProgress(input: $input) {
edges {
node {
userEmail
lastActiveAt
enrollmentStatus
}
}
pageInfo { hasNextPage endCursor }
}
}Variables:
{
"input": {
"filter": { "enrollmentStatus": ["ENROLLED"] },
"orderBy": "LAST_ACTIVE",
"order": "ASC",
"first": 100
}
}Coverage gaps
Some roster_v1 and projects_v1 columns are not yet available in the GraphQL API:
| Data API column | Status |
|---|---|
projects_v1 (individual project submissions) | Not available — only aggregate counts via projectsRequiredTotal / projectsRequiredCompleted |
total_concepts / total_concepts_completed | Not available — use completionPercentage instead |
employee_id (external ID) | Not available |
tag_ids / tag_names (EMC groups) | Not available |
enrollment_history (state changes over time) | Not available — only current state is returned |
If your Data API integration depends on fields not yet available in GraphQL, contact your Udacity account team for guidance on timing and alternatives.
Key differences from the Data API
- No SQL — you write structured GraphQL queries instead of raw SQL
- Cursor-based pagination instead of
LIMIT / OFFSET— more reliable for large or changing datasets (see Pagination) - Separate endpoints per domain — catalog, program progress, assessment progress, and learning plan progress are each queried independently
- Token auth — uses
Authorization: Token <api_key>with aCOMPANYscope - Selective fields — request only the fields you need, reducing payload size
- Enum values — statuses use UPPER_CASE enums (e.g.,
ENROLLEDnotenrolled)
Getting started
- Generate API credentials with a
COMPANYscope - Try queries in the Playground
- See Integration Examples for complete Python and Node.js scripts with pagination