Back to home
Jump to…

API reference

Using the EquipEngine API

The EquipEngine API lets your other systems read your organization's data — people, courses, enrollments and flows. Use it to feed a reporting dashboard or sync training completions into another database. With the extra permissions below, an automation in a tool like Zapier, Make or n8n can also assign courses and flows, unassign people, invite learners, and build draft courses. AI tools like Claude and Cursor can use it too (see Use with AI tools).

API access is included in the Multiply plan. Only admins can create and manage API keys. Everything is JSON over HTTPS, and every endpoint is versioned under /v1.

The same reference is available as an OpenAPI document, which tools like Postman, Insomnia and most code generators can import.

OpenAPI document
Base URL
https://api.equipengine.com/v1

Authentication

Every request needs an API key, sent as a bearer token in the Authorization header. The key decides which organization's data you get, so there's no organization id in the URL.

Creating an API key

  1. Go to Settings → API and click New API key.
  2. Give the key a name that says what uses it, like "Power BI training dashboard". Use a separate key for each integration so you can revoke one without breaking the others.
  3. Choose what it can read. Turn off anything the integration doesn't need — for example, a dashboard that only counts completions doesn't need people's email addresses.
  4. Only if the integration needs to change things, turn on what it can change (off by default).
  5. Choose when it expires: 30 days, 90 days or 1 year.
  6. Click Create key, then Copy. This is the only time you'll see the key. Store it in a password manager or your integration's secret settings.
Settings → API with two keys — one active, one nearing expiry with Extend 1 year and Revoke actions.

Never put a key in a web page or a browser script — anyone who can see it can read your data. Keep it on a server or in your automation tool's secret settings. Every admin in your organization is emailed whenever a key is created, revoked or extended. You can have up to 10 keys that haven't been revoked or expired (keys marked Inactive or Paused count).

Keys start with eqe_live_. A key made in a test environment starts with eqe_test_ and only works there.

curl "https://api.equipengine.com/v1/me" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": {
    "key": {
      "id": "d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a",
      "name": "Power BI training dashboard",
      "scopes": [
        "people:read",
        "courses:read"
      ],
      "expires_at": "2027-10-09T21:14:07.000Z"
    },
    "organization": {
      "id": "b7d1e3f5-a9c2-4e6b-8d0f-2c4e6a8b0d1f",
      "name": "Grace Community Church",
      "slug": "grace"
    }
  }
}

Conventions

  • Ids are opaque strings. Store them as text and don't parse them.
  • Timestamps are ISO 8601 in UTC, with milliseconds, e.g. 2026-03-14T15:02:11.482Z.
  • Lists come back in a stable order (by id, not by date; ministries by name; by updated_at when you pass updated_since). By id, paging never repeats an item, though one added while you page may be missed if its id sorts before your cursor. By updated_at, nothing is skipped, but an item that changes while you page comes back again later: update records in place. Sort on your side if you need a different order. Staff accounts, and people who have left your organization, don't appear in lists.
  • New fields may be added to responses at any time; ignore ones you don't recognise. Fields are never renamed or removed within /v1.
  • Unknown query parameters are ignored.
  • Successful requests return 200, including ones that create something.
  • Every JSON response from an endpoint has an X-Request-Id header (the same value as an error's request_id). Include it if you contact support.
  • Use paths exactly as documented: a trailing slash (/v1/people/) gets a redirect. HEAD works on every GET endpoint.
  • Browsers can't call the API directly (there's no CORS support), which keeps keys out of web pages. Call it from a server or an automation tool.

Permissions

Each key only gets the permissions you give it. An endpoint that needs a permission the key doesn't have returns insufficient_scope. Each endpoint below lists the permission it needs.

PermissionScopeAllows
Peoplepeople:readNames, email addresses and roles.
Coursescourses:readCourses, their modules and lessons, and your ministries.
Enrollmentsenrollments:readWho is enrolled in what, and their lesson progress.
Flowsflows:readYour flows and who they're assigned to.
Build draft coursescourses:writeCreate draft courses and add or edit their modules, lessons, quizzes and tasks. Published courses can't be changed through the API.
Assign and unassignenrollments:writeAssign published courses and flows to a person, a role or a group, and unassign someone from a course. Sends the same emails as assigning in EquipEngine.
Invite learnerspeople:inviteInvite people by email as learners (never as admins or creators). The same daily invite limit and learner limit apply as in Settings → Access.

The last three can change data, so they're off unless you turn them on. A key with any of them shows a Read & write badge, and its Recent activity on Settings → API lists its latest changes. A key's permissions can't be changed after it's created; create a new key instead.

Unassigning someone sticks: automatic syncs and role or group assignments won't re-enroll them; assigning that person directly will.

Managing keys

Expiry, reminders and extending a key

Every key expires. Your admins are emailed 14 days and again 3 days before a key expires. To keep an integration working, click Extend 1 year next to the key on Settings → API: it then expires one year from today. The key itself doesn't change, so you don't need to update anything that uses it. You can also extend a key up to 90 days after it expires, but not while it's Inactive or Paused.

Revoking a key

Click Revoke next to the key. Anything using it stops working immediately, and it can't be undone. Revoke a key straight away if you think it has been shared or leaked, then create a new one.

A key also stops working if the admin who created it is removed from your organization or is no longer an admin. It's marked Inactive on the page; create a new key to replace it. If your organization leaves the Multiply plan, keys are Paused and start working again if you return.

Making changes

Send bodies as JSON with a Content-Type: application/json header. Other content types are refused (form encodings and text/plain with a plain-text 403, before your key is checked). Bodies can be up to 256 KB.

Retrying safely

Send an Idempotency-Key header with each change: a unique string of 1–100 printable ASCII characters, no spaces (a UUID works). If your tool retries the same request (same method, path and body) after a timeout, the retry gets the original result instead of doing the work twice, and the response carries Idempotency-Replayed: true. Only successful results are kept (for 24 hours): after an error, retrying with the same key tries again. A retry that arrives while the first request is still running gets idempotency_in_progress with Retry-After: 2.

Reusing a key for a different request (including the same body with its fields in a different order) returns idempotency_conflict. Idempotency keys are scoped to the API key, so two integrations can't collide.

Headers for a change
Authorization: Bearer $EQUIPENGINE_API_KEY
Content-Type: application/json
Idempotency-Key: 7d1c9f0e-3b2a-4c55-9a61-2f8e0d4b6a13

Pagination

Lists return up to 50 items at a time (set limit for up to 100). When there are more, the response includes a next_cursor; pass it back as cursor to get the next page. It's null on the last page. Cursors are opaque: don't build or change them.

A page of results
{
  "data": [
    "…"
  ],
  "next_cursor": "dXNyXzJjOXY3YjRu"
}

Syncing changes

To keep a copy of your data up to date without re-reading everything, pass updated_since (an ISO-8601 time) to /v1/people, /v1/enrollments, /v1/assignments or /v1/flows/{id}/assignments. You get only what changed at or after that time, oldest change first, each with its updated_at. An enrollment counts as changed when its status changes or the learner completes a lesson.

  1. First sync: pass an early updated_since (e.g. 2020-01-01T00:00:00Z) and page through everything.
  2. Remember the newest updated_at you saw. Next time, pass that time minus 60 seconds: a change made in the same moment as your last read can otherwise be missed. You may see a few records again; update them in place.
  3. Ask /v1/deletions with the same updated_since for records that were removed, including people who left your organization, and delete them on your side. When a course or a person goes, delete their enrollments too: those aren't listed one by one. Deletions are kept for 90 days, so sync at least that often (or start over with a full sync).

With updated_since, paging follows updated_at: use next_cursor as usual, but only with the same query. Sync without filters such as role or status: a record that changes so it no longer matches a filter simply stops appearing.

Each kind of deletion needs the permission that reads that kind of record: people need People, courses need Courses, enrollments and assignments need Enrollments, and flow assignments need Flows. Kinds your key can't read are left out.

Changed since your last sync
curl "https://api.equipengine.com/v1/enrollments?updated_since=2026-10-09T17:21:45Z" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"

curl "https://api.equipengine.com/v1/deletions?updated_since=2026-10-09T17:21:45Z" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"

Errors

Errors use standard HTTP status codes and always have the same shape: a machine-readable code, a message you can show or log, and a request_id to quote if you contact support.

