# OpenAPI spec (https://dodomain.io/docs/openapi)

The machine-readable OpenAPI 3.1 description of the /api/v1 surface, generated from the same schemas the API validates with.

The whole `/api/v1` surface is published as an OpenAPI 3.1 document:

**[`https://dodomain.io/docs/openapi.json`](https://dodomain.io/docs/openapi.json)**

It describes every endpoint on the [REST API](https://dodomain.io/docs/api) page: request and response bodies, query
parameters, both authentication schemes and their OAuth scopes, and the exact error codes each
endpoint can answer with, including the `429` `Retry-After` contract.

## It is generated, not written [#it-is-generated-not-written]

The document is produced from the **same zod schemas the request handlers validate with**. A CI gate
regenerates it on every pull request and fails the build if it differs by a single byte from the
committed file, and a second gate fails if any endpoint exists in the code without an entry in the
spec. So the spec cannot quietly fall behind the API: either it is current, or the build is red.

Two consequences worth knowing:

- **Response objects do not set `additionalProperties: false`.** We add response fields additively:
  `records` and `warnings` on the create-session response both arrived that way, so a client that
  rejected unknown keys would break on a release that broke nothing else. Ignore fields you do not
  recognise.
- **The version is `v1`, not a semver.** The surface is versioned by its `/api/v1` path prefix.

## Using it [#using-it]

```bash
curl -sSL https://dodomain.io/docs/openapi.json -o dodomain-openapi.json
```

Point any OpenAPI-aware tool at that file or URL: an API client (Postman, Insomnia, Bruno, Hoppscotch),
a client generator (`openapi-generator`, `openapi-typescript`, Kiota, Speakeasy), a mock server, or a
contract-test runner.

<Callout title="AI assistants can call the API directly">
  If you want an assistant to *use* DoDomain rather than write code against it, connect the MCP
  server instead (see [Connecting AI assistants](https://dodomain.io/docs/connecting-ai-assistants)). It runs on the same
  authentication, scopes and rate limits described here.
</Callout>

## Authentication in the spec [#authentication-in-the-spec]

Two security schemes are declared, and integrator endpoints accept **either**:

| Scheme      | How it appears                    | Notes                                                                                                                   |
| ----------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `secretKey` | `Authorization: Bearer dd_sk_...` | App-scoped, no scope grammar, server-to-server only.                                                                    |
| `oauth2`    | Authorization-code flow with PKCE | Team-scoped. Each operation lists the scope it needs; a missing scope answers `403` with `details.code: SCOPE_MISSING`. |

Session-scoped endpoints (`/api/v1/sessions/{token}/detect`, `/verify`, and the one-click entry
points) carry `security: []`: that is not an oversight. The unguessable session token in the path
**is** the credential, which is what lets the hosted flow and the embeddable widget call them from a
browser. Treat that token like a bearer token.

Two classes deviate from "either credential", and the spec says so per operation rather than in prose:

- **Credential-management operations take the secret key only.** `POST /api/v1/keys/rotate` and every
  `/api/v1/webhook-endpoints` operation declare `secretKey` as their _sole_ security requirement. An
  OAuth access token is refused with `403` and `details.code: SECRET_KEY_REQUIRED`: signing secrets
  and key rotation stay with the app's own credential rather than with a delegated consent grant.
- **`GET /api/v1/sessions/{token}` accepts either, or neither.** It is one path with two arms, told
  apart by the shape of the path segment: pass a `dd_sess_...` token and send no `Authorization`
  header, or pass the session **id** and authenticate. The spec encodes that as an optional security
  requirement (a leading `{}` alongside the two credential entries) and the `200` as a `oneOf` over
  the two response shapes, because the two arms genuinely return different objects.
