Spunto CMS

What it does not do

The gaps, the sharp edges and the settled refusals — stated here so the first curl doesn't have to teach them.

A page that only lists features costs more than no page at all: it wastes the time of whoever believes it, and the first curl corrects it anyway.

So here is the other list. It has three parts, and they are different in kind.

Settled refusals

These are decisions, not gaps. They are not coming.

No GraphQL. REST only, now and later.

No shareable file URLs. No public bucket, no signed links. Why.

No sorting or grouping by an array property. multi_select and multi_person refuse both, with a 400 that says so, rather than ordering by the document order of a JSON array. Why.

No offset pagination. Keyset cursors only, everywhere. Why.

No 403 for a workspace you are not in. You get a 404, and the API will not tell you which of the two it means. Why.

No HTML in page bodies. The block format cannot express it. Why.

Not built yet

These are absences, and they may change. They are listed because building on their absence, or on their imminent arrival, is a choice you should make knowingly.

No draft/published distinction. There is no publication state, no scheduled publishing and no preview régime. A card is either there or archived. If you need drafts today, a select property is the honest answer, and your consumer filters on it.

No full-text search over card bodies. Search covers card titles and list names only. There is no body index, and views cannot filter or sort on a body either.

No calendar view in the interface. The API accepts kind: "calendar" and the view compiler serves it like any other grouped view; the web app renders table, list and board. The data is there, the screen is not.

No automatic index creation. POST …/index-hints tells you which indexes a view wants, and stops there — the job that would apply them does not exist. Details.

No bulk edit past 5 000 cards. The API refuses rather than pretending to be atomic or silently queueing work. Between 200 and 5 000 you must pass expectedCount. Details.

No duplicating a subtree past 200 cards. Duplication is a synchronous, atomic gesture that hands you back the copy; past that it would be an import, and it refuses rather than pretend. Details.

No rate limits. None today. That is a description of the current implementation, not a guarantee — do not build something that requires their absence.

No outbound email. This deployment sends none. Invitations are tokens someone hands over; nothing verifies that an address belongs to whoever typed it. That last part matters if you are reasoning about account security: it is precisely why an external identity is never linked to an account on the strength of a matching email address.

Sharp edges

These are real behaviours that surprise people. They are documented rather than patched, because in each case the alternative was worse.

A select sorts alphabetically, not in the order of its options. A view "by priority" returns critical, high, low, normal. Encode the order into the values if you need it. Details.

Manual order is global to a list, not per board column. Dropping a card at the top of a column also makes it the first row of the table view. Details.

display is replaced wholesale. Saving a table's columns without sending back groupOrder erases the board's column order.

config is replaced wholesale too. Saving a property's options without sending back optionColors erases the colours. To add or recolour a single option, POST …/fields/{fieldId}/options merges instead.

Properties are keyed by key, views reference fieldId. Two names for the same field, used in different places, and mixing them up gets you an "unknown property" 400. Details.

The type a file is served as is not the type it was uploaded as. An SVG comes back as an application/octet-stream download with image: false. Why.

icon: null removes the icon; omitting icon leaves it alone. The same distinction the rest of PATCH uses, and worth knowing before you build a client that sends a whole card back on every keystroke.

coverFileId is a file id, not a URL, and only an image passes. Upload first (POST …/files), then write the id. A file from another workspace, or one not served as an image (an SVG, a PDF), is a 400. null — or "" — removes the cover, omitting it leaves it alone, and removing it does not delete the file. Files.

createdBy is set from the caller, never from the request body. Sending one is not an error — unknown keys are dropped, so you get a 201 and an author that is you. It is null for imports, seeds and anything created before the column existed.

When this page is wrong

It is written by hand, and the API reference is generated from the code. If they disagree, believe the reference — and the gap is a bug in this page.

On this page