Error · 403
{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key needs the enrollments:write scope.",
    "request_id": "4747c91b-170f-4805-b41b-422b1653e5e6"
  }
}
CodeStatusMeaning
validation_failed400A parameter or body field is missing or invalid, including a ministry, module or lesson in the body that doesn’t belong to the course or your organization, or a limit (see Rate limits). details, when present, lists each field.
invalid_json400The body isn’t valid JSON.
invalid_key401The key is missing, mistyped, revoked or inactive.
key_expired401Extend it on Settings → API (up to 90 days after it expired), or create a new key.
insufficient_scope403The key doesn’t have the permission this endpoint needs.
plan_required403API access needs the Multiply plan.
tenant_suspended403Your organization’s account is suspended.
forbidden403Not returned by any endpoint today; reserved for future checks.
not_found404No such endpoint; nothing with that id (in the path) in your organization; a person, group or cohort in the body that isn’t in your organization; or an enrollment that isn’t active when revoking.
method_not_allowed405That HTTP method isn’t supported on this path.
not_published409Only published courses and flows can be assigned.
course_not_draft409Only draft courses can be changed through the API. Edit published courses in the app.
idempotency_conflict409That Idempotency-Key was already used for a different request.
idempotency_in_progress409A request with that Idempotency-Key is still running. Retry in a few seconds.
payload_too_large413The body is over 256 KB.
unsupported_media_type415The body wasn’t sent as application/json.
rate_limited429Too many requests. Wait for the time in Retry-After, then retry.
internal_error500Something went wrong on our side. Retry, and contact support with the request_id if it keeps happening.

Rate limits

Each key can make 300 requests a minute, of which up to 60 can be changes (any request that isn't a GET or HEAD, including retries and replays). Through AI tools, every message counts as a request, and each change tool call counts as a change. Over the limit, requests return rate_limited (429) with a Retry-After header; wait that long, then retry. Building courses (the courses:write endpoints) is also limited to 60 changes a minute for your whole organization, across all its keys. Requests with a missing or wrong key are limited to 20 a minute from one IP address.

Through the API, an organization can have up to 500 draft courses, a course up to 500 lessons, and a lesson up to 200,000 characters of stored text; one request can add up to 200 lessons. Going over returns validation_failed. Need more? Contact support and tell us what you're building.

Use with AI tools

AI tools such as claude.ai, ChatGPT, Claude Code, Cursor, VS Code and n8n can use your data through the Model Context Protocol (MCP). Ask things like “who on the greeter team is overdue?”, or have it assign a course or draft one from your notes. claude.ai and ChatGPT connect to /mcp with a sign-in (OAuth); other tools use an API key, like any other integration.

  • Every endpoint is a tool (listed below), plus find_people (a name or email search) and search_help for questions about using the app. The tool follows the endpoint's rules, permissions and limits, and returns the same errors as text the AI can read.
  • Permissions decide which tools the AI sees: the key's, or what the admin allowed when connecting. Something that can only read never sees a tool that changes anything.
  • Changes that email people or change access (assigning, unassigning, inviting) are marked so AI tools that support it ask you before running them. So are edits that replace a draft's content.
  • Lists return 25 items at a time (not 50). Change tools take an optional idempotency_key, so a retried call doesn't repeat the change.
  • Changes made this way show under the key's or connection's Recent activity on Settings → API, marked “via AI tool”.

OAuth (building your own client)

/mcp is an OAuth 2.1 protected resource (MCP authorization, 2026-07-28). A request without a token gets 401 with a WWW-Authenticate header pointing at /.well-known/oauth-protected-resource/mcp, which names the authorization server. Its metadata is at /.well-known/oauth-authorization-server/auth.

  • Authorization code with PKCE (S256); public clients (none). Register with a Client ID Metadata Document, or dynamic client registration.
  • Send resource (the /mcp URL) on authorize and token requests: tokens only work there, not on /v1.
  • Scopes are the API permissions (people:read …), plus offline_access for a refresh token. The admin can turn any of them off when approving.
  • Access tokens last an hour; refresh tokens rotate, and reusing an old one revokes its tokens, so the app must sign in again. A connection unused for 90 days lapses.
  • Only admins of a church on the Multiply plan can approve, and every admin is emailed when one does.
MCP URL https://api.equipengine.com/mcp
claude.ai → Settings → Connectors → Add custom connector
Name:                  EquipEngine
Remote MCP server URL: https://api.equipengine.com/mcp

Then click Connect and sign in as an admin of your church. You choose what it can do; no API key needed. Works in the Claude desktop and mobile apps too.

ToolDoesPermissionAsks first
get_connection_infoGet connection infoany key
list_peopleList peoplepeople:read
get_personGet a personpeople:read
training_statusGet a person's trainingpeople:read + enrollments:read
list_groupsList groupspeople:read
overdue_reportOverdue reportenrollments:read + people:read
list_coursesList coursescourses:read
get_course_outlineGet a course outlinecourses:read
list_ministriesList ministriescourses:read
list_enrollmentsList enrollmentsenrollments:read
list_flowsList flowsflows:read
list_flow_assignmentsList flow assignmentsflows:read
list_assignmentsList course assignmentsenrollments:read
list_deletionsList deletionsany key
assign_courseAssign a courseenrollments:writeYes
assign_flowAssign a flowenrollments:writeYes
unassign_enrollmentUnassign an enrollmentenrollments:writeYes
invite_learnersInvite learnerspeople:inviteYes
create_draft_courseCreate a draft coursecourses:write
update_draft_courseUpdate a draft coursecourses:writeYes
add_to_course_outlineAdd modules and lessonscourses:write
reorder_modulesReorder modulescourses:write
rename_moduleRename a modulecourses:write
add_lessons_to_moduleAdd lessons to a modulecourses:write
reorder_lessonsReorder lessonscourses:write
update_lessonUpdate a lessoncourses:writeYes
add_lesson_taskAdd a lesson taskcourses:write
add_quiz_questionsAdd quiz questionscourses:write
find_peopleFind peoplepeople:read
search_helpSearch the help centerany key

Endpoints at a glance

Every endpoint, with what it's for. Each one is documented in full below.

EndpointDoes
GET /v1/meThe key you’re using, its permissions and its organization.
GET /v1/peopleList people; filter by role or email, or search by name.
GET /v1/people/{id}One person.
GET /v1/people/{id}/trainingOne person’s courses, progress and due dates, overdue first.
GET /v1/groupsList cohorts and Planning Center groups, lists and teams.
POST /v1/invitationsInvite up to 100 people as learners, optionally into a cohort.
GET /v1/coursesList courses; filter by status or ministry.
GET /v1/courses/{id}A course outline: modules and lessons, by id or slug.
POST /v1/courses/{id}/assignmentsAssign a published course to a person, a role or a group.
POST /v1/coursesCreate a draft course, optionally with its modules and lessons.
PATCH /v1/courses/{id}Change a draft course's title or description.
POST /v1/courses/{id}/outlineAdd modules and lessons to a draft course.
PUT /v1/courses/{id}/module-orderReorder a draft course's modules.
PATCH /v1/modules/{id}Rename a module (draft courses only).
POST /v1/modules/{id}/lessonsAdd lessons to the end of a module (draft courses only).
PUT /v1/modules/{id}/lesson-orderReorder a module's lessons (draft courses only).
PATCH /v1/lessons/{id}Change a lesson's title or written content (draft courses only).
POST /v1/lessons/{id}/taskAdd a quiz, reflection or acknowledgement to a lesson (draft courses only).
POST /v1/lessons/{id}/questionsAdd questions to a lesson's quiz or reflection (draft courses only).
GET /v1/ministriesList active ministries, by name.
GET /v1/enrollmentsEnrollments with lesson progress; filter by course, person or status.
POST /v1/enrollments/{id}/revokeUnassign one person from one course.
GET /v1/assignmentsCourse assignments: who each course is assigned to, with due dates.
GET /v1/flowsList flows.
GET /v1/flows/{id}/assignmentsWho a flow is assigned to.
POST /v1/flows/{id}/assignmentsAssign a published flow to a person, a role or a group.
GET /v1/reports/overdueWho is past a due date, most overdue first; by course, flow, ministry or group.
GET /v1/deletionsRecords removed since a moment, for keeping a copy in sync.

API reference

Your key

Get your key

The calling key and its organization

GET /v1/me

A quick way to check a key works, which organization it belongs to and what it can do.

Works with any key.

Returns

A Key and organization object in data.

Show 9 fields
  • key object required
  • id string · 1–128 chars required
  • name string required
  • scopes ("people:read" | "courses:read" | "enrollments:read" | "flows:read" | "courses:write" | "enrollments:write" | "people:invite")[] required
  • expires_at timestamp required

    ISO-8601, UTC

  • organization object required
  • id string · 1–128 chars required
  • name string required
  • slug string required
curl "https://api.equipengine.com/v1/me" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": {
    "key": {
      "id": "d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a",
      "name": "Power BI training dashboard",
      "scopes": [
        "people:read",
        "courses:read",
        "enrollments:read",
        "flows:read"
      ],
      "expires_at": "2027-10-09T21:14:07.000Z"
    },
    "organization": {
      "id": "b7d1e3f5-a9c2-4e6b-8d0f-2c4e6a8b0d1f",
      "name": "Grace Community Church",
      "slug": "grace"
    }
  }
}

