Skip to content
The Stacklumen platform is in private alphaSee the products

Your team edits it.Your code reads it.

Terminal’s CMS holds the posts, projects, products and people a site is made of. Editors work on it beside the design, pages bind to it, nothing goes live until it is published, and any front end you build reads the same content through a typed Content API.

  • 16 field kinds
  • Staged publishing
  • Translated fields
  • Typed Content API
Terminal’s CMS editor, open on the Projects collection of Northgate, an architecture practice. On the left, the site’s collections: Projects, marked as having items not yet live, Posts and Team, each with its item count. On the right, the Projects collection’s fields (Title, Summary, Cover image, Category, Opened and Featured), a banner saying how many items are not yet live, and the items as cards, each with its status. A short demo plays: an item is added and arrives as New; it is titled Harbour Library, its address is set to harbour-library so it publishes at /projects/harbour-library, it gets a summary, a cover image from the library with its alt text, and the category Civic; Publish item makes it Published. Then one word of its summary is edited, it reads Changed, and Publish item makes it Published again.

Model it once. Sixteen kinds of field.

A collection is a list of items with the same fields: a blog, a portfolio, a team, a menu, a set of locations. Start from a preset or a blank one and give it the fields the content needs, each with the rules it should keep.

  • Text A line: names, titles, labels
  • Multi-line text Paragraphs that keep their breaks
  • Rich text Headings, lists, quotes, links, images
  • Image With alt text, from the site’s assets
  • Gallery An ordered set of images, each with alt
  • Link A web address, a page, an anchor or mail
  • File URL A document visitors download
  • Number Prices, counts, ratings
  • Date A day, written in the site’s language
  • Date & time An instant, in the site’s time zone
  • Toggle Yes or no: featured, archived
  • Option One choice from a list you set
  • Multi-option Several choices: tags, services
  • Reference Points at an item in another collection
  • Multi-reference Several items, in the order you choose
  • Colour A hex colour, shown as its swatch

Every field can carry

Required
An item may be saved without it, and is held back at publish until it has one.
Unique
No two items share a value; a repeat is held back at publish.
Default
What a new item starts with.
Help
A line of guidance under the field, for whoever edits it next.
Translated
A value per content language, edited side by side.

Design with real content. Bound, not pasted.

Draw one card, bind its parts to a collection’s fields and make it repeat: the list takes the shape you drew, sorted, filtered, limited or paged, and an item page gives every item an address of its own.

The Terminal designer with a Projects collection. A short demo plays: an item is added to the collection and named Harbor Station, and it appears in the list of projects on the client's Work page; then the list is sorted by year, newest first, limited to two, and given a Load more button, then page links. The Collections panel beside the drawing is real: its sort, direction, limit and pagination controls change the list.

Collections 1 collection

Showing 3 of 3 matching · 3 items in Projects. Drafts are never shown.

Sort by
Direction
6
Pagination

One picker binds anything

Text, images and their sources, links, alt text, backgrounds, attributes, colours, classes and whether an element shows at all, from the item’s own fields or a referenced item’s.

A page for every item

A collection’s template page publishes one page per item at its own address, and fills its search description, share title and share image from the item’s fields.

Empty is a state you design

An empty field shows nothing by default. Keep the design’s placeholder or hide the element instead, add a class when a field is set, and format dates and numbers where they land.

Publish when it’s ready. Not when you save.

Every item says where it stands. Edits to a live item wait for a publish, so a page can change in the CMS all afternoon while visitors, and every production key, keep reading what was live.

  • Publish the site and every new and changed item goes with it.
  • Publish item puts one live for the Content API at once; the site’s pages follow at its next publish.
  • Discard changes puts a changed item back to its published values.
  • Unpublish takes a live item off the Content API at once and marks it Draft.
  • An item that breaks a required or unique field is held back, with the reason.
Draft Held back from every publish
New Saved, not live yet
Published Live as it stands
Changed Live, with edits waiting; visitors see the published copy

One item. Every language it speaks.

Add up to ten content languages and mark the fields that change between them. Each translated field holds a value per language, edited one language at a time, and anything untranslated falls back to the site’s own. The Content API serves every language you add.

