Skip to main content
Content and access work together. Content defines what exists. Access rules decide who can see it.

Admin content tree

Admin routes are scoped to a team and hub:
Use content nodes for libraries, folders, modules, lessons, articles, videos, and other structured member content.

Trash and restore

DELETE soft-deletes a node. To list the hub’s deleted nodes, add filter[deleted]=true to the list route. Results come newest deletion first, each with deleted_at set, with the same page[size]/page[after] cursor pagination as the live list. A child deleted together with its parent is not listed separately, because restoring the parent brings it back. Any other filter[deleted] value is a 422. POST .../content/{node_id}/restore brings a deleted node back unpublished (published_at: null), together with the children deleted with it, and places it after its parent’s live children. If the parent is still deleted, the restore returns 409 parent_deleted with meta.parent_id and changes nothing; restore the parent first. Restoring a node that is not deleted returns 422 node_not_deleted.

Member content

Members read content through hub routes:
Progress is member-specific and can feed segments, email, automations, and access decisions. Marking a node complete is one-way: once a member’s progress record reports is_completed: true, no later progress write — single or bulk — can set it back to false. A save that sends false on an already-completed node is accepted but ignored, and completed_at stays fixed at the first time it became true. There is no endpoint to reset a member’s completion; the progress record only goes away if the underlying content node, contact, or hub itself is deleted.

Publishing and visibility

There is no separate “publish” verb for a content node — a node becomes member-visible when both are true:
  1. published_at is set at or before now. A node with published_at unset (draft) or in the future (scheduled) is hidden from GET /api/v1/hub/{hub_id}/content. Set it with mio content create … --published-at <RFC3339> or mio content update <id> --published-at <RFC3339>.
  2. privacy permits the viewer — public, members, or paid (mio content create … --privacy members).
So mio content create --hub hub_abc123 --title "Welcome" --node-type lesson --parent-id cnt_xyz --privacy public --published-at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" yields an immediately member-visible node (a lesson needs a container parent: a root node must be a container, or the API answers 422).

Content nodes vs. media playlists

A hub has two independent member-content surfaces, and it’s easy to conflate them:
  • Content nodes (this guide) — GET /api/v1/hub/{hub_id}/content, managed with mio content ….
  • Media playlists — the hub_media surface at GET /api/v1/hub/{hub_id}/playlists, published with mio media hub-playlists publish (see the media workflow guide), not mio content.
A hub page (homepage, /content, etc.) built in the page builder renders whichever data source its content grid is bound to. A grid bound to dataSource: { "type": "hub_playlists" } shows only media playlists — that’s why mio content create nodes don’t appear on a playlist-bound page even when published. To surface content nodes on a page, bind that page’s grid to the content-node source; to fill a playlist-bound page, publish media playlists.

Access rules

Access rules are hub-scoped:
A rule’s target_type is node (a page-tree node), section or playlist. Those are the only targets a member’s request is checked against, so any other value, content_node and hub included, is a 422 on /data/attributes/target_type and nothing is stored. Use access checks when a frontend needs a direct “can this member see this target?” answer.

Segment conditions and hub staff

An in_segment condition matches only a segment of the hub’s own team. A rule that names another team’s segment matches nobody, and the access-rule API refuses to create one (422). Hub owners, admins and moderators pass an in_segment condition on page and Lessons reads even when they are not in the segment, so they can review what the segment’s members see. That covers the resolved page tree (GET /api/v1/hub/{hub_id}/pages/{page_id}?resolve=true), page sections, access-check, content nodes and their lists, and the playlist, media, search, playback and download routes for that content. It needs an active, unbanned membership of that hub with one of those roles, and the contact must still belong to the hub’s team. A soft-banned, left or banned owner, admin or moderator gets the same answer as any member outside the segment. This is visibility only. They count as matching the segment condition and nothing more: on a rule that needs every condition (all), a paid (has_entitlement) or drip (past_drip_date) condition still applies to them, while on a rule that needs any one condition (any) the segment condition alone lets them in, as it does for a member in the segment. They don’t join the segment, they aren’t added to notifications or realtime streams for it, and it doesn’t let them comment on or react to that content, record progress or save it: those requests get 403 unless they qualify the ordinary way. Discussions spaces are not covered here; a moderator reaches a restricted space only through the space’s moderator setting (see Community workflow).