API reference

People

List people

List people (staff accounts excluded)

GET /v1/people

Everyone in your organization, in a stable order for paging. Filter by role, look someone up by email address, or search with `q` (part of a name or email).

Needs the People people:read permission

Query parameters

  • role "admin" | "creator" | "learner"
  • email string · max 320 chars

    Exact address, any case

  • q string · 2–100 chars

    Part of a name or email address, any case

  • updated_since timestamp

    ISO-8601. Only rows changed at or after this moment, ordered by updated_at (see Syncing changes)

  • limit integer · 1–100

    1–100, default 50

  • cursor string · max 200 chars

    next_cursor from the previous page

Returns

A page of Person objects in data, with next_cursor for the next page (see Pagination).

Show 6 fields
  • id string · 1–128 chars required
  • name string required
  • email string required
  • role "admin" | "creator" | "learner" required

    Their role in your organization

  • created_at timestamp required

    When they joined (ISO-8601, UTC)

  • updated_at timestamp required

    Last change to this person

curl "https://api.equipengine.com/v1/people?role=learner&limit=2" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "3f1c9a52-7d4e-4b8a-9c21-5e6f7a8b9c0d",
      "name": "Jordan Ellis",
      "email": "jordan.ellis@example.com",
      "role": "learner",
      "created_at": "2026-03-14T15:02:11.482Z",
      "updated_at": "2026-09-28T19:40:03.117Z"
    },
    {
      "id": "8a2d4e6f-1b3c-4d5e-8f90-a1b2c3d4e5f6",
      "name": "Sam Rivera",
      "email": "sam.rivera@example.com",
      "role": "learner",
      "created_at": "2026-03-10T09:41:52.000Z",
      "updated_at": "2026-03-10T09:41:52.000Z"
    }
  ],
  "next_cursor": "OGEyZDRlNmYtMWIzYy00ZDVlLThmOTAtYTFiMmMzZDRlNWY2"
}

Get a person

Get a person

GET /v1/people/{id}

Needs the People people:read permission

Path parameters

  • id string required

Returns

A Person object in data.

Show 6 fields
  • id string · 1–128 chars required
  • name string required
  • email string required
  • role "admin" | "creator" | "learner" required

    Their role in your organization

  • created_at timestamp required

    When they joined (ISO-8601, UTC)

  • updated_at timestamp required

    Last change to this person

curl "https://api.equipengine.com/v1/people/3f1c9a52-7d4e-4b8a-9c21-5e6f7a8b9c0d" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": {
    "id": "3f1c9a52-7d4e-4b8a-9c21-5e6f7a8b9c0d",
    "name": "Jordan Ellis",
    "email": "jordan.ellis@example.com",
    "role": "learner",
    "created_at": "2026-03-14T15:02:11.482Z",
    "updated_at": "2026-09-28T19:40:03.117Z"
  }
}

Get a person's training

A person's courses with progress, due dates and what's overdue

GET /v1/people/{id}/training

Answers “how is Jordan doing?” in one call: every course they're taking or have finished, with progress and due dates. Overdue courses come first. A due date is the earliest one among the assignments behind the course that still apply to them: a direct assignment, a group they're still in, their role, or a flow step. A course an admin marked complete in a flow is never overdue.

Needs the People people:read and Enrollments enrollments:read permissions

Path parameters

  • id string required

Returns

A Training status object in data.

Show 22 fields
  • person object required
  • id string · 1–128 chars required
  • name string required
  • email string required
  • role "admin" | "creator" | "learner" required

    Their role in your organization

  • created_at timestamp required

    When they joined (ISO-8601, UTC)

  • updated_at timestamp required

    Last change to this person

  • courses object[] required

    Overdue first (most late first), then in progress by due date, then completed or credited (latest first). Unassigned courses are left out

  • enrollment_id string · 1–128 chars required
  • course object required
  • id string · 1–128 chars required
  • slug string required
  • title string required
  • status "active" | "completed" required
  • credited boolean required

    An admin marked it complete in a flow (never overdue)

  • enrolled_at timestamp required

    ISO-8601, UTC

  • completed_at timestamp | null required
  • lessons_completed integer · -9007199254740991–9007199254740991 required
  • lessons_total integer · -9007199254740991–9007199254740991 required
  • due_date timestamp | null required

    The earliest due date among the assignments behind it that still apply (direct, a group they are still in, or their role). Courses inside a flow branch have no stored due date unless also assigned with one

  • days_overdue integer | null required

    Days past the due date, rounded (0 = less than half a day), while still active and not credited; null if not overdue

  • truncated boolean required

    true if the person has more than 500 courses and the list was cut short

curl "https://api.equipengine.com/v1/people/3f1c9a52-7d4e-4b8a-9c21-5e6f7a8b9c0d/training" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": {
    "person": {
      "id": "3f1c9a52-7d4e-4b8a-9c21-5e6f7a8b9c0d",
      "name": "Jordan Ellis",
      "email": "jordan.ellis@example.com",
      "role": "learner",
      "created_at": "2026-03-14T15:02:11.482Z",
      "updated_at": "2026-09-28T19:40:03.117Z"
    },
    "courses": [
      {
        "enrollment_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
        "course": {
          "id": "5b8e2c1d-9a7f-4e3b-8d6c-0f1e2d3c4b5a",
          "slug": "greeter-basics",
          "title": "Greeter Basics"
        },
        "status": "active",
        "credited": false,
        "enrolled_at": "2026-09-01T14:00:00.000Z",
        "completed_at": null,
        "lessons_completed": 2,
        "lessons_total": 5,
        "due_date": "2026-09-30T00:00:00.000Z",
        "days_overdue": 10
      },
      {
        "enrollment_id": "c3d4e5f6-a7b8-4c9d-8e0f-2a3b4c5d6e7f",
        "course": {
          "id": "6c9f3d2e-0b8a-4f4c-9e7d-1a2b3c4d5e6f",
          "slug": "safe-sanctuary",
          "title": "Safe Sanctuary"
        },
        "status": "completed",
        "credited": false,
        "enrolled_at": "2026-04-02T16:20:00.000Z",
        "completed_at": "2026-04-09T18:45:12.000Z",
        "lessons_completed": 6,
        "lessons_total": 6,
        "due_date": null,
        "days_overdue": null
      }
    ],
    "truncated": false
  }
}

API reference

Groups

List groups

List groups: cohorts and Planning Center groups, lists and teams

GET /v1/groups

Cohorts and the Planning Center groups, lists and teams synced into your organization: what you can assign to, and filter the overdue report by. Search with `q`.

Needs the People people:read permission

Query parameters

  • q string · 1–100 chars

    Part of the name, any case

  • kind "cohort" | "pc_group" | "pc_list" | "pc_team"
  • limit integer · 1–100

    1–100, default 50

  • cursor string · max 200 chars

    next_cursor from the previous page

Returns

A page of Group objects in data, with next_cursor for the next page (see Pagination).

Show 5 fields
  • id string · 1–128 chars required
  • name string required
  • kind "cohort" | "pc_group" | "pc_list" | "pc_team" required

    cohort (made in the app) or a Planning Center group, list or team

  • parent_name string | null required

    Planning Center's group type, service type or list category; null for cohorts

  • member_count integer · -9007199254740991–9007199254740991 required

    People in your organization in the group

curl "https://api.equipengine.com/v1/groups?q=greeter" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "grp_7d1e2f3a",
      "name": "Greeters — 9am",
      "kind": "pc_team",
      "parent_name": "Sunday AM",
      "member_count": 14
    },
    {
      "id": "grp_8e2f3a4b",
      "name": "New greeters (fall)",
      "kind": "cohort",
      "parent_name": null,
      "member_count": 6
    }
  ],
  "next_cursor": null
}

API reference

Invitations

Invite learners

Invite learners by email (learners only)

POST /v1/invitations

Invites up to 100 people as learners. Addresses are trimmed, lowercased and de-duplicated, and each one gets its own outcome, so one bad address doesn’t fail the batch. Invitations expire after 7 days. Your daily invite limit applies (5,000 a day, fewer in your first week), and your learner limit if your plan has one. People who already belong to your organization are never added to the cohort.

Needs the Invite learners people:invite permission

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • emails string[] · 1–100 items required
  • cohort_id string · 1–128 chars

    A manual cohort; new invitees join it when they accept. Existing members are not added (assign them instead).

