When to Use
Explain API-authoring a composition vs UI authoring: the entry’s ui (zlib node tree) + data_sources shape, the six binding types, when this path fits. On-ramp before author-composition-via-api.
Use when the user asks “how do I create a composition via API”, “can I bulk-import compositions”, “what does Studio store in the entry”, “what’s the JSON shape of a composition”, or before invoking author-composition-via-api without context. Concept only: for the actual API authoring run author-composition-via-api. Do NOT use for UI authoring (use build-section / build-connected-template).
Auth preflight: settle the credential before the first API call. Resolve it OAuth-first per authenticate-cma: CS_OAUTH_ACCESS_TOKEN, else the Contentstack MCP’s stored session. Never ask the user for a session authtoken. If nothing resolves, or a refresh fails with 400 invalid_refresh_token, hand them ! CONTENTSTACK_REGION=<code> npx @contentstack/mcp --auth (it needs a TTY and a browser, so it cannot be run for them) and wait. 403 error_code 316 is a valid credential aimed at another org: fix the org or the api_key, do not re-authenticate.
What API-authoring a Composition Means
Two Ways to Author a Composition
Compositions live as entries in a Contentstack content type (the project’s compositions CT, typically <project>_compositions). There are two paths to create those entries:
| Path | Tool | When to use |
|---|---|---|
| UI authoring (default) | Studio canvas: drag, drop, bind via Data Picker, Save, Deploy | Every author-driven workflow. Authors use it. Engineers use it for ad hoc composition. |
| API authoring (headless) | CMA POST /v3/.../entries with a hand-built ui payload | Seed pipelines, scripted provisioning, automated migration, debugging via diff, bulk import |
API authoring is NOT a replacement for the UI. The Data Picker handles every shape the API path describes. Reach for the API only when scripting at scale or seeding fixtures.
What’s in the Composition Entry
The compositions CT has 13 fields (full schema in provision-studio-project). The two that carry the composition itself:
- ui: zlib:<base64>-encoded composition node tree. The whole layout (page, sections, components, repeaters, condition blocks, section slots) lives here as one inflated JSON tree.
- data_sources: JSON object holding resolvedReferences (which fields to populate via CDA ?include[]=), the static-value map, and other resolution metadata.
Other fields (composable_uid, url, linked_schemas, linked_sections, url_metadata, etc.) are CT-level routing/metadata. The actual composition is in ui + data_sources.
The Six Binding Types
Studio’s Data Picker emits one of six binding shapes per bound prop, and these are the six an API author writes by hand:
| Type | Where the value comes from | Common use |
|---|---|---|
| template | The template’s preview entry (the page-level CT) | entry.title, entry.hero.headline |
| repeater | The current Repeater iteration item | inside a Repeater iterating entry.related_posts |
| contentstack | A Pinned Entry on the template (Freeform must be on) | dataSources.contentstack.<uid>.title |
| contentstack_queries | A Pinned Query on the template (Freeform must be on) | a Repeater sourced from dataSources.contentstack_queries.<uid> |
| static_value | A literal value baked into the composition | Labels, button text the author types once |
| component_props | A Section’s exposed prop (passed in from a Template) | Section’s “Card Title” override |
API-authored bindings live at props.<propName>.binding on each node.
The Node Anatomy
Each node in the ui tree has:
- type: registered component UID (page, section, repeater, condition-block, section-slot, or a custom registered component)
- props: bindings + static values + exposed-prop metadata
- slots: { <slot_uid>: [<child_node>, ...] } for children. Every slot must have a non-empty array: empty slots crash the renderer (see troubleshoot-canvas).
- metadata: mode (preview/design), condition, sectionBindingOverride, etc.
When API-authoring is the Right Choice
| Task | Right path |
|---|---|
| Seed compositions for a test environment | API authoring (author-composition-via-api) |
| Migrate an existing hand-coded marketing site | UI authoring (Studio canvas + build-section / build-connected-template) |
| Bulk-import 200 product detail compositions from a CSV | API authoring |
| Author a Hero Strip section that authors will tweak | UI authoring |
| Diff “what Studio writes” against “what my SDK reads” | API authoring (decode + inspect) |
| Auto-provision a Studio project for a new test stack | API authoring + provision-studio-project |
The rule of thumb: API path for machines doing it at scale or in pipelines, UI path for humans composing pages.
What You’ll Need to Know Before API Authoring
- The six binding types and their shapes (above).
- The node anatomy: props.<prop>.binding for bindings, slots: { <uid>: [...] } for children, metadata for mode/condition.
- data_sources.resolvedReferences: what to populate via CDA ?include[]= so reference fields come back as full entries, not stubs.
- The zlib:<base64> encoding: both for writing and for debugging (decode + walk to diagnose).
- The composable_uid contract: equals the CMA entry uid for runtime lookup.
- The pre-publish preflight: every slot’s children array non-empty, no orphan descendant references, Repeater metadata.mode: "preview", Condition Block sibling on reference/modular-block iteration.
What This Skill is NOT
- Not the recipe. For that, run author-composition-via-api.
- Not for UI authoring. Use build-section, build-connected-template, build-repeating-section.
- Not for provisioning the compositions CT itself. That’s provision-studio-project.
- Not for diagnosing a misbehaving composition. That’s troubleshoot router.