Language
The CMS editor in Terminal with one item of a Projects collection, Harbour Library, beside the same item as the Content API serves it. Title and Summary are translated fields; Cover image, Opened and Category are shared across languages. In French the title reads Bibliothèque du Port and the summary is left empty, so the API answers with the French title, the English summary, and localeFallbacks listing summary. A short demo plays: the editor switches to German, where both translated fields are empty and both fall back to English, then to English; then, from the start, to French, where the French title is typed, the item shows Changed, and Publish item makes the production key serve it. The Language switch is a real control: English, French and German change the item and the API answer. Translations are served by the Content API. Pages published from Terminal show the site’s own language.

Headless when you want it. The Content API.

The same collections, read from any front end you build: a site in your own framework, an app, a build step. Typed JSON a page at a time, with a key that lives on your server and never in a browser.

A front end reading Terminal's Content API. On the left, the request for the three most recently opened items of Northgate's Projects collection, written with fetch or with the generated TypeScript client; it sends GET https://www.stacklumen.com/terminal/api/content/v1/collections/projects?sort=opened&order=desc&limit=3 with the key in an Authorization header. On the right, the answer: status 200, its X-Content-Environment, Cache-Control and ETag headers, then the items with their fields and the pagination. A switch above chooses the key. With the production key (cml_live_) the answer holds published items only, as last published: Harbour Library, Larch Street School and Ferry House, 5 in all. With the development key (cml_test_) it holds every item as it is being edited, each labelled with draft and status: the draft Hillcrest Clinic, Harbour Library with a changed summary, and Larch Street School, 6 in all, and it is never cached. A short demo plays: the request is typed and run, the answer streams in, then the key switches to development and back. The switch, the tabs and the Run button are real.
Content key

Published items only, as last published. Cached for 30 seconds. Every item as it is edited, each with its status. Never cached.

const res = await fetch('https://www.stacklumen.com/terminal/api/content/v1/collections/projects?sort=opened&order=desc&limit=3',{headers: {Authorization: `Bearer ${process.env.STACKLUMEN_CONTENT_KEY}`}},);const {items, pagination} = await res.json();// pagination.next: the next page's path, or null on the last
Sends

GET/terminal/api/content/v1/collections/projects?sort=opened&order=desc&limit=3

Authorization: Bearer cml_live_••••••••a41ccml_test_••••••••7e02

Response 200OK Items 1–3 of 5Items 1–3 of 6

X-Content-Environment: productiondevelopment

Cache-Control: private, max-age=30no-store

ETag: W/"9f2c4e1a7b3d5e8f0a6c2b14"W/"3a7d0e5c9b1f4a2e8c6d0b57"