Returns

An Invitation result object in data.

Show 5 fields
  • results object[] required
  • email string required
  • outcome "invited" | "already_member" | "already_invited" | "over_daily_cap" | "over_seat_limit" | "invalid_email" required
  • invited integer · -9007199254740991–9007199254740991 required

    Invitations sent

  • added_to_cohort integer · -9007199254740991–9007199254740991 required

    Always 0 over the API: existing members aren’t added to cohorts

curl -X POST "https://api.equipengine.com/v1/invitations" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "emails": [
      "casey.nguyen@example.com",
      "jordan.ellis@example.com"
    ],
    "cohort_id": "f0e1d2c3-b4a5-4f6e-8d7c-9b0a1f2e3d4c"
  }'
Response · 200
{
  "data": {
    "results": [
      {
        "email": "casey.nguyen@example.com",
        "outcome": "invited"
      },
      {
        "email": "jordan.ellis@example.com",
        "outcome": "already_member"
      }
    ],
    "invited": 1,
    "added_to_cohort": 0
  }
}

API reference

Courses

List courses

List courses

GET /v1/courses

Courses in every ministry, including drafts and archived courses unless you filter by status.

Needs the Courses courses:read permission

Query parameters

  • status "draft" | "published" | "archived"
  • ministry_id string · 1–128 chars
  • limit integer · 1–100

    1–100, default 50

  • cursor string · max 200 chars

    next_cursor from the previous page

Returns

A page of Course objects in data, with next_cursor for the next page (see Pagination).

Show 10 fields
  • id string · 1–128 chars required
  • slug string required

    Used in course URLs; can replace the id in /v1/courses/{id} paths

  • title string required
  • description string | null required
  • status "draft" | "published" | "archived" required
  • ministry_id string | null required

    The ministry it belongs to

  • enrollment_type "open" | "invite" | "code" required

    How learners get in: anyone, assigned only, or with a code

  • estimated_minutes integer | null required

    Estimated time to complete

  • created_at timestamp required

    ISO-8601, UTC

  • updated_at timestamp required

    ISO-8601, UTC

curl "https://api.equipengine.com/v1/courses?status=published" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "5b8e2c1d-9a7f-4e3b-8d6c-0f1e2d3c4b5a",
      "slug": "greeter-basics",
      "title": "Greeter Basics",
      "description": "Everything a new greeter needs for their first Sunday.",
      "status": "published",
      "ministry_id": "9e7d6c5b-4a3f-4e2d-8c1b-0a9f8e7d6c5b",
      "enrollment_type": "invite",
      "estimated_minutes": 25,
      "created_at": "2026-01-08T18:30:00.000Z",
      "updated_at": "2026-09-21T13:45:09.000Z"
    }
  ],
  "next_cursor": null
}

Get a course outline

Get a course outline (id or slug)

GET /v1/courses/{id}

A course with its modules and lessons, in order. Pass the course id or its slug.

Needs the Courses courses:read permission

Path parameters

  • id string required

Returns

A Course outline object in data.

Show 20 fields
  • course object required
  • id string · 1–128 chars required
  • slug string required

    Used in course URLs; can replace the id in /v1/courses/{id} paths

  • title string required
  • description string | null required
  • status "draft" | "published" | "archived" required
  • ministry_id string | null required

    The ministry it belongs to

  • enrollment_type "open" | "invite" | "code" required

    How learners get in: anyone, assigned only, or with a code

  • estimated_minutes integer | null required

    Estimated time to complete

  • created_at timestamp required

    ISO-8601, UTC

  • updated_at timestamp required

    ISO-8601, UTC

  • modules object[] required
  • id string · 1–128 chars required
  • title string required
  • lessons object[] required
  • id string · 1–128 chars required
  • title string required
  • type "video" | "text" | "pdf" required
  • word_count integer | null required

    Words in the lesson body; null if it has none

  • task "quiz" | "reflection" | "acknowledge" | "other" | null required

    What the learner does at the end, if anything

curl "https://api.equipengine.com/v1/courses/greeter-basics" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": {
    "course": {
      "id": "5b8e2c1d-9a7f-4e3b-8d6c-0f1e2d3c4b5a",
      "slug": "greeter-basics",
      "title": "Greeter Basics",
      "description": "Everything a new greeter needs for their first Sunday.",
      "status": "published",
      "ministry_id": "9e7d6c5b-4a3f-4e2d-8c1b-0a9f8e7d6c5b",
      "enrollment_type": "invite",
      "estimated_minutes": 25,
      "created_at": "2026-01-08T18:30:00.000Z",
      "updated_at": "2026-09-21T13:45:09.000Z"
    },
    "modules": [
      {
        "id": "0f9e8d7c-6b5a-4f3e-9d2c-1b0a9f8e7d6c",
        "title": "Before the service",
        "lessons": [
          {
            "id": "1a3b5c7d-9e0f-4a2b-8c4d-6e8f0a2b4c6d",
            "title": "Why we greet",
            "type": "video",
            "word_count": null,
            "task": "reflection"
          },
          {
            "id": "7f6e5d4c-3b2a-4f1e-9d0c-8b7a6f5e4d3c",
            "title": "Your station and what to bring",
            "type": "text",
            "word_count": 640,
            "task": "quiz"
          }
        ]
      }
    ]
  }
}

Assign a course

Assign a published course to a person, role or group

POST /v1/courses/{id}/assignments

Enrolls the person, everyone who has the role now, or every member of the group (people who join the group later are enrolled too), and emails the people newly enrolled. Assigning again is safe: people already enrolled are left alone, and a new due date replaces the old one, unless the course is also assigned to that target through a flow (the flow’s due dates win). Assigning a Planning Center group that isn’t synced yet starts syncing it.

Needs the Assign and unassign enrollments:write permission

Path parameters

  • id string required

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • target object required
  • When type: "user"
  • type "user" required
  • user_id string · 1–128 chars required

    A person in your organization

  • When type: "role"
  • type "role" required
  • role "admin" | "creator" | "learner" required

    Everyone with this role when you assign. A course must be assigned again to include people who get the role later; a flow includes them automatically

  • When type: "group"
  • type "group" required
  • group_id string · 1–128 chars required

    A Planning Center group or a cohort; members who join later are enrolled too

  • on_member_leaves "keep" | "unenroll"

    What happens to someone who leaves the group. Planning Center groups only (cohorts always keep). Default: your organization’s setting on the Planning Center page (unenroll unless changed)

  • due_date date

    YYYY-MM-DD, read as midnight UTC. Leaving it out keeps any existing due date

  • notify boolean

    Send emails (default true): the assignment email to people newly enrolled in a course, or the welcome email for a flow, and, when assigning a course to a Planning Center group, an invitation to members without an account. false sends none, for bulk backfills.

Returns

A Course assignment result object in data.

Show 6 fields
  • assignment_id string · 1–128 chars required
  • created boolean required

    false when this target already had the course

  • due_date_updated boolean required
  • enrolled integer · -9007199254740991–9007199254740991 required

    People newly enrolled

  • reactivated integer · -9007199254740991–9007199254740991 required

    People brought back after being unassigned

  • unchanged integer · -9007199254740991–9007199254740991 required

    People who were already enrolled

curl -X POST "https://api.equipengine.com/v1/courses/5b8e2c1d-9a7f-4e3b-8d6c-0f1e2d3c4b5a/assignments" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "target": {
      "type": "user",
      "user_id": "3f1c9a52-7d4e-4b8a-9c21-5e6f7a8b9c0d"
    },
    "due_date": "2026-11-01"
  }'
Response · 200
{
  "data": {
    "assignment_id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
    "created": true,
    "due_date_updated": false,
    "enrolled": 1,
    "reactivated": 0,
    "unchanged": 0
  }
}

Create a draft course

Create a draft course, optionally with modules and lessons

POST /v1/courses

Creates a draft in your default ministry (or the active ministry you name), optionally with its modules and lessons in one call. It’s all or nothing: if the outline is invalid, no course is created. Lessons are text lessons whose content is Markdown (HTML is removed). Publish the course in the app when it’s ready.

