# Move a page from /v2/api to /api/

Source: https://capacms.com/docs/legacy/migrate

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](https://capacms.com/docs/api/versions)) |
| Only the fields a page renders | `select`, with related entries pulled in by name ([Choosing fields](https://capacms.com/docs/api/entries#choosing-fields-select))                |
| Errors you can act on          | a `code` to branch on and a `hint` that says what to change ([Errors](https://capacms.com/docs/errors))                                          |
| GraphQL                        | the same reads, typed per key ([GraphQL](https://capacms.com/docs/api/graphql))                                                                  |
| Keys that do less              | `cap_` keys limited to reads, or to one model ([Keys and scopes](https://capacms.com/docs/api/authentication))                                   |
| Typed code                     | the [SDK](https://capacms.com/docs/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](https://capacms.com/docs/api/authentication#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](https://capacms.com/docs/api/entries#coming-from-v2api-or-v3api).

```bash
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`](https://capacms.com/docs/legacy/search) for it.

Each one, with the exact request on both sides, is in [What reads differently](https://capacms.com/docs/api/entries#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.
- [Entries](https://capacms.com/docs/api/entries): The full read contract: select, filter, sort, page.
- [SDK](https://capacms.com/docs/sdk): A typed client for /api/, with Next.js helpers.
