Organization profile, members, configuration, audit and credentials.
Get the organization
GET/organization
The API key's organization, with headline usage numbers. WARNING: usage.revenue is a size signal, not a quotable amount — it sums the minor units of every currency the org sells in without converting them. Never state it as money; read listSales (revenueByCurrency) or getPayouts instead.
Responses
200Get the organization.
401Missing, invalid or revoked API key.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/organization \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"cover": "string",
"id": "string",
"name": "string",
"requireCheckoutTaxId": true,
"slug": "string",
"usage": {
"contents": 0,
"enrollments": 0,
"revenue": 0
}
}
}
Update the organization
PATCH/organization
Updates the org's public profile — name, bio, logo, storefront cover — and the checkout tax-id requirement. Only the fields you send are changed. The slug is NOT editable here BY DESIGN: it is the org's subdomain identity ({slug} on the root domain), so renaming it breaks every existing link, and it can only be changed from the dashboard. Images must be an https URL or a previously uploaded avatar path.
Request body
biostring | null
Free-text about section. Null (or empty) clears it.
coverstring | null
Banner image on the org's public storefront. An https URL or an uploaded avatar path. Null clears it.
logostring | null
An https URL or an uploaded avatar path. Null clears it.
requireCheckoutTaxIdboolean
When true, buyers must enter a tax id to complete checkout. Turning it on adds friction to every purchase — usually driven by the org's invoicing obligations.
Responses
200Update the organization.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/organization \
--request PATCH \
--header 'Authorization: Bearer cr_live_…' \
--header 'Content-Type: application/json' \
--data '{
"bio": "string",
"cover": "string",
"logo": "string",
"name": "string",
"requireCheckoutTaxId": true
}'
{
"data": {
"bio": "string",
"cover": "string",
"id": "string",
"logo": "string",
"name": "string",
"requireCheckoutTaxId": true,
"slug": "string"
}
}
Get organization minor-access settings
GET/organization/minor-access
Responses
200Get organization minor-access settings.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/organization/minor-access \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"enabled": true,
"policyReviewedAt": "2026-01-31T12:00:00.000Z",
"safetyContactEmail": "learner@example.com"
}
}
Update organization minor-access settings
PATCH/organization/minor-access
Request body
policyReviewedbooleanrequired
safetyContactEmailemail | nullrequired
Responses
200Update organization minor-access settings.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/organization/minor-access \
--request PATCH \
--header 'Authorization: Bearer cr_live_…' \
--header 'Content-Type: application/json' \
--data '{
"enabled": true,
"policyReviewed": true,
"safetyContactEmail": "learner@example.com"
}'
{
"data": {
"enabled": true,
"policyReviewedAt": "2026-01-31T12:00:00.000Z",
"safetyContactEmail": "learner@example.com"
}
}
Get the legal seller profile
GET/organization/seller-profile
Returns the legal identity and consumer-support details published to buyers. A paid offer cannot sell until every required field is complete.
Responses
200Get the legal seller profile.
401Missing, invalid or revoked API key.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/organization/seller-profile \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"address": {
"city": "string",
"country": "string",
"line1": "string",
"postalCode": "string",
"region": "string",
"line2": "string"
},
"legalName": "string",
"ready": true,
"reviewedAt": "2026-01-31T12:00:00.000Z",
"supportEmail": "learner@example.com",
"supportPhone": "string",
"supportUrl": "string",
"taxId": "string",
"taxIdType": "br_cpf"
}
}
Update the legal seller profile
PUT/organization/seller-profile
Stores the school's country-aware legal seller identity. Public identifiers are disclosed before checkout and snapshotted on each purchase; US EIN remains private and individual SSN/ITIN verification stays with Stripe.
Request body
supportEmailemailrequired
taxIdstring | nullrequired
taxIdTypeenumrequired
br_cpfbr_cnpjus_einus_individualeu_vatnational
supportPhonestring | null
Responses
200Update the legal seller profile.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/organization/seller-profile \
--request PUT \
--header 'Authorization: Bearer cr_live_…' \
--header 'Content-Type: application/json' \
--data '{
"address": {
"city": "string",
"country": "string",
"line1": "string",
"postalCode": "string",
"region": "string",
"line2": "string"
},
"legalName": "string",
"supportEmail": "learner@example.com",
"taxId": "string",
"taxIdType": "br_cpf",
"supportPhone": "string",
"supportUrl": "https://example.com"
}'
{
"data": {
"address": {
"city": "string",
"country": "string",
"line1": "string",
"postalCode": "string",
"region": "string",
"line2": "string"
},
"legalName": "string",
"ready": true,
"reviewedAt": "2026-01-31T12:00:00.000Z",
"supportEmail": "learner@example.com",
"supportPhone": "string",
"supportUrl": "string",
"taxId": "string",
"taxIdType": "br_cpf"
}
}
List staff members
GET/members
The org's staff directory — every member with their org-level role. List-only: inviting or removing members happens in the dashboard (membership is managed by the auth layer, not this API).
Responses
200List staff members.
401Missing, invalid or revoked API key.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/members \
--header 'Authorization: Bearer cr_live_…'
{
"data": [
{
"email": "learner@example.com",
"name": "string",
"role": "owner",
"userId": "string"
}
]
}
Get the organization's shape
GET/organization/shape
Live signals describing what KIND of organization this is — how many members, teams and courses it has, whether payments are connected, and whether it is still a solo operation. Everything is derived from current data, never stored, so it cannot go stale. Useful as a cheap first call to orient yourself before deciding what to suggest or do: a solo org with no payments connected needs very different help from a ten-person business.
Responses
200Get the organization's shape.
401Missing, invalid or revoked API key.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/organization/shape \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"contentCount": 0,
"memberCount": 0,
"paymentsConnected": true,
"pendingInvites": 0,
"solo": true,
"teamCount": 0
}
}
Resolve a human reference to an id
GET/resolve
Given a human name, slug, or email, get the id that other tools need — CALL THIS FIRST, before any tool that takes an id you do not already have. It replaces brute-forcing the list endpoints: one call searches the organization's content (title + slug), learners and staff members (name + email), teams, tags, and offers, and returns a flat ranked list of candidates with exact and prefix matches first.
Scoped to the caller's organization; it never reaches across orgs. Results are a disambiguation aid, not full records — take the id and call the resource's own GET for detail. When several candidates come back and the choice is not obvious, ask the user which one they meant rather than guessing; when nothing comes back, the thing does not exist under that name — do not invent an id.
type narrows the search to a single kind, which is both faster and less ambiguous when you already know what you are looking for (e.g. type=team for "the Backend team"). Note that cohort is searched ONLY when you pass type=cohort: cohorts live per offered root, so they cost a bounded scan of the organization's recent content and may miss cohorts on older ones — fall back to listContentCohorts for an exhaustive list.
Query parameters
qstringrequired
The human reference to look up: a content title or slug, a person's name or email, a team/tag/offer/cohort name. Matching is case-insensitive and partial.
limitinteger
Maximum candidates to return. Keep it small — this is for disambiguation.
typeenum
Restrict to one kind of resource. Omit to search every kind except cohort.
contentlearnermemberteamcohortoffer
Responses
200Resolve a human reference to an id.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/resolve \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"matches": [
{
"href": "string",
"id": "string",
"label": "string",
"sublabel": "string",
"type": "content"
}
],
"query": "string",
"truncated": true
}
}
Get the custom domain
GET/organization/custom-domain
The org's custom-domain status. While unverified, dns lists the records to add at your DNS provider. The {slug}.cursare.com subdomain keeps working either way.
Responses
200Get the custom domain.
401Missing, invalid or revoked API key.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/organization/custom-domain \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"dns": {
"cnameHost": "string",
"cnameTarget": "string",
"ttl": "string",
"txtHost": "string",
"txtValue": "string"
},
"domain": "string",
"verified": true
}
}
Set the custom domain
PUT/organization/custom-domain
Connect (or replace) the org's custom domain. The domain is normalized and validated — platform subdomains and domains already connected to another organization are rejected. Returns the CNAME and ownership TXT records to add (use the suggested TTL), then call verify. Connecting a custom domain never removes the org's cursare.com subdomain.
Request body
domainstringrequired
The domain to connect, e.g. courses.example.com.
Responses
200Set the custom domain.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/organization/custom-domain \
--request PUT \
--header 'Authorization: Bearer cr_live_…' \
--header 'Content-Type: application/json' \
--data '{
"domain": "string"
}'
{
"data": {
"dns": {
"cnameHost": "string",
"cnameTarget": "string",
"ttl": "string",
"txtHost": "string",
"txtValue": "string"
},
"domain": "string",
"verified": true
}
}
Disconnect the custom domain
DELETE/organization/custom-domain
Removes the custom domain entirely. Learners keep reaching the org at its {slug}.cursare.com subdomain.
Responses
200Disconnect the custom domain.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/organization/custom-domain \
--request DELETE \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"deleted": true
}
}
Verify the custom domain
POST/organization/custom-domain/verify
Performs a live DNS lookup of the _cursare-verify TXT record (counts against the write rate limit). On a match the domain is marked verified and registered for TLS + routing. On a mismatch this returns 400 — DNS records can take a few minutes (up to the record's TTL) to propagate, so retry later.
Responses
200Verify the custom domain.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/organization/custom-domain/verify \
--request POST \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"verified": true
}
}
List audit events
GET/audit-events
The organization's administrative trail, newest first — who did what, when. This is what answers “what changed in my org this week?”: course publishes and deletions, price and offer changes, learner grants and removals, refunds, team and member changes, integration connects and disconnects. Org-admin only. Read-only: events are written by the actions themselves and cannot be edited through the API. Use the export endpoint when the requested period is outside recent history.
Query parameters
cursorstring
Opaque continuation cursor returned by the previous page. Do not construct or modify it.
limitinteger
How many events to return, newest first. Applied AFTER since/until, so a full page back means the window holds more — narrow it and read again.
sincedate-time
Only events at or after this instant — this is how you scope to a period (since = 7 days ago answers “what changed this week?”). Omit for the whole trail.
untildate-time
Only events at or before this instant.
Responses
200List audit events.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/audit-events \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"items": [
{
"action": "string",
"actorName": "string",
"actorUserId": "string",
"createdAt": "2026-01-31T12:00:00.000Z",
"detail": {},
"id": "string",
"targetId": "string"
}
],
"nextCursor": "string"
}
}
Export audit history
GET/audit-events/export
Streams the authorized organization's verified audit history as JSON Lines for a required date range. The stream merges archived and recent events exactly once and never exposes private storage paths, provider URLs, credentials, actor profile snapshots, or secrets. Organization owner or admin only.
Query parameters
sincedate-timerequired
Inclusive UTC start instant for the required export range.
untildate-timerequired
Exclusive UTC end instant for the required export range.
Responses
200Export audit history.
400Validation failed or the request body is malformed.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/audit-events/export \
--header 'Authorization: Bearer cr_live_…'
List API keys
GET/api-keys
The org's live (non-revoked) developer keys — never the secret itself, only its prefix, name and last-used time. Use it to audit which credentials exist and which have gone stale. To tell which row is the credential you are calling with, match prefix against the first 12 characters of your own bearer token; revoking that one cuts off the current session (see revokeApiKey). If you authenticated with an OAuth access token rather than a cr_live_… key, none of these rows is yours and revoking any of them cannot lock you out. New keys are minted in the dashboard (Settings → Developer → API keys) on purpose: a key grants full org-admin authority and outlives the session that created it, so it is not something an assistant issues.
Responses
200List API keys.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/api-keys \
--header 'Authorization: Bearer cr_live_…'
{
"data": [
{
"createdAt": "2026-01-31T12:00:00.000Z",
"expiresAt": "2026-01-31T12:00:00.000Z",
"id": "string",
"lastUsedAt": "2026-01-31T12:00:00.000Z",
"name": "string",
"prefix": "string"
}
]
}
Revoke an API key
DELETE/api-keys/{id}
Immediately and permanently disables the key — every integration still using it starts getting 401s on the next request. There is no un-revoke. Confirm with the operator which key is safe to kill (check lastUsedAt from listApiKeys) before calling this; revoking the key that authenticates the current session will end it.
Path parameters
idstring
The API key id (from listApiKeys) — not the secret.
Responses
200Revoke an API key.
401Missing, invalid or revoked API key.
403The key's organization does not own this resource.
404The resource does not exist (or belongs to another org).
429Rate limit exceeded (240 reads/min, 60 writes/min per key).
500Unexpected server error.
curl https://api.cursare.com/v1/api-keys/{id} \
--request DELETE \
--header 'Authorization: Bearer cr_live_…'
{
"data": {
"id": "string",
"revoked": true
}
}