Plan-Based Learner Provisioning
Organization admins can control individual learner access directly in Terminal when access is managed one learner at a time.
Use plan-based learner provisioning when your system needs group-based access control through reusable plan keys, or when learner access should change automatically based on actions in your system, such as signups, purchases, upgrades, downgrades, or cancellations.
For learners that already exist in McCoy, use the Learner Directory to find each learner's public learner_id.
Most plan-based integrations have three required steps:
- Create tenant plans that map your plan keys to McCoy course IDs.
- Create or update learner identities by email.
- Add or remove plans by
learner_idwhen learners sign up, purchase, upgrade, downgrade, or cancel.
Required Permissions
Ask your organization admin to create or update an API key on terminal.mccoy.org with these permissions:
| Permission | Used for |
|---|---|
manage_plans |
Create and manage plan-to-course mappings |
provision_learners |
Create/update/deactivate learner identities and assign or revoke plans |
read_learners |
Optional. List learners and find public learner_id values |
read_courses |
Optional. List available course IDs for plan setup |
Before You Start
Your organization admin can provide:
- Your API base URL
- Your API key for the tenant you want to operate on
- Your tenant URL
- The course IDs that tenant can include in plans
If you want McCoy to send learner-facing access emails during provisioning, configure the tenant's learner portal branding in Terminal before sending learner plan requests.
Course IDs must already be available to the API key's tenant before they can be used in a plan. If your API key has read_courses, you can list available course IDs with GET /api/v1/courses.
Create Plans
A plan maps your commercial plan key, such as free or paid-2, to a set of McCoy courses.
curl -X POST "https://api.mccoy.org/api/v1/plans" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"plan_key": "paid-2",
"display_name": "Paid 2",
"course_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"660e8400-e29b-41d4-a716-446655440001"
],
"is_active": true,
"metadata": {
"sku": "paid_2_annual"
}
}'
Response:
{
"data": {
"plan_key": "paid-2",
"display_name": "Paid 2",
"courses": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Customer Success Foundations"
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Advanced Account Management"
}
],
"is_active": true,
"metadata": {
"sku": "paid_2_annual"
},
"created_at": "2026-05-12T14:30:00.000Z",
"updated_at": "2026-05-12T14:30:00.000Z"
}
}
Plan responses include courses so your admin tools can display course names without an extra course lookup.
Manage Plans
List plans:
Get one plan:
Update a plan:
curl -X PUT "https://api.mccoy.org/api/v1/plans/paid-2" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Paid 2 Annual",
"course_ids": [
"550e8400-e29b-41d4-a716-446655440000"
],
"is_active": true
}'
Deactivate a plan:
Plans are never hard-deleted. To reactivate a plan, send PUT with "is_active": true.
Existing learners
Plan changes apply to existing learners immediately: entitlement-backed grants are resynced to the updated course list, while manual/admin grants are left untouched. Deactivating a plan removes only that plan's entitlement-backed grants.
Deactivating or reactivating a plan sends no email by default. To email each learner holding the plan, send "send_email": true with is_active on PUT, or add ?send_email=true to DELETE. Email is sent only when the plan's active state actually changes.
Create or Update Learner Identity
Create a learner identity before assigning access. McCoy returns the public learner_id that you use for plan changes, progress tracking, and Surface embedding.
curl -X POST "https://api.mccoy.org/api/v1/learners" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "learner@example.com",
"first_name": "Ava",
"last_name": "Chen"
}'
Response:
{
"data": {
"learner_id": "lrn_01HX7N8V2K9Q4R6M3B5C7D9E0F",
"email": "learner@example.com",
"first_name": "Ava",
"last_name": "Chen",
"action": "created",
"tenant_url": "https://acme.surface.mccoy.org",
"is_active": true
}
}
Learner identity responses include learner_id for McCoy learner attribution and tenant_url for learner sign-in. action is created when McCoy created a new learner record and updated when McCoy updated an existing same-tenant learner. This endpoint does not grant, change, or revoke plan access.
Any successful POST /api/v1/learners reactivates a learner previously deactivated with DELETE /api/v1/learners/{learner_id}.
Grant Learner Plan Access
Add plans to an existing learner by learner_id; do not resend the learner's email or name.
curl -X POST "https://api.mccoy.org/api/v1/learners/lrn_01HX7N8V2K9Q4R6M3B5C7D9E0F/plans" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"add": ["paid-2"],
"send_email": true,
"external_reference_id": "order-123",
"expires_at": null,
"metadata": {
"purchased_sku": "paid_2_annual"
}
}'
Response:
{
"data": {
"learner_id": "lrn_01HX7N8V2K9Q4R6M3B5C7D9E0F",
"email": "learner@example.com",
"action": "updated",
"plans": ["paid-2"],
"tenant_url": "https://acme.surface.mccoy.org",
"external_reference_id": "order-123",
"expires_at": null,
"is_active": true,
"courses": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Customer Success Foundations"
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "Advanced Account Management"
}
],
"email_queued": true
}
}
Plan mutation responses include the learner's active plans, courses for human-readable confirmation views, and the primary active entitlement's external_reference_id / expires_at. external_reference_id is not the learner identity.
To grant multiple active plans at once, send multiple keys in add:
McCoy stores one entitlement per plan key. Course access from all active plans stacks, and overlapping course IDs are returned once in courses. Provisioning is incremental: add grants or reactivates the listed plans, while remove revokes only the listed plans. Manual/admin grants are preserved.
You can add and remove in the same request when a learner changes products:
By default, McCoy may send one learner-facing email when a request changes the learner's active plan set. McCoy chooses the copy from the committed transition: first access sends an account-ready email, later add/remove changes send an access-updated email, and full plan-backed removal sends access-removed copy. Duplicate no-op add/remove requests do not send email. Send send_email: false if your system handles learner communication. For first access emails, the learner must already have first_name set and the tenant's learner portal branding must be configured in Terminal.
Adding a plan reactivates a deactivated learner identity. Removing plans does not reactivate a deactivated learner.
Finding Learners
Use Learner Directory when you need to list existing learners or find a public McCoy learner_id for Surface embedding.
Remove Learner Plan Access
Send remove with the plan keys from a cancellation or downgrade event. McCoy revokes only those active plan entitlements and preserves every other active plan and manual/admin grant.
curl -X POST "https://api.mccoy.org/api/v1/learners/lrn_01HX7N8V2K9Q4R6M3B5C7D9E0F/plans" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"remove": ["paid-2"],
"external_reference_id": "cancel-123",
"metadata": {
"reason": "opted_out"
}
}'
Response:
{
"data": {
"learner_id": "lrn_01HX7N8V2K9Q4R6M3B5C7D9E0F",
"email": "learner@example.com",
"action": "updated",
"plans": [],
"tenant_url": "https://acme.surface.mccoy.org",
"external_reference_id": null,
"expires_at": null,
"is_active": true,
"courses": [],
"email_queued": false
}
}
If the removed plan was the learner's last active entitlement, the response external_reference_id is null; any cancellation reference you send is stored on the revoked entitlement.
Removes are idempotent. Re-sending the same remove request for an already-removed plan returns 200 and leaves the learner's remaining plans unchanged.
Removing plan access is separate from learner deactivation. remove does not reactivate a deactivated learner identity.
Deprecated Combined Provisioning
POST /api/v1/learners no longer accepts plans, plan_key, plan_keys, identity_only, expires_at, external_reference_id, metadata, send_email, or send_account_ready_email. Use the learner identity endpoint first, then POST /api/v1/learners/{learner_id}/plans for entitlement-backed access.
Deactivate Learners
Use DELETE /api/v1/learners/{learner_id} to soft-deactivate a learner without removing records, access grants, sessions, or progress history.
curl -X DELETE "https://api.mccoy.org/api/v1/learners/lrn_01HX7N8V2K9Q4R6M3B5C7D9E0F" \
-H "X-Api-Key: YOUR_API_KEY"
Response:
{
"data": {
"learner_id": "lrn_01HX7N8V2K9Q4R6M3B5C7D9E0F",
"email": "learner@example.com",
"first_name": "Ava",
"last_name": "Chen",
"is_active": false
}
}
Deactivated learners are hidden from learner-directory endpoints and cannot start new learner activity. Reporting and progress history remain readable by API key reporting endpoints. Send a successful learner identity POST /api/v1/learners or plan add to reactivate the identity.
Authentication Flow
McCoy is the source of learner_id. The provisioning API is one way to create or update learners; Terminal-managed learners can also be listed with GET /api/v1/learners. Learners sign in through your tenant URL using the email address you provisioned.
If McCoy cannot provision a learner for this tenant, provisioning returns 409 LEARNER_PROVISIONING_CONFLICT.
Common Errors
| Error code | HTTP status | Meaning |
|---|---|---|
INVALID_REQUEST |
400 | A required field is missing or a field is not allowed for the selected endpoint |
UNKNOWN_PLAN |
400 | An add key does not exist or is inactive for this tenant |
INVALID_COURSES |
400 | A plan includes course IDs unavailable to this tenant |
PLAN_EXISTS |
409 | A plan with this plan_key already exists for this tenant |
PLAN_NOT_FOUND |
404 | The requested plan does not exist for this tenant |
LEARNER_NOT_FOUND |
404 | A learner plan request used a learner_id that does not exist in this tenant |
LEARNER_PROVISIONING_CONFLICT |
409 | McCoy cannot provision the learner for this tenant |
INSUFFICIENT_PERMISSIONS |
403 | Your API key is missing manage_plans, provision_learners, or read_learners |
Recommended Sequence
- List or confirm available course IDs with McCoy.
- Create plans for each customer-facing product tier.
- Create or update each learner identity and store the returned
learner_id. - Send
addand/orremovetoPOST /api/v1/learners/{learner_id}/planswhen that individual learner's product access changes. - Send
removewith the canceled plan keys when a learner should lose specific plan-backed access. - Use Learner Directory if you need to find
learner_idvalues for Surface embedding.