{"collection": {"id": "6c1f0b7e-3a52-4d8e-b0a4-91e2c7d5f318","slug": "projects","name": "Projects","fields": [5 fields]},"items": [{"id": "a3e9c2d4-7f10-4b6a-9e35-0c8d1f2b6a71","slug": "harbour-library","title": "Harbour Library","position": 10,"createdAt": "2026-08-27T10:04:12.000Z","updatedAt": "2026-09-14T09:12:40.000Z","publishedAt": "2026-09-14T09:12:40.000Z","path": "/projects/harbour-library","fields": {"title": "Harbour Library","summary": "A library and reading room on the harbour front.","cover": "https://www.stacklumen.com/cdn/assets/2d7c41e9-5b8a-4f06-a3c1-7e9b0d6f2a58/0b6e3f1a-94c2-4d7e-8a15-c3f9e2d07b64-harbour-library.jpg","category": "Civic","opened": "2026-09-03"},"alts": {"cover": "The reading room’s long window over the harbour at dusk"}},{"id": "5b17e0f8-2c94-4a3d-8f61-d4a0b9c27e05","slug": "larch-street-school","title": "Larch Street School","position": 20,"createdAt": "2026-03-02T14:30:55.000Z","updatedAt": "2026-05-11T08:47:19.000Z","publishedAt": "2026-04-28T16:05:03.000Z","path": "/projects/larch-street-school","fields": {"title": "Larch Street School","summary": "A primary school of three courtyards for 420 pupils.","cover": "https://www.stacklumen.com/cdn/assets/2d7c41e9-5b8a-4f06-a3c1-7e9b0d6f2a58/7d2a95c0-1e6b-4f38-9c07-b5e4a8f13d26-larch-street-school.jpg","category": "Education","opened": "2026-04-20"},"alts": {"cover": "Children crossing the middle courtyard."}},{"id": "e842a1c6-0d3b-4f97-b2e8-6a5c1f0d93b4","slug": "ferry-house","title": "Ferry House","position": 30,"createdAt": "2025-10-15T13:18:31.000Z","updatedAt": "2025-11-20T11:02:44.000Z","publishedAt": "2025-11-20T11:02:44.000Z","path": "/projects/ferry-house","fields": {"title": "Ferry House","summary": "Twenty-four homes around a shared garden by the river.","cover": "https://www.stacklumen.com/cdn/assets/2d7c41e9-5b8a-4f06-a3c1-7e9b0d6f2a58/c19f4e72-3a8d-4b05-a6e1-0f7d2c9b84a3-ferry-house.jpg","category": "Housing","opened": "2025-11-08"},"alts": {"cover": "The garden court in late afternoon."}}],"pagination": {"total": 5,"limit": 3,"offset": 0,"next": "/terminal/api/content/v1/collections/projects?sort=opened&order=desc&limit=3&offset=3"}}
{"collection": {"id": "6c1f0b7e-3a52-4d8e-b0a4-91e2c7d5f318","slug": "projects","name": "Projects","fields": [5 fields]},"items": [{"id": "9c4d7a2b-61e8-4f05-b3a9-2e0f8c71d6a4","slug": "hillcrest-clinic","title": "Hillcrest Clinic","position": 60,"createdAt": "2026-10-16T09:21:07.000Z","updatedAt": "2026-10-21T17:38:52.000Z","publishedAt": null,"path": "/projects/hillcrest-clinic","fields": {"title": "Hillcrest Clinic","summary": "A day clinic set into the slope, lit from above.","cover": "https://www.stacklumen.com/cdn/assets/2d7c41e9-5b8a-4f06-a3c1-7e9b0d6f2a58/4e8b0d13-c6f2-49a7-8d5e-a1b3f7c2e960-hillcrest-clinic.jpg","category": "Health","opened": "2026-10-05"},"draft": true,"status": "draft","alts": {"cover": "The waiting room under its roof lights."}},{"id": "a3e9c2d4-7f10-4b6a-9e35-0c8d1f2b6a71","slug": "harbour-library","title": "Harbour Library","position": 10,"createdAt": "2026-08-27T10:04:12.000Z","updatedAt": "2026-10-21T15:48:02.000Z","publishedAt": "2026-09-14T09:12:40.000Z","path": "/projects/harbour-library","fields": {"title": "Harbour Library","summary": "A library, reading room and harbour archive on the harbour front.","cover": "https://www.stacklumen.com/cdn/assets/2d7c41e9-5b8a-4f06-a3c1-7e9b0d6f2a58/0b6e3f1a-94c2-4d7e-8a15-c3f9e2d07b64-harbour-library.jpg","category": "Civic","opened": "2026-09-03"},"draft": false,"status": "changed","alts": {"cover": "The reading room’s long window over the harbour at dusk"}},{"id": "5b17e0f8-2c94-4a3d-8f61-d4a0b9c27e05","slug": "larch-street-school","title": "Larch Street School","position": 20,"createdAt": "2026-03-02T14:30:55.000Z","updatedAt": "2026-05-11T08:44:06.000Z","publishedAt": "2026-04-28T16:05:03.000Z","path": "/projects/larch-street-school","fields": {"title": "Larch Street School","summary": "A primary school of three courtyards for 420 pupils.","cover": "https://www.stacklumen.com/cdn/assets/2d7c41e9-5b8a-4f06-a3c1-7e9b0d6f2a58/7d2a95c0-1e6b-4f38-9c07-b5e4a8f13d26-larch-street-school.jpg","category": "Education","opened": "2026-04-20"},"draft": false,"status": "published","alts": {"cover": "Children crossing the middle courtyard."}}],"pagination": {"total": 6,"limit": 3,"offset": 0,"next": "/terminal/api/content/v1/collections/projects?sort=opened&order=desc&limit=3&offset=3"}}

The routes

https://www.stacklumen.com/terminal/api/content/v1

  • GET / The site, its languages and time zone, and its collections
  • GET /collections/:collection A page of items: sort, order, filter, locale, expand
  • GET /collections/:collection/items/:item One item, by its address or id
  • GET /types A generated TypeScript module: a type per collection and a client

Two kinds of key

  • cml_live_… Production key Published items only, as they were last published. What the live site should read.
  • cml_test_… Development key Every item as it is being edited, drafts too, each labelled with its status. For previews.

