Spunto CMS

Quickstart

From an empty account to reading your own content over the API.

This walks the shortest path: create a workspace, put a card in it, mint an API key, read it back. Everything here is done against https://cms.spunto.net; a self-hosted instance works the same way with its own host.

1. Get an account

Sign in at /login. That page carries both halves — signing in and signing up — and it knows which one this instance allows.

Whether anyone can register is per instance. The code defaults to invitation-only; cms.spunto.net is open. If registration is closed, you need an invitation token minted by an admin of a workspace; the link they send you carries it.

2. Create a workspace and a list

A workspace is the tenant boundary: members, content, properties, API keys and the event log all live inside exactly one. Create one from /app.

Inside it, create a collection — a list of cards. Give it a key prefix like DOC; cards in it will be numbered DOC-1, DOC-2, and so on.

Add a card or two. A card has a title, a body, a status, and whatever properties you declare.

3. Mint an API key

Go to /w/{workspaceId}/settings/api and create a key — you need to be an admin or the owner of that workspace. You will get a token that starts with spk_, shown once: the API only stores its SHA-256, so there is no way to recover it later.

A key carries its own workspace and its own role, both fixed when it is issued. It does not follow the membership of whoever created it: if they leave the workspace, or get demoted, the key keeps doing exactly what it was issued to do until someone revokes it.

Give it the smallest role that works. viewer reads, member writes content, admin also changes the registry.

4. Read your content back

The public API root is https://cms.spunto.net/api. Health first, to check the token plumbing before anything else is in the way:

curl https://cms.spunto.net/api/health

Now list your workspaces with the key:

curl https://cms.spunto.net/api/workspaces \
  -H "Authorization: Bearer $SPUNTO_TOKEN"

Then read a collection — this one call returns the list, its properties, its statuses, its saved views and your role, so a client can render a whole screen without a second round trip:

curl "https://cms.spunto.net/api/workspaces/$WORKSPACE/collections/$COLLECTION" \
  -H "Authorization: Bearer $SPUNTO_TOKEN"

And query its cards. Reading a view is a POST, because a view definition is a filter tree and a tree does not fit in a query string:

curl -X POST \
  "https://cms.spunto.net/api/workspaces/$WORKSPACE/collections/$COLLECTION/query" \
  -H "Authorization: Bearer $SPUNTO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"definition":{"sort":[{"field":{"fieldId":"sys:order"},"direction":"asc"}]},"limit":50}'

You get { "rows": [...], "nextCursor": "..." }. Pass nextCursor back as cursor for the next page; null means you reached the end.

5. Use the typed client instead

Inside a TypeScript project, the generated client is less work and it type-checks your filters against the same specification the server validates them with:

import { createSpuntoCmsClient } from "@spunto-cms/client"

const api = createSpuntoCmsClient({
  baseUrl: "https://cms.spunto.net/api",
  token: process.env.SPUNTO_CMS_TOKEN,
})

const { data, error } = await api.POST(
  "/workspaces/{workspaceId}/collections/{collectionId}/query",
  {
    params: { path: { workspaceId, collectionId } },
    body: { definition: { sort: [] }, limit: 50 },
  },
)

The client is generated, not hand-written

Types come from the OpenAPI specification via openapi-typescript, and requests go through openapi-fetch. Nothing in it is maintained by hand, so it cannot drift from the API.

Where to go next

On this page