# Routes | Route | Screen | Query | API | |---|---|---|---| | /settings | Settings (Version, Media card, Media cleanup, Retention) | none | GET/PATCH /v1/media/purge-settings, GET/PATCH /v1/settings | | /settings/media/variants | Image variants (admin) | none | GET/PATCH/DELETE /v1/image-variants, POST /v1/image-variants/{name}/purge | | /settings/media/cache | Cache (admin) | none | GET /v1/image-variants/cache, POST /v1/image-variants/cache/purge {scope: all|unused|variant|image} | | /media | Media library | page, size, q, filter, sort, view | GET /v1/media/?page=&size=&q=&filter=&variants=&sort= (page, size; total in the `X-Total-Count` header) | | /media (detail) | Variants tab | none | GET /v1/media/{id}/variants, POST and DELETE /v1/media/{id}/variants/{name} | | /articles | Articles list | page, size, tab, q | GET /v1/article/?page=&size=&q=&status= (page, size; total in the `X-Total-Count` header; array body unchanged) | | /activity | Activity | q, actor, category, outcome, security, range, from, to, page, size | GET /v1/activity (page, size), GET /v1/activity/export (shipped, CSV) | | /integration | Integration | none | none | | /settings/sites | Sites (owner only; other roles get a 403 page and no sidebar entry): list, add and edit slide-over, delete with reassignment. The key is read-only after creation | none | GET/POST /v1/site/, PATCH/DELETE /v1/site/{id} (DELETE takes `reassign_to`) | | /authors | Authors, all roles. Owner and admin: list, search, add and edit slide-over, delete with reassignment. Other roles: read-only list and detail | q, page, size | GET/POST /v1/author/, PATCH/DELETE /v1/author/{id}, POST /v1/author/me | | /articles | Articles list gains Authors column; Site column and filter when more than one site | site | GET /v1/article/?site={key} | | /article/:id/edit | Editor gains the "Site and authors" section (Site select and authors, any role that can edit the article) | none | PATCH /v1/article/{id} {site_id, author_ids[]} | | /profile | Profile gains the "Author profile" card, all roles | none | POST /v1/author/me | | /integration/api-keys | API keys | none | GET/POST /v1/api-key, DELETE /v1/api-key/{id} | | /schema | Schema list, custom and built-in types (owner or admin; others get a 403 page and no sidebar entry). Available now | none | GET /v1/content-type | | /schema/new | Schema builder, new type (owner or admin). Available now | none | POST /v1/content-type | | /schema/:key | Schema builder for a custom type, read-only view for a built-in (owner or admin). Review step on save when the type has entries. Available now | none | GET/PUT/DELETE /v1/content-type/{key} | | /content/:key | Entry list (any verified user). Available now | page, size, sort | GET /v1/content-type/{key}, GET /v1/content-type/{key}/entry?page=&size=&sort= | | /content/:key/new | New entry (any verified user). Available now | none | POST /v1/content-type/{key}/entry | | /content/:key/:id | Entry editor (any verified user). Available now | none | GET/PUT/DELETE /v1/content-type/{key}/entry/{id} | | /settings/permissions | Permissions grid (owner or admin). Later | none | not built | ## Pagination in the URL (Articles, Media) `/articles?page=2&size=50` and `/media?page=7&size=50`. Parameter names follow the repo (activity already uses `page` and `size`). Allowed sizes 5, 25, 50, 100. Defaults `page=1`, `size=25`, omitted from the URL when default. Changing search, tab, filter or size resets `page` to 1. `page` above the last page redirects (replace) to the last page and shows "That page doesn't exist. Showing the last page." An invalid `size` falls back to 25. **API:** `GET /v1/article/` accepts `page` (default 1) and `size` (5, 25, 50, 100; default 25), as `GET /v1/activity` does. The body stays a JSON array so existing consumers keep working; the total comes back in `X-Total-Count` (the response shape is to be confirmed with the specifier). Anonymous callers only see online articles, and the total counts only those. Invalid `page` or `size` answers 400, the same as Activity. **Media API:** `GET /v1/media/` takes the same `page` and `size`, with the same 400 and the total in `X-Total-Count`; the array body stays. The earlier `cursor` and `limit` parameters are replaced by them. **Page size memory:** the size chosen on Articles, Activity or Media is remembered for the user and used when the URL has no `size`; an explicit `size` in the URL wins. **SPA:** never shows that 400. A `size` outside 5, 25, 50, 100 or a `page` that is not a positive integer is replaced in the URL by the default (`history.replace`), silently. A `page` past the last page is replaced by the last page and the notice "That page doesn't exist. Showing the last page." appears. ## Activity pagination `page` and `size`, allowed sizes 5, 25, 50, 100, default 25 (as implemented). ## Activity export `GET /v1/activity/export`: implemented. CSV only, synchronous (one streamed download, no job, no progress, no later notification). Accepts the list filters q, actor, category, outcome, security, range, from, to. Owner and admin only; others get 403. "Export current view" calls it with the active filters. "Export all activity" calls it with no filters and an optional `from`/`to` range. JSON (`format=json`) is **proposed**: new backend feature. ## Public image request `/media/photo.png?v=`. Unknown or disabled variant: 400 `{ "status": 400, "error": "unknown_variant", "message": "Unknown variant", "detail": "" }`. Any other query parameter: 400 `{ "status": 400, "error": "unsupported_parameter", "message": "Unsupported parameter", "detail": "" }`. Missing source image: 404. ## Sites and authors in the URL `/articles?site=journal` filters by site key; it combines with `page`, `size`, `tab` and `q`, and resets `page` to 1 when it changes. An unknown key is dropped from the URL with no error. `/authors?q=camille&page=2&size=25` follows the same pagination rules as Articles. Deep links: `/authors?edit={slug}` and `/settings/sites?edit={key}` open the slide-over for owner and admin (authors) and the owner (sites); an unknown value shows the info toast "That author no longer exists" or "That site no longer exists". For other roles `/authors?edit={slug}` opens the read-only detail, and `/settings/sites` and its `?edit=` link show the 403 page "You don't have access to Sites". Sites pagination: `/settings/sites?q=&page=&size=` with the same rules as Articles (sizes 5, 25, 50, 100; default 25; defaults omitted; search and size changes reset to page 1; a page past the end falls back to the last page). API: GET /v1/site/?q=&page=&size=, total in `X-Total-Count` (design assumption). Authors: `/authors?q=&page=&size=` as above. ## SEO No new page routes. The panel and dialog are overlays on `/article/:id/edit`: `?panel=seo` (optionally `&field=seo_title|seo_description|slug|cover|authors|body` for the suggestion jump) and `?dialog=seo-api`. `/articles?seo=suggestions` is the Needs attention filter. API: GET /v1/article/{id}/seo, GET /v1/site/{key}/sitemap?page=&size=, PATCH /v1/article/{id} (new fields), PATCH /v1/site/{id} {site_name, title_template, default_description, social_image_id, default_robots}, GET /v1/article/?seo=ready|suggestions|hidden. 401 without a session; 403 owner_only on site SEO fields; 409 slug_taken {suggestion} on PATCH slug; 400 on invalid canonical_url or title_template. ## Custom content types in the URL `/content/events?page=2&size=50&sort=created_at:desc`. `page` and `size` follow Articles (sizes 5, 25, 50, 100; default 25). `sort` is one parameter, `:`, default `updated_at:desc`. Defaults are omitted from the URL; a size or sort change resets `page` to 1; invalid values are replaced silently by the default; a `page` past the end falls back to the last page. The response carries `total`, `page`, `size` and `sort`: use them for the summary ("Showing 1–5 of 37"), the pager and the "37 entries use this content type" line of the review step. No filter or search on custom fields yet. Auth: `/schema*` owner or admin (403 page otherwise), `/content*` any verified user (unverified accounts are redirected to /register/pending). Unknown type: 404 content_type_not_found page. Unknown entry: 404 entry_not_found page.