> ## Documentation Index
> Fetch the complete documentation index at: https://e2e.tester.army/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# API

> Check your API with fetch and value matchers, in the same files and run as UI tests.

An API test calls your API with `fetch` and checks the response with the
value matchers. It sits in the same files and the same run as UI tests. A
test that takes only `app` calls no model.

```ts title="tests/users-api.e2e.ts"

const User = z.object({ id: z.number(), name: z.string() });

test('GET /api/users returns the seeded users', async ({ app }) => {
  const response = await fetch(`${app.baseUrl}/api/users`);
  expect(response.status).toBe(200);
  expect(response.headers.get('content-type')).toContain('application/json');
  const users = expect(await response.json()).toMatchSchema(z.array(User));
  expect(users).toHaveLength(3);
  expect(users.map((user) => user.name)).toEqual(['Ada', 'Grace', expect.any(String)]);
});
```

`toMatchSchema` takes any [Standard Schema](https://e2e.tester.army/docs/reference/expect#matching-a-schema),
such as Zod, Valibot, or ArkType, and returns the value, typed. Use the
schema your app parses the API with, and the test fails when the API stops
matching it.

## Where the API runs

`app.baseUrl` is the target's `app.url` without a trailing slash, with the
port the run picked when the URL declares port `0`. So one file runs against the `app.command` the
config starts and against a deployed preview.

When the API writes state after it answers, use
[`expect.poll`](https://e2e.tester.army/docs/reference/expect#polling-a-value) to read again until a
matcher passes.

## Signed-in requests

To call the API as a signed-in user, write
[a fixture](https://e2e.tester.army/docs/reference/test#your-own-fixtures) that sends
`browser.cookies()` with each request. Every test that uses this `test`
gets `browser`, so it needs a web target:

```ts title="tests/api.ts"

export const test = base.extend<{ api: (path: string, init?: RequestInit) => Promise<Response> }>({
  api: async ({ app, browser }, use) => {
    await use(async (path, init) => {
      const url = new URL(`${app.baseUrl}${path}`);
      if (url.origin !== new URL(app.baseUrl).origin) throw new Error(`${url} is not on the app`);
      const headers = new Headers(init?.headers);
      headers.set('cookie', (await browser.cookies()).map((c) => `${c.name}=${c.value}`).join('; '));
      return fetch(url, { ...init, headers });
    });
  },
});
```

## Without a browser

On a web target, an API test still opens a browser context. A target
without an engine opens nothing, and has no `app.baseUrl`, so read the URL
from an environment variable. `e2e init` sets one up when you select no
engine.

## Next

<CardGroup cols={2}>
  <Card title="Value matchers" icon="circle-check" href="/reference/expect#value-matchers">
    Every matcher, `toMatchSchema`, and `expect.poll`.
  </Card>
  <Card title="Signing in" icon="key" href="/authentication">
    Sign in one time and reuse the session in every test.
  </Card>
</CardGroup>