Needs the Build draft courses courses:write permission

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • title string · 1–200 chars required
  • description string · max 2000 chars
  • ministry_id string · 1–128 chars

    Defaults to your default ministry

  • enrollment_type "open" | "invite"

    Default open

  • outline object[] · 1–30 items

    New modules and their lessons. Without one, the course starts with an empty "Module 1". At most 200 lessons

  • title string · 1–200 chars required
  • lessons object[] · 1–50 items required
  • title string · 1–200 chars required
  • content string · max 20000 chars

    Markdown: headings, lists, bold, links. HTML is removed.

  • task object

    Something the learner does at the end of the lesson

  • When kind: "acknowledge"
  • kind "acknowledge" required
  • prompt string · 1–500 chars required

    What the learner confirms, e.g. "I have read the safety policy."

  • When kind: "quiz"
  • kind "quiz" required
  • questions object[] · 1–20 items required

    Graded: each choice question needs exactly one correct option (at least one for multi_select)

  • question string · 1–500 chars required
  • type "multiple_choice" | "true_false" | "multi_select" | "short_text" required

    short_text is an open written answer (no options)

  • options object[] · max 8 items

    Answer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text. is_correct is ignored on reflections, which are never graded

  • text string · 1–300 chars required
  • is_correct boolean
  • When kind: "reflection"
  • kind "reflection" required
  • questions object[] · 1–20 items required

    Not graded

  • question string · 1–500 chars required
  • type "multiple_choice" | "true_false" | "multi_select" | "short_text" required

    short_text is an open written answer (no options)

  • options object[] · max 8 items

    Answer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text. is_correct is ignored on reflections, which are never graded

  • text string · 1–300 chars required
  • is_correct boolean

Returns

A Created course object in data.

Show 7 fields
  • course object required
  • id string · 1–128 chars required
  • slug string required
  • title string required
  • description string | null required
  • status "draft" required
  • outline object | null required
curl -X POST "https://api.equipengine.com/v1/courses" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "title": "Ushers 101",
    "description": "What every usher needs before their first service.",
    "outline": [
      {
        "title": "Getting ready",
        "lessons": [
          {
            "title": "Why we serve",
            "content": "Ushers are often the **first face** people see."
          },
          {
            "title": "Your first Sunday",
            "content": "- Arrive 30 minutes early\n- Check in with the team lead",
            "task": {
              "kind": "acknowledge",
              "prompt": "I know where to check in on Sunday."
            }
          }
        ]
      }
    ]
  }'
Response · 200
{
  "data": {
    "course": {
      "id": "0c2e4a6b-8d1f-4b3c-9e5d-7a9b1c3e5f70",
      "slug": "ushers-101",
      "title": "Ushers 101",
      "description": "What every usher needs before their first service.",
      "status": "draft"
    },
    "outline": {
      "modules_created": 1,
      "lessons_created": 2,
      "modules": [
        {
          "id": "1d3f5b7c-9e2a-4c4d-8f6e-8b0c2d4f6a81",
          "title": "Getting ready"
        }
      ],
      "lessons": [
        {
          "id": "2e4a6c8d-0f3b-4d5e-9a7f-9c1d3e5a7b92",
          "module_id": "1d3f5b7c-9e2a-4c4d-8f6e-8b0c2d4f6a81",
          "title": "Why we serve"
        },
        {
          "id": "3f5b7d9e-1a4c-4e6f-8b8a-0d2e4f6b8ca3",
          "module_id": "1d3f5b7c-9e2a-4c4d-8f6e-8b0c2d4f6a81",
          "title": "Your first Sunday"
        }
      ]
    }
  }
}

Update a draft course

Change a draft course's title or description (id or slug)

PATCH /v1/courses/{id}

Changes the title and/or description. The slug stays the same, so links keep working. Published courses return course_not_draft.

Needs the Build draft courses courses:write permission

Path parameters

  • id string required

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • title string · 1–200 chars
  • description string | null

    null clears it

Returns

A Draft course object in data.

Show 5 fields
  • id string · 1–128 chars required
  • slug string required
  • title string required
  • description string | null required
  • status "draft" required
curl -X PATCH "https://api.equipengine.com/v1/courses/ushers-101" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "title": "Ushers 101: the basics"
  }'
Response · 200
{
  "data": {
    "id": "0c2e4a6b-8d1f-4b3c-9e5d-7a9b1c3e5f70",
    "slug": "ushers-101",
    "title": "Ushers 101: the basics",
    "description": "What every usher needs before their first service.",
    "status": "draft"
  }
}

Add modules and lessons

Add modules and lessons to a draft course (id or slug)

POST /v1/courses/{id}/outline

Appends new modules, or adds lessons to an existing module of the course with existing_module_id. Nothing already there is changed or removed. New lessons are text lessons. To add lessons to one module, POST /v1/modules/{id}/lessons is simpler.

Needs the Build draft courses courses:write permission

Path parameters

  • id string required

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • modules object[] · 1–30 items required

    At most 200 lessons per request

  • title string · 1–200 chars required

    Required, but ignored when existing_module_id is set

  • lessons object[] · 1–50 items required
  • title string · 1–200 chars required
  • content string · max 20000 chars

    Markdown: headings, lists, bold, links. HTML is removed.

  • task object

    Something the learner does at the end of the lesson

  • When kind: "acknowledge"
  • kind "acknowledge" required
  • prompt string · 1–500 chars required

    What the learner confirms, e.g. "I have read the safety policy."

  • When kind: "quiz"
  • kind "quiz" required
  • questions object[] · 1–20 items required

    Graded: each choice question needs exactly one correct option (at least one for multi_select)

  • question string · 1–500 chars required
  • type "multiple_choice" | "true_false" | "multi_select" | "short_text" required

    short_text is an open written answer (no options)

  • options object[] · max 8 items

    Answer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text. is_correct is ignored on reflections, which are never graded

  • text string · 1–300 chars required
  • is_correct boolean
  • When kind: "reflection"
  • kind "reflection" required
  • questions object[] · 1–20 items required

    Not graded

  • question string · 1–500 chars required
  • type "multiple_choice" | "true_false" | "multi_select" | "short_text" required

    short_text is an open written answer (no options)

  • options object[] · max 8 items

    Answer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text. is_correct is ignored on reflections, which are never graded

  • text string · 1–300 chars required
  • is_correct boolean
  • existing_module_id string · 1–128 chars

    Add the lessons to this module of the course instead of creating one

Returns

An Outline result object in data.

Show 9 fields
  • modules_created integer · -9007199254740991–9007199254740991 required
  • lessons_created integer · -9007199254740991–9007199254740991 required
  • modules object[] required

    Modules this call created

  • id string · 1–128 chars required
  • title string required
  • lessons object[] required

    Lessons this call created

  • id string · 1–128 chars required
  • module_id string · 1–128 chars required
  • title string required
curl -X POST "https://api.equipengine.com/v1/courses/0c2e4a6b-8d1f-4b3c-9e5d-7a9b1c3e5f70/outline" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "modules": [
      {
        "title": "During the service",
        "lessons": [
          {
            "title": "Seating latecomers",
            "content": "Wait for a **natural break** before walking anyone to a seat.",
            "task": {
              "kind": "quiz",
              "questions": [
                {
                  "question": "When should you seat someone who arrives late?",
                  "type": "multiple_choice",
                  "options": [
                    {
                      "text": "Straight away",
                      "is_correct": false
                    },
                    {
                      "text": "At a natural break",
                      "is_correct": true
                    }
                  ]
                }
              ]
            }
          }
        ]
      }
    ]
  }'
Response · 200
{
  "data": {
    "modules_created": 1,
    "lessons_created": 1,
    "modules": [
      {
        "id": "4a6c8e0f-2b5d-4f7a-9c9b-1e3f5a7c9db4",
        "title": "During the service"
      }
    ],
    "lessons": [
      {
        "id": "5b7d9f1a-3c6e-4a8b-8d0c-2f4a6b8d0ec5",
        "module_id": "4a6c8e0f-2b5d-4f7a-9c9b-1e3f5a7c9db4",
        "title": "Seating latecomers"
      }
    ]
  }
}

API reference

Modules

Reorder modules

Reorder a draft course's modules (id or slug)

PUT /v1/courses/{id}/module-order

Puts the course’s modules in the order you list. Modules you leave out (say, one added since you last read the outline) keep their order after these.

Needs the Build draft courses courses:write permission

Path parameters

  • id string required

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • module_ids string[] · 1–200 items required

    In the new order. Modules you leave out keep their order after these

Returns

A Module order object in data.

Show 4 fields
  • modules object[] required
  • id string · 1–128 chars required
  • title string required
  • position integer · -9007199254740991–9007199254740991 required
curl -X PUT "https://api.equipengine.com/v1/courses/0c2e4a6b-8d1f-4b3c-9e5d-7a9b1c3e5f70/module-order" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "module_ids": [
      "4a6c8e0f-2b5d-4f7a-9c9b-1e3f5a7c9db4",
      "1d3f5b7c-9e2a-4c4d-8f6e-8b0c2d4f6a81"
    ]
  }'
