JSON API
Every resource endpoint negotiates content: send
Accept: application/json and the same handler that renders HTML returns
JSON — same auth, same permissions, same policies, same validation. There
is no separate API layer to keep in sync.
Listing
GET /admin/posts?posts_page=2&posts_per=20&posts_q=go&posts_sort=-ID
Accept: application/json{ "items": [ { "ID": 21, "Title": "…" } ], "total": 137, "page": 2, "per_page": 20 }Query parameters are namespaced by resource slug (posts_page,
posts_per, posts_sort — prefix - for descending, posts_q for quick
search) so several grids can coexist on one URL. Filters use their own
namespaced parameters as shown in the filter panel’s form.
Single record
GET /admin/posts/21
Accept: application/jsonreturns the record as stored (relations included when the grid preloads them).
Schema
GET /admin/posts/_schema describes the form for headless clients:
{
"slug": "posts", "title": "Posts", "key": "ID",
"fields": [
{ "name": "Title", "kind": "text", "label": "Title", "required": true, "rules": "required|max:255" }
]
}Mutations
Create, update, and delete return dcat-admin’s envelope — one shape for every mutation:
POST /admin/posts (form-encoded fields)
PUT /admin/posts/21
DELETE /admin/posts/21 (comma-separate ids for batch: /posts/1,2,3){ "status": true, "data": { "message": "Saved.", "type": "success", "then": { "action": "redirect", "value": "/admin/posts" } } }then.action is one of redirect, location, download, refresh,
script — a hint your client may honor or ignore. Validation failures come
back as 422:
{ "status": false, "errors": { "Title": ["Title is required."] } }Custom actions (POST /admin/posts/_action/{name} with ids=1,2) return
the same envelope; your handlers build it with steward.Success,
steward.Error, steward.Warning, steward.Info and the chainable
Redirect / Refresh / Download / Script / Code helpers.
Authentication & CSRF
JSON requests ride the same session cookie as the browser. State-changing
methods need the CSRF token — read it from the csrf-token meta tag (or
keep a session per client and reuse the token) and send it as
X-CSRF-Token. Unauthenticated JSON requests get 401 with an envelope
rather than a redirect.
Responses set Vary: HX-Request, Accept, so caches never mix the HTML,
fragment, and JSON representations of one URL.