---
title: "Account endpoints — API reference | Data Tactical Group"
route: "/docs/api/reference/account"
updated: "2026-10-23"
---

# Account

17 endpoints, generated from the API's own OpenAPI document. The signed-in person, their active organization, the rules the screens state, sessions, closing and reopening.

## GET `/v2/account`

**The person, the active organization, membership, plan, trial, rules and entitlements**

Credentials: API key or App session. Operation id `acctGetAccount`.

Either credential (an API key or a session); resolves the caller's active organization (`X-DTG-Org`). Any role. Everything is resolved against the active organization; `rules` states every window, ceiling and allowance the app shows, and `entitlements` what this member may do now. No side effects beyond remembering, for a browser session, the organization last used (`last_org_id`).

### Parameters

**`X-DTG-Org` header · string (uuid)**

The organization this request acts in (a uuid). Omitted: the caller's own organization, else their earliest active membership. An API key is bound to its organization and may only name that one.

### Responses

#### `200` success

The account view.

As `application/json`.

**`id` string (uuid) always present**

The person's account id.

**`email` string always present**

The person's email address.

**`display_name` string or null always present**

The person's display name; null when never set.

**`status` "active" | "closed" always present**

Account status; always `active` for a caller (a closed account cannot authenticate).

**`email_verified` boolean always present**

True once the email address is verified.

**`marketing_opt_out` boolean always present**

True when the person has opted out of product (marketing) email.

**`created_at` string (date-time) always present**

When the account was created.

**`last_org_id` string (uuid) or null always present**

T16: the organization this person last used in a browser session; null when none, or when that organization has since closed.

**`landing_org_id` string (uuid) or null always present**

T16: where a sign-in should open: the last-used organization while still an active membership, else the paid organization joined most recently (unless the own one is paid), else the own one. Null when the person has no active membership. Send it as `X-DTG-Org`.

**`organization` object always present**

The active organization (the one `X-DTG-Org` selected, else the own one).

**`organization.id` string (uuid) always present**

The organization's id.

**`organization.name` string always present**

The organization's name.

**`organization.own` boolean always present**

True when it is this person's own organization.

**`organization.plan_code` string always present**

The organization's plan code (the plan it pays for, whatever this member's seat).

**`organization.addons` array of string always present**

Active add-ons, by code (for example `api`).

**`organization.seats` object always present**

Seats.

**`organization.seats.included` integer always present**

Seats the plan includes (or DTG's override), raised to the organization's T163 grandfathered count while one is recorded (see `OrgSeatState.grandfathered`).

**`organization.seats.purchased` integer always present**

Extra seats bought.

**`organization.seats.capacity` integer always present**

Included plus purchased.

**`organization.seats.seated` integer always present**

Active members holding a seat.

**`organization.seats.free_members` integer always present**

Active members without a seat (free members).

**`organization.counties` object always present**

Counties, as resolved for this member.

**`organization.counties.included` integer always present**

Counties included; -1 means every county.

**`organization.counties.purchased` integer always present**

Extra county slots bought.

**`organization.counties.capacity` integer always present**

Included plus purchased; -1 means every county.

**`organization.counties.active` integer or "all" always present**

Counties active for this member now, or `all`.

**`membership` object always present**

The caller's membership in the active organization.

**`membership.id` string (uuid) always present**

The membership id.

**`membership.role` "owner" | "admin" | "member" always present**

Role.

**`membership.seated` boolean always present**

True when the member holds a seat.

**`membership.api_enabled` boolean always present**

True when an owner or admin has granted this member API access (needed to mint live keys).

**`membership.billing_contact` boolean always present**

True when the member is a billing contact (invoices and payment method).

**`organizations` array of AcctOrganizationRow always present**

Every active organization this person belongs to, own first, then by join date.

**`organizations[].id` string (uuid) always present**

The organization's id; send it as `X-DTG-Org` to act in it.

**`organizations[].name` string always present**

The organization's display name.

**`organizations[].role` "owner" | "admin" | "member" always present**

The person's role in it.

**`organizations[].seated` boolean always present**

True when the person holds a paid seat there; an unseated (free) member gets the free plan's capabilities.

**`organizations[].own` boolean or null always present**

True for the person's own organization (the one made at sign-up). Null when the person has no own organization any more.

**`plan` object always present**

The plan.

**`plan.code` string always present**

The plan this MEMBER resolves to: the organization's plan when seated, `free` when unseated.

**`plan.organization_plan` string always present**

The organization's plan code.

**`plan.name` string always present**

The organization's plan display name.

**`plan.billing_interval` "month" | "year" always present**

Billing interval.

**`plan.billing_mode` "self\_serve" | "invoiced" always present**

`self_serve` (card through the app) or `invoiced` (a contract DTG manages).

**`plan.contract_expires_at` string (date-time) or null always present**

End of an invoiced contract; null when none.

**`plan.pending_change` object or null always present**

A scheduled plan change, or null.

**`plan.pending_change.plan_code` string always present**

The plan the organization moves to.

**`plan.pending_change.effective_at` string (date-time) or null always present**

When the move takes effect.

**`plan.seat_reassignment_pending` boolean always present**

True when a downgrade left more seated members than seats and an owner or admin must choose who keeps one.

**`plan.county_choice_pending` boolean always present**

True when the owner must confirm which counties stay after a plan change.

**`rules` AcctRules always present**

Every window, ceiling and allowance the app states, with the active organization's plan.

**`rules.invite_expiry_days` integer always present**

Days an invitation stays open before it expires (default 14).

**`rules.link_lifetimes` object always present**

T33: how long each mailed link works.

**`rules.link_lifetimes.password_reset_seconds` integer always present**

Lifetime of a password-reset link, in seconds (default 3600).

**`rules.link_lifetimes.email_verification_seconds` integer always present**

Lifetime of an email-verification link, in seconds (default 3600).

**`rules.link_lifetimes.invitation_days` integer always present**

Lifetime of an invitation link, in days (the same value as `invite_expiry_days`).

**`rules.support` object always present**

T47: the support contact for every screen that says 'contact support'.

**`rules.support.email` string or null always present**

The support address. Null when the deployment configured none.

**`rules.ownership_claim_notice_days` integer always present**

T36: days an ownership claim by a domain-verified admin waits before it completes; an owner signing in during the window cancels it (default 7).

**`rules.trial_days` integer always present**

Length of a trial, in days (default 7).

**`rules.session` object always present**

Browser session lifetimes (item 26).

**`rules.session.sliding_days` integer always present**

A session idle for this many days ends (default 7).

**`rules.session.max_days` integer always present**

A session ends this many days after sign-in however active it is (default 30).

**`rules.signin_throttle` object always present**

Failed sign-ins allowed per email address before sign-in is refused for the rest of the window.

**`rules.signin_throttle.max_failures` integer always present**

Failed attempts allowed in the window (10).

**`rules.signin_throttle.window_seconds` integer always present**

Length of the window, in seconds (3600).

**`rules.two_factor_lockout` object always present**

D74: wrong second-factor codes before the factor locks.

**`rules.two_factor_lockout.max_failures` integer always present**

Wrong codes allowed before the lock (default 10).

**`rules.two_factor_lockout.lock_seconds` integer always present**

How long the lock lasts, in seconds (default 900).

**`rules.notification_retention_days` integer always present**

Days a bell notification is kept (default 90).

**`rules.audit_retention_months` integer always present**

Months the organization's audit trail is kept (D60, default 24).

**`rules.cluster_restore_days` integer always present**

Days a deleted cluster can be restored (D70, default 30).

**`rules.rate_limits` object always present**

The per-minute request ceilings. A sandbox key has lower ceilings that are not stated here.

**`rules.rate_limits.per_key_per_min` integer always present**

Requests a minute per credential (120).

**`rules.rate_limits.per_session_per_min` integer always present**

Requests a minute per browser session; always equal to `per_key_per_min` since D79 (one ceiling for every credential).

**`rules.rate_limits.per_org_per_min` integer always present**

Requests a minute across every member, key and session of one organization (300).

**`rules.annual_multiplier` number always present**

An annual price is the monthly price times this (default 10).

**`rules.dunning_grace_days` integer always present**

Days after a failed payment before the subscription itself ends; service pauses at once (default 16).

**`rules.product_events` "on" | "off" always present**

T166: whether this Taigan records our own product events (`PRODUCT_EVENTS`, off by default until the owner approves the policy wording). `off`: the app's event client sends nothing. `on`: it posts the screens opened to `POST /v2/events`.

**`rules.plan` object or null always present**

The active organization's plan as sold. Null on `GET /v2/account/profile` (no organization is resolved there).

**`rules.plan.code` string always present**

The plan code.

**`rules.plan.name` string always present**

The plan's display name (D42-FINAL).

**`rules.plan.counties_included` integer or "all" always present**

Counties the plan includes, or `all` for every county.

**`rules.plan.seats_included` integer always present**

Seats the plan includes.

**`rules.plan.points_quota_month` integer or null always present**

The plan's monthly points allowance (data values emitted on the API, CSV and XLSX channels, per organization); null when unlimited. An add-on can raise the effective figure: see `entitlements.points_quota_month`.

**`two_factor` AcctTwoFactor always present**

The person's second factor.

**`two_factor.enabled` boolean always present**

True when the person has turned on the second factor (TOTP).

**`two_factor.backup_codes_remaining` integer or null always present**

How many unused backup codes remain. Null when the factor is off, or when the count could not be read.

**`platform_admin` boolean always present**

T133: true when this request's session is one `/v2/admin` would accept on its first three facts: a session (never an API key), an owner or admin of DTG's house organization, the second factor on. The session's own challenge is not part of it: a house admin whose session began from a link is true, and `/v2/admin` asks that session for the code ( [`second_factor_required`](/docs/api/errors#code-second-factor-required) ). False for everyone else, and always present, so it says nothing about anyone but the person asking. Kelpie shows the platform console in the account menu on true.

**`trial` object or null always present**

This person's running trial in this organization, or null (a trial runs only in the person's own organization).