Response · 200
{
  "data": {
    "modules": [
      {
        "id": "4a6c8e0f-2b5d-4f7a-9c9b-1e3f5a7c9db4",
        "title": "During the service",
        "position": 0
      },
      {
        "id": "1d3f5b7c-9e2a-4c4d-8f6e-8b0c2d4f6a81",
        "title": "Getting ready",
        "position": 1
      }
    ]
  }
}

Rename a module

Rename a module (draft courses only)

PATCH /v1/modules/{id}

Changes a module’s title. Published courses return course_not_draft.

Needs the Build draft courses courses:write permission

Path parameters

  • id string required

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • title string · 1–200 chars required

Returns

A Module object in data.

Show 3 fields
  • id string · 1–128 chars required
  • title string required
  • position integer · -9007199254740991–9007199254740991 required
curl -X PATCH "https://api.equipengine.com/v1/modules/1d3f5b7c-9e2a-4c4d-8f6e-8b0c2d4f6a81" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "title": "Before your first Sunday"
  }'
Response · 200
{
  "data": {
    "id": "1d3f5b7c-9e2a-4c4d-8f6e-8b0c2d4f6a81",
    "title": "Before your first Sunday",
    "position": 0
  }
}

Add lessons to a module

Add lessons to the end of a module (draft courses only)

POST /v1/modules/{id}/lessons

Adds text lessons after the module’s current ones, optionally each with a task. Nothing already in the module changes. Published courses return course_not_draft.

Needs the Build draft courses courses:write permission

Path parameters

  • id string required

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • lessons object[] · 1–50 items required

    Text lessons, added after the module’s current lessons. At most 50 per request

  • title string · 1–200 chars required
  • content string · max 20000 chars

    Markdown: headings, lists, bold, links. HTML is removed.

  • task object

    Something the learner does at the end of the lesson

  • When kind: "acknowledge"
  • kind "acknowledge" required
  • prompt string · 1–500 chars required

    What the learner confirms, e.g. "I have read the safety policy."

  • When kind: "quiz"
  • kind "quiz" required
  • questions object[] · 1–20 items required

    Graded: each choice question needs exactly one correct option (at least one for multi_select)

  • question string · 1–500 chars required
  • type "multiple_choice" | "true_false" | "multi_select" | "short_text" required

    short_text is an open written answer (no options)

  • options object[] · max 8 items

    Answer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text. is_correct is ignored on reflections, which are never graded

  • text string · 1–300 chars required
  • is_correct boolean
  • When kind: "reflection"
  • kind "reflection" required
  • questions object[] · 1–20 items required

    Not graded

  • question string · 1–500 chars required
  • type "multiple_choice" | "true_false" | "multi_select" | "short_text" required

    short_text is an open written answer (no options)

  • options object[] · max 8 items

    Answer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text. is_correct is ignored on reflections, which are never graded

  • text string · 1–300 chars required
  • is_correct boolean

Returns

A Lessons added object in data.

Show 5 fields
  • lessons_created integer · -9007199254740991–9007199254740991 required
  • lessons object[] required
  • id string · 1–128 chars required
  • module_id string · 1–128 chars required
  • title string required
curl -X POST "https://api.equipengine.com/v1/modules/1d3f5b7c-9e2a-4c4d-8f6e-8b0c2d4f6a81/lessons" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "lessons": [
      {
        "title": "Where to stand",
        "content": "Greeters stand **inside** the main doors."
      },
      {
        "title": "Quick check",
        "task": {
          "kind": "acknowledge",
          "prompt": "I know where to stand."
        }
      }
    ]
  }'
Response · 200
{
  "data": {
    "lessons_created": 2,
    "lessons": [
      {
        "id": "9f1b3d5e-7a0c-4e2f-8b4d-6c8e0a2b4d61",
        "module_id": "1d3f5b7c-9e2a-4c4d-8f6e-8b0c2d4f6a81",
        "title": "Where to stand"
      },
      {
        "id": "a02c4e6f-8b1d-4f3a-9c5e-7d9f1b3c5e72",
        "module_id": "1d3f5b7c-9e2a-4c4d-8f6e-8b0c2d4f6a81",
        "title": "Quick check"
      }
    ]
  }
}

Reorder lessons

Reorder a module's lessons (draft courses only)

PUT /v1/modules/{id}/lesson-order

Puts one module’s lessons in the order you list. Same rules as reordering modules. To move a lesson to another module, use the app.

Needs the Build draft courses courses:write permission

Path parameters

  • id string required

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • lesson_ids string[] · 1–200 items required

    In the new order. Lessons you leave out keep their order after these

Returns

A Lesson order object in data.

Show 4 fields
  • lessons object[] required
  • id string · 1–128 chars required
  • title string required
  • position integer · -9007199254740991–9007199254740991 required
curl -X PUT "https://api.equipengine.com/v1/modules/1d3f5b7c-9e2a-4c4d-8f6e-8b0c2d4f6a81/lesson-order" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "lesson_ids": [
      "3f5b7d9e-1a4c-4e6f-8b8a-0d2e4f6b8ca3",
      "2e4a6c8d-0f3b-4d5e-9a7f-9c1d3e5a7b92"
    ]
  }'
Response · 200
{
  "data": {
    "lessons": [
      {
        "id": "3f5b7d9e-1a4c-4e6f-8b8a-0d2e4f6b8ca3",
        "title": "Your first Sunday",
        "position": 0
      },
      {
        "id": "2e4a6c8d-0f3b-4d5e-9a7f-9c1d3e5a7b92",
        "title": "Why we serve",
        "position": 1
      }
    ]
  }
}

API reference

Lessons

Update a lesson

Change a lesson's title or written content (draft courses only)

PATCH /v1/lessons/{id}

Changes the title, replaces the written content (empty content clears it), or with mode: "append" adds to it. Leaving content out changes only the title. Video and PDF lessons keep their media; edit those in the app.

Needs the Build draft courses courses:write permission

Path parameters

  • id string required

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • title string · 1–200 chars
  • content string · max 20000 chars

    Markdown: headings, lists, bold, links. HTML is removed.

  • mode "replace" | "append"

    append adds content below the current text. Default replace, where empty content clears the lesson. Ignored without content

Returns

A Lesson summary object in data.

Show 3 fields
  • id string · 1–128 chars required
  • title string required
  • word_count integer · -9007199254740991–9007199254740991 required
curl -X PATCH "https://api.equipengine.com/v1/lessons/2e4a6c8d-0f3b-4d5e-9a7f-9c1d3e5a7b92" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "content": "Thank you for serving!",
    "mode": "append"
  }'
Response · 200
{
  "data": {
    "id": "2e4a6c8d-0f3b-4d5e-9a7f-9c1d3e5a7b92",
    "title": "Why we serve",
    "word_count": 12
  }
}

Add a lesson task

Add a quiz, reflection or acknowledgement to a lesson (draft courses only)

POST /v1/lessons/{id}/task

Adds a quiz (graded), a reflection (ungraded questions) or an acknowledgement to a lesson that doesn’t have a task yet. A lesson that already has one returns validation_failed; add questions to it instead.

Needs the Build draft courses courses:write permission

Path parameters

  • id string required

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • When kind: "acknowledge"
  • kind "acknowledge" required
  • prompt string · 1–500 chars required

    What the learner confirms, e.g. "I have read the safety policy."

  • When kind: "quiz"
  • kind "quiz" required
  • questions object[] · 1–20 items required

    Graded: each choice question needs exactly one correct option (at least one for multi_select)

  • question string · 1–500 chars required
  • type "multiple_choice" | "true_false" | "multi_select" | "short_text" required

    short_text is an open written answer (no options)

  • options object[] · max 8 items

    Answer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text. is_correct is ignored on reflections, which are never graded

  • text string · 1–300 chars required
  • is_correct boolean
  • When kind: "reflection"
  • kind "reflection" required
  • questions object[] · 1–20 items required

    Not graded

  • question string · 1–500 chars required
  • type "multiple_choice" | "true_false" | "multi_select" | "short_text" required

    short_text is an open written answer (no options)

  • options object[] · max 8 items

    Answer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text. is_correct is ignored on reflections, which are never graded

  • text string · 1–300 chars required
  • is_correct boolean

Returns

A Task object in data.

Show 3 fields
  • task_id string · 1–128 chars required
  • lesson_id string · 1–128 chars required
  • kind "acknowledge" | "quiz" | "reflection" required
curl -X POST "https://api.equipengine.com/v1/lessons/2e4a6c8d-0f3b-4d5e-9a7f-9c1d3e5a7b92/task" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "kind": "reflection",
    "questions": [
      {
        "question": "What would make a first-time guest feel welcome?",
        "type": "short_text"
      }
    ]
  }'
