Move a page from /v2/api to /api/
Port a site to the new read API one page at a time, while the legacy API keeps serving the rest.
/api/ is the dated successor to /v2/api and /v3/api. You do not have to move everything at once, or at all: the legacy routes stay available and unchanged. Move a page when you want what /api/ adds.
| You get | On /api/ |
|---|---|
| A contract that holds still | every answer is served at a dated version, and Capa-Version pins the one you built against (Versions) |
| Only the fields a page renders | select, with related entries pulled in by name (Choosing fields) |
| Errors you can act on | a code to branch on and a hint that says what to change (Errors) |
| GraphQL | the same reads, typed per key (GraphQL) |
| Keys that do less | cap_ keys limited to reads, or to one model (Keys and scopes) |
| Typed code | the SDK and its codegen |
1. Keep your key
Your legacy key (pk_, sk_ or unprefixed) works on /api/ too, so the first ported page needs no new key. Send it in x-api-key as you do today.
When you want a key that does less, mint a cap_ key under Developers > Keys. A cap_ key works on /api/ only: the legacy routes refuse it with 401 {"error":"Invalid API key"}, because they do not read scopes. A half-ported site therefore runs two keys, and GET /api/me tells you which surfaces the key in your hand works on. Why a scoped key is refused on the legacy surface has the reasoning.
2. Translate the request
Most legacy parameters have a direct /api/ form:
| Legacy | /api/ |
|---|---|
GET /v2/api/articles | GET /api/entries/articles |
?depth=1 | ?select=*,author(*),coauthors(*), naming each relation |
?views[gt]=10 | ?filter[views][gt]=10 |
?ids=a,b,c | ?filter[id][in]=a,b,c |
?limit=50&page=3 | ?limit=50&after=<page.next from page 2> |
?sort=-views | ?sort=-views |
The full table, including nested limits, deep relations and structure, is in Coming from /v2/api or /v3/api.
curl 'https://cdn.capacms.com/api/entries/articles?select=title,slug,author(name)&filter[slug]=a-preview-you-can-trust' \
-H "x-api-key: $CAPA_KEY"3. Check what reads differently
A few things answer differently on /api/, by design. Check these on the page you port:
- Order. Legacy lists by
id./api/lists newest first. Passsort=idto keep the old order. - Unknown names are refused. A field the model does not have, or a parameter
/api/does not read, is a400with a hint, where legacy ignored it and answered the whole list. - Paging is by cursor, at most 200 entries a page. Follow
page.next. - Text matching ignores case in
contains,startsWithandendsWith. - An empty image, video or file is
null, where legacy sent an empty object. Testimage?.url. - Search stays on legacy.
/api/has no search yet, so keep calling/v2/api/searchfor it.
Each one, with the exact request on both sides, is in What reads differently.
4. Ship the page
Pin the version you built against by sending Capa-Version with every request, and keep the legacy calls for the pages you have not moved. Nothing on the legacy side needs to change while you go.