**`trial.id` string (uuid) always present**

The trial id.

**`trial.county` string always present**

The trial county's name.

**`trial.fips` string always present**

The trial county's 5-digit FIPS code.

**`trial.started_at` string (date-time) always present**

When the trial started.

**`trial.expires_at` string (date-time) always present**

When the trial ends.

**`entitlements` AcctEntitlementSummary always present**

What this member may do in the active organization now.

**`entitlements.counties_included` integer always present**

Counties the plan includes; -1 means every county.

**`entitlements.counties_purchased` integer always present**

Extra county slots bought.

**`entitlements.county_capacity` integer always present**

Counties the organization may hold: included plus purchased; -1 means every county.

**`entitlements.quarterly_access` boolean always present**

True when quarterly data may be requested.

**`entitlements.depth_cap_naics` integer always present**

Deepest NAICS level served (digits).

**`entitlements.depth_cap_soc` integer always present**

Deepest SOC level served.

**`entitlements.api_access` boolean always present**

True when this member's API keys may read data (the organization holds the API add-on and the member is seated).

**`entitlements.csv_access` boolean always present**

True when CSV and XLSX export is included.

**`entitlements.csv_quota_month` integer or null always present**

Monthly CSV export count limit; null (the usual case) means no export count limit, points meter exports instead.

**`entitlements.points_quota_month` integer or null always present**

The organization's monthly points allowance (data values emitted on the API, CSV and XLSX channels); null when unlimited. See `GET /v2/plans`.

**`entitlements.account_rate_per_min` integer always present**

Requests a minute allowed across the whole organization (300; lower on a sandbox key).

**`entitlements.api_keys_per_member` integer always present**

Active live API keys this member may hold (0 for an unseated member; the sandbox cap on a sandbox key).

**`entitlements.vintage_access` boolean always present**

True when vintages other than the current one may be requested.

**`entitlements.active_counties` integer or "all" always present**

How many counties are active for this member now (the trial county included), or `all`.

**`entitlements.county_choice_pending` boolean always present**

True when a plan change left more counties than the new plan holds and the owner still has to confirm which stay (`POST /v2/account/counties/confirm`).

**`entitlements.trial_active` boolean always present**

True while this person's trial is running in this (their own) organization.

**`entitlements.trial_csv` boolean always present**

True while a trial grants export of the trial county.

**`entitlements.quota_scope` "organization" always present**