Response · 200
{
  "data": {
    "task_id": "6c8e0a2b-4d7f-4b9c-9e1d-3a5b7c9e1fd6",
    "lesson_id": "2e4a6c8d-0f3b-4d5e-9a7f-9c1d3e5a7b92",
    "kind": "reflection"
  }
}

Add quiz questions

Add questions to a lesson's quiz or reflection (draft courses only)

POST /v1/lessons/{id}/questions

Adds questions to the end of a lesson’s quiz or reflection. Existing questions and learners’ earlier answers are never changed; people who take it from now on get the new questions. A lesson with no task, or with an acknowledgement, returns validation_failed (add a task first).

Needs the Build draft courses courses:write permission

Path parameters

  • id string required

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • questions object[] · 1–20 items required
  • question string · 1–500 chars required
  • type "multiple_choice" | "true_false" | "multi_select" | "short_text" required

    short_text is an open written answer (no options)

  • options object[] · max 8 items

    Answer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text. is_correct is ignored on reflections, which are never graded

  • text string · 1–300 chars required
  • is_correct boolean

Returns

A Questions added object in data.

Show 3 fields
  • task_id string · 1–128 chars required
  • question_ids string[] required
  • graded boolean required
curl -X POST "https://api.equipengine.com/v1/lessons/5b7d9f1a-3c6e-4a8b-8d0c-2f4a6b8d0ec5/questions" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "questions": [
      {
        "question": "Ushers should greet people by name when they can.",
        "type": "true_false",
        "options": [
          {
            "text": "True",
            "is_correct": true
          },
          {
            "text": "False",
            "is_correct": false
          }
        ]
      }
    ]
  }'
Response · 200
{
  "data": {
    "task_id": "7d9f1b3c-5e8a-4cad-8f2e-4b6c8d0f2ae7",
    "question_ids": [
      "8e0a2c4d-6f9b-4dbe-9a3f-5c7d9e1a3bf8"
    ],
    "graded": true
  }
}

API reference

Ministries

List ministries

List active ministries

GET /v1/ministries

Needs the Courses courses:read permission

Returns

A list of Ministry objects in data.

Show 5 fields
  • id string · 1–128 chars required
  • name string required
  • slug string required
  • description string | null required
  • is_default boolean required

    The ministry new courses go into by default

curl "https://api.equipengine.com/v1/ministries" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "2a4c6e8f-0b1d-4f3a-9c5e-7a9b1c3d5e7f",
      "name": "General",
      "slug": "general",
      "description": null,
      "is_default": true
    },
    {
      "id": "9e7d6c5b-4a3f-4e2d-8c1b-0a9f8e7d6c5b",
      "name": "Hospitality",
      "slug": "hospitality",
      "description": "Greeters, ushers and the welcome desk.",
      "is_default": false
    }
  ]
}

API reference

Enrollments

List enrollments

List enrollments with lesson progress

GET /v1/enrollments

Who is enrolled in what, with lesson progress. Filter by course, person or status.

Needs the Enrollments enrollments:read permission

Query parameters

  • course_id string · 1–128 chars
  • user_id string · 1–128 chars
  • status "active" | "completed" | "revoked"
  • updated_since timestamp

    ISO-8601. Only rows changed at or after this moment, ordered by updated_at (see Syncing changes)

  • limit integer · 1–100

    1–100, default 50

  • cursor string · max 200 chars

    next_cursor from the previous page

Returns

A page of Enrollment objects in data, with next_cursor for the next page (see Pagination).

Show 10 fields
  • id string · 1–128 chars required
  • user_id string · 1–128 chars required

    The person (see People)

  • course_id string · 1–128 chars required
  • status "active" | "completed" | "revoked" required

    revoked = unassigned; progress is kept

  • enrolled_at timestamp required

    ISO-8601, UTC

  • completed_at timestamp | null required
  • revoked_at timestamp | null required
  • lessons_completed integer · -9007199254740991–9007199254740991 required
  • lessons_total integer · -9007199254740991–9007199254740991 required

    Lessons in the course now

  • updated_at timestamp required

    Last change, including lesson progress (not a change to the course itself)

curl "https://api.equipengine.com/v1/enrollments?course_id=5b8e2c1d-9a7f-4e3b-8d6c-0f1e2d3c4b5a&status=active" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b",
      "user_id": "3f1c9a52-7d4e-4b8a-9c21-5e6f7a8b9c0d",
      "course_id": "5b8e2c1d-9a7f-4e3b-8d6c-0f1e2d3c4b5a",
      "status": "active",
      "enrolled_at": "2026-09-01T14:00:00.000Z",
      "completed_at": null,
      "revoked_at": null,
      "lessons_completed": 3,
      "lessons_total": 6,
      "updated_at": "2026-10-08T17:22:45.000Z"
    }
  ],
  "next_cursor": null
}

Unassign an enrollment

Revoke an active enrollment (reversible: re-assigning brings the person back)

POST /v1/enrollments/{id}/revoke

Unassigns one person from one course. Their progress is kept. Automatic syncs and role or group assignments won’t re-enroll them; assigning that person directly will. Only active enrollments can be revoked: a revoked or completed one returns not_found.

Needs the Assign and unassign enrollments:write permission

Path parameters

  • id string required

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

No body needed.

Returns

A Revocation result object in data.

Show 2 fields
  • id string · 1–128 chars required
  • status "revoked" required
curl -X POST "https://api.equipengine.com/v1/enrollments/4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b/revoke" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)"
Response · 200
{
  "data": {
    "id": "4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b",
    "status": "revoked"
  }
}

API reference

Assignments

List course assignments

List course assignments (people, roles and groups a course is assigned to)

GET /v1/assignments

Who each course is assigned to: a person, everyone with a role, or a group, with its due date. Flow steps create course assignments too. Filter by course, or pass updated_since to get only what changed.

Needs the Enrollments enrollments:read permission

Query parameters

  • course_id string · 1–128 chars
  • updated_since timestamp

    ISO-8601. Only rows changed at or after this moment, ordered by updated_at (see Syncing changes)

  • limit integer · 1–100

    1–100, default 50

  • cursor string · max 200 chars

    next_cursor from the previous page

Returns

A page of Assignment objects in data, with next_cursor for the next page (see Pagination).

Show 10 fields
  • id string · 1–128 chars required
  • course_id string · 1–128 chars required
  • target_kind "user" | "role" | "group" required

    Which of the target fields below is set

  • target_user_id string | null required
  • target_role "admin" | "creator" | "learner" | null required
  • target_group_id string | null required

    A Planning Center group or a cohort

  • due_date timestamp | null required
  • on_member_leaves "keep" | "unenroll" required

    Groups only: what happens to someone who leaves the group

  • assigned_at timestamp required

    ISO-8601, UTC

  • updated_at timestamp required

    ISO-8601, UTC

curl "https://api.equipengine.com/v1/assignments?course_id=5b8e2c1d-9a7f-4e3b-8d6c-0f1e2d3c4b5a" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
      "course_id": "5b8e2c1d-9a7f-4e3b-8d6c-0f1e2d3c4b5a",
      "target_kind": "group",
      "target_user_id": null,
      "target_role": null,
      "target_group_id": "e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a8b",
      "due_date": "2026-11-01T00:00:00.000Z",
      "on_member_leaves": "unenroll",
      "assigned_at": "2026-09-15T12:00:00.000Z",
      "updated_at": "2026-09-20T08:31:14.000Z"
    }
  ],
  "next_cursor": null
}

API reference

Flows

List flows

List flows

GET /v1/flows

Needs the Flows flows:read permission

Query parameters

  • status "draft" | "published" | "archived"
  • limit integer · 1–100

    1–100, default 50

  • cursor string · max 200 chars

    next_cursor from the previous page

Returns

A page of Flow objects in data, with next_cursor for the next page (see Pagination).

Show 9 fields
  • id string · 1–128 chars required
  • slug string required
  • title string required
  • description string | null required
  • category string | null required
  • status "draft" | "published" | "archived" required
  • course_ids string[] required

    Course steps in order; branch steps omitted

  • created_at timestamp required

    ISO-8601, UTC

  • updated_at timestamp required

    ISO-8601, UTC

curl "https://api.equipengine.com/v1/flows" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "6c7d8e9f-0a1b-4c2d-8e3f-4a5b6c7d8e9f",
      "slug": "new-volunteer-onboarding",
      "title": "New Volunteer Onboarding",
      "description": "The first month for anyone joining a serving team.",
      "category": "Onboarding",
      "status": "published",
      "course_ids": [
        "5b8e2c1d-9a7f-4e3b-8d6c-0f1e2d3c4b5a",
        "c4d5e6f7-8a9b-4c0d-9e1f-2a3b4c5d6e7f"
      ],
      "created_at": "2026-02-02T16:00:00.000Z",
      "updated_at": "2026-08-30T10:12:44.000Z"
    }
  ],
  "next_cursor": null
}

