# Recipe: paginate, filter and choose a body format > List articles page by page, search and sort them, and read bodies as markdown, HTML or JSON blocks. `GET /v1/article/` always answers an envelope, never a bare array: ```sh curl "{{BASE_URL}}/v1/article/?format=html&size=5&page=1" \ -H "Authorization: Bearer $RENEWCMS_API_KEY" ``` ```json {"items":[{"id":1,"title":"How we price a bathroom renovation","body":"

What drives the price

Room size, plumbing changes and finish level.

","body_format":"html","is_online":true,"site_id":2,"authors":[],"created_at":"2026-10-23T01:11:16.767761797+02:00","updated_at":"2026-10-23T01:11:24.236138156+02:00","excerpt":"What drives the price of a bathroom renovation.","slug":"how-we-price-a-bathroom-renovation","seo_status":"suggestions","seo_suggestions":3}],"total":1,"page":1,"size":5,"counts":{"all":1,"online":1,"draft":0,"suggestions":1}} ``` In JSON, `<` and `>` are escaped as `<` and `>`; a JSON parser returns the plain characters. ## Walking every page 1. Request `page=1` with the `size` you want (`5`, `25`, `50` or `100`). 2. Read `total` and `size`. The number of pages is `ceil(total / size)`. 3. Request the next pages until `page * size >= total`. A page past the end answers `200` with `items: []`, so an empty `items` also ends the loop. Ordering is applied to the whole list before slicing and ends with the id, so pages never overlap or skip. ## Filters Combine them freely. | Parameter | Example | Effect | | --- | --- | --- | | `q` | `q=grout` | Title contains the text, ignoring case and accents. Up to 200 characters. | | `status` | `status=draft` | `online` or `draft`. | | `site` | `site=renew-blog` | Only that site's articles. | | `seo` | `seo=suggestions` | `ready`, `suggestions` or `hidden`. | | `sort` | `sort=title:asc` | `title`, `status`, `created_at` or `updated_at`, then `:asc` or `:desc`. Default `updated_at:desc`. | `counts` ignores every filter, so tab totals do not move while searching. `total` follows the filters. ```sh curl "{{BASE_URL}}/v1/article/?site=renew-blog&status=draft&sort=title:asc&q=grout" \ -H "Authorization: Bearer $RENEWCMS_API_KEY" ``` ```json {"items":[{"id":2,"title":"Draft about grout","body":"x","body_format":"markdown","is_online":false,"site_id":2,"authors":[],"created_at":"2026-10-23T01:12:24.922895943+02:00","updated_at":"2026-10-23T01:12:24.926728869+02:00","excerpt":"","slug":"draft-about-grout","seo_status":"suggestions","seo_suggestions":4}],"total":1,"page":1,"size":25,"counts":{"all":2,"online":1,"draft":1,"suggestions":2}} ``` ## The `format` parameter | Value | `body` | `body_format` | | --- | --- | --- | | absent or `markdown` | markdown string | `markdown` | | `html` | the stored HTML | `html` | | `json` | array of blocks | `json` | It applies to every article route, including the writes. Request bodies of `POST` and `PATCH` stay HTML whatever the format. An invalid `format` is reported before any other error. ## Invalid values A bad `page`, `size`, `status`, `sort` or `seo` answers `400` with a plain-text body: ``` The request cannot be processed due to a mismatch in the format of the body. ``` Media lists paginate the same way (`GET /v1/media/?page=2&size=50`) with their own `counts`.