Always `organization`: every quota here is shared by the whole organization.

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active membership in an active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the signed-in person's own account is suspended by DTG; `reason` where recorded), [`org_suspended`](/docs/api/errors#code-org-suspended) (the selected organization is suspended by DTG; the body carries `organization {id, name}` and `reason`), [`org_closed`](/docs/api/errors#code-org-closed) (the selected organization is closed; the body carries `organization {id, name}`), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (a key of the mode this process does not serve: a sandbox key on the live API, or a live key on the sandbox; the body carries `expected` and `base_url`, the address that serves it).

As `application/problem+json`.

#### `404` error

Not Found. Codes: [`not_found`](/docs/api/errors#code-not-found) (the `X-DTG-Org` header is not a uuid, names an organization the caller is not an active member of, or, on an API key, names an organization other than the key's own).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential, 300 per organization; lower ceilings for a sandbox key), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`no_membership`](/docs/api/errors#code-no-membership) (403), [`account_suspended`](/docs/api/errors#code-account-suspended) (403), [`org_suspended`](/docs/api/errors#code-org-suspended) (403), [`org_closed`](/docs/api/errors#code-org-closed) (403), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## PATCH `/v2/account`

**Edit the display name or marketing consent**

Credentials: API key or App session. Operation id `acctUpdateAccount`.

Either credential (an API key or a session); resolves the caller's active organization (`X-DTG-Org`). Any role. Changes the person, not the organization. The same edit as `PATCH /v2/account/profile`.

### Parameters

**`X-DTG-Org` header · string (uuid)**

The organization this request acts in (a uuid). Omitted: the caller's own organization, else their earliest active membership. An API key is bound to its organization and may only name that one.

### Request body

Required; `application/json`.

**`display_name` string**

New display name: a non-blank string of at most 120 characters, stored trimmed. (at most 120 characters)

**`marketing_opt_out` boolean**

True to stop product (marketing) email, false to receive it again. Transactional mail is sent either way. A change writes an `account.marketing_changed` audit row.

### Responses

#### `204` success

No Content.

#### `400` error

Bad Request. Codes: [`bad_request`](/docs/api/errors#code-bad-request) (`display_name` is not a non-blank string of at most 120 characters, `marketing_opt_out` is not a boolean, or neither field was sent), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON, or uses an unsupported content encoding).

As `application/problem+json`.

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active membership in an active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the signed-in person's own account is suspended by DTG; `reason` where recorded), [`org_suspended`](/docs/api/errors#code-org-suspended) (the selected organization is suspended by DTG; the body carries `organization {id, name}` and `reason`), [`org_closed`](/docs/api/errors#code-org-closed) (the selected organization is closed; the body carries `organization {id, name}`), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (a key of the mode this process does not serve: a sandbox key on the live API, or a live key on the sandbox; the body carries `expected` and `base_url`, the address that serves it). A sandbox key: [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (it may write only clusters).

As `application/problem+json`.

#### `404` error

Not Found. Codes: [`not_found`](/docs/api/errors#code-not-found) (the `X-DTG-Org` header is not a uuid, names an organization the caller is not an active member of, or, on an API key, names an organization other than the key's own).

As `application/problem+json`.

#### `413` error

Payload Too Large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the body is over this endpoint's 16 KB limit; the body carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential, 300 per organization; lower ceilings for a sandbox key), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`bad_request`](/docs/api/errors#code-bad-request) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`no_membership`](/docs/api/errors#code-no-membership) (403), [`account_suspended`](/docs/api/errors#code-account-suspended) (403), [`org_suspended`](/docs/api/errors#code-org-suspended) (403), [`org_closed`](/docs/api/errors#code-org-closed) (403), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (403), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`body_too_large`](/docs/api/errors#code-body-too-large) (413), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/account/password`

**Whether this person must choose a password (T100)**

Credentials: API key or App session. Operation id `acctGetPasswordState`.

Account-only (resolves no organization). T100 (Ops entry 72, O63 S11): a password chosen at sign-up stands only when the verification link is opened in the browser that signed up (the sign-up's `dtg.pending_signup` cookie). Verified from another browser, it is discarded, the link signs the person in, and `password_required` is true until they choose one here.

### Responses

#### `200` success

OK.

As `application/json`.

**`password_required` boolean always present**

True when the email-and-password account has no password (discarded by T100, or by a contested sign-up, AUTH-2).

**`min_length` integer always present**

The password floor (10).

#### `401` error

Unauthorized. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated).

As `application/problem+json`.

Problem codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/password`

**Choose the password, once, while verifying (T100)**

Credentials: API key or App session. Operation id `acctSetPassword`.

Account-only, a signed-in session only (an API key answers 403 [`session_required`](/docs/api/errors#code-session-required) ). Sets the password of an account that has none; audited as `auth.password_set`. A password is CHANGED with the current one (`/v2/auth/change-password`), never here.

### Request body

Required; `application/json`.

**`new_password` string required**

The new password.

### Responses

#### `200` success

OK.

As `application/json`.

**`password_required` boolean**

False once set.

#### `400` error

Bad Request. Codes: [`password_too_short`](/docs/api/errors#code-password-too-short), [`bad_request`](/docs/api/errors#code-bad-request).

As `application/problem+json`.

#### `401` error

Unauthorized. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`session_required`](/docs/api/errors#code-session-required), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the account is suspended by DTG).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`password_already_set`](/docs/api/errors#code-password-already-set).

As `application/problem+json`.

Problem codes: [`password_too_short`](/docs/api/errors#code-password-too-short) (400), [`bad_request`](/docs/api/errors#code-bad-request) (400), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`session_required`](/docs/api/errors#code-session-required) (403), [`account_suspended`](/docs/api/errors#code-account-suspended) (403), [`password_already_set`](/docs/api/errors#code-password-already-set) (409). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/account/profile`

**The person, their organizations (possibly none), second factor and rules**

Credentials: API key or App session. Operation id `acctGetProfile`.

Account-only: it authenticates the person (an API key or a session) and resolves NO organization, so `X-DTG-Org` is ignored and it answers a person with no active membership at all, or whose only organization is suspended or closed. It does not apply the live/sandbox instance check. T42: the profile, sessions, security and account close stay reachable for a person whose organizations are all gone. `rules.plan` is null.

### Responses

#### `200` success

The profile.

As `application/json`.

**`id` string (uuid) always present**

The person's account id.

**`email` string always present**

The person's email address.

**`display_name` string or null always present**

The person's display name; null when never set.

**`status` "active" | "closed" always present**

Account status; always `active` for a caller (a closed account cannot authenticate).

**`email_verified` boolean always present**

True once the email address is verified.

**`marketing_opt_out` boolean always present**

True when the person has opted out of product (marketing) email.

**`created_at` string (date-time) always present**

When the account was created.

**`last_org_id` string (uuid) or null always present**

T16: the organization this person last used in a browser session; null when none, or when that organization has since closed.

**`landing_org_id` string (uuid) or null always present**

T16: where a sign-in should open: the last-used organization while still an active membership, else the paid organization joined most recently (unless the own one is paid), else the own one. Null when the person has no active membership. Send it as `X-DTG-Org`.

**`organizations` array of AcctOrganizationRow always present**

Every active organization this person belongs to, own first; empty when none.

**`organizations[].id` string (uuid) always present**

The organization's id; send it as `X-DTG-Org` to act in it.

**`organizations[].name` string always present**

The organization's display name.

**`organizations[].role` "owner" | "admin" | "member" always present**

The person's role in it.

**`organizations[].seated` boolean always present**

True when the person holds a paid seat there; an unseated (free) member gets the free plan's capabilities.

**`organizations[].own` boolean or null always present**

True for the person's own organization (the one made at sign-up). Null when the person has no own organization any more.

**`two_factor` AcctTwoFactor always present**

The person's second factor.

**`two_factor.enabled` boolean always present**

True when the person has turned on the second factor (TOTP).

**`two_factor.backup_codes_remaining` integer or null always present**

How many unused backup codes remain. Null when the factor is off, or when the count could not be read.

**`rules` AcctRules always present**

The rules block, with `plan` null (no organization is resolved).

**`rules.invite_expiry_days` integer always present**

Days an invitation stays open before it expires (default 14).

**`rules.link_lifetimes` object always present**

T33: how long each mailed link works.

**`rules.link_lifetimes.password_reset_seconds` integer always present**

Lifetime of a password-reset link, in seconds (default 3600).

**`rules.link_lifetimes.email_verification_seconds` integer always present**

Lifetime of an email-verification link, in seconds (default 3600).

**`rules.link_lifetimes.invitation_days` integer always present**

Lifetime of an invitation link, in days (the same value as `invite_expiry_days`).

**`rules.support` object always present**

T47: the support contact for every screen that says 'contact support'.

**`rules.support.email` string or null always present**

The support address. Null when the deployment configured none.

**`rules.ownership_claim_notice_days` integer always present**

T36: days an ownership claim by a domain-verified admin waits before it completes; an owner signing in during the window cancels it (default 7).

**`rules.trial_days` integer always present**

Length of a trial, in days (default 7).

**`rules.session` object always present**

Browser session lifetimes (item 26).

**`rules.session.sliding_days` integer always present**

A session idle for this many days ends (default 7).

**`rules.session.max_days` integer always present**

A session ends this many days after sign-in however active it is (default 30).

**`rules.signin_throttle` object always present**

Failed sign-ins allowed per email address before sign-in is refused for the rest of the window.

**`rules.signin_throttle.max_failures` integer always present**

Failed attempts allowed in the window (10).

**`rules.signin_throttle.window_seconds` integer always present**

Length of the window, in seconds (3600).

**`rules.two_factor_lockout` object always present**

D74: wrong second-factor codes before the factor locks.

**`rules.two_factor_lockout.max_failures` integer always present**

Wrong codes allowed before the lock (default 10).

**`rules.two_factor_lockout.lock_seconds` integer always present**

How long the lock lasts, in seconds (default 900).

**`rules.notification_retention_days` integer always present**

Days a bell notification is kept (default 90).

**`rules.audit_retention_months` integer always present**

Months the organization's audit trail is kept (D60, default 24).

**`rules.cluster_restore_days` integer always present**

Days a deleted cluster can be restored (D70, default 30).

**`rules.rate_limits` object always present**

The per-minute request ceilings. A sandbox key has lower ceilings that are not stated here.

**`rules.rate_limits.per_key_per_min` integer always present**

Requests a minute per credential (120).

**`rules.rate_limits.per_session_per_min` integer always present**

Requests a minute per browser session; always equal to `per_key_per_min` since D79 (one ceiling for every credential).

**`rules.rate_limits.per_org_per_min` integer always present**

Requests a minute across every member, key and session of one organization (300).

**`rules.annual_multiplier` number always present**

An annual price is the monthly price times this (default 10).

**`rules.dunning_grace_days` integer always present**

Days after a failed payment before the subscription itself ends; service pauses at once (default 16).

**`rules.product_events` "on" | "off" always present**

T166: whether this Taigan records our own product events (`PRODUCT_EVENTS`, off by default until the owner approves the policy wording). `off`: the app's event client sends nothing. `on`: it posts the screens opened to `POST /v2/events`.

**`rules.plan` object or null always present**

The active organization's plan as sold. Null on `GET /v2/account/profile` (no organization is resolved there).

**`rules.plan.code` string always present**

The plan code.

**`rules.plan.name` string always present**

The plan's display name (D42-FINAL).

**`rules.plan.counties_included` integer or "all" always present**

Counties the plan includes, or `all` for every county.

**`rules.plan.seats_included` integer always present**

Seats the plan includes.

**`rules.plan.points_quota_month` integer or null always present**

The plan's monthly points allowance (data values emitted on the API, CSV and XLSX channels, per organization); null when unlimited. An add-on can raise the effective figure: see `entitlements.points_quota_month`.

**`terms_acceptance_required` boolean**

T136: true while the gate is on (`TERMS_AT_FIRST_SIGN_IN=on`, off by default) for an account DTG staff set up whose person has not yet accepted the Terms of Service and the Privacy Policy; every route that resolves an organization answers `403 terms_acceptance_required` until `POST /v2/account/legal/accept`. False for every other account.

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential; no organization bucket on an account-only route), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## PATCH `/v2/account/profile`

**Edit the display name or marketing consent (account-only)**

Credentials: API key or App session. Operation id `acctUpdateProfile`.

Account-only: it authenticates the person (an API key or a session) and resolves NO organization, so `X-DTG-Org` is ignored and it answers a person with no active membership at all, or whose only organization is suspended or closed. It does not apply the live/sandbox instance check. The same edit as `PATCH /v2/account`.

### Request body

Required; `application/json`.

**`display_name` string**

New display name: a non-blank string of at most 120 characters, stored trimmed. (at most 120 characters)

**`marketing_opt_out` boolean**

True to stop product (marketing) email, false to receive it again. Transactional mail is sent either way. A change writes an `account.marketing_changed` audit row.

### Responses

#### `204` success

No Content.

#### `400` error

Bad Request. Codes: [`bad_request`](/docs/api/errors#code-bad-request) (`display_name` is not a non-blank string of at most 120 characters, `marketing_opt_out` is not a boolean, or neither field was sent), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON, or uses an unsupported content encoding).

As `application/problem+json`.

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `403` error

Forbidden. [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (a sandbox key may write only clusters), and the authentication middleware's [`no_membership`](/docs/api/errors#code-no-membership), [`account_suspended`](/docs/api/errors#code-account-suspended), [`org_suspended`](/docs/api/errors#code-org-suspended), [`org_closed`](/docs/api/errors#code-org-closed), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (`base_url` names the address that serves the key).

As `application/problem+json`.

#### `413` error

Payload Too Large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the body is over this endpoint's 16 KB limit; the body carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential; no organization bucket on an account-only route), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`bad_request`](/docs/api/errors#code-bad-request) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`no_membership`](/docs/api/errors#code-no-membership) (403), [`account_suspended`](/docs/api/errors#code-account-suspended) (403), [`org_suspended`](/docs/api/errors#code-org-suspended) (403), [`org_closed`](/docs/api/errors#code-org-closed) (403), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (403), [`body_too_large`](/docs/api/errors#code-body-too-large) (413), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/account/legal`

**Whether this person still owes the acceptance of the Terms (T136)**

Credentials: API key or App session. Operation id `acctGetLegal`.

Account-only: it authenticates the person (an API key or a session) and resolves NO organization, so `X-DTG-Org` is ignored and it answers a person with no active membership at all, or whose only organization is suspended or closed. It does not apply the live/sandbox instance check. T136: an account DTG staff set up (`deploy.sh account`, the house bootstrap) records no acceptance, because its person submitted no form; `acceptance_required` is true until the person accepts with `POST /v2/account/legal/accept`. `last_accepted` is the latest recorded acceptance, or null. The refusal applies only while `TERMS_AT_FIRST_SIGN_IN=on` (off by default until the app asks); an owed acceptance can be recorded either way.

### Responses

#### `200` success

The acceptance state.

As `application/json`.

**`acceptance_required` boolean always present**

True while the person owes the acceptance and the gate is on (`TERMS_AT_FIRST_SIGN_IN=on`, off by default); every route that resolves an organization answers `403 terms_acceptance_required` meanwhile.

**`terms_version` string always present**

The Terms of Service version in force (`TERMS_VERSION`).

**`privacy_version` string always present**

The Privacy Policy version in force (`PRIVACY_VERSION`).

**`last_accepted` object or null always present**

The latest recorded acceptance, or null.

**`last_accepted.terms_version` string**

The Terms of Service version accepted.

**`last_accepted.privacy_version` string**

The Privacy Policy version accepted.

**`last_accepted.surface` "sign\_up" | "invite\_sign\_up" | "first\_sign\_in"**

Where it was accepted: the sign-up form, an invitation's sign-up, or the first sign-in to an account DTG set up.

**`last_accepted.accepted_at` string (date-time)**

When.

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential; no organization bucket on an account-only route), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/legal/accept`

**Accept the Terms of Service and the Privacy Policy (T136)**

Credentials: API key or App session. Operation id `acctAcceptLegal`.

Account-only: it authenticates the person (an API key or a session) and resolves NO organization, so `X-DTG-Org` is ignored and it answers a person with no active membership at all, or whose only organization is suspended or closed. It does not apply the live/sandbox instance check. A session only: the person accepts, never a key and never staff on their behalf. The body names the versions the person was shown, which must be the ones in force. When the account owes the acceptance, it is recorded exactly as a sign-up records it (a row with the versions in force, surface `first_sign_in`, and the audit event `legal.accepted`), and the account is served as any other from then on. When nothing is owed (a sign-up recorded it, or a second click), nothing is written and the answer is 200.

### Request body

Required; `application/json`.

**`terms_version` string required**

The Terms of Service version the person was shown.

**`privacy_version` string required**

The Privacy Policy version the person was shown.

### Responses

#### `200` success

Nothing was owed; nothing was written.

As `application/json`.

**`accepted` boolean always present**

True when this request recorded the acceptance; false when nothing was owed.

**`acceptance_required` boolean always present**

Always false after the request.

**`terms_version` string always present**

The Terms of Service version in force, now accepted.

**`privacy_version` string always present**

The Privacy Policy version in force, now accepted.

#### `201` success

The acceptance was recorded.

As `application/json`.

**`accepted` boolean always present**

True when this request recorded the acceptance; false when nothing was owed.

**`acceptance_required` boolean always present**

Always false after the request.

**`terms_version` string always present**

The Terms of Service version in force, now accepted.

**`privacy_version` string always present**

The Privacy Policy version in force, now accepted.

#### `400` error

Bad Request. Codes: [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON, or uses an unsupported content encoding).

As `application/problem+json`.

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`session_required`](/docs/api/errors#code-session-required) (an API key; only the person, signed in, accepts), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the account is suspended by DTG).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`terms_version_changed`](/docs/api/errors#code-terms-version-changed) (the versions sent are not the ones in force; `terms_version` and `privacy_version` name them).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential; no organization bucket on an account-only route), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`session_required`](/docs/api/errors#code-session-required) (403), [`account_suspended`](/docs/api/errors#code-account-suspended) (403), [`terms_version_changed`](/docs/api/errors#code-terms-version-changed) (409), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/account/close`

**What closing this account would do**

Credentials: API key or App session. Operation id `acctPreviewAccountClose`.

Account-only: it authenticates the person (an API key or a session) and resolves NO organization, so `X-DTG-Org` is ignored and it answers a person with no active membership at all, or whose only organization is suspended or closed. It does not apply the live/sandbox instance check. D56: lists the organizations that would block the close, those that would close with it and those it would leave, so the confirm dialog can state the consequences. No side effects.

### Responses

#### `200` success

The preview.

As `application/json`.

**`blocked_by` array of AcctOrgRef always present**

Organizations that block the close: this person is their only owner and they still have other members. Empty when nothing blocks; while non-empty, `POST /v2/account/close` answers 409 [`last_owner`](/docs/api/errors#code-last-owner).

**`blocked_by[].org_id` string (uuid) always present**

The organization's id.

**`blocked_by[].name` string always present**

The organization's name.

**`organizations_closing` array of AcctOrgRef always present**

Organizations where this person is the only active member: they close with the account (a paid subscription is cancelled at once, no refund).

**`organizations_closing[].org_id` string (uuid) always present**

The organization's id.

**`organizations_closing[].name` string always present**

The organization's name.

**`organizations_leaving` array of AcctOrgRef always present**

Organizations that carry on without this person: the membership ends, the seat frees, their keys there are revoked.

**`organizations_leaving[].org_id` string (uuid) always present**

The organization's id.

**`organizations_leaving[].name` string always present**

The organization's name.

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential; no organization bucket on an account-only route), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/close`

**Close this account**

Credentials: API key or App session. Operation id `acctCloseAccount`.

Account-only: it authenticates the person (an API key or a session) and resolves NO organization, so `X-DTG-Org` is ignored and it answers a person with no active membership at all, or whose only organization is suspended or closed. It does not apply the live/sandbox instance check. D56: cannot be undone. Takes no body. Every membership ends (seat freed, keys revoked, pending join requests denied); the address and display name are replaced by a tombstone; the sign-in identity, credentials, every session (this one included), the person's notifications, support tickets and known devices are deleted; marketing mail stops; a running trial ends `cancelled`. An organization where the person was the only member closes with them and its subscription is cancelled at once with no refund (retried hourly if the payment gateway fails). The owners and admins of each organization that carries on get a `member_departed` notification; an `account.closed` audit row is written. The person is mailed, once, at the address as it was before the tombstone (T124: 'Your Data Tactical Group account is closed': what was removed at once, what remains and for how long, in the Privacy Policy's words, and that the account cannot be reopened); a close made through the admin door sends no mail. Refused while the person is the only owner of an organization with other members. Idempotent: closing a closed account reports zeros.

### Responses

#### `200` success

What the close did.

As `application/json`.

**`account_id` string (uuid) always present**

The closed account's id.

**`memberships_removed` integer always present**

Memberships ended.

**`organizations_closed` array of string (uuid) always present**

Ids of the organizations that closed with the account (those where this person was the only member).

**`trial_cancelled` boolean always present**

True when a running trial was ended (`cancelled`).

#### `400` error

Bad Request. Codes: [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON, or uses an unsupported content encoding).

As `application/problem+json`.

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `403` error

Forbidden. [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (a sandbox key may write only clusters), and the authentication middleware's [`no_membership`](/docs/api/errors#code-no-membership), [`account_suspended`](/docs/api/errors#code-account-suspended), [`org_suspended`](/docs/api/errors#code-org-suspended), [`org_closed`](/docs/api/errors#code-org-closed), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (`base_url` names the address that serves the key).

As `application/problem+json`.

#### `404` error

Not Found. Codes: [`not_found`](/docs/api/errors#code-not-found) (the account row no longer exists).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`last_owner`](/docs/api/errors#code-last-owner) (the person is the only owner of an organization that still has other members; the body lists them in `organizations`; transfer ownership first).

As `application/problem+json`.

#### `413` error

Payload Too Large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the body is over this endpoint's 4 KB limit; the body carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential; no organization bucket on an account-only route), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`no_membership`](/docs/api/errors#code-no-membership) (403), [`account_suspended`](/docs/api/errors#code-account-suspended) (403), [`org_suspended`](/docs/api/errors#code-org-suspended) (403), [`org_closed`](/docs/api/errors#code-org-closed) (403), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`last_owner`](/docs/api/errors#code-last-owner) (409), [`body_too_large`](/docs/api/errors#code-body-too-large) (413), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/account/sessions`

**The person's live browser sessions**

Credentials: API key or App session. Operation id `acctListSessions`.

Account-only: it authenticates the person (an API key or a session) and resolves NO organization, so `X-DTG-Org` is ignored and it answers a person with no active membership at all, or whose only organization is suspended or closed. It does not apply the live/sandbox instance check. Lists every unexpired session of the person (ORG\_LICENSING 12.9), whichever credential asks.

### Responses

#### `200` success

The sessions.

As `application/json`.

**`policy` "one\_active\_session\_per\_credential" always present**

Always `one_active_session_per_credential`: a new sign-in ends the previous session (ORG\_LICENSING 4.2).

**`data` array of AcctSession always present**

The sessions.

**`data[].id` string (uuid) always present**

The session id (the `{id}` for `DELETE /v2/account/sessions/{id}`).

**`data[].created_at` string (date-time) always present**

When the session was created (the sign-in).

**`data[].last_seen_at` string (date-time) always present**

When the session was last refreshed.

**`data[].expires_at` string (date-time) always present**

When the session expires unless refreshed (the sliding window); it ends at the 30-day maximum regardless.

**`data[].ip` string or null always present**

The address the session signed in from, when recorded.

**`data[].user_agent` string or null always present**

The browser's user agent at sign-in, when recorded.

**`data[].current` boolean always present**

True for the session making this request (always false on an API key).

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential; no organization bucket on an account-only route), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## DELETE `/v2/account/sessions/{id}`

**End one session**

Credentials: API key or App session. Operation id `acctRevokeSession`.

Account-only: it authenticates the person (an API key or a session) and resolves NO organization, so `X-DTG-Org` is ignored and it answers a person with no active membership at all, or whose only organization is suspended or closed. It does not apply the live/sandbox instance check. Ends one of the person's own sessions (it may be the current one). Takes effect within 15 seconds on every server process (sessions are cached that long).

### Parameters

**`id` path · string (uuid) required**

The session id (a uuid, from `GET /v2/account/sessions`).

### Responses

#### `204` success

No Content.

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `403` error

Forbidden. [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (a sandbox key may write only clusters), and the authentication middleware's [`no_membership`](/docs/api/errors#code-no-membership), [`account_suspended`](/docs/api/errors#code-account-suspended), [`org_suspended`](/docs/api/errors#code-org-suspended), [`org_closed`](/docs/api/errors#code-org-closed), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (`base_url` names the address that serves the key).

As `application/problem+json`.

#### `404` error

Not Found. Codes: [`not_found`](/docs/api/errors#code-not-found) (no such session of this person).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential; no organization bucket on an account-only route), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`no_membership`](/docs/api/errors#code-no-membership) (403), [`account_suspended`](/docs/api/errors#code-account-suspended) (403), [`org_suspended`](/docs/api/errors#code-org-suspended) (403), [`org_closed`](/docs/api/errors#code-org-closed) (403), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/sessions/sign-out-everywhere`

**End every session**

Credentials: API key or App session. Operation id `acctSignOutEverywhere`.

Account-only: it authenticates the person (an API key or a session) and resolves NO organization, so `X-DTG-Org` is ignored and it answers a person with no active membership at all, or whose only organization is suspended or closed. It does not apply the live/sandbox instance check. Takes no body. Ends every browser session of the person, the current one included, within 15 seconds. API keys are not affected.

### Responses

#### `204` success

No Content.

#### `400` error

Bad Request. Codes: [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON, or uses an unsupported content encoding).

As `application/problem+json`.

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `403` error

Forbidden. [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (a sandbox key may write only clusters), and the authentication middleware's [`no_membership`](/docs/api/errors#code-no-membership), [`account_suspended`](/docs/api/errors#code-account-suspended), [`org_suspended`](/docs/api/errors#code-org-suspended), [`org_closed`](/docs/api/errors#code-org-closed), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (`base_url` names the address that serves the key).

As `application/problem+json`.

#### `413` error

Payload Too Large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the body is over this endpoint's 4 KB limit; the body carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential; no organization bucket on an account-only route), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`no_membership`](/docs/api/errors#code-no-membership) (403), [`account_suspended`](/docs/api/errors#code-account-suspended) (403), [`org_suspended`](/docs/api/errors#code-org-suspended) (403), [`org_closed`](/docs/api/errors#code-org-closed) (403), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (403), [`body_too_large`](/docs/api/errors#code-body-too-large) (413), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/account/trial`

**Trial eligibility and state**

Credentials: API key or App session. Operation id `acctGetTrial`.

Either credential (an API key or a session); resolves the caller's active organization (`X-DTG-Org`). Any role. D16-trial/D40: one self-serve trial per person. Reports whether one can start, the running one, or the earlier one. No side effects.

### Parameters

**`X-DTG-Org` header · string (uuid)**

The organization this request acts in (a uuid). Omitted: the caller's own organization, else their earliest active membership. An API key is bound to its organization and may only name that one.

### Responses

#### `200` success

Eligibility.

As `application/json`.

**`eligible` boolean always present**

True when `POST /v2/account/trial` would start one.

**`reason` "eligible" | "active" | "cooldown" | "unverified" always present**

`eligible`; `active` (a trial is running); `cooldown` (A trial has already run for this person.); `unverified` (the email address is not verified; checked first).

**`eligible_again_at` null always present**

Always null (no date is stated to customers).

**`active_trial` object or null always present**

The running trial when `reason` is `active`; else null.

**`active_trial.id` string (uuid) always present**

The trial id.

**`active_trial.county` string always present**

The trial county's name.

**`active_trial.fips` string always present**

Its 5-digit FIPS code.

**`active_trial.expires_at` string (date-time) always present**

When it ends.

**`previous` object or null always present**

The person's latest earlier trial when `reason` is `cooldown` or `eligible`; null otherwise.

**`previous.id` string (uuid) always present**

The trial id.

**`previous.county` string always present**

Its county's name.

**`previous.fips` string always present**

Its 5-digit FIPS code.

**`previous.started_at` string (date-time) always present**

When it started.

**`previous.ended_at` string (date-time) or null always present**

When it ended; null when it lapsed without being marked.

**`cooldown_days` null always present**

Always null (not stated to customers).

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active membership in an active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the signed-in person's own account is suspended by DTG; `reason` where recorded), [`org_suspended`](/docs/api/errors#code-org-suspended) (the selected organization is suspended by DTG; the body carries `organization {id, name}` and `reason`), [`org_closed`](/docs/api/errors#code-org-closed) (the selected organization is closed; the body carries `organization {id, name}`), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (a key of the mode this process does not serve: a sandbox key on the live API, or a live key on the sandbox; the body carries `expected` and `base_url`, the address that serves it).

As `application/problem+json`.

#### `404` error

Not Found. Codes: [`not_found`](/docs/api/errors#code-not-found) (the `X-DTG-Org` header is not a uuid, names an organization the caller is not an active member of, or, on an API key, names an organization other than the key's own).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential, 300 per organization; lower ceilings for a sandbox key), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`no_membership`](/docs/api/errors#code-no-membership) (403), [`account_suspended`](/docs/api/errors#code-account-suspended) (403), [`org_suspended`](/docs/api/errors#code-org-suspended) (403), [`org_closed`](/docs/api/errors#code-org-closed) (403), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/trial`

**Start a one-county trial**

Credentials: API key or App session. Operation id `acctStartTrial`.

Either credential (an API key or a session); resolves the caller's active organization (`X-DTG-Org`). Any role; needs a verified email address. The trial runs in the person's OWN organization whichever organization the request acts in, for `rules.trial_days` days: the county is added to it and every paid capability is unlocked for that county (full depth, quarterly, earlier vintages, export of that county), but not API access. A confirmation mail and a `trial_started` notification are sent. One trial per person: once one has run, the answer is `409 trial_cooldown`, "A trial has already run for this person."

### Parameters

**`X-DTG-Org` header · string (uuid)**

The organization this request acts in (a uuid). Omitted: the caller's own organization, else their earliest active membership. An API key is bound to its organization and may only name that one.

### Request body

Required; `application/json`.

**`fips` string required**

One 5-digit county FIPS code (for example `22071`). (pattern ^\[0-9\]{5}$)

### Responses

#### `201` success

The trial.

As `application/json`.

**`trial_id` string (uuid) always present**

The trial id.

**`county` string always present**

The county's name.

**`fips` string always present**

Its 5-digit FIPS code.

**`expires_at` string (date-time) always present**

When the trial ends.

**`reactivated` boolean always present**

Always false on this route (true only for a trial DTG grants that restores an earlier county).

**`organization_id` string (uuid) or null always present**

The organization the trial runs in when it is the active one; null when the request acted in another organization (the trial always runs in the person's OWN organization).

**`note` string always present**

Says that the trial runs in the person's own organization.

#### `400` error

Bad Request. Codes: [`bad_request`](/docs/api/errors#code-bad-request) (`fips` is missing or not one 5-digit code, or names no county), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON, or uses an unsupported content encoding).

As `application/problem+json`.

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active membership in an active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the signed-in person's own account is suspended by DTG; `reason` where recorded), [`org_suspended`](/docs/api/errors#code-org-suspended) (the selected organization is suspended by DTG; the body carries `organization {id, name}` and `reason`), [`org_closed`](/docs/api/errors#code-org-closed) (the selected organization is closed; the body carries `organization {id, name}`), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (a key of the mode this process does not serve: a sandbox key on the live API, or a live key on the sandbox; the body carries `expected` and `base_url`, the address that serves it), [`email_unverified`](/docs/api/errors#code-email-unverified) (verify the email address first). A sandbox key: [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (it may write only clusters).

As `application/problem+json`.

#### `404` error

Not Found. Codes: [`not_found`](/docs/api/errors#code-not-found) (the `X-DTG-Org` header is not a uuid, names an organization the caller is not an active member of, or, on an API key, names an organization other than the key's own).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`trial_active`](/docs/api/errors#code-trial-active) (a trial is running; the body carries `trial`), [`trial_cooldown`](/docs/api/errors#code-trial-cooldown) (A trial has already run for this person. `eligible_again_at` is null).

As `application/problem+json`.

#### `413` error

Payload Too Large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the body is over this endpoint's 4 KB limit; the body carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential, 300 per organization; lower ceilings for a sandbox key), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`bad_request`](/docs/api/errors#code-bad-request) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`no_membership`](/docs/api/errors#code-no-membership) (403), [`account_suspended`](/docs/api/errors#code-account-suspended) (403), [`org_suspended`](/docs/api/errors#code-org-suspended) (403), [`org_closed`](/docs/api/errors#code-org-closed) (403), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (403), [`email_unverified`](/docs/api/errors#code-email-unverified) (403), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`trial_active`](/docs/api/errors#code-trial-active) (409), [`trial_cooldown`](/docs/api/errors#code-trial-cooldown) (409), [`body_too_large`](/docs/api/errors#code-body-too-large) (413), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/trial/cancel`

**End the running trial**

Credentials: API key or App session. Operation id `acctCancelTrial`.

Either credential (an API key or a session); resolves the caller's active organization (`X-DTG-Org`). Any role. Takes no body. Ends the person's running trial (`cancelled`) and removes the trial county from their own organization.

### Parameters

**`X-DTG-Org` header · string (uuid)**

The organization this request acts in (a uuid). Omitted: the caller's own organization, else their earliest active membership. An API key is bound to its organization and may only name that one.

### Responses

#### `204` success

No Content.

#### `400` error

Bad Request. Codes: [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON, or uses an unsupported content encoding).

As `application/problem+json`.

#### `401` error

Unauthorized. Codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (the API key was revoked; `revoked_at`, `reason`, and a sentence saying when and why); [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no API key or session; the key is unknown, or its account, membership or organization is no longer active; the session has expired, passed its 30-day maximum age, or belongs to an unverified email address).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active membership in an active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the signed-in person's own account is suspended by DTG; `reason` where recorded), [`org_suspended`](/docs/api/errors#code-org-suspended) (the selected organization is suspended by DTG; the body carries `organization {id, name}` and `reason`), [`org_closed`](/docs/api/errors#code-org-closed) (the selected organization is closed; the body carries `organization {id, name}`), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (a key of the mode this process does not serve: a sandbox key on the live API, or a live key on the sandbox; the body carries `expected` and `base_url`, the address that serves it). A sandbox key: [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (it may write only clusters).

As `application/problem+json`.

#### `404` error

Not Found. Codes: [`not_found`](/docs/api/errors#code-not-found) (the `X-DTG-Org` header is not a uuid, names an organization the caller is not an active member of, or, on an API key, names an organization other than the key's own), [`not_found`](/docs/api/errors#code-not-found) also when the person has no running trial.

As `application/problem+json`.

#### `413` error

Payload Too Large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the body is over this endpoint's 4 KB limit; the body carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

Only the per-minute rate limit: [`rate_limited`](/docs/api/errors#code-rate-limited) (120 requests a minute per credential, 300 per organization; lower ceilings for a sandbox key), with `Retry-After` and `X-RateLimit-*`.

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

**`X-RateLimit-Limit` header**

The per-minute request ceiling that applies.

**`X-RateLimit-Remaining` header**

Requests left in the current window.

**`X-RateLimit-Reset` header**

Seconds until the window refills: on every answer, the time until the bucket is full again; on a refusal, until a request is allowed (as `Retry-After`).

Problem codes: [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`no_membership`](/docs/api/errors#code-no-membership) (403), [`account_suspended`](/docs/api/errors#code-account-suspended) (403), [`org_suspended`](/docs/api/errors#code-org-suspended) (403), [`org_closed`](/docs/api/errors#code-org-closed) (403), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (403), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`body_too_large`](/docs/api/errors#code-body-too-large) (413), [`rate_limited`](/docs/api/errors#code-rate-limited) (429). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/email/unsubscribe`

**Stop product (marketing) email**

Credentials: none. Operation id `acctUnsubscribe`.

Anonymous: the link in a product mail. D40: sets the token's account to opted out at once; idempotent. Transactional mail continues. Not rate limited.

### Parameters

**`token` query · string required**

The unsubscribe token from the mail's link.

### Responses

#### `200` success

Unsubscribed: a one-line plain-text confirmation naming the address.

As `text/plain`.

#### `400` error

Bad Request. Codes: [`bad_request`](/docs/api/errors#code-bad-request) (`token` is missing or empty).

As `application/problem+json`.

#### `404` error

Not Found. Codes: [`not_found`](/docs/api/errors#code-not-found) (unknown token).

As `application/problem+json`.

Problem codes: [`bad_request`](/docs/api/errors#code-bad-request) (400), [`not_found`](/docs/api/errors#code-not-found) (404). Every problem is a [problem document](/docs/api/errors).

Generated from `taigan/openapi.yaml` at catahoula `77925fab712f`(API document version 2.0).
