Integration Examples
Complete, runnable scripts you can use as starting points for integrating with the Udacity Public API.
Python
Fetch the full catalog
Fetch all programs, assessments, and learning plans from the Catalog API. No pagination required.
Python
import os
import requests
API_KEY = os.environ["UDACITY_API_KEY"]
CATALOG_URL = "https://api.udacity.com/api/public/api/v1/catalog/graphql"
query = """
{
catalog {
companyId
programs { key title type duration }
assessments { id title category }
learningPlans { id title key }
}
}
"""
response = requests.post(
CATALOG_URL,
json={"query": query},
headers={
"Content-Type": "application/json",
"Authorization": f"Token {API_KEY}",
},
)
response.raise_for_status()
result = response.json()
if "errors" in result:
raise Exception(f"GraphQL errors: {result['errors']}")
catalog = result["data"]["catalog"]
print(f"Company: {catalog['companyId']}")
print(f"Programs: {len(catalog['programs'])}")
print(f"Assessments: {len(catalog['assessments'])}")
print(f"Learning Plans: {len(catalog['learningPlans'])}")
for program in catalog["programs"]:
print(f" - {program['title']} ({program['key']}, {program['type']})")Python
Paginate through all program progress
Fetch every program progress record using cursor-based pagination. This pattern works for any paginated endpoint.
Python
import os
import requests
import time
import { getCatalogApiUrl, getProgramProgressApiUrl } from "@/lib/api-urls";
API_KEY = os.environ["UDACITY_API_KEY"]
PROGRESS_URL = "https://api.udacity.com/api/public/api/v1/program-progress/graphql"
QUERY = """
query GetProgress($input: ProgramProgressInput!) {
programProgress(input: $input) {
totalCount
pageInfo { hasNextPage endCursor }
edges {
node {
userId
userEmail
enrollmentStatus
programKey
completionPercentage
lastActiveAt
}
}
}
}
"""
PAGE_SIZE = 100
def fetch_all_progress():
all_records = []
cursor = None
while True:
variables = {"input": {"first": PAGE_SIZE}}
if cursor:
variables["input"]["after"] = cursor
response = requests.post(
PROGRESS_URL,
json={"query": QUERY, "variables": variables},
headers={
"Content-Type": "application/json",
"Authorization": f"Token {API_KEY}",
},
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
print(f"Rate limited — waiting {retry_after}s")
time.sleep(retry_after)
continue
response.raise_for_status()
result = response.json()
if "errors" in result:
raise Exception(f"GraphQL errors: {result['errors']}")
data = result["data"]["programProgress"]
records = [edge["node"] for edge in data["edges"]]
all_records.extend(records)
print(f"Fetched {len(all_records)} / {data['totalCount']} records")
if not data["pageInfo"]["hasNextPage"]:
break
cursor = data["pageInfo"]["endCursor"]
return all_records
records = fetch_all_progress()
print(f"\nTotal records: {len(records)}")
enrolled = [r for r in records if r["enrollmentStatus"] == "ENROLLED"]
print(f"Currently enrolled: {len(enrolled)}")Node.js
Paginate and filter progress
Fetch program progress records filtered to enrolled learners, with pagination. Uses the built-in fetch API (Node.js 18+).
JavaScript
const API_KEY = process.env.UDACITY_API_KEY;
const PROGRESS_URL = "https://api.udacity.com/api/public/api/v1/program-progress/graphql";
const QUERY = `
query GetProgress($input: ProgramProgressInput!) {
programProgress(input: $input) {
totalCount
pageInfo { hasNextPage endCursor }
edges {
node {
userId
userEmail
enrollmentStatus
programKey
completionPercentage
lastActiveAt
}
}
}
}
`;
async function fetchPage(cursor) {
const variables = {
input: {
filter: { enrollmentStatus: ["ENROLLED"] },
orderBy: "LAST_ACTIVE",
order: "DESC",
first: 100,
...(cursor ? { after: cursor } : {}),
},
};
const response = await fetch(PROGRESS_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Token ${API_KEY}`,
},
body: JSON.stringify({ query: QUERY, variables }),
});
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "60");
console.log(`Rate limited — waiting ${retryAfter}s`);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
return fetchPage(cursor);
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const result = await response.json();
if (result.errors) {
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
}
return result.data.programProgress;
}
async function fetchAllEnrolled() {
const allRecords = [];
let cursor = null;
do {
const data = await fetchPage(cursor);
const records = data.edges.map((e) => e.node);
allRecords.push(...records);
console.log(`Fetched ${allRecords.length} / ${data.totalCount}`);
cursor = data.pageInfo.hasNextPage ? data.pageInfo.endCursor : null;
} while (cursor);
return allRecords;
}
const records = await fetchAllEnrolled();
console.log(`\nTotal enrolled: ${records.length}`);
for (const r of records.slice(0, 5)) {
console.log(` ${r.userEmail} — ${r.programKey} — ${r.completionPercentage}%`);
}Patterns
Key patterns
- Environment variable for API key — never hardcode credentials
- Check for
errorsin the response — GraphQL returns HTTP 200 even when the query fails - Handle 429 rate limits — back off using the
Retry-Afterheader (see Rate Limits) - Cursor-based pagination — loop until
hasNextPageisfalse(see Pagination)