# The API contract

One account model and one set of operations across the web app and CLI.

## Read the machine-readable contract

The versioned API begins at `/api/v1`. The [OpenAPI document](/api/v1/openapi.json) is the reference for request fields and responses. Use it instead of reconstructing an endpoint from a screenshot or this overview.

Account operations accept an account session or a bearer credential issued through the device sign-in flow. Creating a report requires an account; existing public-link reports remain readable without signing in. Project and experiment data are owner-scoped.

## Resource overview

| Resource | Purpose |
| --- | --- |
| /reports | Create a public website audit; list account reports. |
| /reports/{id} | Read a public report or delete an owned report. |
| /reports/{id}/events | Read recorded run stages as JSONL; resume with ?after=SEQUENCE. |
| /reports/{id}/fixes | Read all observations with editorial priorities, evidence and an agent brief. |
| /projects | Create and list private projects. |
| /projects/{id} | Read, update, or delete an owned project. |
| /experiments | Create and list experiment records. |
| /experiments/{id} | Read a record, upload its immutable result, or delete it. |
| /telemetry | Submit or inspect account telemetry. |
| /usage | Read account usage and limits. |
| /account | Inspect the authenticated account and terms status. |
| /account/terms | Read or explicitly accept the current terms version. |
| /auth/device | Begin a device authorization request. |
| /auth/device/token | Poll an approved device request. |
| /auth/tokens | List host credential metadata. |

## Submit and read a website audit

Accept the current [terms](/account/terms) and authenticate with `evx auth login`. Save this request as `audit.json`:

```json
{ "url": "https://example.com" }
```

Submit through the CLI, which supplies the saved host credential to `POST /api/v1/reports`:

```sh
evx api POST /reports --file audit.json
```

The response uses HTTP 202 when the audit has been accepted. This is an illustrative response shape, not a completed live report:

```json
{
  "id": "00000000-0000-4000-8000-000000000001",
  "url": "/reports/00000000-0000-4000-8000-000000000001",
  "visibility": "public-link",
  "savedToAccount": true
}
```

Read `GET /api/v1/reports/{id}` to inspect `status`. It can be queued, running, completed, or failed. A completed run contains its report; a failed run contains an error. The CLI's `--wait` option handles polling.

The report's `progress` array records actual service stages and available counts. `GET /api/v1/reports/{id}/events?after=2` returns a bounded JSONL snapshot after sequence 2; poll until `X-Evx-Run-Status` is completed or failed. These events have the report's public-link visibility. Older reports can have no events. Static runs do not include model trials or sandbox execution.

A project creation uses `POST /api/v1/projects` with `name` and an optional `description`. It returns HTTP 201 with the project's ID, normalized fields, and creation timestamp. Collection reads return `items` and `nextCursor`; pass a non-null cursor back as `?cursor=VALUE` to read the next page.

## Review terms before account writes

New account work and host credentials require explicit acceptance of the [terms](/terms). Existing accounts are prompted too; earlier use is not recorded as agreement. Reads, exports and deletion remain available. New audits require a signed-in account and current terms acceptance.

Read `evx api GET /account/terms`. After reviewing the returned version and obtaining authority to agree for the account, save `{ "version": "2026-09-08", "accepted": true }` to `acceptance.json` and send it using the command below. This example version must match the current response. Do not automatically accept on behalf of someone who has not authorized you.

```sh
evx api PUT /account/terms --file acceptance.json
```

A required agreement returns HTTP 428 with `terms_required` and review links. A stale version returns HTTP 409 with `terms_version_changed`; read and review the new terms before making a new acceptance request. Retrying the same current acceptance keeps its first timestamp. Browser users can [review account terms](/account/terms). New CLI connections offer the same acceptance form inline at `/connect`, retaining the entered device code while you review the policy in a separate tab.

## Handle errors as data

```json
{ "error": { "code": "sign_in_required", "message": "Sign in to use your account data." }, "requestId": "00000000-0000-4000-8000-000000000001" }
```

Failures include a stable code, a readable message, and a request identifier. A rate-limited response can include `retryAfterSeconds`. Inspect the status and error code before retrying; changing credentials will not fix an invalid request.

Keep credentials in authorization headers. Do not put bearer tokens in URLs, which can become part of browser history or logs. See [CLI authentication](/docs/cli#authentication) for host credential storage and revocation.