A key reads one site, or every site in the workspace, each request naming its site under /sites/:site. It only ever reads, and one made in the dashboard lasts until you revoke it unless you give it an expiry.

  1. PublishAn item goes live in Terminal
  2. Webhookcollection.item.published, signed
  3. BuildYour front end reads the Content API
  4. LiveThe new content is on your site

This site reads it too. Our blog runs on it.

Every post on stacklumen.com/blog lives in a collection in Terminal’s CMS and is read through the Content API with a production key, the way your own code would. These are the latest, read for this page.

Agents work it too. Over MCP, with your permission.

An agent connected to Terminal can build a collection, fill and publish it, and wire a codebase to it as a headless CMS: the key, the types and the webhook. It signs in as you, does only what you allow, and the key and webhook tools need a permission of their own.

An agent connected to Terminal's MCP server (160 tools), working in an Astro site's repository. Asked to set up a Projects collection from projects.csv and wire the site to read it, the agent calls create_collection (Projects, 6 fields, with an item page at /projects/[slug]), add_items (12 added), publish_items (12 published, none held back), get_content_api (the endpoints and generated TypeScript), create_content_key (a production key, shown once) and create_webhook (collection.item.published goes to the site's deploy hook). Beside the conversation, the repository gains src/lib/stacklumen.ts generated from the types endpoint, a .env file holding the key, which git ignores, and the Projects list and item pages. The agent ends: "Projects is live, the site reads it through the Content API, and a publish rebuilds it." A short demo plays the session; the pause button stops it.
  • create_collection A collection and its fields, with seed items and an item page if you like
  • add_items Up to 500 items in one call, matched by address when you upsert
  • publish_items Put items live, holding back any that break a field’s rules
  • get_content_api The endpoints, the schema and the generated TypeScript for a codebase
  • create_content_key A production or development key, shown once
  • create_webhook A signed webhook, so a front end rebuilds when content changes

Bring your content with you. From a file or a repository.

A CSV file

Fill a collection from a spreadsheet. The preview says what every row will do, counts empty required fields and repeated unique values, and reads lists, dates and times and translations. Publish what you import in the same step, or review it first.

A repository

Import an Astro project from GitHub and its content collections become CMS collections, every item at the address it was published at, with each dynamic route as the collection’s item page, bound to the item’s fields.

The details. For whoever wires it up.

Field kinds
16, from text to multi-reference and gallery
Size
2 collections and 100 items on any site; 20 and 10,000 with the CMS site plan
Item pages
Up to 1,000 per collection, each at its own address
Field settings
Required, unique, default, help, translated
Item states
Draft, New, Published, Changed
Content languages
Translated fields, served with ?locale=
Page size
Up to 100 items a request, with a next link
References
?expand= up to 10 fields, one level deep
Rich text
Markdown and HTML, or parsed blocks with ?richtext=blocks
Caching
ETag on every answer; send If-None-Match for an empty 304
Rate
600 requests a minute per key
Keys
Production or development, for one site or every site; never expiring, or 1 year, 90 or 30 days
Webhooks
Signed with HMAC-SHA256, retried for up to 72 hours
Browsers
No CORS: keys stay on servers and in builds

Collections in the docs · The Content API in the docs

Questions, answered.

Do I need a developer to use the CMS?

No. Collections, items, publishing and binding content to a design all happen in Terminal’s designer and CMS editor. The Content API is there for teams that also want to read the same content from code of their own.

Can one collection fill pages in Terminal and a front end we host ourselves?

Yes. A collection is one set of items: lists and item pages built in Terminal read it when the site publishes, and a front end elsewhere reads the same items through the Content API with a key.

Does publishing an edit need a deploy?

Not for a site published from Terminal: publishing the site publishes its new and changed items with it. A front end you host reads items through the API the moment they are published, one at a time if you like, and a webhook tells its build when something changes.

What does a visitor see while an item is being edited?

The published copy. Edits to a live item wait for a publish, so the item is marked Changed and the site, and every production key, keep the version that was live until you publish or discard the changes.

Where do keys come from, and who can make them?

From a site’s Content → Content API page in Terminal, or Agents & API. Owners and admins make, rotate and revoke them, and every owner and admin is told when one is made. A key is shown once and stored hashed, and it can only read.

Can I move content in from somewhere else?

Yes. A CSV file fills a collection after a preview that counts every row, and a repository import turns an Astro project’s content collections into CMS collections with their item pages.

Put the content in one place. Read it from anywhere.