---
title: "Read a connect session (by token, or by id with a credential)"
method: GET
path: "/api/v1/sessions/{token}"
tags: ["Sessions"]
---

# Read a connect session (by token, or by id with a credential)

`GET /api/v1/sessions/{token}`

## Request

- Base URL: `https://app.dodomain.io`
- URL: `https://app.dodomain.io/api/v1/sessions/{token}`
- Auth: one of:
  - none
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://app.dodomain.io/api/auth/oauth2/authorize`, token URL `https://app.dodomain.io/api/auth/oauth2/token`, scopes: `sessions:read`)

ONE path with TWO arms, discriminated by the SHAPE of the path segment — never by which credential you send.

**Token arm** — pass the `dd_sess_...` token and send no `Authorization` header. Public: the token is the capability. Returns `PublicSession`: the records exactly as you requested them, plus status, detected provider and expiry. Once the session's 24 hours are up this arm answers `410 expired` forever, which is correct for a capability URL and useless for observability — hence the other arm.

**Id arm** — pass the session id and authenticate with a `dd_sk_` secret key or an OAuth token carrying `sessions:read`. Returns `IntegratorSession`, and it can do two things the token arm cannot: it is addressable by the `sessionId` that every webhook payload carries (the token never appears in one), and **it reads an EXPIRED session** rather than refusing it — reporting `expired: true` instead of `410`. That flag is DERIVED at read (`expiresAt <= now`), so it is already true in the window before the reaper persists `status: "expired"`; do not infer expiry from `status` alone. Its `records` are the COMPOSED names (`type`/`host`/`fqdn`) actually looked up on DNS, not the raw request echo the token arm returns.

`tier` and `detectedProvider` are `null` on both arms until detection runs — that pair is the 'detection has not run yet' signal, never a detection failure.

## Path parameters

- `token` string, required

## Headers

- `DoDomain-Team` string

## Response `200`

`PublicSession` when addressed by token, `IntegratorSession` when addressed by id. The two are told apart by their fields: only `IntegratorSession` carries `appId`, `connectionId`, `createdAt` and `expired`.

- union
  - PublicSession
    - `detectedProvider` string, nullable, required
    - `domain` string, required
    - `expiresAt` string, date-time, required
    - `id` string, required
    - `recipe` string, nullable, required
    - `records` object[], required
      - `host` string, required
      - `priority` integer
      - `ttl` integer
      - `type` 'A' | 'AAAA' | 'CNAME' | 'TXT' | 'MX', required
      - `value` string, required
    - `returnUrl` string, nullable, required
    - `status` string, required
    - `tier` integer, nullable, required
  - IntegratorSession
    - `appId` string, required
    - `connectionId` string, nullable, required
    - `createdAt` string, date-time, required
    - `detectedProvider` string, nullable, required
    - `domain` string, required
    - `expired` boolean, required
    - `expiresAt` string, date-time, required
    - `id` string, required
    - `recipe` string, nullable, required
    - `records` object[], required
      - `fqdn` string, required
      - `host` string, required
      - `type` 'A' | 'AAAA' | 'CNAME' | 'TXT' | 'MX', required
    - `status` string, required
    - `tier` integer, nullable, required
    - `tlsIssuanceAdvisories` object[], required
      - `code` 'caa_excludes_issuer' | 'caa_restricts_issuance' | 'stale_acme_challenge' | 'tls_issuance_unchecked', required
      - `evidence` string[], required
      - `evidenceFqdn` string, required
      - `fqdn` string, required
      - `note` string, required
      - `severity` 'warning' | 'info', required

## Other responses

- `400`: `400 invalid_request` — The request body or query failed schema validation, or a documented capability rule rejected it. `details` carries the field-level report when the failure came from schema validation.
- `401`: `401 unauthorized` — No `Authorization: Bearer` header, or the credential is malformed, unknown, or (for OAuth tokens) fails verification.
- `403`: `403 forbidden` — The OAuth access token was not granted the scope this operation requires. `details.code` is `SCOPE_MISSING` and `details.requiredScope` names the scope. Secret keys (`dd_sk_`) carry no scopes and never see this for a missing scope. The one-click start routes also answer it for a session created without one-click (a Free-plan team's session): no `details`, and the end user falls back to the manual records.
- `404`: `404 not_found` — The addressed resource does not exist, or the caller does not own it — the two collapse to one status on purpose, so an id cannot be enumerated by a caller who does not own it.
- `410`: `410 expired` — The connect session's 24-hour lifetime has elapsed. Returned before any verification, metering, or webhook work happens.
- `429`: `429 rate_limited` — Rate limited. `Retry-After` (seconds) is set and `details.retryAfterSeconds` repeats it. Branch on `details.reason`: `request_rate` means the caller is calling too fast (`details.limit` carries the cap), `recently_checked` means this one connection was DNS-checked inside its cooldown.
- `500`: `500 internal` — An unhandled server-side failure. Reported to our error tracker; safe to retry.

---

[API](https://skmtc.dev/dodomain/apis/dodomain-api.md) · [All operations](https://skmtc.dev/dodomain/apis/dodomain-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dodomain/apis/dodomain-api/revisions/44cfb944de58?raw)
