Woolf API v0 is retiring on October 15, 2026 — how to move to v1
Woolf API v0 is retiring on October 15, 2026
We are switching off version 0 of the Woolf partner API on October 15, 2026. After that date, any request your systems send to the old v0 endpoints will fail, and anything that depends on those requests — enrolling students, sending grades, syncing submissions — will stop working.
This guide walks you through the migration one step at a time. Most integrations can complete it in a few days of engineering work. If anything here is unclear, contact your Woolf partner manager or support@woolf.university — we're happy to review your migration plan or get on a call with your team.
How do I know if this affects me?
Look at the URL your server sends Woolf API requests to:
| You are calling... | Version | Affected? |
|---|---|---|
https://api.woolf.university/ (nothing after the domain) | v0 | Yes — you must migrate |
https://api.woolf.university/public | v0 | Yes — you must migrate |
https://api.woolf.university/v1 | v1 | No — you're already done |
The in-browser Woolf SDK is not affected. If your LMS pages load the Woolf SDK (@woolfuniversity/sdk) or call the tracking endpoints (/track/consumption, /track/submission, etc.), nothing changes there. This deprecation only covers the GraphQL API your server calls.
Why v1 is worth the move
- v0 stops working on October 15, 2026. After the deadline, v0 requests are rejected. Migrating early means you find and fix issues on your schedule, not during an outage.
- v1 can create submissions directly from your server. In v0,
submitAssignmentcould only update a submission that already existed. In v1,addSubmissioncreates the record too — one call, no pre-existing activity needed. - Everything works with one token. v0 forced you to mint and refresh per-student OAuth tokens (
authorizeOAuth,refresh) for some operations. In v1, your server does everything with your college API token. - Better reporting, fewer calls. v1's
studentDegreeProgressreturns a student's complete degree progress (workload, grades, per-course breakdown) in a single query — data that took multiple v0 queries to assemble. - Bulk operations with job tracking. v1 accepts bulk uploads and returns a
jobIdyou can check withjobDetails, instead of you looping over hundreds of single calls.
Part 1 — Must-do changes
Work through these in order. Each section says exactly what to find in your code and what to replace it with.
Step 1: Change the URL
Find where your code sends requests to the Woolf API and add /v1 to the path.
Before: POST https://api.woolf.university/ (or /public) After: POST https://api.woolf.university/v1
Everything else about how you call the API stays the same: it is still GraphQL, still POST, still Content-Type: application/json, and still the same Authorization: Bearer <your college token> header. Your API token does not change.
Step 2: Replace each mutation you use
Find every GraphQL mutation name your code sends, look it up in this table, and replace it with the v1 version. Every v0 mutation is listed — if you use it, it's here.
| If your code calls (v0)... | Replace it with (v1)... | What else changes |
|---|---|---|
addDegreeStudent | addStudentToDegree | nameFirst, nameLast, externalId move inside a student: {...} object; price is now tuitionCost (see example A) |
addStudentsToCollege | addStudentToCollege | v1 takes one student per call — loop over your list |
createResource, apiCreateResource | addResource | Response is now { resource { ... } } instead of the resource directly |
modifyResource, apiModifyResource | modifyResource | Same name, response wrapped the same way as addResource |
deleteResource | — (remove the call) | This v0 mutation performs no action and always returns false. You can safely delete the call from your code |
archiveResources, apiArchiveResources, unarchiveResources | Not yet available in v1 | If your integration archives or unarchives resources, contact support@woolf.university before you migrate and we will work out a path with you |
addGrade | addGrade | Arguments move inside a grade: {...} object; score is renamed value (see example B) |
teacherAddGrade | addGrade | Same as above; pass the teacher via teacherId |
addFeedback | addFeedback | Arguments move inside a feedback: {...} object; descr is renamed content |
teacherAddFeedback | addFeedback | Same as above; pass the teacher via teacherId |
submitAssignment, studentSubmitAssignment, apiStudentSubmitAssignment | addSubmission | Arguments move inside a submission: {...} object; descr is renamed content; college token instead of student token (see example C) |
addEvidence | — | Attach evidence via the evidenceAssets field on addSubmission / addGrade / addFeedback instead |
archiveGrade | archiveActivity | Pass the activity's ID instead of (resource, student, weight) |
importAsset, removeAsset | — | Assets are now passed via the assets field on addResource / modifyResource |
submitStudentsForEvaluation | submitStudentsForEvaluation | Same name; arguments move inside an evaluation: {...} object |
authorizeOAuth, refresh | generateUserToken | One call with your college token; no refresh flow needed |
addWebhook, changeWebhook, removeWebhook | Same names, same arguments | No change needed |
Example A — enrolling a student
# v0 — old mutation { addDegreeStudent( email: "student@example.com" degreeId: "degree-uuid" externalId: "your-internal-id-123" nameFirst: "Priya" nameLast: "Sharma" price: 400000 ) { id } } # v1 — new mutation { addStudentToDegree( email: "student@example.com" degreeId: "degree-uuid" tuitionCost: 400000 student: { externalId: "your-internal-id-123" nameFirst: "Priya" nameLast: "Sharma" } ) { id } }
Example B — sending a grade
# v0 — old # note: "score" mutation { addGrade( resourceId: "resource-uuid" studentId: "student-uuid" score: 85.0 weightId: "weight-uuid" ) { id } } # v1 — new # note: "value", wrapped in "grade" mutation { addGrade(grade: { resourceId: "resource-uuid" studentId: "student-uuid" value: 85.0 weightId: "weight-uuid" }) { activityId } }
Example C — submitting an assignment
# v0 — old: required an existing activityId and used "descr". # Failed if the activity didn't exist yet. mutation { submitAssignment( activityId: "activity-uuid" resourceId: "resource-uuid" descr: "<p>The student's submission text</p>" ) { id } } # v1 — new: "content" instead of "descr", wrapped in "submission". mutation { addSubmission(submission: { activityId: "activity-uuid" # include to UPDATE an existing submission resourceId: "resource-uuid" studentId: "student-uuid" content: "<p>The student's submission text</p>" }) { activityId } }
One behavior change to be aware of: in v1, if you leave out activityId, addSubmission creates a new submission rather than failing. If your code relied on the v0 behavior of "error when the activity doesn't exist," always pass activityId when you intend an update.
Step 3: Replace each query you use
| If your code calls (v0)... | Replace it with (v1)... | Notes |
|---|---|---|
listCourses(degreeId) | courses(degreeId, pagination) | Results are now paginated: read them from the list field, total from count |
showResource(id) | resource(id) | |
showStudentDegreeProgress(degreeId, studentIds) | studentDegreeProgress(id, degreeId) | One call per student (id = student email, your external ID, or Woolf UUID); returns far more detail than v0 did |
showStudentCourseProgress(courseId, studentIds) | activities(courseId, studentId, ...) | Also see the courseStudents roster query below — usually a better fit |
showStudentResourceProgress(resourceIds, studentId) | activities(resourceId, studentId, ...) |
Step 4: Update code that mints student tokens
If you generate per-student tokens (for example, to initialize the Woolf SDK on your pages), replace the v0 OAuth flow:
# v0 — old: two mutations plus refresh handling mutation { authorizeOAuth(userId: "user-uuid") { accessToken } } mutation { refresh(token: "old-token") { accessToken } } # v1 — new: one call with your college token mutation { generateUserToken(id: "student-uuid") }
Tokens still expire — call generateUserToken again when you need a fresh one, instead of running a refresh flow.
Step 5: Check your webhook handler
The webhook mutations (addWebhook, changeWebhook, removeWebhook) are unchanged, and your existing webhook subscriptions keep working. However, v1 webhook payloads are simplified: they contain deliveryId, signature, event, and data. If your handler reads any other fields from the payload (e.g. delivery metadata), update it to use only those four.
Step 6: Test before you switch production
- Ask your Woolf contact for sandbox credentials (
https://api.x.university/v1) if you don't have them. - Note that schema introspection is disabled on production endpoints — autocomplete against the live API won't work. Ask us for the current v1 schema file (SDL), or explore via GraphiQL at
{base}/v1/graphiqlin the sandbox. - Run your integration's main flows end-to-end in the sandbox: enroll a test student, send a grade, send a submission, receive a webhook.
- Switch production traffic, then watch your logs for GraphQL errors (
errors[].extensions.code) for the first few days. Remember responses can be HTTP 200 and still contain errors — always check theerrorsarray.
Part 2 — Nice-to-haves (optional, but they'll simplify your code)
None of these are required to meet the deadline. They're new v1 capabilities that replace things partners used to build themselves.
studentDegreeProgress— one query returns a student's full degree snapshot: total workload done vs. required, GPA fields, and per-tier/per-course breakdowns. If you currently stitch this together from several queries, this replaces all of them.courseStudentsroster query — returns every (student, course) enrollment for your college, with filters. The killer combination isreadyForSubmission: true+isActiveDegreeStudent: true, which returns exactly the students who have cleared every gate (workload, assignments, meetings, grade threshold) — the same logic Woolf uses internally, so your numbers always match ours.- Bulk uploads with
jobDetails—bulkUploadActivityreturns ajobId; polljobDetails(jobId)to see when it finishes. Much faster than looping single calls, and rate-limit friendly. - Look students up by your own IDs — anywhere v1 takes a student
idstring, you can pass theexternalIdyou set at enrollment (or the student's email) instead of storing Woolf's UUIDs. - A second API key for background jobs — if you run nightly syncs and real-time calls on the same key you can hit rate limits (HTTP 429). Ask us for a separate key for your batch traffic.
Timeline
| Date | What happens |
|---|---|
| Now | v1 is live; sandbox available for migration testing |
| October 15, 2026 | v0 endpoints (/ and /public) are switched off; unmigrated integrations stop working |
Questions? Contact your Woolf partner manager or support@woolf.university.