List flow assignments

List a flow's assignments

GET /v1/flows/{id}/assignments

Who a flow is assigned to: individual people, everyone with a role, or a group. Pass the flow’s id or its slug.

Needs the Flows flows:read permission

Path parameters

  • id string required

Query parameters

  • updated_since timestamp

    ISO-8601. Only rows changed at or after this moment, ordered by updated_at (see Syncing changes)

  • limit integer · 1–100

    1–100, default 50

  • cursor string · max 200 chars

    next_cursor from the previous page

Returns

A page of Flow assignment objects in data, with next_cursor for the next page (see Pagination).

Show 8 fields
  • id string · 1–128 chars required
  • flow_id string · 1–128 chars required
  • target_kind "user" | "role" | "group" required

    Which of the target fields below is set

  • target_user_id string | null required
  • target_role "admin" | "creator" | "learner" | null required
  • target_group_id string | null required

    A Planning Center group or a cohort

  • assigned_at timestamp required

    ISO-8601, UTC

  • updated_at timestamp required

    ISO-8601, UTC

curl "https://api.equipengine.com/v1/flows/6c7d8e9f-0a1b-4c2d-8e3f-4a5b6c7d8e9f/assignments" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
      "flow_id": "6c7d8e9f-0a1b-4c2d-8e3f-4a5b6c7d8e9f",
      "target_kind": "group",
      "target_user_id": null,
      "target_role": null,
      "target_group_id": "e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a8b",
      "assigned_at": "2026-09-15T12:00:00.000Z",
      "updated_at": "2026-09-15T12:00:00.000Z"
    }
  ],
  "next_cursor": null
}

Assign a flow

Assign a published flow to a person, role or group

POST /v1/flows/{id}/assignments

Enrolls everyone in the flow’s courses (courses on a branch track when the learner reaches that branch), with each step’s due date, and sends the flow welcome email. Assigning the same target again only adds people who weren’t enrolled. Refused with not_published if the flow, or any course in it (including on a branch track), isn’t published.

Needs the Assign and unassign enrollments:write permission

Path parameters

  • id string required

Headers

  • Idempotency-Key string · 1–100 chars

    A unique string of printable ASCII characters, no spaces (a UUID works). A retry of the same request with the same key gets the first successful response instead of repeating the change.

Body

  • target object required
  • When type: "user"
  • type "user" required
  • user_id string · 1–128 chars required

    A person in your organization

  • When type: "role"
  • type "role" required
  • role "admin" | "creator" | "learner" required

    Everyone with this role when you assign. A course must be assigned again to include people who get the role later; a flow includes them automatically

  • When type: "group"
  • type "group" required
  • group_id string · 1–128 chars required

    A Planning Center group or a cohort; members who join later are enrolled too

  • on_member_leaves "keep" | "unenroll"

    What happens to someone who leaves the group. Planning Center groups only (cohorts always keep). Default: your organization’s setting on the Planning Center page (unenroll unless changed)

  • notify boolean

    Send emails (default true): the assignment email to people newly enrolled in a course, or the welcome email for a flow, and, when assigning a course to a Planning Center group, an invitation to members without an account. false sends none, for bulk backfills.

Returns

A Flow assignment result object in data.

Show 4 fields
  • flow_assignment_id string · 1–128 chars required
  • created boolean required

    false when this target already had the flow

  • targeted integer · -9007199254740991–9007199254740991 required

    People the assignment covers

  • newly_enrolled integer · -9007199254740991–9007199254740991 required

    People newly enrolled, or re-enrolled, in at least one course by this call

curl -X POST "https://api.equipengine.com/v1/flows/6c7d8e9f-0a1b-4c2d-8e3f-4a5b6c7d8e9f/assignments" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "target": {
      "type": "group",
      "group_id": "e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a8b"
    }
  }'
Response · 200
{
  "data": {
    "flow_assignment_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
    "created": true,
    "targeted": 12,
    "newly_enrolled": 9
  }
}

API reference

Reports

Overdue report

People past a due date on a course they are still taking (and not credited in a flow), most overdue first

GET /v1/reports/overdue

Everyone past a due date on a course they're still taking, most overdue first. Narrow it to a course, a flow, a ministry or a group. Someone who finished, was unassigned, or was marked complete in a flow isn't overdue; nor is a course with no due date. Days late are rounded, as on the dashboard.

Needs the Enrollments enrollments:read and People people:read permissions

Query parameters

  • course_id string · 1–128 chars

    Course id or slug

  • flow_id string · 1–128 chars

    Flow id or slug: the flow's courses (see due_date for courses inside branches)

  • ministry_id string · 1–128 chars
  • group_id string · 1–128 chars

    People currently in this group or cohort (see List groups)

  • limit integer · 1–100

    1–100, default 50

  • cursor string · max 200 chars

    next_cursor from the previous page

Returns

A page of Overdue enrollment objects in data, with next_cursor for the next page (see Pagination).

Show 14 fields
  • enrollment_id string · 1–128 chars required
  • person object required
  • id string · 1–128 chars required
  • name string required
  • email string required
  • course object required
  • id string · 1–128 chars required
  • slug string required
  • title string required
  • due_date timestamp required

    The earliest due date among the assignments behind it that still apply (direct, a group they are still in, or their role). Courses inside a flow branch have no stored due date unless also assigned with one

  • days_overdue integer · -9007199254740991–9007199254740991 required

    Days late, rounded (0 = less than half a day)

  • enrolled_at timestamp required

    ISO-8601, UTC

  • lessons_completed integer · -9007199254740991–9007199254740991 required
  • lessons_total integer · -9007199254740991–9007199254740991 required
curl "https://api.equipengine.com/v1/reports/overdue?course_id=safe-sanctuary" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": [
    {
      "enrollment_id": "d4e5f6a7-b8c9-4d0e-9f1a-3b4c5d6e7f8a",
      "person": {
        "id": "8a2d4e6f-1b3c-4d5e-8f90-a1b2c3d4e5f6",
        "name": "Sam Rivera",
        "email": "sam.rivera@example.com"
      },
      "course": {
        "id": "6c9f3d2e-0b8a-4f4c-9e7d-1a2b3c4d5e6f",
        "slug": "safe-sanctuary",
        "title": "Safe Sanctuary"
      },
      "due_date": "2026-09-15T00:00:00.000Z",
      "days_overdue": 25,
      "enrolled_at": "2026-08-20T13:05:00.000Z",
      "lessons_completed": 1,
      "lessons_total": 6
    }
  ],
  "next_cursor": null
}

API reference

Deletions

List deletions

Records removed since a moment, so a sync can delete them too (kinds follow your read permissions)

GET /v1/deletions

Records removed since a moment: people who left your organization (or deleted their account), and courses, enrollments, assignments and flow assignments that were deleted. Use it with updated_since on the lists to keep a copy in sync. When a course or person goes, delete their enrollments too (those aren’t listed one by one). You only see kinds your key can read; deletions are kept for 90 days.

Works with any key.

Query parameters

  • updated_since timestamp required

    ISO-8601, required. Deletions are kept for 90 days

  • kind "person" | "course" | "enrollment" | "assignment" | "flow_assignment"

    One kind only. Each kind needs its read permission: person → people:read, course → courses:read, enrollment and assignment → enrollments:read, flow_assignment → flows:read; others come back empty

  • limit integer · 1–100

    1–100, default 50

  • cursor string · max 200 chars

    next_cursor from the previous page

Returns

A page of Deletion objects in data, with next_cursor for the next page (see Pagination).

Show 3 fields
  • kind "person" | "course" | "enrollment" | "assignment" | "flow_assignment" required

    person = left your organization or deleted their account

  • id string required

    The id the record had

  • deleted_at timestamp required

    ISO-8601, UTC

curl "https://api.equipengine.com/v1/deletions?updated_since=2026-10-01T00%3A00%3A00Z" \
  -H "Authorization: Bearer $EQUIPENGINE_API_KEY"
Response · 200
{
  "data": [
    {
      "kind": "assignment",
      "id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
      "deleted_at": "2026-10-03T14:05:22.000Z"
    },
    {
      "kind": "person",
      "id": "8a2d4e6f-1b3c-4d5e-8f90-a1b2c3d4e5f6",
      "deleted_at": "2026-10-07T09:12:40.000Z"
    }
  ],
  "next_cursor": null
}