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 documenthttps://api.equipengine.com/v1Authentication
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
- Go to Settings → API and click New API key.
- 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.
- 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.
- Only if the integration needs to change things, turn on what it can change (off by default).
- Choose when it expires: 30 days, 90 days or 1 year.
- 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.
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"{
"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_atwhen you passupdated_since). By id, paging never repeats an item, though one added while you page may be missed if its id sorts before your cursor. Byupdated_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-Idheader (the same value as an error'srequest_id). Include it if you contact support. - Use paths exactly as documented: a trailing slash (
/v1/people/) gets a redirect.HEADworks on everyGETendpoint. - 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.
| Permission | Scope | Allows |
|---|---|---|
| People | people:read | Names, email addresses and roles. |
| Courses | courses:read | Courses, their modules and lessons, and your ministries. |
| Enrollments | enrollments:read | Who is enrolled in what, and their lesson progress. |
| Flows | flows:read | Your flows and who they're assigned to. |
| Build draft courses | courses:write | Create draft courses and add or edit their modules, lessons, quizzes and tasks. Published courses can't be changed through the API. |
| Assign and unassign | enrollments:write | Assign 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 learners | people:invite | Invite 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.
Authorization: Bearer $EQUIPENGINE_API_KEY
Content-Type: application/json
Idempotency-Key: 7d1c9f0e-3b2a-4c55-9a61-2f8e0d4b6a13Pagination
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.
{
"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.
- First sync: pass an early
updated_since(e.g.2020-01-01T00:00:00Z) and page through everything. - Remember the newest
updated_atyou 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. - Ask
/v1/deletionswith the sameupdated_sincefor 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.
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": {
"code": "insufficient_scope",
"message": "This API key needs the enrollments:write scope.",
"request_id": "4747c91b-170f-4805-b41b-422b1653e5e6"
}
}| Code | Status | Meaning |
|---|---|---|
validation_failed | 400 | A 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_json | 400 | The body isn’t valid JSON. |
invalid_key | 401 | The key is missing, mistyped, revoked or inactive. |
key_expired | 401 | Extend it on Settings → API (up to 90 days after it expired), or create a new key. |
insufficient_scope | 403 | The key doesn’t have the permission this endpoint needs. |
plan_required | 403 | API access needs the Multiply plan. |
tenant_suspended | 403 | Your organization’s account is suspended. |
forbidden | 403 | Not returned by any endpoint today; reserved for future checks. |
not_found | 404 | No 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_allowed | 405 | That HTTP method isn’t supported on this path. |
not_published | 409 | Only published courses and flows can be assigned. |
course_not_draft | 409 | Only draft courses can be changed through the API. Edit published courses in the app. |
idempotency_conflict | 409 | That Idempotency-Key was already used for a different request. |
idempotency_in_progress | 409 | A request with that Idempotency-Key is still running. Retry in a few seconds. |
payload_too_large | 413 | The body is over 256 KB. |
unsupported_media_type | 415 | The body wasn’t sent as application/json. |
rate_limited | 429 | Too many requests. Wait for the time in Retry-After, then retry. |
internal_error | 500 | Something 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) andsearch_helpfor 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/mcpURL) on authorize and token requests: tokens only work there, not on/v1. - Scopes are the API permissions (
people:read…), plusoffline_accessfor 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.
https://api.equipengine.com/mcp Name: EquipEngine
Remote MCP server URL: https://api.equipengine.com/mcp| Tool | Does | Permission | Asks first |
|---|---|---|---|
get_connection_info | Get connection info | any key | |
list_people | List people | people:read | |
get_person | Get a person | people:read | |
training_status | Get a person's training | people:read + enrollments:read | |
list_groups | List groups | people:read | |
overdue_report | Overdue report | enrollments:read + people:read | |
list_courses | List courses | courses:read | |
get_course_outline | Get a course outline | courses:read | |
list_ministries | List ministries | courses:read | |
list_enrollments | List enrollments | enrollments:read | |
list_flows | List flows | flows:read | |
list_flow_assignments | List flow assignments | flows:read | |
list_assignments | List course assignments | enrollments:read | |
list_deletions | List deletions | any key | |
assign_course | Assign a course | enrollments:write | Yes |
assign_flow | Assign a flow | enrollments:write | Yes |
unassign_enrollment | Unassign an enrollment | enrollments:write | Yes |
invite_learners | Invite learners | people:invite | Yes |
create_draft_course | Create a draft course | courses:write | |
update_draft_course | Update a draft course | courses:write | Yes |
add_to_course_outline | Add modules and lessons | courses:write | |
reorder_modules | Reorder modules | courses:write | |
rename_module | Rename a module | courses:write | |
add_lessons_to_module | Add lessons to a module | courses:write | |
reorder_lessons | Reorder lessons | courses:write | |
update_lesson | Update a lesson | courses:write | Yes |
add_lesson_task | Add a lesson task | courses:write | |
add_quiz_questions | Add quiz questions | courses:write | |
find_people | Find people | people:read | |
search_help | Search the help center | any key |
Endpoints at a glance
Every endpoint, with what it's for. Each one is documented in full below.
| Endpoint | Does |
|---|---|
GET /v1/me | The key you’re using, its permissions and its organization. |
GET /v1/people | List people; filter by role or email, or search by name. |
GET /v1/people/{id} | One person. |
GET /v1/people/{id}/training | One person’s courses, progress and due dates, overdue first. |
GET /v1/groups | List cohorts and Planning Center groups, lists and teams. |
POST /v1/invitations | Invite up to 100 people as learners, optionally into a cohort. |
GET /v1/courses | List courses; filter by status or ministry. |
GET /v1/courses/{id} | A course outline: modules and lessons, by id or slug. |
POST /v1/courses/{id}/assignments | Assign a published course to a person, a role or a group. |
POST /v1/courses | Create 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}/outline | Add modules and lessons to a draft course. |
PUT /v1/courses/{id}/module-order | Reorder a draft course's modules. |
PATCH /v1/modules/{id} | Rename a module (draft courses only). |
POST /v1/modules/{id}/lessons | Add lessons to the end of a module (draft courses only). |
PUT /v1/modules/{id}/lesson-order | Reorder 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}/task | Add a quiz, reflection or acknowledgement to a lesson (draft courses only). |
POST /v1/lessons/{id}/questions | Add questions to a lesson's quiz or reflection (draft courses only). |
GET /v1/ministries | List active ministries, by name. |
GET /v1/enrollments | Enrollments with lesson progress; filter by course, person or status. |
POST /v1/enrollments/{id}/revoke | Unassign one person from one course. |
GET /v1/assignments | Course assignments: who each course is assigned to, with due dates. |
GET /v1/flows | List flows. |
GET /v1/flows/{id}/assignments | Who a flow is assigned to. |
POST /v1/flows/{id}/assignments | Assign a published flow to a person, a role or a group. |
GET /v1/reports/overdue | Who is past a due date, most overdue first; by course, flow, ministry or group. |
GET /v1/deletions | Records removed since a moment, for keeping a copy in sync. |
API reference
Your key
Get your key
The calling key and its organization
/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.
ShowHide 9 fields
keyobject requiredidstring · 1–128 chars requirednamestring requiredscopes("people:read" | "courses:read" | "enrollments:read" | "flows:read" | "courses:write" | "enrollments:write" | "people:invite")[] requiredexpires_attimestamp requiredISO-8601, UTC
organizationobject requiredidstring · 1–128 chars requirednamestring requiredslugstring required
curl "https://api.equipengine.com/v1/me" \
-H "Authorization: Bearer $EQUIPENGINE_API_KEY"{
"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)
/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"emailstring · max 320 charsExact address, any case
qstring · 2–100 charsPart of a name or email address, any case
updated_sincetimestampISO-8601. Only rows changed at or after this moment, ordered by
updated_at(see Syncing changes)limitinteger · 1–1001–100, default 50
cursorstring · max 200 charsnext_cursorfrom the previous page
Returns
A page of Person objects in data, with next_cursor for the next page (see Pagination).
ShowHide 6 fields
idstring · 1–128 chars requirednamestring requiredemailstring requiredrole"admin" | "creator" | "learner" requiredTheir role in your organization
created_attimestamp requiredWhen they joined (ISO-8601, UTC)
updated_attimestamp requiredLast change to this person
curl "https://api.equipengine.com/v1/people?role=learner&limit=2" \
-H "Authorization: Bearer $EQUIPENGINE_API_KEY"{
"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
/v1/people/{id} Needs the People people:read permission
Path parameters
idstring required
Returns
A Person object in data.
ShowHide 6 fields
idstring · 1–128 chars requirednamestring requiredemailstring requiredrole"admin" | "creator" | "learner" requiredTheir role in your organization
created_attimestamp requiredWhen they joined (ISO-8601, UTC)
updated_attimestamp requiredLast change to this person
curl "https://api.equipengine.com/v1/people/3f1c9a52-7d4e-4b8a-9c21-5e6f7a8b9c0d" \
-H "Authorization: Bearer $EQUIPENGINE_API_KEY"{
"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
/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
idstring required
Returns
A Training status object in data.
ShowHide 22 fields
personobject requiredidstring · 1–128 chars requirednamestring requiredemailstring requiredrole"admin" | "creator" | "learner" requiredTheir role in your organization
created_attimestamp requiredWhen they joined (ISO-8601, UTC)
updated_attimestamp requiredLast change to this person
coursesobject[] requiredOverdue first (most late first), then in progress by due date, then completed or credited (latest first). Unassigned courses are left out
enrollment_idstring · 1–128 chars requiredcourseobject requiredidstring · 1–128 chars requiredslugstring requiredtitlestring requiredstatus"active" | "completed" requiredcreditedboolean requiredAn admin marked it complete in a flow (never overdue)
enrolled_attimestamp requiredISO-8601, UTC
completed_attimestamp | null requiredlessons_completedinteger · -9007199254740991–9007199254740991 requiredlessons_totalinteger · -9007199254740991–9007199254740991 requireddue_datetimestamp | null requiredThe 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_overdueinteger | null requiredDays past the due date, rounded (0 = less than half a day), while still active and not credited; null if not overdue
truncatedboolean requiredtrue 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"{
"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
/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
qstring · 1–100 charsPart of the name, any case
kind"cohort" | "pc_group" | "pc_list" | "pc_team"limitinteger · 1–1001–100, default 50
cursorstring · max 200 charsnext_cursorfrom the previous page
Returns
A page of Group objects in data, with next_cursor for the next page (see Pagination).
ShowHide 5 fields
idstring · 1–128 chars requirednamestring requiredkind"cohort" | "pc_group" | "pc_list" | "pc_team" requiredcohort(made in the app) or a Planning Center group, list or teamparent_namestring | null requiredPlanning Center's group type, service type or list category; null for cohorts
member_countinteger · -9007199254740991–9007199254740991 requiredPeople in your organization in the group
curl "https://api.equipengine.com/v1/groups?q=greeter" \
-H "Authorization: Bearer $EQUIPENGINE_API_KEY"{
"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)
/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-Keystring · 1–100 charsA 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
emailsstring[] · 1–100 items requiredcohort_idstring · 1–128 charsA manual cohort; new invitees join it when they accept. Existing members are not added (assign them instead).
Returns
An Invitation result object in data.
ShowHide 5 fields
resultsobject[] requiredemailstring requiredoutcome"invited" | "already_member" | "already_invited" | "over_daily_cap" | "over_seat_limit" | "invalid_email" requiredinvitedinteger · -9007199254740991–9007199254740991 requiredInvitations sent
added_to_cohortinteger · -9007199254740991–9007199254740991 requiredAlways 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"
}'{
"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
/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_idstring · 1–128 charslimitinteger · 1–1001–100, default 50
cursorstring · max 200 charsnext_cursorfrom the previous page
Returns
A page of Course objects in data, with next_cursor for the next page (see Pagination).
ShowHide 10 fields
idstring · 1–128 chars requiredslugstring requiredUsed in course URLs; can replace the id in
/v1/courses/{id}pathstitlestring requireddescriptionstring | null requiredstatus"draft" | "published" | "archived" requiredministry_idstring | null requiredThe ministry it belongs to
enrollment_type"open" | "invite" | "code" requiredHow learners get in: anyone, assigned only, or with a code
estimated_minutesinteger | null requiredEstimated time to complete
created_attimestamp requiredISO-8601, UTC
updated_attimestamp requiredISO-8601, UTC
curl "https://api.equipengine.com/v1/courses?status=published" \
-H "Authorization: Bearer $EQUIPENGINE_API_KEY"{
"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)
/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
idstring required
Returns
A Course outline object in data.
ShowHide 20 fields
courseobject requiredidstring · 1–128 chars requiredslugstring requiredUsed in course URLs; can replace the id in
/v1/courses/{id}pathstitlestring requireddescriptionstring | null requiredstatus"draft" | "published" | "archived" requiredministry_idstring | null requiredThe ministry it belongs to
enrollment_type"open" | "invite" | "code" requiredHow learners get in: anyone, assigned only, or with a code
estimated_minutesinteger | null requiredEstimated time to complete
created_attimestamp requiredISO-8601, UTC
updated_attimestamp requiredISO-8601, UTC
modulesobject[] requiredidstring · 1–128 chars requiredtitlestring requiredlessonsobject[] requiredidstring · 1–128 chars requiredtitlestring requiredtype"video" | "text" | "pdf" requiredword_countinteger | null requiredWords in the lesson body; null if it has none
task"quiz" | "reflection" | "acknowledge" | "other" | null requiredWhat the learner does at the end, if anything
curl "https://api.equipengine.com/v1/courses/greeter-basics" \
-H "Authorization: Bearer $EQUIPENGINE_API_KEY"{
"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
/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
idstring required
Headers
Idempotency-Keystring · 1–100 charsA 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
targetobject required- When
type: "user" type"user" requireduser_idstring · 1–128 chars requiredA person in your organization
- When
type: "role" type"role" requiredrole"admin" | "creator" | "learner" requiredEveryone 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" requiredgroup_idstring · 1–128 chars requiredA 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 (
unenrollunless changed)due_datedateYYYY-MM-DD, read as midnight UTC. Leaving it out keeps any existing due date
notifybooleanSend 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.
falsesends none, for bulk backfills.
Returns
A Course assignment result object in data.
ShowHide 6 fields
assignment_idstring · 1–128 chars requiredcreatedboolean requiredfalse when this target already had the course
due_date_updatedboolean requiredenrolledinteger · -9007199254740991–9007199254740991 requiredPeople newly enrolled
reactivatedinteger · -9007199254740991–9007199254740991 requiredPeople brought back after being unassigned
unchangedinteger · -9007199254740991–9007199254740991 requiredPeople 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"
}'{
"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
/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-Keystring · 1–100 charsA 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
titlestring · 1–200 chars requireddescriptionstring · max 2000 charsministry_idstring · 1–128 charsDefaults to your default ministry
enrollment_type"open" | "invite"Default
openoutlineobject[] · 1–30 itemsNew modules and their lessons. Without one, the course starts with an empty "Module 1". At most 200 lessons
titlestring · 1–200 chars requiredlessonsobject[] · 1–50 items requiredtitlestring · 1–200 chars requiredcontentstring · max 20000 charsMarkdown: headings, lists, bold, links. HTML is removed.
taskobjectSomething the learner does at the end of the lesson
- When
kind: "acknowledge" kind"acknowledge" requiredpromptstring · 1–500 chars requiredWhat the learner confirms, e.g. "I have read the safety policy."
- When
kind: "quiz" kind"quiz" requiredquestionsobject[] · 1–20 items requiredGraded: each choice question needs exactly one correct option (at least one for multi_select)
questionstring · 1–500 chars requiredtype"multiple_choice" | "true_false" | "multi_select" | "short_text" requiredshort_textis an open written answer (no options)optionsobject[] · max 8 itemsAnswer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text.
is_correctis ignored on reflections, which are never gradedtextstring · 1–300 chars requiredis_correctboolean- When
kind: "reflection" kind"reflection" requiredquestionsobject[] · 1–20 items requiredNot graded
questionstring · 1–500 chars requiredtype"multiple_choice" | "true_false" | "multi_select" | "short_text" requiredshort_textis an open written answer (no options)optionsobject[] · max 8 itemsAnswer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text.
is_correctis ignored on reflections, which are never gradedtextstring · 1–300 chars requiredis_correctboolean
Returns
A Created course object in data.
ShowHide 7 fields
courseobject requiredidstring · 1–128 chars requiredslugstring requiredtitlestring requireddescriptionstring | null requiredstatus"draft" requiredoutlineobject | 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."
}
}
]
}
]
}'{
"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)
/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
idstring required
Headers
Idempotency-Keystring · 1–100 charsA 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
titlestring · 1–200 charsdescriptionstring | nullnullclears it
Returns
A Draft course object in data.
ShowHide 5 fields
idstring · 1–128 chars requiredslugstring requiredtitlestring requireddescriptionstring | null requiredstatus"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"
}'{
"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)
/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
idstring required
Headers
Idempotency-Keystring · 1–100 charsA 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
modulesobject[] · 1–30 items requiredAt most 200 lessons per request
titlestring · 1–200 chars requiredRequired, but ignored when
existing_module_idis setlessonsobject[] · 1–50 items requiredtitlestring · 1–200 chars requiredcontentstring · max 20000 charsMarkdown: headings, lists, bold, links. HTML is removed.
taskobjectSomething the learner does at the end of the lesson
- When
kind: "acknowledge" kind"acknowledge" requiredpromptstring · 1–500 chars requiredWhat the learner confirms, e.g. "I have read the safety policy."
- When
kind: "quiz" kind"quiz" requiredquestionsobject[] · 1–20 items requiredGraded: each choice question needs exactly one correct option (at least one for multi_select)
questionstring · 1–500 chars requiredtype"multiple_choice" | "true_false" | "multi_select" | "short_text" requiredshort_textis an open written answer (no options)optionsobject[] · max 8 itemsAnswer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text.
is_correctis ignored on reflections, which are never gradedtextstring · 1–300 chars requiredis_correctboolean- When
kind: "reflection" kind"reflection" requiredquestionsobject[] · 1–20 items requiredNot graded
questionstring · 1–500 chars requiredtype"multiple_choice" | "true_false" | "multi_select" | "short_text" requiredshort_textis an open written answer (no options)optionsobject[] · max 8 itemsAnswer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text.
is_correctis ignored on reflections, which are never gradedtextstring · 1–300 chars requiredis_correctbooleanexisting_module_idstring · 1–128 charsAdd the lessons to this module of the course instead of creating one
Returns
An Outline result object in data.
ShowHide 9 fields
modules_createdinteger · -9007199254740991–9007199254740991 requiredlessons_createdinteger · -9007199254740991–9007199254740991 requiredmodulesobject[] requiredModules this call created
idstring · 1–128 chars requiredtitlestring requiredlessonsobject[] requiredLessons this call created
idstring · 1–128 chars requiredmodule_idstring · 1–128 chars requiredtitlestring 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
}
]
}
]
}
}
]
}
]
}'{
"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)
/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
idstring required
Headers
Idempotency-Keystring · 1–100 charsA 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_idsstring[] · 1–200 items requiredIn the new order. Modules you leave out keep their order after these
Returns
A Module order object in data.
ShowHide 4 fields
modulesobject[] requiredidstring · 1–128 chars requiredtitlestring requiredpositioninteger · -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"
]
}'{
"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)
/v1/modules/{id} Changes a module’s title. Published courses return course_not_draft.
Needs the Build draft courses courses:write permission
Path parameters
idstring required
Headers
Idempotency-Keystring · 1–100 charsA 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
titlestring · 1–200 chars required
Returns
A Module object in data.
ShowHide 3 fields
idstring · 1–128 chars requiredtitlestring requiredpositioninteger · -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"
}'{
"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)
/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
idstring required
Headers
Idempotency-Keystring · 1–100 charsA 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
lessonsobject[] · 1–50 items requiredText lessons, added after the module’s current lessons. At most 50 per request
titlestring · 1–200 chars requiredcontentstring · max 20000 charsMarkdown: headings, lists, bold, links. HTML is removed.
taskobjectSomething the learner does at the end of the lesson
- When
kind: "acknowledge" kind"acknowledge" requiredpromptstring · 1–500 chars requiredWhat the learner confirms, e.g. "I have read the safety policy."
- When
kind: "quiz" kind"quiz" requiredquestionsobject[] · 1–20 items requiredGraded: each choice question needs exactly one correct option (at least one for multi_select)
questionstring · 1–500 chars requiredtype"multiple_choice" | "true_false" | "multi_select" | "short_text" requiredshort_textis an open written answer (no options)optionsobject[] · max 8 itemsAnswer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text.
is_correctis ignored on reflections, which are never gradedtextstring · 1–300 chars requiredis_correctboolean- When
kind: "reflection" kind"reflection" requiredquestionsobject[] · 1–20 items requiredNot graded
questionstring · 1–500 chars requiredtype"multiple_choice" | "true_false" | "multi_select" | "short_text" requiredshort_textis an open written answer (no options)optionsobject[] · max 8 itemsAnswer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text.
is_correctis ignored on reflections, which are never gradedtextstring · 1–300 chars requiredis_correctboolean
Returns
A Lessons added object in data.
ShowHide 5 fields
lessons_createdinteger · -9007199254740991–9007199254740991 requiredlessonsobject[] requiredidstring · 1–128 chars requiredmodule_idstring · 1–128 chars requiredtitlestring 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."
}
}
]
}'{
"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)
/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
idstring required
Headers
Idempotency-Keystring · 1–100 charsA 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_idsstring[] · 1–200 items requiredIn the new order. Lessons you leave out keep their order after these
Returns
A Lesson order object in data.
ShowHide 4 fields
lessonsobject[] requiredidstring · 1–128 chars requiredtitlestring requiredpositioninteger · -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"
]
}'{
"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)
/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
idstring required
Headers
Idempotency-Keystring · 1–100 charsA 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
titlestring · 1–200 charscontentstring · max 20000 charsMarkdown: headings, lists, bold, links. HTML is removed.
mode"replace" | "append"appendaddscontentbelow the current text. Defaultreplace, where empty content clears the lesson. Ignored withoutcontent
Returns
A Lesson summary object in data.
ShowHide 3 fields
idstring · 1–128 chars requiredtitlestring requiredword_countinteger · -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"
}'{
"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)
/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
idstring required
Headers
Idempotency-Keystring · 1–100 charsA 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" requiredpromptstring · 1–500 chars requiredWhat the learner confirms, e.g. "I have read the safety policy."
- When
kind: "quiz" kind"quiz" requiredquestionsobject[] · 1–20 items requiredGraded: each choice question needs exactly one correct option (at least one for multi_select)
questionstring · 1–500 chars requiredtype"multiple_choice" | "true_false" | "multi_select" | "short_text" requiredshort_textis an open written answer (no options)optionsobject[] · max 8 itemsAnswer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text.
is_correctis ignored on reflections, which are never gradedtextstring · 1–300 chars requiredis_correctboolean- When
kind: "reflection" kind"reflection" requiredquestionsobject[] · 1–20 items requiredNot graded
questionstring · 1–500 chars requiredtype"multiple_choice" | "true_false" | "multi_select" | "short_text" requiredshort_textis an open written answer (no options)optionsobject[] · max 8 itemsAnswer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text.
is_correctis ignored on reflections, which are never gradedtextstring · 1–300 chars requiredis_correctboolean
Returns
A Task object in data.
ShowHide 3 fields
task_idstring · 1–128 chars requiredlesson_idstring · 1–128 chars requiredkind"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"
}
]
}'{
"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)
/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
idstring required
Headers
Idempotency-Keystring · 1–100 charsA 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
questionsobject[] · 1–20 items requiredquestionstring · 1–500 chars requiredtype"multiple_choice" | "true_false" | "multi_select" | "short_text" requiredshort_textis an open written answer (no options)optionsobject[] · max 8 itemsAnswer choices: 2–8 for multiple_choice and multi_select, exactly 2 for true_false, none for short_text.
is_correctis ignored on reflections, which are never gradedtextstring · 1–300 chars requiredis_correctboolean
Returns
A Questions added object in data.
ShowHide 3 fields
task_idstring · 1–128 chars requiredquestion_idsstring[] requiredgradedboolean 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
}
]
}
]
}'{
"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
/v1/ministries Needs the Courses courses:read permission
Returns
A list of Ministry objects in data.
ShowHide 5 fields
idstring · 1–128 chars requirednamestring requiredslugstring requireddescriptionstring | null requiredis_defaultboolean requiredThe ministry new courses go into by default
curl "https://api.equipengine.com/v1/ministries" \
-H "Authorization: Bearer $EQUIPENGINE_API_KEY"{
"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
/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_idstring · 1–128 charsuser_idstring · 1–128 charsstatus"active" | "completed" | "revoked"updated_sincetimestampISO-8601. Only rows changed at or after this moment, ordered by
updated_at(see Syncing changes)limitinteger · 1–1001–100, default 50
cursorstring · max 200 charsnext_cursorfrom the previous page
Returns
A page of Enrollment objects in data, with next_cursor for the next page (see Pagination).
ShowHide 10 fields
idstring · 1–128 chars requireduser_idstring · 1–128 chars requiredThe person (see People)
course_idstring · 1–128 chars requiredstatus"active" | "completed" | "revoked" requiredrevoked = unassigned; progress is kept
enrolled_attimestamp requiredISO-8601, UTC
completed_attimestamp | null requiredrevoked_attimestamp | null requiredlessons_completedinteger · -9007199254740991–9007199254740991 requiredlessons_totalinteger · -9007199254740991–9007199254740991 requiredLessons in the course now
updated_attimestamp requiredLast 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"{
"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)
/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
idstring required
Headers
Idempotency-Keystring · 1–100 charsA 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.
ShowHide 2 fields
idstring · 1–128 chars requiredstatus"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)"{
"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)
/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_idstring · 1–128 charsupdated_sincetimestampISO-8601. Only rows changed at or after this moment, ordered by
updated_at(see Syncing changes)limitinteger · 1–1001–100, default 50
cursorstring · max 200 charsnext_cursorfrom the previous page
Returns
A page of Assignment objects in data, with next_cursor for the next page (see Pagination).
ShowHide 10 fields
idstring · 1–128 chars requiredcourse_idstring · 1–128 chars requiredtarget_kind"user" | "role" | "group" requiredWhich of the target fields below is set
target_user_idstring | null requiredtarget_role"admin" | "creator" | "learner" | null requiredtarget_group_idstring | null requiredA Planning Center group or a cohort
due_datetimestamp | null requiredon_member_leaves"keep" | "unenroll" requiredGroups only: what happens to someone who leaves the group
assigned_attimestamp requiredISO-8601, UTC
updated_attimestamp requiredISO-8601, UTC
curl "https://api.equipengine.com/v1/assignments?course_id=5b8e2c1d-9a7f-4e3b-8d6c-0f1e2d3c4b5a" \
-H "Authorization: Bearer $EQUIPENGINE_API_KEY"{
"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
/v1/flows Needs the Flows flows:read permission
Query parameters
status"draft" | "published" | "archived"limitinteger · 1–1001–100, default 50
cursorstring · max 200 charsnext_cursorfrom the previous page
Returns
A page of Flow objects in data, with next_cursor for the next page (see Pagination).
ShowHide 9 fields
idstring · 1–128 chars requiredslugstring requiredtitlestring requireddescriptionstring | null requiredcategorystring | null requiredstatus"draft" | "published" | "archived" requiredcourse_idsstring[] requiredCourse steps in order; branch steps omitted
created_attimestamp requiredISO-8601, UTC
updated_attimestamp requiredISO-8601, UTC
curl "https://api.equipengine.com/v1/flows" \
-H "Authorization: Bearer $EQUIPENGINE_API_KEY"{
"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
/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
idstring required
Query parameters
updated_sincetimestampISO-8601. Only rows changed at or after this moment, ordered by
updated_at(see Syncing changes)limitinteger · 1–1001–100, default 50
cursorstring · max 200 charsnext_cursorfrom the previous page
Returns
A page of Flow assignment objects in data, with next_cursor for the next page (see Pagination).
ShowHide 8 fields
idstring · 1–128 chars requiredflow_idstring · 1–128 chars requiredtarget_kind"user" | "role" | "group" requiredWhich of the target fields below is set
target_user_idstring | null requiredtarget_role"admin" | "creator" | "learner" | null requiredtarget_group_idstring | null requiredA Planning Center group or a cohort
assigned_attimestamp requiredISO-8601, UTC
updated_attimestamp requiredISO-8601, UTC
curl "https://api.equipengine.com/v1/flows/6c7d8e9f-0a1b-4c2d-8e3f-4a5b6c7d8e9f/assignments" \
-H "Authorization: Bearer $EQUIPENGINE_API_KEY"{
"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
/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
idstring required
Headers
Idempotency-Keystring · 1–100 charsA 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
targetobject required- When
type: "user" type"user" requireduser_idstring · 1–128 chars requiredA person in your organization
- When
type: "role" type"role" requiredrole"admin" | "creator" | "learner" requiredEveryone 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" requiredgroup_idstring · 1–128 chars requiredA 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 (
unenrollunless changed)notifybooleanSend 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.
falsesends none, for bulk backfills.
Returns
A Flow assignment result object in data.
ShowHide 4 fields
flow_assignment_idstring · 1–128 chars requiredcreatedboolean requiredfalse when this target already had the flow
targetedinteger · -9007199254740991–9007199254740991 requiredPeople the assignment covers
newly_enrolledinteger · -9007199254740991–9007199254740991 requiredPeople 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"
}
}'{
"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
/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_idstring · 1–128 charsCourse id or slug
flow_idstring · 1–128 charsFlow id or slug: the flow's courses (see due_date for courses inside branches)
ministry_idstring · 1–128 charsgroup_idstring · 1–128 charsPeople currently in this group or cohort (see List groups)
limitinteger · 1–1001–100, default 50
cursorstring · max 200 charsnext_cursorfrom the previous page
Returns
A page of Overdue enrollment objects in data, with next_cursor for the next page (see Pagination).
ShowHide 14 fields
enrollment_idstring · 1–128 chars requiredpersonobject requiredidstring · 1–128 chars requirednamestring requiredemailstring requiredcourseobject requiredidstring · 1–128 chars requiredslugstring requiredtitlestring requireddue_datetimestamp requiredThe 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_overdueinteger · -9007199254740991–9007199254740991 requiredDays late, rounded (0 = less than half a day)
enrolled_attimestamp requiredISO-8601, UTC
lessons_completedinteger · -9007199254740991–9007199254740991 requiredlessons_totalinteger · -9007199254740991–9007199254740991 required
curl "https://api.equipengine.com/v1/reports/overdue?course_id=safe-sanctuary" \
-H "Authorization: Bearer $EQUIPENGINE_API_KEY"{
"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)
/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_sincetimestamp requiredISO-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
limitinteger · 1–1001–100, default 50
cursorstring · max 200 charsnext_cursorfrom the previous page
Returns
A page of Deletion objects in data, with next_cursor for the next page (see Pagination).
ShowHide 3 fields
kind"person" | "course" | "enrollment" | "assignment" | "flow_assignment" requiredperson= left your organization or deleted their accountidstring requiredThe id the record had
deleted_attimestamp requiredISO-8601, UTC
curl "https://api.equipengine.com/v1/deletions?updated_since=2026-10-01T00%3A00%3A00Z" \
-H "Authorization: Bearer $EQUIPENGINE_API_KEY"{
"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
}