Sutra Admin API Guide
The Sutra Admin API is a server-to-server API for integrations that need to manage Sutra spaces on behalf of a Sutra user. Use it for operational workflows such as syncing members from another system, creating and configuring spaces (general settings, behavior, appearance, images, and paid access), updating plans, setting custom properties, creating page content, uploading media, and preparing broadcasts.
The exact request and response schemas live in openapi.yaml. This guide explains the API model so the endpoint reference is easier to use.
Base URL
https://api.sutra.co/api/admin/v1
All requests and responses are JSON. Requests with a body must send Content-Type: application/json.
Prerequisites
API access is included automatically on Silver and Gold accounts.
Authentication
Direct REST API calls authenticate with a dedicated Sutra API token:
Authorization: Bearer sutra_live_sk_<prefix>_<secret>
If you are using the Sutra MCP server, OAuth is the recommended setup. Hosted MCP at https://mcp.sutra.co/mcp lets compatible AI clients connect with a browser-based Sutra login and consent flow. Local MCP setup can also use OAuth and stores/refreshes credentials locally so you do not need to copy an API token into your MCP client. See mcp-server.md.
Getting Your API Token
- Log in to your Sutra account.
- Go to Settings → Sutra API in your account settings.
- Click Create Token to generate a new API token.
- Copy the token immediately — it is only shown once and cannot be retrieved later.
API access requires Silver or Gold. If you are on a qualifying plan and don't see the API section in your settings, contact support@sutra.co for assistance.
Token Details
API tokens are opaque credentials. Do not send tokens in query parameters.
An API token acts as a Sutra user. In practice, the user is the token creator when one is recorded, otherwise the API application owner. The token does not have a single root space in the product model. By default, it can access every space that this user can manage, including archived and pending (scheduled) spaces.
Sutra can optionally restrict a token to a specific set of spaces. That restriction is only a security limit on the token; it is not how the user's space access is created.
Access Model
The API is intentionally user-centered:
- A user can manage many top-level spaces.
- A token inherits the manageable spaces of that user.
- By default, the token can access every space that user can manage. That includes archived spaces (so archiving is reversible through the API) and pending spaces (spaces scheduled to launch).
- If Sutra restricts a token to specific spaces, the token can access only those spaces after confirming the user can manage them.
- Token space restrictions are exact. Restricting a token to a child space does not include that child's descendants.
- All resource identifiers in requests are public API IDs, not database IDs.
This means most integrations should begin by listing authorized spaces:
GET /spaces
Then use the returned sp_ IDs for follow-up calls.
When the user supplies a space name or Sutra URL, pass the name or extracted URL slug as q to resolve the corresponding sp_ ID.
To list spaces where the token user is a member but not necessarily an admin or manager, use:
GET /me/membership_spaces
This endpoint returns only basic space metadata and the token user's own membership state. It does not grant access to member lists, content, messages, properties, payments, or other admin subresources.
Public IDs
The Admin API uses stable public IDs with readable prefixes:
sp_ space
mem_ member
usr_ user
contact_ CRM contact
inv_ invitation
blk_ content block or message
reply_ reflection
surv_ survey
ques_ survey question
ans_ survey answer
choice_ survey submission choice
plan_ plan
enr_ enrollment
pay_ payment
cpn_ coupon
bcst_ broadcast
auto_ automation
aenr_ automation enrollment
astep_ automation step
mprop_ member property definition
mval_ member property value
sprop_ space property definition
sval_ space property value
shbk_ website header/footer or generic share block
nvbr_ website navbar
media_ media upload or external media reference
Never pass internal numeric IDs to the Admin API.
Scopes
Scopes are domain-specific and usually split into read and write permissions. For example:
spaces:read
spaces:write
members:read
members:write
plans:read
plans:write
Some endpoints touch two domains and require both scopes:
- Member and contact property values require member scopes plus member property scopes.
- Space property values require space scopes plus space property scopes.
- Sending a broadcast requires
broadcasts:send.
See scopes.md for the implemented scope list.
Common Workflows
Sync Members Into a Space
GET /spaces to find the target space.
GET /spaces/{space_id}/members to inspect current members.
POST /spaces/{space_id}/members or POST /spaces/{space_id}/members/bulk to add members.
PATCH /spaces/{space_id}/members/{member_id} to update member role or notification settings.
DELETE /spaces/{space_id}/members/{member_id} or POST /spaces/{space_id}/members/bulk_delete to remove members.
Manage Member Properties
Member properties describe people in the context of a space, such as company, role, or interests. The same definitions apply to members and contacts.
GET /spaces/{space_id}/member_properties to list definitions.
POST /spaces/{space_id}/member_properties to create a definition.
PATCH /spaces/{space_id}/members/{member_id}/properties to set values for one member.
PATCH /spaces/{space_id}/contacts/{contact_id}/properties to set values for one contact.
PATCH /spaces/{space_id}/members/properties/bulk or PATCH /spaces/{space_id}/contacts/properties/bulk to set values for up to 100 people.
- Set a value to
null to clear it.
Contacts are non-member people in the CRM/contact layer. They can be listed, searched, filtered by member-property values, and tagged without enrolling them as members.
GET /spaces/{space_id}/contacts to list contacts.
GET /spaces/{space_id}/contacts/{contact_id} to inspect one contact.
GET /spaces/{space_id}/contacts?property_key=in_person_workshop&property_value=Sweden to filter by a property value.
PATCH /spaces/{space_id}/contacts/properties/bulk to bulk-set contact property values.
Property filters require member_properties:read in addition to members:read.
Manage Space Properties
Space properties describe spaces themselves, especially entries inside lists, showcases, events spaces, courses, or sections. For example, a showcase can define properties such as category, location, date, or featured status for its child spaces.
GET /spaces/{space_id}/space_properties to list definitions owned by a space.
POST /spaces/{space_id}/space_properties to create a definition.
PATCH /spaces/{child_space_id}/properties to set values on a child space.
- Set a value to
null to clear it.
For child spaces inside a list, showcase, events space, course, or section, the value endpoint uses the parent space's property definitions.
Build A Course Structure
Use the child-space endpoints for course, section, module, and page structure. These endpoints create real parent-child relationships and keep the visible space cards in sync with the underlying database relationship.
POST /spaces to create the top-level course.
POST /spaces/{course_id}/children with placement.surface: "auto" to create sections under the course.
POST /spaces/{section_id}/children with placement.surface: "auto" to create modules or pages under a section.
- Use
PUT /spaces/{parent_id}/children/{child_space_id} to attach an existing space under a parent.
- Use
PATCH /spaces/{parent_id}/children/{child_space_id}/placement to move a child between collection and document surfaces or to repair the visible card.
- Use
DELETE /spaces/{parent_id}/children/{child_space_id} to detach the relationship without deleting the child space.
placement.surface: "auto" chooses the right surface for the parent space type. For collection surfaces, position is only visible when the parent uses custom ordering; send set_parent_ordering: "custom_order" when the API should switch the parent to custom order. For document surfaces, use anchor_node_uid with insert: "before", "after", or "inside" to place the child card relative to an existing document node. Do not send both position and anchor_node_uid in the same placement request.
When creating a child whose visible content will be written through the document endpoints, send content_scaffold: "none". content_scaffold: "ui_default" adds starter module content meant for manual editing.
Space configuration is split across four endpoints, mirroring the web app's Settings → General area:
PATCH /spaces/{space_id} — core identity and access: name, description (sanitized HTML), format (type), privacy (open/private), public visibility, member limit (size), registration gates, behavior flags, emoji icon, shareable link (url_handle), and state.
PATCH /spaces/{space_id}/settings — behavioral settings: scheduling (launch/auto-archive/read-only), membership and notification options, content behavior, sidebar and page display, registration display, SEO metadata, members map, Cobolt AI indexing, and list/post display options.
PATCH /spaces/{space_id}/appearance — theme colors, fonts, accent palette, gradients, corner style, density, and hidden layout elements.
POST /spaces/{space_id}/images — set or remove the cover (banner), logo, thumbnail, or social-share (meta) image from a URL.
All of these are partial updates: only the keys you send are written, and explicit null clears nullable fields. GET /spaces/{space_id} echoes everything back, including nested settings, appearance, payment, and website objects (list endpoints stay flat).
Things to know:
- Archiving is a two-phase transition.
PATCH /spaces/{space_id} with state: "archived" answers immediately and then asynchronously ghosts descendants, clears shareable links and redirects, and cancels Stripe subscriptions in the tree. Unarchiving restores descendants but not the cleared links, redirects, or subscriptions. Archived spaces remain reachable by the token, so the transition is reversible.
- Scheduling gates travel with their times. Setting
launch_on_date: true requires launch_time in the same call. While a future launch is scheduled, the space reads as state: "pending", and any settings write re-pends it.
- Some settings are tier-gated. Private privacy, format changes, shareable links, scheduling, completions, auto-tagging, theming, layout, and cover/logo images require the space's top-level account to be on a qualifying plan; failing the gate returns
422 with code upgrade_required and the required permission.
- Appearance cascades. Writing any base theme color also pushes the parent's full current theme — fonts, corner style, density, accents, gradients, and logo — to every descendant space.
- Visibility cascades. Changing
visibility propagates the new value to every descendant space.
Build A Website (Website Mode)
Website mode turns a top-level space into a public website with its own header (a navbar) and footer. It is root-scoped — pass any space the token can manage and the API operates on its website root — and enabling it requires the space's top-level account to be on the Silver plan or above (otherwise 422 upgrade_required).
PATCH /spaces/{space_id}/website with enabled: true turns website mode on. The first enable auto-creates a default header (a navbar) and footer; enabled: false turns it off while preserving the configs, so re-enabling is lossless.
GET /spaces/{space_id}/website reads the current state, including the active header_config_id and footer_config_id (shbk_ share blocks).
- Author the header's navigation through the navbar endpoints —
POST /spaces/{space_id}/navbar_configs, or PATCH /spaces/{space_id}/navbar_configs/{id} on the auto-created one — to set links, logo, title, and alignment. A site_header share block references a navbar through a navbar node (navbarConfigId).
- Author the footer with
PATCH /spaces/{space_id}/share_block_configs/{id} (freeform blocks) on the auto-created site_footer.
- Point the website at a different header or footer with
PATCH /spaces/{space_id}/website and header_config_id / footer_config_id — each must match the site_header / site_footer kind; null clears the pointer.
Header and footer blocks are managed through the share-block endpoints (/spaces/{space_id}/share_block_configs), and a header's link set lives in a navbar (/spaces/{space_id}/navbar_configs). Deleting a share block that is the active header or footer returns 409 (config_in_use) — repoint or disable website mode first. Set show_spaces_in_page: true to render child spaces inline within the page while in website mode. Set header_hidden: true for Funnel mode — website mode with the shared site header hidden site-wide (a sequence of focused landing pages); the header config is preserved, so header_hidden: false toggles back to Website. Display mode maps to Normal (enabled: false), Website (enabled: true), and Funnel (enabled: true + header_hidden: true).
Styling the header. The header bar's background and full-bleed width come from the site_header share block's style, which is shaped { styles: { <css> }, fullWidth: bool, flexLayout, flexGap, flexItemSizing, flexRowAlign, minHeight }. Put CSS under style.styles — top-level CSS keys are ignored — and use only the keys in GET /design/capabilities at nodes.shareblock.appearance.style_keys.styles, so every authored value remains editable in the user-facing UI. Set fullWidth: true for an edge-to-edge bar. For row layouts, use flexItemSizing: "fit" when children should size to their own content rather than equal-width columns, and flexRowAlign: "center" to center wrapped rows of chips or badge pills. The navbar itself uses named style fields (linkSource, alignment, spacing, separator, activeIndicator, fullWidth, showLogo/showTitle, titleText, textColor/hoverColor/activeTextColor, …) — not raw CSS — plus a links array of { id, label, destination: { type: 'space'|'url'|'section', value }, displayAs, target } (set linkSource: 'custom' to use your own links). For a full-viewport hero below the header, set the hero box's minHeight attr (it accepts vh/calc, e.g. calc(100vh - 64px)) rather than min-height in its styles. The full shapes and allowed values are in GET /design/capabilities under website, and navbar/share-block create/update responses echo an advisory warnings array for input the renderer or editor would ignore.
Enable Paid Access (Plans)
Paid access is managed through the plans system. Plan endpoints (under plans:write) manage pricing; the space-level payment gate is a separate endpoint under spaces:write, so plan-only tokens can never flip a space into or out of paid access.
POST /spaces/{space_id}/plans to create at least one active plan. amount is in integer cents (5000 = $50.00); amount_cents is accepted as an alias.
- Connect Stripe in the web app (Settings → Payment) — Stripe onboarding is interactive and cannot be completed through the API.
PATCH /spaces/{space_id}/payment with enabled: true. This validates both preconditions — at least one active plan (no_active_plans) and a charge-ready Stripe account (stripe_not_connected) — then switches the space to payment privacy with plans checkout.
- To turn paid access off, send
enabled: false. While live enrollments exist this returns 409 (live_enrollments) unless you also send force: true, which cancels their Stripe subscriptions. Active plans are soft-deactivated and the space's privacy moves to target_privacy (default private).
The same endpoint manages checkout options: multiple plan selection (requires all active plans to share one currency and one billing family), guest checkout (rejected while a plan has prerequisites), a guest invitation message, and checkout terms.
Changing privacy to or from "payment" through PATCH /spaces/{space_id} is rejected — the payment endpoint owns that transition so paid access is always enabled and torn down safely.
Inspect Plans And Enrollments
Plans describe what can be purchased or enrolled in for a space. Enrollments represent people enrolled in a plan.
GET /spaces/{space_id}/plans to list plans.
GET /plans/{plan_id} to inspect one plan.
GET /plans/{plan_id}/enrollments to list enrollments for that plan.
GET /enrollments/{enrollment_id} to inspect one enrollment.
Plan mutations may include an advisory warnings array — for example when the space's payment gate is off, or when deactivating the last active plan would leave an enabled paywall with nothing to buy. The write succeeded; the warning tells you what to fix next.
Create A Reflection On A Message
Messages are discussion posts. Reflections are user responses attached to messages.
GET /spaces/{space_id}/messages to list messages.
POST /messages/{message_id}/reflections to create a reflection.
GET /messages/{message_id}/reflections to list reflections on that message.
PATCH /reflections/{reflection_id} or DELETE /reflections/{reflection_id} to update or delete a reflection.
Edit Page Content
Use the document endpoints for visible lesson/page content. The document API writes the Tiptap document and refreshes the legacy content-block mirror internally.
GET /document/capabilities to inspect supported node types and reserved-node policy.
GET /spaces/{space_id}/document to read the document, revision, and content_digest.
POST /spaces/{space_id}/document/nodes to insert one or more nodes.
PATCH /spaces/{space_id}/document/nodes/{node_uid} to update a node.
PATCH /spaces/{space_id}/document/nodes/{node_uid}/placement to move a node.
DELETE /spaces/{space_id}/document/nodes/{node_uid} to remove a node.
Send base_revision on document mutations when you want optimistic concurrency. A base_revision can be either the returned revision or content_digest. Full document replacement with PUT /spaces/{space_id}/document requires base_revision or force: true.
Incremental document endpoints accept response_mode=compact. Compact responses omit the complete document and return the new revision/content_digest together with canonical changed nodes or affected UIDs. The MCP uses this mode by default; call GET /spaces/{space_id}/document when a complete snapshot is needed. Size and node-count limits are enforced against the recomposed document, not only the submitted fragment.
Managed structure and media nodes are reserved. Create or move child space cards through the child-space placement endpoints, and create media nodes through the media endpoints.
For special blocks with editor-specific renderer contracts, prefer the first-class special-block endpoints over raw node authoring:
GET /document/special_block_types or GET /spaces/{space_id}/document/special_block_types to inspect supported configs for info_box, icon_text, quote, badge, divider, and contact_form.
POST /spaces/{space_id}/document/special_blocks with { "type": "...", "config": { ... } } to create one of those blocks.
PATCH or DELETE /spaces/{space_id}/document/special_blocks/{node_uid} to update or remove an existing special block.
The same special-block endpoints are available under /spaces/{space_id}/registration_page/... for the editable registration page draft. Contact forms can bind custom fields to member properties; the space-scoped special_block_types response includes compatible bindings.
Edit A Registration Page
Registration pages use a draft/live workflow. Draft writes update editable_registration_page; visitors keep seeing the current live registration_page until you publish.
GET /spaces/{space_id}/registration_page to read the draft and published documents, including each revision and content_digest.
PUT /spaces/{space_id}/registration_page to replace the draft document, or use /registration_page/nodes endpoints to insert, update, move, or delete individual draft nodes.
POST /spaces/{space_id}/registration_page/publish to copy the draft to the live registration page and enable the space's registration gate.
Draft reads and writes require content:read / content:write. Publishing also requires spaces:write because it sets block_until_registered: true. Like document endpoints, full draft replacement requires base_revision or force: true, and base_revision can be either the draft revision or content_digest.
Use media endpoints for local files and external video embeds.
For a local lesson video:
POST /spaces/{space_id}/media_uploads with filename, content_type, size_bytes, and kind: "video".
- Upload the bytes to the returned presigned
upload.url with the returned upload.headers.
POST /media_uploads/{media_id}/complete to verify the object and queue processing. Include the S3 response ETag when available; completion rejects an ETag that does not match the stored object.
GET /media_uploads/{media_id} to poll media_status, playback_url, and thumbnail_url.
For a downloadable PDF or other file, use the same upload flow with kind: "file" and the matching content type. For Loom, YouTube, or Vimeo, use POST /spaces/{space_id}/media_references with the source URL. Media creation can insert a document node automatically; send placement.insert_node: false when you only need the media record.
Prepare And Send A Broadcast
POST /spaces/{space_id}/broadcasts to create a draft.
PATCH /broadcasts/{broadcast_id} to revise it.
POST /broadcasts/{broadcast_id}/send to send it.
GET /broadcasts/{broadcast_id}/delivery_status to inspect delivery progress.
Important Distinctions
Space Update vs. Settings vs. Appearance vs. Payment
PATCH /spaces/{space_id} owns the space's identity and access fields (the columns on the space itself). PATCH /spaces/{space_id}/settings owns behavioral settings (a separate settings record). PATCH /spaces/{space_id}/appearance owns theme and layout. PATCH /spaces/{space_id}/payment owns the paid-access gate and checkout toggles, with POST/PATCH /plans managing the plans themselves.
Two fields are easy to confuse: privacy (open/private/payment — who can join) lives on the space update endpoint, while visibility ("public" or "" — whether the space is publicly listed) is a separate field there too. A private space cannot be publicly visible.
Members vs. Enrollments
Members belong to spaces. Use member endpoints when you want to manage who is in a space.
Enrollments belong to plans. Use enrollment endpoints when you want to understand who has enrolled through a specific plan.
Member Properties vs. Space Properties
Member properties describe people. Values are set on a member in a space.
Space properties describe spaces. Values are set on a space, often a child space inside a parent showcase, list, events space, course, or section.
Messages vs. Reflections
Messages are top-level discussion posts in a space. Reflections are responses attached to a message.
Document Content vs. Content Blocks
The /document endpoints are the primary editing surface for visible page content. The older /content endpoints expose the legacy content-block mirror and are kept as a compatibility surface. New integrations should prefer /document for page content, /media_uploads or /media_references for media, and child-space placement endpoints for space cards.
Use media uploads when Sutra should store and process a local file. Use external media references for URL-only embeds such as Loom, YouTube, or Vimeo. Both surfaces return media_ IDs and can create document nodes, but only uploads return presigned storage instructions.
Spaces vs. Child Spaces
Top-level spaces and child spaces are both returned as spaces. Parent-child relationships are managed through the child endpoints:
GET /spaces/{space_id}/children
POST /spaces/{space_id}/children
PUT /spaces/{space_id}/children/{child_space_id}
PATCH /spaces/{space_id}/children/{child_space_id}/placement
DELETE /spaces/{space_id}/children/{child_space_id}
PATCH /spaces/{space_id}/children/reorder
Creating a child under a parent means the new space is placed in that parent. Attaching an existing child places an existing space under a parent. Detaching removes that parent-child relationship and matching document card, but does not delete the child space. Creating a space without a parent creates a top-level space for the authenticated user.
Admin Spaces vs. Membership Spaces
GET /spaces returns spaces the token user can administer through the Admin API. GET /me/membership_spaces returns a limited inventory of spaces where the token user is a member, including spaces where the user is not an admin. Membership spaces with admin_access: false still return 404 from admin subresource endpoints such as /spaces/{space_id}/members.
Operational Basics
List endpoints are cursor-paginated. Send limit and cursor query parameters. Responses include a pagination object with next_cursor and has_more.
Idempotency
Single-item mutation endpoints accept an optional Idempotency-Key header. Use a unique key per logical operation when retrying requests after network failures.
MCP tools expose the same header as an optional idempotency_key argument on mutating tools. Pass a stable key when retrying an MCP write so the server can replay the original response instead of applying the operation twice.
The header is required for non-atomic or externally side-effecting operations:
DELETE /spaces/{space_id}/members/{member_id}
POST /spaces/{space_id}/members/bulk
POST /spaces/{space_id}/members/bulk_delete
PATCH /spaces/{space_id}/members/properties/bulk
PATCH /spaces/{space_id}/contacts/properties/bulk
POST /spaces/{space_id}/invitations/bulk
POST /broadcasts/{broadcast_id}/send
Idempotency-Key: import-run-2026-04-28-member-123
Rate Limits And Request Size
Responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. A rate-limited request returns 429 with Retry-After.
Standard request bodies are limited to 1 MB. Bulk request bodies are limited to 5 MB.
Errors
Errors use a consistent envelope:
{
"error": {
"code": "validation_failed",
"message": "The request is invalid.",
"request_id": "req_..."
}
}
See errors.md for standard error codes.
MCP Server
The Sutra Admin API ships with an MCP server that lets AI agents (Claude, ChatGPT connectors, Cursor, Windsurf, etc.) interact with the API through the Model Context Protocol. If your AI client supports remote MCP servers, add https://mcp.sutra.co/mcp and sign in with Sutra OAuth. For local stdio clients, run npx -y @sutraspaces/mcp-server; OAuth is recommended, and API-token configuration remains available for headless environments.
Design API
The Design API lets API and MCP clients read a space's rendered Tiptap page, validate proposed design JSON, create previewable drafts, publish with digest and destructive-omission guards, restore from the publish backup, and import external images into Sutra-controlled storage.
Reference Files