Skip to content

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:

  1. Create tenant plans that map your plan keys to McCoy course IDs.
  2. Create or update learner identities by email.
  3. Add or remove plans by learner_id when 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:

curl -X GET "https://api.mccoy.org/api/v1/plans" \
  -H "X-Api-Key: YOUR_API_KEY"

Get one plan:

curl -X GET "https://api.mccoy.org/api/v1/plans/paid-2" \
  -H "X-Api-Key: YOUR_API_KEY"

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:

curl -X DELETE "https://api.mccoy.org/api/v1/plans/paid-2" \
  -H "X-Api-Key: YOUR_API_KEY"

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:

{
  "add": ["paid-1", "lab-access"],
  "send_email": false
}

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:

{
  "add": ["paid-2"],
  "remove": ["paid-1"],
  "send_email": false
}

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
  1. List or confirm available course IDs with McCoy.
  2. Create plans for each customer-facing product tier.
  3. Create or update each learner identity and store the returned learner_id.
  4. Send add and/or remove to POST /api/v1/learners/{learner_id}/plans when that individual learner's product access changes.
  5. Send remove with the canceled plan keys when a learner should lose specific plan-backed access.
  6. Use Learner Directory if you need to find learner_id values for Surface embedding.