Features / Deliver

REST API

Every collection as a JSON endpoint: filter, sort, paginate, resolve references, count facets. ETags, batch lookups and an OpenAPI spec that knows your collections.

The frontend needs the ten newest published posts in one category, with the author resolved and without the body. In RustyCMS that is one GET with four parameters. Every collection you define as a schema gets its endpoints immediately — list and single entry — and the OpenAPI spec knows about them too.

Filters that fit in a URL

GET /api/content/post?category=energy&_status=published
GET /api/content/product?category_in=electronics,tools&price_min=50&subtitle_exists=true
GET /api/content/post?_or=category:energy,category:grid&featured=true
GET /api/content/post?author.name=Meier

Exact match, _prefix, _contains, _min/_max, _in, _ne, _exists. Filters are ANDed, _or adds exactly one OR block. A dot in the field name reaches into nested objects; arrays match as soon as one element does. This is deliberately not a query language: the grammar fits in a table, and every operator behaves the same across all stores — file, SQLite, Postgres.

Sort, paginate, load only what you need

_sort and _order sort; a schema can declare a default. Paginate by page (_page/_per_page) or by _limit/_offset; the response carries total and total_pages, the X-Total-Count header the overall count. _resolve embeds referenced entries, _depth caps the recursion, cycles are detected. A missing reference is marked _missing instead of failing the whole request. _fields=title,author.name returns only what the frontend renders.

Facets that keep a promise

GET /api/content/post/_facets?by=category,featured&_status=published
# → { "category": { "energy": 12, "grid": 7 }, "featured": { "true": 4, "false": 15 } }

Facets count with exactly the filters of the list. The guarantee: the number next to a value is the total of the same list with that filter added. So a facet can be a link — the user clicks “energy (12)” and gets twelve hits.

Fewer round trips, fewer bytes

Single entries carry an ETag; send If-None-Match and the API answers 304 with no body. The batch endpoint bundles up to 50 lookups in one POST, each with its own status and its own permission check — a page with header, footer and three teasers is one request. Validation errors come back with the field attached: “Field is required”, “Value does not match pattern”, “Value must be unique” — never a 500.

A spec that knows your collections

The OpenAPI spec lives at /api-docs/openapi.json, Swagger UI at /swagger-ui. It is built at runtime: the static endpoints plus one set of paths per collection, derived from your schemas. Add a schema and it shows up in the spec. Feed that to a standard generator and you get TypeScript types — the frontend knows at compile time which fields a post has.

Want to see this on your own project?

Tell me briefly what you're planning. I'll show the admin UI on a real installation and say honestly whether it fits.

Request early access