API reference
Download PDFEvery endpoint a key can call, with its parameters, response fields and a ready-to-run example.
Read your school's data from your own tools.
Authenticating
Every request carries an API key as a bearer token:
Authorization: Bearer YOUR_API_KEYCreate a key at Settings → API keys. The full key is shown once, when you create it. Keys are read-only, reach only your own school, and stop working the moment you revoke them.
Start with GET /api/reporting/v1/health — it proves the key and the header are right before you touch any data.
Conventions
- Field names are
snake_case; timestamps are ISO-8601 in UTC; money is always integer cents, never a decimal. - List endpoints return
{ data, total, page, page_size }.totalcounts every matching row, not just the page. - Paginate with
?page=and?page_size=.page_sizeis clamped to 1–100, and defaults to 50. - Errors return
{ "error": { "code", "message" } }with a matching HTTP status. Branch oncode— it is stable;messagemay be reworded.
Rate limits
Requests are limited per key: 600 requests per 60 seconds. Over the limit you get 429 with code rate_limited. Give each integration its own key so one busy tool cannot exhaust another's budget.
Core data
List contacts
Newest first. Combine filters to narrow the list; omitted filters are not applied.
| Name | In | Type | Description |
|---|---|---|---|
| page | query | integer | Page number, starting at 1. Values below 1 are clamped to 1. |
| page_size | query | integer | Rows per page. Clamped to the 1–100 range. |
| role | query | string | Only contacts with this role. |
| status | query | string | Only contacts with this `contact_status`, e.g. `active`. |
| marketing_status | query | string | Only contacts with this email consent state, e.g. `subscribed`. |
curl -X GET 'https://app.zinstruct.com/api/public/v1/contacts?page=1&page_size=50' \
-H 'Authorization: Bearer YOUR_API_KEY'A person in your CRM — a lead, a member, or a customer.
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Stable identifier. |
| string | Email address. | |
| name | string | null | Display name. Falls back to first + last name when the name field is empty. |
| first_name | string | null | Given name. |
| last_name | string | null | Family name. |
| role | string | null | Your own classification of this contact. |
| phone | string | null | Phone number. Falls back to the mobile number when the primary is empty. |
| contact_status | string | Lifecycle state, e.g. `active` or `archived`. Defaults to `active`. |
| marketing_status | string | Email consent, e.g. `subscribed` or `unsubscribed`. Defaults to `unsubscribed`. |
| source | string | null | Where the contact first came from. Immutable first-touch provenance. |
| lifetime_value_cents | integer | Total revenue attributed to this contact, in cents. |
| created_at | string | null | When the contact was created. ISO-8601, UTC. |
| last_activity_at | string | null | Most recent recorded activity. ISO-8601, UTC. |
- 200 — A page of contacts.
- 401 — The token is missing, malformed, invalid, or revoked.
- 429 — Rate limit exceeded for this key.
Get one contact
| Name | In | Type | Description |
|---|---|---|---|
| id* | path | string (uuid) | The contact id. |
curl -X GET 'https://app.zinstruct.com/api/public/v1/contacts/00000000-0000-0000-0000-000000000000' \
-H 'Authorization: Bearer YOUR_API_KEY'A person in your CRM — a lead, a member, or a customer.
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Stable identifier. |
| string | Email address. | |
| name | string | null | Display name. Falls back to first + last name when the name field is empty. |
| first_name | string | null | Given name. |
| last_name | string | null | Family name. |
| role | string | null | Your own classification of this contact. |
| phone | string | null | Phone number. Falls back to the mobile number when the primary is empty. |
| contact_status | string | Lifecycle state, e.g. `active` or `archived`. Defaults to `active`. |
| marketing_status | string | Email consent, e.g. `subscribed` or `unsubscribed`. Defaults to `unsubscribed`. |
| source | string | null | Where the contact first came from. Immutable first-touch provenance. |
| lifetime_value_cents | integer | Total revenue attributed to this contact, in cents. |
| created_at | string | null | When the contact was created. ISO-8601, UTC. |
| last_activity_at | string | null | Most recent recorded activity. ISO-8601, UTC. |
- 200 — The contact.
- 401 — The token is missing, malformed, invalid, or revoked.
- 404 — No contact with that id.
- 429 — Rate limit exceeded for this key.
List products
Newest first.
| Name | In | Type | Description |
|---|---|---|---|
| page | query | integer | Page number, starting at 1. Values below 1 are clamped to 1. |
| page_size | query | integer | Rows per page. Clamped to the 1–100 range. |
| type | query | string | Only products of this type, e.g. `course`. |
| status | query | string | Only products with this status, e.g. `published`. |
curl -X GET 'https://app.zinstruct.com/api/public/v1/products?page=1&page_size=50' \
-H 'Authorization: Bearer YOUR_API_KEY'A catalog item — a course, coaching program, community, download, newsletter, or podcast.
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Stable identifier. |
| type | string | Product type, e.g. `course`, `coaching`, `community`, `download`, `newsletter`, `podcast`. |
| sub_type | string | null | Further classification within the type, where one applies. |
| title | string | Product title. |
| description | string | null | Product description. |
| status | string | `draft` or `published`. Defaults to `draft`. |
| created_at | string | null | When the product was created. ISO-8601, UTC. |
| updated_at | string | null | When the product was last changed. ISO-8601, UTC. |
- 200 — A page of products.
- 401 — The token is missing, malformed, invalid, or revoked.
- 429 — Rate limit exceeded for this key.
Get one product
| Name | In | Type | Description |
|---|---|---|---|
| id* | path | string (uuid) | The product id. |
curl -X GET 'https://app.zinstruct.com/api/public/v1/products/00000000-0000-0000-0000-000000000000' \
-H 'Authorization: Bearer YOUR_API_KEY'A catalog item — a course, coaching program, community, download, newsletter, or podcast.
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Stable identifier. |
| type | string | Product type, e.g. `course`, `coaching`, `community`, `download`, `newsletter`, `podcast`. |
| sub_type | string | null | Further classification within the type, where one applies. |
| title | string | Product title. |
| description | string | null | Product description. |
| status | string | `draft` or `published`. Defaults to `draft`. |
| created_at | string | null | When the product was created. ISO-8601, UTC. |
| updated_at | string | null | When the product was last changed. ISO-8601, UTC. |
- 200 — The product.
- 401 — The token is missing, malformed, invalid, or revoked.
- 404 — No product with that id.
- 429 — Rate limit exceeded for this key.
List offers
Newest first.
| Name | In | Type | Description |
|---|---|---|---|
| page | query | integer | Page number, starting at 1. Values below 1 are clamped to 1. |
| page_size | query | integer | Rows per page. Clamped to the 1–100 range. |
| product_id | query | string | Only offers selling this product. |
| status | query | string | Only offers with this status, e.g. `published`. |
curl -X GET 'https://app.zinstruct.com/api/public/v1/offers?page=1&page_size=50' \
-H 'Authorization: Bearer YOUR_API_KEY'The sellable pricing of a product. One product can have several offers.
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Stable identifier. |
| product_id | string (uuid) | The product this offer sells. |
| name | string | Offer name. |
| description | string | null | Offer description. |
| price_cents | integer | Price in the smallest currency unit. Money is always integer cents — never a float. |
| currency | string | ISO-4217 currency code, lowercase. Defaults to `usd`. |
| plan | string | `one_time` or a recurring plan. Defaults to `one_time`. |
| interval | string | null | Billing interval for recurring plans, e.g. `month` or `year`. Null for one-time offers. |
| interval_count | integer | How many intervals per billing cycle. |
| status | string | `draft` or `published`. Defaults to `draft`. |
| pwyw | boolean | True when the buyer names their own price. |
| trial_days | integer | null | Free trial length in days, or null for no trial. |
| created_at | string | null | When the offer was created. ISO-8601, UTC. |
| updated_at | string | null | When the offer was last changed. ISO-8601, UTC. |
- 200 — A page of offers.
- 401 — The token is missing, malformed, invalid, or revoked.
- 429 — Rate limit exceeded for this key.
Get one offer
| Name | In | Type | Description |
|---|---|---|---|
| id* | path | string (uuid) | The offer id. |
curl -X GET 'https://app.zinstruct.com/api/public/v1/offers/00000000-0000-0000-0000-000000000000' \
-H 'Authorization: Bearer YOUR_API_KEY'The sellable pricing of a product. One product can have several offers.
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Stable identifier. |
| product_id | string (uuid) | The product this offer sells. |
| name | string | Offer name. |
| description | string | null | Offer description. |
| price_cents | integer | Price in the smallest currency unit. Money is always integer cents — never a float. |
| currency | string | ISO-4217 currency code, lowercase. Defaults to `usd`. |
| plan | string | `one_time` or a recurring plan. Defaults to `one_time`. |
| interval | string | null | Billing interval for recurring plans, e.g. `month` or `year`. Null for one-time offers. |
| interval_count | integer | How many intervals per billing cycle. |
| status | string | `draft` or `published`. Defaults to `draft`. |
| pwyw | boolean | True when the buyer names their own price. |
| trial_days | integer | null | Free trial length in days, or null for no trial. |
| created_at | string | null | When the offer was created. ISO-8601, UTC. |
| updated_at | string | null | When the offer was last changed. ISO-8601, UTC. |
- 200 — The offer.
- 401 — The token is missing, malformed, invalid, or revoked.
- 404 — No offer with that id.
- 429 — Rate limit exceeded for this key.
List enrollments
Most recently enrolled first.
| Name | In | Type | Description |
|---|---|---|---|
| page | query | integer | Page number, starting at 1. Values below 1 are clamped to 1. |
| page_size | query | integer | Rows per page. Clamped to the 1–100 range. |
| product_id | query | string | Only enrollments in this product. |
| contact_id | query | string | Only enrollments belonging to this contact. |
| status | query | string | Only enrollments with this status, e.g. `active`. |
curl -X GET 'https://app.zinstruct.com/api/public/v1/enrollments?page=1&page_size=50' \
-H 'Authorization: Bearer YOUR_API_KEY'A contact's access to a product, with cached progress.
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Stable identifier. |
| contact_id | string (uuid) | The enrolled contact. |
| product_id | string (uuid) | The product they have access to. |
| granted_via_offer_id | string | null | The offer that granted access, when access came from a purchase or claim. |
| status | string | Access state, e.g. `active`. Defaults to `active`. |
| progress_pct | number | Completion percentage, 0–100. |
| enrolled_at | string | null | When access began. ISO-8601, UTC. |
| completed_at | string | null | When the product was completed, or null. ISO-8601, UTC. |
- 200 — A page of enrollments.
- 401 — The token is missing, malformed, invalid, or revoked.
- 429 — Rate limit exceeded for this key.
Get one enrollment
| Name | In | Type | Description |
|---|---|---|---|
| id* | path | string (uuid) | The enrollment id. |
curl -X GET 'https://app.zinstruct.com/api/public/v1/enrollments/00000000-0000-0000-0000-000000000000' \
-H 'Authorization: Bearer YOUR_API_KEY'A contact's access to a product, with cached progress.
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | Stable identifier. |
| contact_id | string (uuid) | The enrolled contact. |
| product_id | string (uuid) | The product they have access to. |
| granted_via_offer_id | string | null | The offer that granted access, when access came from a purchase or claim. |
| status | string | Access state, e.g. `active`. Defaults to `active`. |
| progress_pct | number | Completion percentage, 0–100. |
| enrolled_at | string | null | When access began. ISO-8601, UTC. |
| completed_at | string | null | When the product was completed, or null. ISO-8601, UTC. |
- 200 — The enrollment.
- 401 — The token is missing, malformed, invalid, or revoked.
- 404 — No enrollment with that id.
- 429 — Rate limit exceeded for this key.
Engagement
List courses with engagement
Every course with its structure counts, enrollment count, watch activity, and mean completion. Watch metrics read a materialized table and stay at 0 until viewing is recorded.
| Name | In | Type | Description |
|---|---|---|---|
| page | query | integer | Page number, starting at 1. Values below 1 are clamped to 1. |
| page_size | query | integer | Rows per page. Clamped to the 1–100 range. |
curl -X GET 'https://app.zinstruct.com/api/reporting/v1/courses?page=1&page_size=50' \
-H 'Authorization: Bearer YOUR_API_KEY'Engagement rollup for one course.
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | The course product id. |
| title | string | Course title. |
| modules_count | integer | Modules in the course. |
| lessons_count | integer | Lessons across all modules. |
| students_enrolled | integer | Contacts with an enrollment in this course. |
| lessons_watched_over_30s | integer | Lesson views exceeding 30 seconds of watch time. |
| distinct_students_watched | integer | Distinct students with any recorded watch time. |
| avg_completion_pct | number | Mean enrollment progress, 0–100. |
- 200 — A page of courses.
- 401 — The token is missing, malformed, invalid, or revoked.
- 429 — Rate limit exceeded for this key.
List a course’s modules with engagement
Modules in their display order, each with lesson count and watch activity.
| Name | In | Type | Description |
|---|---|---|---|
| course_id* | path | string (uuid) | The course product id. |
| page | query | integer | Page number, starting at 1. Values below 1 are clamped to 1. |
| page_size | query | integer | Rows per page. Clamped to the 1–100 range. |
curl -X GET 'https://app.zinstruct.com/api/reporting/v1/courses/00000000-0000-0000-0000-000000000000/modules?page=1&page_size=50' \
-H 'Authorization: Bearer YOUR_API_KEY'Engagement rollup for one module within a course.
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | The module id. |
| title | string | Module title. |
| lessons_count | integer | Lessons in this module. |
| lessons_watched_over_30s | integer | Lesson views exceeding 30 seconds of watch time. |
| distinct_students_watched | integer | Distinct students with any recorded watch time in this module. |
- 200 — A page of modules.
- 401 — The token is missing, malformed, invalid, or revoked.
- 404 — No course with that id.
- 429 — Rate limit exceeded for this key.
List students with activity
Contacts with sign-in, session, and revenue metrics attached.
| Name | In | Type | Description |
|---|---|---|---|
| page | query | integer | Page number, starting at 1. Values below 1 are clamped to 1. |
| page_size | query | integer | Rows per page. Clamped to the 1–100 range. |
| role | query | string | Only students with this role. |
| sort | query | string | Pass `last_active_at` to order by most recently active. |
curl -X GET 'https://app.zinstruct.com/api/reporting/v1/students?page=1&page_size=50' \
-H 'Authorization: Bearer YOUR_API_KEY'A contact viewed through the engagement lens: activity, sessions, and revenue.
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | The contact id. Same identifier the public API uses for contacts. |
| name | string | null | Display name. |
| string | Email address. | |
| role | string | null | Your own classification of this contact. |
| joined_at | string | null | When the contact was created. ISO-8601, UTC. |
| last_active_at | string | null | Most recent recorded activity. ISO-8601, UTC. |
| logins_30d | integer | Sign-ins in the last 30 days. |
| avg_session_minutes | number | Mean session length in minutes over the last 30 days, to one decimal place. |
| total_session_minutes_30d | integer | Total session minutes over the last 30 days. |
| paying | boolean | True when this contact has ever bought. |
| lifetime_revenue_cents | integer | Total revenue attributed to this contact, in cents. |
- 200 — A page of students.
- 401 — The token is missing, malformed, invalid, or revoked.
- 429 — Rate limit exceeded for this key.
Get one student
| Name | In | Type | Description |
|---|---|---|---|
| student_id* | path | string (uuid) | The contact id. |
curl -X GET 'https://app.zinstruct.com/api/reporting/v1/students/00000000-0000-0000-0000-000000000000' \
-H 'Authorization: Bearer YOUR_API_KEY'A contact viewed through the engagement lens: activity, sessions, and revenue.
| Field | Type | Description |
|---|---|---|
| id | string (uuid) | The contact id. Same identifier the public API uses for contacts. |
| name | string | null | Display name. |
| string | Email address. | |
| role | string | null | Your own classification of this contact. |
| joined_at | string | null | When the contact was created. ISO-8601, UTC. |
| last_active_at | string | null | Most recent recorded activity. ISO-8601, UTC. |
| logins_30d | integer | Sign-ins in the last 30 days. |
| avg_session_minutes | number | Mean session length in minutes over the last 30 days, to one decimal place. |
| total_session_minutes_30d | integer | Total session minutes over the last 30 days. |
| paying | boolean | True when this contact has ever bought. |
| lifetime_revenue_cents | integer | Total revenue attributed to this contact, in cents. |
- 200 — The student.
- 401 — The token is missing, malformed, invalid, or revoked.
- 404 — No student with that id.
- 429 — Rate limit exceeded for this key.
List what a student has watched
Every lesson with recorded watch time for this student, newest first.
| Name | In | Type | Description |
|---|---|---|---|
| student_id* | path | string (uuid) | The contact id. |
| page | query | integer | Page number, starting at 1. Values below 1 are clamped to 1. |
| page_size | query | integer | Rows per page. Clamped to the 1–100 range. |
curl -X GET 'https://app.zinstruct.com/api/reporting/v1/students/00000000-0000-0000-0000-000000000000/watch-history?page=1&page_size=50' \
-H 'Authorization: Bearer YOUR_API_KEY'One lesson a student has watched, with how much of it.
| Field | Type | Description |
|---|---|---|
| course_id | string | null | The course the lesson belongs to. |
| course_title | string | null | Course title. |
| module_id | string | null | The module the lesson belongs to. |
| module_title | string | null | Module title. |
| lesson_id | string (uuid) | The lesson id. |
| lesson_title | string | Lesson title. |
| lesson_duration_seconds | integer | null | Lesson length in seconds, when known. |
| watched_seconds | integer | Seconds of this lesson the student has watched. |
| watched_pct | number | Watched seconds as a percentage of duration, 0–100. |
| watched_over_30s | boolean | True when watch time exceeds 30 seconds. |
| last_watched_at | string | null | Most recent watch event for this lesson. ISO-8601, UTC. |
- 200 — A page of watch history.
- 401 — The token is missing, malformed, invalid, or revoked.
- 404 — No student with that id.
- 429 — Rate limit exceeded for this key.
Rank lessons by earliest drop-off
Lessons ordered by how early their audience falls away — earliest drop first, larger drops breaking ties. Lessons with no retention data are excluded. This endpoint takes `limit` rather than `page_size`, and always reports page 1.
| Name | In | Type | Description |
|---|---|---|---|
| sort | query | string | Only `worst_retention` is supported. Any other value returns 400. |
| limit | query | integer | How many lessons to return. Clamped to the 1–100 range. |
curl -X GET 'https://app.zinstruct.com/api/reporting/v1/lessons?sort=worst_retention&limit=10' \
-H 'Authorization: Bearer YOUR_API_KEY'A lesson ranked by how early its audience falls away.
| Field | Type | Description |
|---|---|---|
| lesson_id | string (uuid) | The lesson id. |
| title | string | Lesson title. |
| biggest_dropoff | Dropoff |
- 200 — The ranked lessons.
- 400 — An unsupported `sort` value.
- 401 — The token is missing, malformed, invalid, or revoked.
- 429 — Rate limit exceeded for this key.
Get a lesson’s retention curve
The full drop-off curve for one lesson, plus the single biggest fall.
| Name | In | Type | Description |
|---|---|---|---|
| lesson_id* | path | string (uuid) | The lesson id. |
curl -X GET 'https://app.zinstruct.com/api/reporting/v1/lessons/00000000-0000-0000-0000-000000000000/retention' \
-H 'Authorization: Bearer YOUR_API_KEY'Video drop-off for one lesson — where viewers stop watching.
| Field | Type | Description |
|---|---|---|
| lesson_id | string (uuid) | The lesson id. |
| title | string | Lesson title. |
| course_id | string | null | The course the lesson belongs to. |
| module_id | string | null | The module the lesson belongs to. |
| duration_seconds | integer | null | Lesson length in seconds, when known. |
| views | integer | Recorded views. |
| avg_watch_seconds | number | Mean seconds watched per view. |
| avg_watch_pct | number | Mean percentage of the lesson watched, 0–100. |
| retention | RetentionPoint[] | The curve, ordered by offset. |
| biggest_dropoff | Dropoff |
- 200 — The curve.
- 401 — The token is missing, malformed, invalid, or revoked.
- 404 — No lesson with that id.
- 429 — Rate limit exceeded for this key.
Get the KPI summary
A single flat object of headline numbers for your school — the cheapest call for a dashboard tile.
curl -X GET 'https://app.zinstruct.com/api/reporting/v1/summary' \
-H 'Authorization: Bearer YOUR_API_KEY'One flat object of pre-aggregated KPIs for your school.
| Field | Type | Description |
|---|---|---|
| tenant | string | Your school slug. |
| students | integer | Total contacts. |
| active_students_30d | integer | Distinct contacts with activity in the last 30 days. |
| paying_customers | integer | Contacts who have bought. |
| courses | integer | Course products. |
| modules | integer | Modules across all courses. |
| lessons | integer | Lessons across all modules. |
| logins_30d | integer | Sign-ins in the last 30 days. |
| avg_session_minutes | number | Mean session length in minutes. |
| lessons_watched_over_30s_30d | integer | Lesson views over 30 seconds in the last 30 days. |
| completion_rate_pct | number | Completed enrollments as a percentage of all enrollments, 0–100. |
- 200 — The summary.
- 401 — The token is missing, malformed, invalid, or revoked.
- 429 — Rate limit exceeded for this key.
- 500 — The summary could not be built.
Getting started
Check a key works
Returns `{ "ok": true }` when the key authenticated. The right first call when wiring up an integration: it proves the key and the header are correct without touching any data.
curl -X GET 'https://app.zinstruct.com/api/reporting/v1/health' \
-H 'Authorization: Bearer YOUR_API_KEY'Liveness probe result.
| Field | Type | Description |
|---|---|---|
| ok | boolean | Always true when the key authenticated. |
- 200 — The key is valid.
- 401 — The token is missing, malformed, invalid, or revoked.
- 429 — Rate limit exceeded for this key.