Docs

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.

View as Markdown

/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 getOn /api/
A contract that holds stillevery answer is served at a dated version, and Capa-Version pins the one you built against (Versions)
Only the fields a page rendersselect, with related entries pulled in by name (Choosing fields)
Errors you can act ona code to branch on and a hint that says what to change (Errors)
GraphQLthe same reads, typed per key (GraphQL)
Keys that do lesscap_ keys limited to reads, or to one model (Keys and scopes)
Typed codethe 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/articlesGET /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. Pass sort=id to keep the old order.
  • Unknown names are refused. A field the model does not have, or a parameter /api/ does not read, is a 400 with 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, startsWith and endsWith.
  • An empty image, video or file is null, where legacy sent an empty object. Test image?.url.
  • Search stays on legacy. /api/ has no search yet, so keep calling /v2/api/search for 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.