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

# Billing

16 endpoints, generated from the API's own OpenAPI document. The self-serve subscription: plan, interval, seats, county slots, the API add-on, the card, invoices.

## GET `/v2/plans`

**The plan catalogue**

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

Either credential (an API key or a session); resolves the caller's active organization (`X-DTG-Org`). Any role. T14/T31: the Trial row, then every active plan in price order, with prices, counties, seats, the monthly points allowance and the API add-on. Every price is read from the payment catalog the checkout charges. Takes no query parameters. 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

The catalogue.

As `application/json`.

**`data` array of AcctPlanRow always present**

The rows.

**`data[].code` string always present**

The plan code (`trial` for the Trial row).

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

The display name (D42-FINAL).

**`data[].price` object always present**

Prices in US dollars.

**`data[].price.month_usd` number or null always present**

Monthly price; null for the Trial row.

**`data[].price.year_usd` number or null always present**

Annual price; null for the Trial row.

**`data[].counties` object always present**

Counties.

**`data[].counties.included` integer always present**

Counties included; -1 means every county.

**`data[].counties.all` boolean always present**

True when every county is included.

**`data[].counties.extra_month_usd` number or null always present**

Price of one extra county slot a month; null when not sold.

**`data[].counties.extra_year_usd` number or null always present**

Price of one extra county slot a year; null when not sold.

**`data[].seats` object always present**

Seats.

**`data[].seats.included` integer or null always present**

Seats included; null for the Trial row.

**`data[].seats.extra_month_usd` number or null always present**

Price of one extra seat a month; null when not sold.

**`data[].seats.extra_year_usd` number or null always present**

Price of one extra seat a year; null when not sold.

**`data[].points_quota_month` integer or null always present**

The plan's monthly points allowance without the API add-on; null when unlimited, and on the Trial row.

**`data[].csv_access` boolean or null always present**

True when CSV and XLSX export is included; null on the Trial row.

**`data[].vintage_access` boolean or null always present**

True when earlier vintages are included; null on the Trial row.

**`data[].quarterly_access` boolean or null always present**

True when quarterly data is included; null on the Trial row.

**`data[].api_addon` object always present**

The API add-on with this plan.

**`data[].api_addon.available` boolean always present**

True when the add-on is sold with this plan.

**`data[].api_addon.month_usd` number or null always present**

Always null: the add-on is sold with an annual plan only.

**`data[].api_addon.year_usd` number or null always present**

Its annual price; null when not sold.

**`data[].api_addon.points_quota_month` integer or null always present**

The monthly points allowance with the add-on; null when not sold or unlimited.

**`data[].trial` object or null always present**

The trial's terms on the Trial row; null on every plan row.

**`data[].trial.days` integer always present**

Trial length in days (`rules.trial_days`).

**`data[].trial.cooldown_days` null always present**

Always null (not stated to customers).

**`data[].trial.counties` integer always present**

Always 1: a trial unlocks one county.

**`data[].trial.export` "trial\_county" always present**

Always `trial_county`: export of the trial county only.

**`data[].trial.live_keys` boolean always present**

Always false: a trial grants no live API keys.

**`meta` object always present**

T153: which payment catalog the prices were read from.

**`meta.stripe_mode` "fake" | "test" | "live" always present**

The Stripe mode of the catalog every paid price here was read from: `live` (production on live keys), `test` (Stripe test mode) or `fake` (the development simulation). A pricing check reads it to know which catalog it compared.

#### `400` error

Bad Request. Codes: [`unknown_parameter`](/docs/api/errors#code-unknown-parameter) (any query parameter; the body carries `parameters`).

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).

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: [`unknown_parameter`](/docs/api/errors#code-unknown-parameter) (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), [`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).

## GET `/v2/account/billing`

**The organization's billing state**

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

The plan, interval, add-ons, seats, county slots, the current subscription and its renewal, what is scheduled for the period end (`pending_change`), the default payment method (brand, last four, expiry) and the 12 most recent invoices. Either credential (API key or session). The owner or a billing contact only (O63 S11, Q22): anyone else, an admin included, is answered 403 [`role_required`](/docs/api/errors#code-role-required) (`required: [owner, billing_contact]`); whether service is paused reaches every member as `organization.past_due` on `GET /v2/account`. Prices and allowances in the response are read from the plan catalog; `GET /v2/plans` is canonical. While `subscription.status` is `past_due`, L18 pauses county detail, CSV/XLSX export, live API keys and cluster editing (not billing): fix the card with the payment-method endpoints and pay with `POST /v2/account/billing/retry`.

### Responses

#### `200` success

The billing summary.

As `application/json`.

**`gateway` "fake" | "test" | "live" always present**

Which payment gateway this server runs: `live` (Stripe live mode, production only), `test` (Stripe test mode) or `fake` (an in-process simulation used in development and tests). T153: the Stripe secret key's prefix decides between `test` and `live`.

**`publishable_key` string or null always present**

The Stripe publishable key to initialise Stripe.js and the Payment Element with.

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

`self_serve` (card subscription managed through these endpoints) or `invoiced` (a contract managed by Data Tactical Group: every self-serve change answers 403 [`contract_managed`](/docs/api/errors#code-contract-managed) ).

**`contract` object or null always present**

The contract terms for an `invoiced` organization; null for `self_serve`.

**`contract.expires_at` string (date-time) or null**

When the contract ends; null for no end date.

**`contract.notes` string or null**

DTG's notes on the contract.

**`plan` object always present**

The plan the organization holds now.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

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

The plan's display name (e.g. Local, Regional, National, Free).

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

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Active add-ons.

**`seats` object always present**

Seats: the plan's included seats plus purchased extra seats.

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

Seats the plan includes (the plan's allowance; see `GET /v2/plans`), raised to `grandfathered` while that is set.

**`seats.grandfathered` integer or null**

T163: an organization that had more seated members than its plan's new included count when Regional fell to 10 seats and National to 15 keeps that many included seats until its seated members fall to the plan's count or its plan changes. This is that count; null for every other organization.

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

Extra seats the organization holds beyond `included`. A scheduled decrease does not lower this until period end.

**`seats.billed_quantity` integer always present**

The seat quantity on the Stripe subscription (for an `invoiced` organization, the contracted extra seats; 0 with no subscription). Can differ from `purchased` briefly while a scheduled decrease has been sent to Stripe ahead of the renewal.

**`seats.seat_price_usd_month` number or null always present**

The plan's monthly price of one extra seat, in US dollars; null when the plan sells no extra seats.

**`seats.proration` "always\_invoice" always present**

How a seat increase is billed: always `always_invoice` (prorated to the rest of the period and charged to the card at purchase).

**`seats.proration_note` string always present**

A sentence stating the seat billing rule for display.

**`counties` object always present**

County slots: the plan's included counties plus paid county slots. The county SET is managed at `/v2/account/counties`; this is the billing quantity behind it.

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

Counties the plan includes; -1 means every county (National).

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

Paid county slots beyond `included`.

**`counties.billed_quantity` integer always present**

The county-slot quantity on the Stripe subscription (for an `invoiced` organization, the contracted slots; 0 with no subscription).

**`counties.active` integer always present**

Counties currently in the organization's active set, not counting a trial county.

**`counties.county_price_usd_month` number or null always present**

The plan's monthly price of one extra county slot, in US dollars; null when the plan sells no extra counties.

**`counties.proration` "always\_invoice" always present**

How a slot increase is billed: always `always_invoice` (prorated and charged at purchase).

**`counties.proration_note` string always present**

A sentence stating the county-slot billing rule for display.

**`counties.anchor_day` integer or null always present**

Day of the month of the organization's change window (the subscription's anchor date), on which county set changes land; null before a first subscription. (1 to 31)

**`subscription` object or null always present**

The current Stripe subscription (status active, trialing, past\_due or incomplete); null when there is none (Free, or contract-managed without one).

**`subscription.id` string**

Stripe subscription id (`sub_...`).

**`subscription.status` "active" | "trialing" | "past\_due" | "incomplete"**

`active`; `trialing`; `past_due` (a renewal was declined: L18 pauses county detail, CSV/XLSX export, live API keys and cluster editing at once, while the plan and its capacity stay held; pay with `POST /v2/account/billing/retry` after fixing the card); `incomplete` (the first payment has not succeeded).

**`subscription.current_period_start` string (date-time) or null**

Start of the current billing period.

**`subscription.current_period_end` string (date-time) or null**

End of the current billing period: where scheduled changes and a cancellation take effect.

**`subscription.renewal_date` string (date-time) or null**

When the subscription renews (the period end); null when it is set to cancel at period end.

**`subscription.cancel_at_period_end` boolean**

True when the subscription ends at `current_period_end` instead of renewing.

**`subscription.past_due_since` string (date-time) or null**

When the subscription first became past due; null when it is not past due.

**`subscription.dunning_grace_days` integer**

Days a past-due subscription has before it is cancelled and the organization drops to Free (the downgrade composite applies). Service is paused from the decline, not from the end of this window.

**`pending_change` BillPendingChange or null always present**

What is scheduled for the period end; null when nothing (and no cancellation) is scheduled.

**`pending_change.plan_code` string or null**

The plan the organization moves to at period end; null when no plan move is scheduled (an interval-only move leaves this null and sets `interval`).

**`pending_change.interval` "month" | "year" | null**

The interval the organization moves to at period end; null when no interval move is scheduled.

**`pending_change.seats_purchased` integer or null**

The purchased seat quantity from period end; null when no seat decrease is scheduled.

**`pending_change.counties_purchased` integer or null**

The paid county-slot quantity from period end; null when no slot release is scheduled.

**`pending_change.remove_addons` array of string**

Add-ons removed at period end; empty when none.

**`pending_change.cancel` boolean**

True when the subscription is set to cancel at period end (`POST /v2/account/billing/cancel`); `POST /v2/account/billing/resume` clears it and nothing else.

**`pending_change.effective_at` string (date-time)**

The period end at which everything here takes effect.

**`pending_change.changes` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval**

The scheduled set, one item per quantity. A cancellation is not an item here (see `cancel`).

**`pending_change.changes[].kind` "plan"**

Always `plan`.

**`pending_change.changes[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`pending_change.changes[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`pending_change.changes[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`pending_change.changes[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`pending_change.changes[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`pending_change.changes[].addons` array of string**

The add-ons that will be removed.

**`payment_method` BillPaymentMethod or null always present**

The default payment method; null when none is set.

**`payment_method.id` string**

Stripe PaymentMethod id (`pm_...`).

**`payment_method.brand` string**

Card brand as Stripe reports it, e.g. `visa`, `mastercard`.

**`payment_method.last4` string**

Last four digits of the card number.

**`payment_method.exp_month` integer**

Card expiry month. (1 to 12)

**`payment_method.exp_year` integer**

Card expiry year (four digits).

**`invoices` array of BillInvoice always present**

Up to the 12 most recent invoices, newest first.

**`invoices[].id` string always present**

Stripe invoice id (`in_...`).

**`invoices[].number` string or null always present**

The invoice number printed on it; null while a draft.

**`invoices[].status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`invoices[].total_cents` integer always present**

Invoice total in US cents.

**`invoices[].amount_due_cents` integer always present**

Amount still owed, in US cents.

**`invoices[].amount_paid_cents` integer always present**

Amount paid, in US cents.

**`invoices[].created` string (date-time) always present**

When the invoice was created.

**`invoices[].due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`invoices[].period` object always present**

The service period the invoice covers.

**`invoices[].period.start` string (date-time) always present**

Period start.

**`invoices[].period.end` string (date-time) always present**

Period end.

**`invoices[].hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`invoices[].collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

**`invoices[].description` string or null always present**

Free-text memo on the invoice (contract invoices), else null.

**`invoices[].lines` array of BillInvoiceLine always present**

The invoice's lines.

**`invoices[].lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`invoices[].lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`invoices[].lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`invoices[].lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 names an organization the caller does not belong to).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (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). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/account/billing/invoices`

**The organization's recent invoices**

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

Up to the 12 most recent Stripe invoices, newest first (the same list as `invoices` in `GET /v2/account/billing`). Not paged. Either credential (API key or session). The owner or a billing contact only (O63 S11, Q22; 403 [`role_required`](/docs/api/errors#code-role-required) otherwise).

### Responses

#### `200` success

The invoices.

As `application/json`.

**`data` array of BillInvoice always present**

Up to the 12 most recent invoices, newest first. Empty when the organization has never had a Stripe customer.

**`data[].id` string always present**

Stripe invoice id (`in_...`).

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

The invoice number printed on it; null while a draft.

**`data[].status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`data[].total_cents` integer always present**

Invoice total in US cents.

**`data[].amount_due_cents` integer always present**

Amount still owed, in US cents.

**`data[].amount_paid_cents` integer always present**

Amount paid, in US cents.

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

When the invoice was created.

**`data[].due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`data[].period` object always present**

The service period the invoice covers.

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

Period start.

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

Period end.

**`data[].hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`data[].collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

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

Free-text memo on the invoice (contract invoices), else null.

**`data[].lines` array of BillInvoiceLine always present**

The invoice's lines.

**`data[].lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`data[].lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`data[].lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`data[].lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 names an organization the caller does not belong to).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (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). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/billing/payment-method`

**Start adding a card (a SetupIntent for the Payment Element)**

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

Creates a Stripe SetupIntent for the organization (creating its Stripe customer on first use) and returns its client secret for Stripe's Payment Element, which collects the card in the browser (card data never reaches Taigan). Then call `POST /v2/account/billing/payment-method/confirm`. No request body. Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact). Not refused for a contract-managed organization. T108: takes the organization's billing lock, as every billing write does (409 [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) after 15 s).

### Responses

#### `201` success

SetupIntent created.

As `application/json`.

**`setup_intent_id` string always present**

SetupIntent id (`seti_...`); send it to `POST /v2/account/billing/payment-method/confirm` once Stripe.js has confirmed it.

**`client_secret` string always present**

The SetupIntent's client secret, for the Payment Element. Treat as a secret; do not log it.

**`publishable_key` string or null always present**

Stripe publishable key to initialise Stripe.js with.

**`gateway` "fake" | "test" | "live" always present**

`live` (Stripe live mode, production only), `test` (Stripe test mode) or `fake` (development simulation).

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 names an organization the caller does not belong to).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`role_required`](/docs/api/errors#code-role-required) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/billing/payment-method/confirm`

**Make the confirmed card the default payment method**

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

After the Payment Element has confirmed the SetupIntent, makes its card the organization's default payment method for every future charge. Writes the audit row `billing.payment_method_set` (brand and last four only). Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact). T108: takes the organization's billing lock (409 [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) after 15 s); an unknown `setup_intent_id` is the same 404 as another organization's (it answered 500).

### Request body

Required; `application/json`.

**`setup_intent_id` string required**

The SetupIntent from `POST /v2/account/billing/payment-method`, after the Payment Element confirmed it.

### Responses

#### `200` success

The new default payment method.

As `application/json`.

**`payment_method` BillPaymentMethod always present**

The organization's default payment method: the card brand, last four digits and expiry only (card data never reaches Taigan; it is captured by Stripe's Payment Element). When the card details cannot be read only `id` is present.

**`payment_method.id` string always present**

Stripe PaymentMethod id (`pm_...`).

**`payment_method.brand` string**

Card brand as Stripe reports it, e.g. `visa`, `mastercard`.

**`payment_method.last4` string**

Last four digits of the card number.

**`payment_method.exp_month` integer**

Card expiry month. (1 to 12)

**`payment_method.exp_year` integer**

Card expiry year (four digits).

#### `400` error

Bad request. Codes: [`bad_request`](/docs/api/errors#code-bad-request) (`setup_intent_id` is missing or not a string), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON).

As `application/problem+json`.

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 SetupIntent does not belong to this organization), [`not_found`](/docs/api/errors#code-not-found) (the `X-DTG-Org` header names an organization the caller does not belong to).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`setup_intent_incomplete`](/docs/api/errors#code-setup-intent-incomplete) (the SetupIntent has not succeeded yet; confirm it with the Payment Element first; the problem carries Stripe's `status`, e.g. `requires_payment_method`).

As `application/problem+json`.

#### `413` error

Payload too large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the JSON body is over 16 KB; the problem carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`role_required`](/docs/api/errors#code-role-required) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`setup_intent_incomplete`](/docs/api/errors#code-setup-intent-incomplete) (409), [`body_too_large`](/docs/api/errors#code-body-too-large) (413). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/billing/subscribe`

**Create the organization's first subscription**

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

Subscribes the organization to a plan at an interval, optionally with extra seats, paid county slots and (annual only) the API add-on. Charges the default payment method, so one must be set first. Ends any running trial as converted (the trial county becomes an ordinary county). An annual term is non-refundable from the purchase; cancelling stops renewal and access runs to the paid-through date. Writes the audit row `billing.subscribed`. Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact). Billing changes for one organization are serialized: a second one waits for the first and, after 15 seconds, answers 409 [`billing_in_progress`](/docs/api/errors#code-billing-in-progress).

### Request body

Required; `application/json`.

**`plan_code` string required**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`interval` object**

Billing interval; default `month`. (default "month")

**`seats` integer**

Extra seats to buy beyond the plan's included seats. (default 0; 0 to 500)

**`counties` integer**

Paid county slots to buy beyond the plan's included counties; only on a plan that sells extra counties. (default 0; 0 to 3194)

**`addons` array of "api"**

Add-ons to buy with the plan: only `api`, and only with `interval: year`.

### Responses

#### `201` success

Subscription created (check `status`: `incomplete` means the first payment did not succeed).

As `application/json`.

**`subscription_id` string always present**

Stripe subscription id (`sub_...`).

**`status` string always present**

Stripe's status for the new subscription: `active` (or `trialing`) when the first payment succeeded; `incomplete` when it did not (no entitlement until it is paid).

**`summary` BillSummary always present**

The organization's billing state as Kelpie's seats-and-billing screen shows it.

**`summary.gateway` "fake" | "test" | "live" always present**

Which payment gateway this server runs: `live` (Stripe live mode, production only), `test` (Stripe test mode) or `fake` (an in-process simulation used in development and tests). T153: the Stripe secret key's prefix decides between `test` and `live`.

**`summary.publishable_key` string or null always present**

The Stripe publishable key to initialise Stripe.js and the Payment Element with.

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

`self_serve` (card subscription managed through these endpoints) or `invoiced` (a contract managed by Data Tactical Group: every self-serve change answers 403 [`contract_managed`](/docs/api/errors#code-contract-managed) ).

**`summary.contract` object or null always present**

The contract terms for an `invoiced` organization; null for `self_serve`.

**`summary.contract.expires_at` string (date-time) or null**

When the contract ends; null for no end date.

**`summary.contract.notes` string or null**

DTG's notes on the contract.

**`summary.plan` object always present**

The plan the organization holds now.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

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

The plan's display name (e.g. Local, Regional, National, Free).

**`summary.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Active add-ons.

**`summary.seats` object always present**

Seats: the plan's included seats plus purchased extra seats.

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

Seats the plan includes (the plan's allowance; see `GET /v2/plans`), raised to `grandfathered` while that is set.

**`summary.seats.grandfathered` integer or null**

T163: an organization that had more seated members than its plan's new included count when Regional fell to 10 seats and National to 15 keeps that many included seats until its seated members fall to the plan's count or its plan changes. This is that count; null for every other organization.

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

Extra seats the organization holds beyond `included`. A scheduled decrease does not lower this until period end.

**`summary.seats.billed_quantity` integer always present**

The seat quantity on the Stripe subscription (for an `invoiced` organization, the contracted extra seats; 0 with no subscription). Can differ from `purchased` briefly while a scheduled decrease has been sent to Stripe ahead of the renewal.

**`summary.seats.seat_price_usd_month` number or null always present**

The plan's monthly price of one extra seat, in US dollars; null when the plan sells no extra seats.

**`summary.seats.proration` "always\_invoice" always present**

How a seat increase is billed: always `always_invoice` (prorated to the rest of the period and charged to the card at purchase).

**`summary.seats.proration_note` string always present**

A sentence stating the seat billing rule for display.

**`summary.counties` object always present**

County slots: the plan's included counties plus paid county slots. The county SET is managed at `/v2/account/counties`; this is the billing quantity behind it.

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

Counties the plan includes; -1 means every county (National).

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

Paid county slots beyond `included`.

**`summary.counties.billed_quantity` integer always present**

The county-slot quantity on the Stripe subscription (for an `invoiced` organization, the contracted slots; 0 with no subscription).

**`summary.counties.active` integer always present**

Counties currently in the organization's active set, not counting a trial county.

**`summary.counties.county_price_usd_month` number or null always present**

The plan's monthly price of one extra county slot, in US dollars; null when the plan sells no extra counties.

**`summary.counties.proration` "always\_invoice" always present**

How a slot increase is billed: always `always_invoice` (prorated and charged at purchase).

**`summary.counties.proration_note` string always present**

A sentence stating the county-slot billing rule for display.

**`summary.counties.anchor_day` integer or null always present**

Day of the month of the organization's change window (the subscription's anchor date), on which county set changes land; null before a first subscription. (1 to 31)

**`summary.subscription` object or null always present**

The current Stripe subscription (status active, trialing, past\_due or incomplete); null when there is none (Free, or contract-managed without one).

**`summary.subscription.id` string**

Stripe subscription id (`sub_...`).

**`summary.subscription.status` "active" | "trialing" | "past\_due" | "incomplete"**

`active`; `trialing`; `past_due` (a renewal was declined: L18 pauses county detail, CSV/XLSX export, live API keys and cluster editing at once, while the plan and its capacity stay held; pay with `POST /v2/account/billing/retry` after fixing the card); `incomplete` (the first payment has not succeeded).

**`summary.subscription.current_period_start` string (date-time) or null**

Start of the current billing period.

**`summary.subscription.current_period_end` string (date-time) or null**

End of the current billing period: where scheduled changes and a cancellation take effect.

**`summary.subscription.renewal_date` string (date-time) or null**

When the subscription renews (the period end); null when it is set to cancel at period end.

**`summary.subscription.cancel_at_period_end` boolean**

True when the subscription ends at `current_period_end` instead of renewing.

**`summary.subscription.past_due_since` string (date-time) or null**

When the subscription first became past due; null when it is not past due.

**`summary.subscription.dunning_grace_days` integer**

Days a past-due subscription has before it is cancelled and the organization drops to Free (the downgrade composite applies). Service is paused from the decline, not from the end of this window.

**`summary.pending_change` BillPendingChange or null always present**

What is scheduled for the period end; null when nothing (and no cancellation) is scheduled.

**`summary.pending_change.plan_code` string or null**

The plan the organization moves to at period end; null when no plan move is scheduled (an interval-only move leaves this null and sets `interval`).

**`summary.pending_change.interval` "month" | "year" | null**

The interval the organization moves to at period end; null when no interval move is scheduled.

**`summary.pending_change.seats_purchased` integer or null**

The purchased seat quantity from period end; null when no seat decrease is scheduled.

**`summary.pending_change.counties_purchased` integer or null**

The paid county-slot quantity from period end; null when no slot release is scheduled.

**`summary.pending_change.remove_addons` array of string**

Add-ons removed at period end; empty when none.

**`summary.pending_change.cancel` boolean**

True when the subscription is set to cancel at period end (`POST /v2/account/billing/cancel`); `POST /v2/account/billing/resume` clears it and nothing else.

**`summary.pending_change.effective_at` string (date-time)**

The period end at which everything here takes effect.

**`summary.pending_change.changes` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval**

The scheduled set, one item per quantity. A cancellation is not an item here (see `cancel`).

**`summary.pending_change.changes[].kind` "plan"**

Always `plan`.

**`summary.pending_change.changes[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`summary.pending_change.changes[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`summary.pending_change.changes[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`summary.pending_change.changes[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`summary.pending_change.changes[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`summary.pending_change.changes[].addons` array of string**

The add-ons that will be removed.

**`summary.payment_method` BillPaymentMethod or null always present**

The default payment method; null when none is set.

**`summary.payment_method.id` string**

Stripe PaymentMethod id (`pm_...`).

**`summary.payment_method.brand` string**

Card brand as Stripe reports it, e.g. `visa`, `mastercard`.

**`summary.payment_method.last4` string**

Last four digits of the card number.

**`summary.payment_method.exp_month` integer**

Card expiry month. (1 to 12)

**`summary.payment_method.exp_year` integer**

Card expiry year (four digits).

**`summary.invoices` array of BillInvoice always present**

Up to the 12 most recent invoices, newest first.

**`summary.invoices[].id` string always present**

Stripe invoice id (`in_...`).

**`summary.invoices[].number` string or null always present**

The invoice number printed on it; null while a draft.

**`summary.invoices[].status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`summary.invoices[].total_cents` integer always present**

Invoice total in US cents.

**`summary.invoices[].amount_due_cents` integer always present**

Amount still owed, in US cents.

**`summary.invoices[].amount_paid_cents` integer always present**

Amount paid, in US cents.

**`summary.invoices[].created` string (date-time) always present**

When the invoice was created.

**`summary.invoices[].due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`summary.invoices[].period` object always present**

The service period the invoice covers.

**`summary.invoices[].period.start` string (date-time) always present**

Period start.

**`summary.invoices[].period.end` string (date-time) always present**

Period end.

**`summary.invoices[].hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`summary.invoices[].collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

**`summary.invoices[].description` string or null always present**

Free-text memo on the invoice (contract invoices), else null.

**`summary.invoices[].lines` array of BillInvoiceLine always present**

The invoice's lines.

**`summary.invoices[].lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`summary.invoices[].lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`summary.invoices[].lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`summary.invoices[].lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

#### `400` error

Bad request. Codes: [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (the API add-on was requested with a non-annual interval, D48), [`bad_request`](/docs/api/errors#code-bad-request) (`plan_code` missing, unknown or not purchasable; `interval` not `month`/`year`; `seats` not an integer 0 to 500; `counties` not an integer 0 to 3194, or extra counties on a plan that sells none; an add-on other than `api`; a field of the wrong type), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON).

As `application/problem+json`.

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `402` error

Payment required. Codes: [`payment_method_required`](/docs/api/errors#code-payment-method-required) (add a payment method first: `POST /v2/account/billing/payment-method`).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`contract_managed`](/docs/api/errors#code-contract-managed) (the organization is `billing_mode: invoiced`: its plan is managed under a contract by Data Tactical Group), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 names an organization the caller does not belong to).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`already_subscribed`](/docs/api/errors#code-already-subscribed) (the organization already has a subscription; use `POST /v2/account/billing/change`), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (another billing change for this organization is running; the request waited 15 seconds; retry).

As `application/problem+json`.

#### `413` error

Payload too large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the JSON body is over 16 KB; the problem carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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: [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (400), [`bad_request`](/docs/api/errors#code-bad-request) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`payment_method_required`](/docs/api/errors#code-payment-method-required) (402), [`role_required`](/docs/api/errors#code-role-required) (403), [`contract_managed`](/docs/api/errors#code-contract-managed) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`already_subscribed`](/docs/api/errors#code-already-subscribed) (409), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (409), [`body_too_large`](/docs/api/errors#code-body-too-large) (413). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/billing/preview`

**Price a change before making it**

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

Classifies and prices a change to the subscription without making it: what is charged now (prorated, `always_invoice`), the next renewal invoice, what is scheduled now and what the change would leave scheduled, and whether the change needs a surviving-county choice. Everything is computed on the period-end state: every quantity the request names as requested, every other one as already scheduled. Adding the API add-on restarts the whole subscription's term today: `term_restart` is true and `credit_cents` and `new_renewal_date` are set. `surviving_fips` is ignored here. Takes no lock and changes nothing. Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact).

### Request body

Required; `application/json`.

**`plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`seats` number**

The purchased (extra) seat quantity wanted, beyond the plan's included seats. The whole target, not a delta.

**`counties` number**

The paid county-slot quantity wanted, beyond the plan's included counties. The whole target, not a delta.

**`addons` array of string**

The complete set of add-ons wanted (`[]` removes the API add-on at period end; `["api"]` adds it, annual plans only, restarting the subscription's term, D49).

**`surviving_fips` array of string**

Only on `POST /v2/account/billing/change`: when the change leaves fewer county places than the organization's active counties (`counties_requiring_choice` in the preview), the counties to keep. Exactly `capacity` codes, each one of the preview's `candidates`. A choice already made stands while it still fits the period-end capacity. Ignored by the preview.

### Responses

#### `200` success

The preview.

As `application/json`.

**`current` BillStateSnapshot always present**

A billing state: plan, interval, add-ons and purchased quantities.

**`current.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`current.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Add-ons.

**`current.seats_purchased` number always present**

Purchased seats beyond the plan's included seats.

**`current.counties_purchased` number always present**

Paid county slots beyond the plan's included counties.

**`target` object always present**

The state the request asks for (unnamed quantities at their current values).

**`target.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`target.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Add-ons.

**`target.seats_purchased` number always present**

Purchased seats beyond the plan's included seats.

**`target.counties_purchased` number always present**

Paid county slots beyond the plan's included counties.

**`kind` "upgrade" | "downgrade" | "mixed" | "none" always present**

`upgrade` (only increases: applied now), `downgrade` (only decreases: scheduled for period end), `mixed` (both: the increases now at the current plan and interval, the decreases scheduled), `none` (no change).

**`effective` "immediately" | "period\_end" always present**

`immediately` when anything applies now (upgrade or mixed), else `period_end` (also for `none`).

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

Now for an immediate change, else the current period end.

**`proration_behavior` "always\_invoice" | "none" always present**

`always_invoice` when the change applies now: the prorated amount is invoiced and charged to the card at once. `none` for a period-end change: nothing is charged now and nothing is credited.

**`immediate_charge_cents` integer always present**

What the card is charged now, in US cents (the sum of the proration lines, never below 0); 0 for a period-end change. (at least 0)

**`proration_lines` array of BillAmountLine always present**

The prorated lines charged now. When the API add-on is being added (term restart) these are every line of the restarted term.

**`proration_lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`proration_lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`proration_lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`term_restart` boolean always present**

True when the change adds the API add-on: the WHOLE subscription restarts its term today, every item moving to the new anchor together; the new term is charged now.

**`credit_cents` integer or null always present**

Only when `term_restart` is true: the magnitude, in US cents, of any credit lines Stripe returns for unused time (commonly 0, because Stripe nets a continuing item into one line). Null otherwise.

**`new_renewal_date` string (date-time) or null always present**

Only when `term_restart` is true: the renewal date after the restart. Null otherwise.

**`next_invoice` object always present**

The next renewal invoice, priced on the period-end state (what this change asks for plus what is already scheduled). Empty (`total_cents` 0, no lines) on a term restart, where everything is due now.

**`next_invoice.total_cents` integer always present**

Total of the next invoice in US cents.

**`next_invoice.lines` array of BillAmountLine always present**

Its lines.

**`next_invoice.lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`next_invoice.lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`next_invoice.lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`unit_prices_cents` object always present**

Unit prices, in US cents per interval, for the target plan and interval, from the price catalog; 0 where the catalog holds no such price.

**`unit_prices_cents.plan` integer always present**

The plan price.

**`unit_prices_cents.seat` integer always present**

One extra seat.

**`unit_prices_cents.county` integer always present**

One extra county slot; 0 when the plan sells none.

**`unit_prices_cents.api_addon` integer always present**

The API add-on (0 on a monthly interval: it is sold annual only).

**`counties_requiring_choice` object or null always present**

Set when the period-end county capacity is below the organization's active counties (counties already scheduled to leave are not counted): the change must then carry `surviving_fips`. Null otherwise.

**`counties_requiring_choice.capacity` integer always present**

How many counties the period-end state holds (included plus paid slots).

**`counties_requiring_choice.candidates` array of BillCountyCandidate always present**

The active counties to choose from, in KEEP order: the first `capacity` are the default choice.

**`counties_requiring_choice.candidates[].fips` string always present**

Five-digit county FIPS code.

**`counties_requiring_choice.candidates[].source` "manual" | "state" | "msa" always present**

How the county entered the active set: `manual` (picked), `state` or `msa` (added as part of a state or metro).

**`counties_requiring_choice.candidates[].added_at` string (date-time) always present**

When it was added.

**`counties_requiring_choice.candidates[].last_used` string (date-time) or null always present**

The organization's last data request touching this county in the past 90 days; null when none.

**`county_slots_releasable` integer always present**

How many paid county slots the period-end state holds that the active set does not need (for example after a move to a plan with more included counties), so the customer could release them.

**`seats_becoming_free_members` integer always present**

How many seated members exceed the period-end seat count and would become free members.

**`scheduled` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval always present**

What is scheduled now, before this change.

**`scheduled[].kind` "plan"**

Always `plan`.

**`scheduled[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`scheduled[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`scheduled[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`scheduled[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`scheduled[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`scheduled[].addons` array of string**

The add-ons that will be removed.

**`scheduled_after` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval always present**

What this change would leave scheduled (in a mixed change, the plan move appears here).

**`scheduled_after[].kind` "plan"**

Always `plan`.

**`scheduled_after[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`scheduled_after[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`scheduled_after[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`scheduled_after[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`scheduled_after[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`scheduled_after[].addons` array of string**

The add-ons that will be removed.

**`replaces` BillPendingReplaced always present**

For each scheduled item a change would replace (a second change to the same quantity), the item it replaces, keyed by its kind (`plan`, `counties`, `seats`, `addon_removal`). An empty object when it replaces nothing.

**`replaces.plan` BillPendingItemPlan**

A scheduled plan or interval move, applied at period end.

**`replaces.plan.kind` "plan" always present**

Always `plan`.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`replaces.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`replaces.counties` BillPendingItemCounties**

A scheduled decrease of the paid county-slot quantity (a slot release), applied at period end, not credited; the slots stay usable until then.

**`replaces.counties.kind` "counties" always present**

Always `counties`.

**`replaces.counties.counties_purchased` integer always present**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`replaces.counties.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`replaces.seats` BillPendingItemSeats**

A scheduled decrease of purchased seats, applied at period end, not credited; the seats stay usable until then.

**`replaces.seats.kind` "seats" always present**

Always `seats`.

**`replaces.seats.seats_purchased` integer always present**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`replaces.seats.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`replaces.addon_removal` BillPendingItemAddonRemoval**

A scheduled add-on removal, applied at period end. When the API add-on leaves, the organization's live API keys are revoked at that moment; sandbox keys stay.

**`replaces.addon_removal.kind` "addon\_removal" always present**

Always `addon_removal`.

**`replaces.addon_removal.addons` array of string always present**

The add-ons that will be removed.

**`replaces.addon_removal.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

#### `400` error

Bad request. Codes: [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (the state the change produces would hold the API add-on on a non-annual interval: adding it off-annual, or keeping it through a move to monthly; for a deferred change the period-end state is checked, so a move to monthly is allowed when the add-on's removal is scheduled), [`bad_request`](/docs/api/errors#code-bad-request) (unknown or non-purchasable `plan_code`; `interval` not `month`/`year`; a field of the wrong type), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON).

As `application/problem+json`.

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`contract_managed`](/docs/api/errors#code-contract-managed) (the organization is `billing_mode: invoiced`: its plan is managed under a contract by Data Tactical Group), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 names an organization the caller does not belong to).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`not_subscribed`](/docs/api/errors#code-not-subscribed) (the organization has no active subscription; use `POST /v2/account/billing/subscribe`).

As `application/problem+json`.

#### `413` error

Payload too large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the JSON body is over 16 KB; the problem carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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: [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (400), [`bad_request`](/docs/api/errors#code-bad-request) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`role_required`](/docs/api/errors#code-role-required) (403), [`contract_managed`](/docs/api/errors#code-contract-managed) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`not_subscribed`](/docs/api/errors#code-not-subscribed) (409), [`body_too_large`](/docs/api/errors#code-body-too-large) (413). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/billing/change`

**Change the plan, interval, seats, county slots or add-ons**

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

Applies a change. Increases (a higher plan, month to year, the add-on added, more seats or county slots) take effect now, prorated to the rest of the period and charged to the card at once (`always_invoice`); a declined card still grants them, except the API add-on. Decreases (a lower plan, year to month, add-on removal, fewer seats or slots) are scheduled for period end and are not credited; what was paid for stays usable until then. A mixed request applies its increases now at the current plan and interval and schedules the rest. The request touches only the quantities it names; a second change to the same quantity replaces the first (`replaced`); naming one at its current value withdraws its scheduled change. Adding the API add-on restarts the whole subscription's term with Stripe's pending-if-incomplete behaviour: a declined card leaves the add-on ungranted. When a scheduled add-on removal takes effect, the organization's live API keys are revoked; sandbox keys stay. When the period-end state holds fewer counties than are active, the request must carry `surviving_fips` (see the preview's `counties_requiring_choice`); at period end the counties not kept leave the active set, and members beyond the seat count become free members. Writes the audit row `billing.changed`. Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact). Billing changes for one organization are serialized: a second one waits for the first and, after 15 seconds, answers 409 [`billing_in_progress`](/docs/api/errors#code-billing-in-progress).

### Request body

Required; `application/json`.

**`plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`seats` number**

The purchased (extra) seat quantity wanted, beyond the plan's included seats. The whole target, not a delta.

**`counties` number**

The paid county-slot quantity wanted, beyond the plan's included counties. The whole target, not a delta.

**`addons` array of string**

The complete set of add-ons wanted (`[]` removes the API add-on at period end; `["api"]` adds it, annual plans only, restarting the subscription's term, D49).

**`surviving_fips` array of string**

Only on `POST /v2/account/billing/change`: when the change leaves fewer county places than the organization's active counties (`counties_requiring_choice` in the preview), the counties to keep. Exactly `capacity` codes, each one of the preview's `candidates`. A choice already made stands while it still fits the period-end capacity. Ignored by the preview.

### Responses

#### `200` success

The change applied or scheduled, with the preview it was decided on and the new summary.

As `application/json`.

**`applied` "immediately" | "period\_end" always present**

`immediately` when an increase was applied now (and charged), else `period_end` (the change, if any, is scheduled; also returned for a no-op).

**`replaced` BillPendingReplaced always present**

For each scheduled item a change would replace (a second change to the same quantity), the item it replaces, keyed by its kind (`plan`, `counties`, `seats`, `addon_removal`). An empty object when it replaces nothing.

**`replaced.plan` BillPendingItemPlan**

A scheduled plan or interval move, applied at period end.

**`replaced.plan.kind` "plan" always present**

Always `plan`.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`replaced.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`replaced.counties` BillPendingItemCounties**

A scheduled decrease of the paid county-slot quantity (a slot release), applied at period end, not credited; the slots stay usable until then.

**`replaced.counties.kind` "counties" always present**

Always `counties`.

**`replaced.counties.counties_purchased` integer always present**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`replaced.counties.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`replaced.seats` BillPendingItemSeats**

A scheduled decrease of purchased seats, applied at period end, not credited; the seats stay usable until then.

**`replaced.seats.kind` "seats" always present**

Always `seats`.

**`replaced.seats.seats_purchased` integer always present**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`replaced.seats.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`replaced.addon_removal` BillPendingItemAddonRemoval**

A scheduled add-on removal, applied at period end. When the API add-on leaves, the organization's live API keys are revoked at that moment; sandbox keys stay.

**`replaced.addon_removal.kind` "addon\_removal" always present**

Always `addon_removal`.

**`replaced.addon_removal.addons` array of string always present**

The add-ons that will be removed.

**`replaced.addon_removal.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview` object always present**

The preview the change was decided on.

**`preview.current` BillStateSnapshot always present**

A billing state: plan, interval, add-ons and purchased quantities.

**`preview.current.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.current.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.current.addons` array of string always present**

Add-ons.

**`preview.current.seats_purchased` number always present**

Purchased seats beyond the plan's included seats.

**`preview.current.counties_purchased` number always present**

Paid county slots beyond the plan's included counties.

**`preview.target` object always present**

The state the request asks for (unnamed quantities at their current values).

**`preview.target.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.target.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.target.addons` array of string always present**

Add-ons.

**`preview.target.seats_purchased` number always present**

Purchased seats beyond the plan's included seats.

**`preview.target.counties_purchased` number always present**

Paid county slots beyond the plan's included counties.

**`preview.kind` "upgrade" | "downgrade" | "mixed" | "none" always present**

`upgrade` (only increases: applied now), `downgrade` (only decreases: scheduled for period end), `mixed` (both: the increases now at the current plan and interval, the decreases scheduled), `none` (no change).

**`preview.effective` "immediately" | "period\_end" always present**

`immediately` when anything applies now (upgrade or mixed), else `period_end` (also for `none`).

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

Now for an immediate change, else the current period end.

**`preview.proration_behavior` "always\_invoice" | "none" always present**

`always_invoice` when the change applies now: the prorated amount is invoiced and charged to the card at once. `none` for a period-end change: nothing is charged now and nothing is credited.

**`preview.immediate_charge_cents` integer always present**

What the card is charged now, in US cents (the sum of the proration lines, never below 0); 0 for a period-end change. (at least 0)

**`preview.proration_lines` array of BillAmountLine always present**

The prorated lines charged now. When the API add-on is being added (term restart) these are every line of the restarted term.

**`preview.proration_lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`preview.proration_lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`preview.proration_lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`preview.term_restart` boolean always present**

True when the change adds the API add-on: the WHOLE subscription restarts its term today, every item moving to the new anchor together; the new term is charged now.

**`preview.credit_cents` integer or null always present**

Only when `term_restart` is true: the magnitude, in US cents, of any credit lines Stripe returns for unused time (commonly 0, because Stripe nets a continuing item into one line). Null otherwise.

**`preview.new_renewal_date` string (date-time) or null always present**

Only when `term_restart` is true: the renewal date after the restart. Null otherwise.

**`preview.next_invoice` object always present**

The next renewal invoice, priced on the period-end state (what this change asks for plus what is already scheduled). Empty (`total_cents` 0, no lines) on a term restart, where everything is due now.

**`preview.next_invoice.total_cents` integer always present**

Total of the next invoice in US cents.

**`preview.next_invoice.lines` array of BillAmountLine always present**

Its lines.

**`preview.next_invoice.lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`preview.next_invoice.lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`preview.next_invoice.lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`preview.unit_prices_cents` object always present**

Unit prices, in US cents per interval, for the target plan and interval, from the price catalog; 0 where the catalog holds no such price.

**`preview.unit_prices_cents.plan` integer always present**

The plan price.

**`preview.unit_prices_cents.seat` integer always present**

One extra seat.

**`preview.unit_prices_cents.county` integer always present**

One extra county slot; 0 when the plan sells none.

**`preview.unit_prices_cents.api_addon` integer always present**

The API add-on (0 on a monthly interval: it is sold annual only).

**`preview.counties_requiring_choice` object or null always present**

Set when the period-end county capacity is below the organization's active counties (counties already scheduled to leave are not counted): the change must then carry `surviving_fips`. Null otherwise.

**`preview.counties_requiring_choice.capacity` integer always present**

How many counties the period-end state holds (included plus paid slots).

**`preview.counties_requiring_choice.candidates` array of BillCountyCandidate always present**

The active counties to choose from, in KEEP order: the first `capacity` are the default choice.

**`preview.counties_requiring_choice.candidates[].fips` string always present**

Five-digit county FIPS code.

**`preview.counties_requiring_choice.candidates[].source` "manual" | "state" | "msa" always present**

How the county entered the active set: `manual` (picked), `state` or `msa` (added as part of a state or metro).

**`preview.counties_requiring_choice.candidates[].added_at` string (date-time) always present**

When it was added.

**`preview.counties_requiring_choice.candidates[].last_used` string (date-time) or null always present**

The organization's last data request touching this county in the past 90 days; null when none.

**`preview.county_slots_releasable` integer always present**

How many paid county slots the period-end state holds that the active set does not need (for example after a move to a plan with more included counties), so the customer could release them.

**`preview.seats_becoming_free_members` integer always present**

How many seated members exceed the period-end seat count and would become free members.

**`preview.scheduled` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval always present**

What is scheduled now, before this change.

**`preview.scheduled[].kind` "plan"**

Always `plan`.

**`preview.scheduled[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.scheduled[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.scheduled[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.scheduled[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.scheduled[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.scheduled[].addons` array of string**

The add-ons that will be removed.

**`preview.scheduled_after` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval always present**

What this change would leave scheduled (in a mixed change, the plan move appears here).

**`preview.scheduled_after[].kind` "plan"**

Always `plan`.

**`preview.scheduled_after[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.scheduled_after[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.scheduled_after[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.scheduled_after[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.scheduled_after[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.scheduled_after[].addons` array of string**

The add-ons that will be removed.

**`preview.replaces` BillPendingReplaced always present**

For each scheduled item a change would replace (a second change to the same quantity), the item it replaces, keyed by its kind (`plan`, `counties`, `seats`, `addon_removal`). An empty object when it replaces nothing.

**`preview.replaces.plan` BillPendingItemPlan**

A scheduled plan or interval move, applied at period end.

**`preview.replaces.plan.kind` "plan" always present**

Always `plan`.

**`preview.replaces.plan.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.replaces.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.replaces.counties` BillPendingItemCounties**

A scheduled decrease of the paid county-slot quantity (a slot release), applied at period end, not credited; the slots stay usable until then.

**`preview.replaces.counties.kind` "counties" always present**

Always `counties`.

**`preview.replaces.counties.counties_purchased` integer always present**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.replaces.counties.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview.replaces.seats` BillPendingItemSeats**

A scheduled decrease of purchased seats, applied at period end, not credited; the seats stay usable until then.

**`preview.replaces.seats.kind` "seats" always present**

Always `seats`.

**`preview.replaces.seats.seats_purchased` integer always present**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.replaces.seats.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview.replaces.addon_removal` BillPendingItemAddonRemoval**

A scheduled add-on removal, applied at period end. When the API add-on leaves, the organization's live API keys are revoked at that moment; sandbox keys stay.

**`preview.replaces.addon_removal.kind` "addon\_removal" always present**

Always `addon_removal`.

**`preview.replaces.addon_removal.addons` array of string always present**

The add-ons that will be removed.

**`preview.replaces.addon_removal.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`summary` BillSummary always present**

The organization's billing state as Kelpie's seats-and-billing screen shows it.

**`summary.gateway` "fake" | "test" | "live" always present**

Which payment gateway this server runs: `live` (Stripe live mode, production only), `test` (Stripe test mode) or `fake` (an in-process simulation used in development and tests). T153: the Stripe secret key's prefix decides between `test` and `live`.

**`summary.publishable_key` string or null always present**

The Stripe publishable key to initialise Stripe.js and the Payment Element with.

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

`self_serve` (card subscription managed through these endpoints) or `invoiced` (a contract managed by Data Tactical Group: every self-serve change answers 403 [`contract_managed`](/docs/api/errors#code-contract-managed) ).

**`summary.contract` object or null always present**

The contract terms for an `invoiced` organization; null for `self_serve`.

**`summary.contract.expires_at` string (date-time) or null**

When the contract ends; null for no end date.

**`summary.contract.notes` string or null**

DTG's notes on the contract.

**`summary.plan` object always present**

The plan the organization holds now.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

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

The plan's display name (e.g. Local, Regional, National, Free).

**`summary.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Active add-ons.

**`summary.seats` object always present**

Seats: the plan's included seats plus purchased extra seats.

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

Seats the plan includes (the plan's allowance; see `GET /v2/plans`), raised to `grandfathered` while that is set.

**`summary.seats.grandfathered` integer or null**

T163: an organization that had more seated members than its plan's new included count when Regional fell to 10 seats and National to 15 keeps that many included seats until its seated members fall to the plan's count or its plan changes. This is that count; null for every other organization.

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

Extra seats the organization holds beyond `included`. A scheduled decrease does not lower this until period end.

**`summary.seats.billed_quantity` integer always present**

The seat quantity on the Stripe subscription (for an `invoiced` organization, the contracted extra seats; 0 with no subscription). Can differ from `purchased` briefly while a scheduled decrease has been sent to Stripe ahead of the renewal.

**`summary.seats.seat_price_usd_month` number or null always present**

The plan's monthly price of one extra seat, in US dollars; null when the plan sells no extra seats.

**`summary.seats.proration` "always\_invoice" always present**

How a seat increase is billed: always `always_invoice` (prorated to the rest of the period and charged to the card at purchase).

**`summary.seats.proration_note` string always present**

A sentence stating the seat billing rule for display.

**`summary.counties` object always present**

County slots: the plan's included counties plus paid county slots. The county SET is managed at `/v2/account/counties`; this is the billing quantity behind it.

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

Counties the plan includes; -1 means every county (National).

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

Paid county slots beyond `included`.

**`summary.counties.billed_quantity` integer always present**

The county-slot quantity on the Stripe subscription (for an `invoiced` organization, the contracted slots; 0 with no subscription).

**`summary.counties.active` integer always present**

Counties currently in the organization's active set, not counting a trial county.

**`summary.counties.county_price_usd_month` number or null always present**

The plan's monthly price of one extra county slot, in US dollars; null when the plan sells no extra counties.

**`summary.counties.proration` "always\_invoice" always present**

How a slot increase is billed: always `always_invoice` (prorated and charged at purchase).

**`summary.counties.proration_note` string always present**

A sentence stating the county-slot billing rule for display.

**`summary.counties.anchor_day` integer or null always present**

Day of the month of the organization's change window (the subscription's anchor date), on which county set changes land; null before a first subscription. (1 to 31)

**`summary.subscription` object or null always present**

The current Stripe subscription (status active, trialing, past\_due or incomplete); null when there is none (Free, or contract-managed without one).

**`summary.subscription.id` string**

Stripe subscription id (`sub_...`).

**`summary.subscription.status` "active" | "trialing" | "past\_due" | "incomplete"**

`active`; `trialing`; `past_due` (a renewal was declined: L18 pauses county detail, CSV/XLSX export, live API keys and cluster editing at once, while the plan and its capacity stay held; pay with `POST /v2/account/billing/retry` after fixing the card); `incomplete` (the first payment has not succeeded).

**`summary.subscription.current_period_start` string (date-time) or null**

Start of the current billing period.

**`summary.subscription.current_period_end` string (date-time) or null**

End of the current billing period: where scheduled changes and a cancellation take effect.

**`summary.subscription.renewal_date` string (date-time) or null**

When the subscription renews (the period end); null when it is set to cancel at period end.

**`summary.subscription.cancel_at_period_end` boolean**

True when the subscription ends at `current_period_end` instead of renewing.

**`summary.subscription.past_due_since` string (date-time) or null**

When the subscription first became past due; null when it is not past due.

**`summary.subscription.dunning_grace_days` integer**

Days a past-due subscription has before it is cancelled and the organization drops to Free (the downgrade composite applies). Service is paused from the decline, not from the end of this window.

**`summary.pending_change` BillPendingChange or null always present**

What is scheduled for the period end; null when nothing (and no cancellation) is scheduled.

**`summary.pending_change.plan_code` string or null**

The plan the organization moves to at period end; null when no plan move is scheduled (an interval-only move leaves this null and sets `interval`).

**`summary.pending_change.interval` "month" | "year" | null**

The interval the organization moves to at period end; null when no interval move is scheduled.

**`summary.pending_change.seats_purchased` integer or null**

The purchased seat quantity from period end; null when no seat decrease is scheduled.

**`summary.pending_change.counties_purchased` integer or null**

The paid county-slot quantity from period end; null when no slot release is scheduled.

**`summary.pending_change.remove_addons` array of string**

Add-ons removed at period end; empty when none.

**`summary.pending_change.cancel` boolean**

True when the subscription is set to cancel at period end (`POST /v2/account/billing/cancel`); `POST /v2/account/billing/resume` clears it and nothing else.

**`summary.pending_change.effective_at` string (date-time)**

The period end at which everything here takes effect.

**`summary.pending_change.changes` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval**

The scheduled set, one item per quantity. A cancellation is not an item here (see `cancel`).

**`summary.pending_change.changes[].kind` "plan"**

Always `plan`.

**`summary.pending_change.changes[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`summary.pending_change.changes[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`summary.pending_change.changes[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`summary.pending_change.changes[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`summary.pending_change.changes[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`summary.pending_change.changes[].addons` array of string**

The add-ons that will be removed.

**`summary.payment_method` BillPaymentMethod or null always present**

The default payment method; null when none is set.

**`summary.payment_method.id` string**

Stripe PaymentMethod id (`pm_...`).

**`summary.payment_method.brand` string**

Card brand as Stripe reports it, e.g. `visa`, `mastercard`.

**`summary.payment_method.last4` string**

Last four digits of the card number.

**`summary.payment_method.exp_month` integer**

Card expiry month. (1 to 12)

**`summary.payment_method.exp_year` integer**

Card expiry year (four digits).

**`summary.invoices` array of BillInvoice always present**

Up to the 12 most recent invoices, newest first.

**`summary.invoices[].id` string always present**

Stripe invoice id (`in_...`).

**`summary.invoices[].number` string or null always present**

The invoice number printed on it; null while a draft.

**`summary.invoices[].status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`summary.invoices[].total_cents` integer always present**

Invoice total in US cents.

**`summary.invoices[].amount_due_cents` integer always present**

Amount still owed, in US cents.

**`summary.invoices[].amount_paid_cents` integer always present**

Amount paid, in US cents.

**`summary.invoices[].created` string (date-time) always present**

When the invoice was created.

**`summary.invoices[].due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`summary.invoices[].period` object always present**

The service period the invoice covers.

**`summary.invoices[].period.start` string (date-time) always present**

Period start.

**`summary.invoices[].period.end` string (date-time) always present**

Period end.

**`summary.invoices[].hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`summary.invoices[].collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

**`summary.invoices[].description` string or null always present**

Free-text memo on the invoice (contract invoices), else null.

**`summary.invoices[].lines` array of BillInvoiceLine always present**

The invoice's lines.

**`summary.invoices[].lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`summary.invoices[].lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`summary.invoices[].lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`summary.invoices[].lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

#### `400` error

Bad request. Codes: [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (as on the preview), [`bad_request`](/docs/api/errors#code-bad-request) (unknown or non-purchasable `plan_code`; `interval` not `month`/`year`; a field of the wrong type; or the surviving-county choice is missing or invalid: then the code is [`surviving_counties_required`](/docs/api/errors#code-surviving-counties-required), with `capacity` and `candidates`, the FIPS codes to choose from, and `errors[0]` repeats it), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON).

As `application/problem+json`.

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`contract_managed`](/docs/api/errors#code-contract-managed) (the organization is `billing_mode: invoiced`: its plan is managed under a contract by Data Tactical Group), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 names an organization the caller does not belong to).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`not_subscribed`](/docs/api/errors#code-not-subscribed) (the organization has no active subscription; use `POST /v2/account/billing/subscribe`), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (another billing change for this organization is running; the request waited 15 seconds; retry).

As `application/problem+json`.

#### `413` error

Payload too large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the JSON body is over 16 KB; the problem carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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: [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (400), [`bad_request`](/docs/api/errors#code-bad-request) (400), [`surviving_counties_required`](/docs/api/errors#code-surviving-counties-required) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`role_required`](/docs/api/errors#code-role-required) (403), [`contract_managed`](/docs/api/errors#code-contract-managed) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`not_subscribed`](/docs/api/errors#code-not-subscribed) (409), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (409), [`body_too_large`](/docs/api/errors#code-body-too-large) (413). Every problem is a [problem document](/docs/api/errors).

## DELETE `/v2/account/billing/pending/{kind}`

**Withdraw one scheduled period-end change**

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

Withdraws the scheduled change for one quantity. The other scheduled quantities, and a scheduled cancellation (which `POST /v2/account/billing/resume` withdraws), stay. Writes the audit row `billing.pending_cancelled`. Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact). Billing changes for one organization are serialized: a second one waits for the first and, after 15 seconds, answers 409 [`billing_in_progress`](/docs/api/errors#code-billing-in-progress).

### Parameters

**`kind` path · "plan" | "counties" | "seats" | "addon\_removal" required**

Which scheduled change to withdraw: `plan`, `counties` (a slot release), `seats` or `addon_removal`.

### Responses

#### `200` success

The withdrawn item and the new summary.

As `application/json`.

**`cancelled` object always present**

The scheduled item that was withdrawn.

**`cancelled.kind` "plan"**

Always `plan`.

**`cancelled.plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`cancelled.interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`cancelled.effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`cancelled.counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`cancelled.seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`cancelled.addons` array of string**

The add-ons that will be removed.

**`summary` BillSummary always present**

The organization's billing state as Kelpie's seats-and-billing screen shows it.

**`summary.gateway` "fake" | "test" | "live" always present**

Which payment gateway this server runs: `live` (Stripe live mode, production only), `test` (Stripe test mode) or `fake` (an in-process simulation used in development and tests). T153: the Stripe secret key's prefix decides between `test` and `live`.

**`summary.publishable_key` string or null always present**

The Stripe publishable key to initialise Stripe.js and the Payment Element with.

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

`self_serve` (card subscription managed through these endpoints) or `invoiced` (a contract managed by Data Tactical Group: every self-serve change answers 403 [`contract_managed`](/docs/api/errors#code-contract-managed) ).

**`summary.contract` object or null always present**

The contract terms for an `invoiced` organization; null for `self_serve`.

**`summary.contract.expires_at` string (date-time) or null**

When the contract ends; null for no end date.

**`summary.contract.notes` string or null**

DTG's notes on the contract.

**`summary.plan` object always present**

The plan the organization holds now.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

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

The plan's display name (e.g. Local, Regional, National, Free).

**`summary.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Active add-ons.

**`summary.seats` object always present**

Seats: the plan's included seats plus purchased extra seats.

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

Seats the plan includes (the plan's allowance; see `GET /v2/plans`), raised to `grandfathered` while that is set.

**`summary.seats.grandfathered` integer or null**

T163: an organization that had more seated members than its plan's new included count when Regional fell to 10 seats and National to 15 keeps that many included seats until its seated members fall to the plan's count or its plan changes. This is that count; null for every other organization.

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

Extra seats the organization holds beyond `included`. A scheduled decrease does not lower this until period end.

**`summary.seats.billed_quantity` integer always present**

The seat quantity on the Stripe subscription (for an `invoiced` organization, the contracted extra seats; 0 with no subscription). Can differ from `purchased` briefly while a scheduled decrease has been sent to Stripe ahead of the renewal.

**`summary.seats.seat_price_usd_month` number or null always present**

The plan's monthly price of one extra seat, in US dollars; null when the plan sells no extra seats.

**`summary.seats.proration` "always\_invoice" always present**

How a seat increase is billed: always `always_invoice` (prorated to the rest of the period and charged to the card at purchase).

**`summary.seats.proration_note` string always present**

A sentence stating the seat billing rule for display.

**`summary.counties` object always present**

County slots: the plan's included counties plus paid county slots. The county SET is managed at `/v2/account/counties`; this is the billing quantity behind it.

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

Counties the plan includes; -1 means every county (National).

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

Paid county slots beyond `included`.

**`summary.counties.billed_quantity` integer always present**

The county-slot quantity on the Stripe subscription (for an `invoiced` organization, the contracted slots; 0 with no subscription).

**`summary.counties.active` integer always present**

Counties currently in the organization's active set, not counting a trial county.

**`summary.counties.county_price_usd_month` number or null always present**

The plan's monthly price of one extra county slot, in US dollars; null when the plan sells no extra counties.

**`summary.counties.proration` "always\_invoice" always present**

How a slot increase is billed: always `always_invoice` (prorated and charged at purchase).

**`summary.counties.proration_note` string always present**

A sentence stating the county-slot billing rule for display.

**`summary.counties.anchor_day` integer or null always present**

Day of the month of the organization's change window (the subscription's anchor date), on which county set changes land; null before a first subscription. (1 to 31)

**`summary.subscription` object or null always present**

The current Stripe subscription (status active, trialing, past\_due or incomplete); null when there is none (Free, or contract-managed without one).

**`summary.subscription.id` string**

Stripe subscription id (`sub_...`).

**`summary.subscription.status` "active" | "trialing" | "past\_due" | "incomplete"**

`active`; `trialing`; `past_due` (a renewal was declined: L18 pauses county detail, CSV/XLSX export, live API keys and cluster editing at once, while the plan and its capacity stay held; pay with `POST /v2/account/billing/retry` after fixing the card); `incomplete` (the first payment has not succeeded).

**`summary.subscription.current_period_start` string (date-time) or null**

Start of the current billing period.

**`summary.subscription.current_period_end` string (date-time) or null**

End of the current billing period: where scheduled changes and a cancellation take effect.

**`summary.subscription.renewal_date` string (date-time) or null**

When the subscription renews (the period end); null when it is set to cancel at period end.

**`summary.subscription.cancel_at_period_end` boolean**

True when the subscription ends at `current_period_end` instead of renewing.

**`summary.subscription.past_due_since` string (date-time) or null**

When the subscription first became past due; null when it is not past due.

**`summary.subscription.dunning_grace_days` integer**

Days a past-due subscription has before it is cancelled and the organization drops to Free (the downgrade composite applies). Service is paused from the decline, not from the end of this window.

**`summary.pending_change` BillPendingChange or null always present**

What is scheduled for the period end; null when nothing (and no cancellation) is scheduled.

**`summary.pending_change.plan_code` string or null**

The plan the organization moves to at period end; null when no plan move is scheduled (an interval-only move leaves this null and sets `interval`).

**`summary.pending_change.interval` "month" | "year" | null**

The interval the organization moves to at period end; null when no interval move is scheduled.

**`summary.pending_change.seats_purchased` integer or null**

The purchased seat quantity from period end; null when no seat decrease is scheduled.

**`summary.pending_change.counties_purchased` integer or null**

The paid county-slot quantity from period end; null when no slot release is scheduled.

**`summary.pending_change.remove_addons` array of string**

Add-ons removed at period end; empty when none.

**`summary.pending_change.cancel` boolean**

True when the subscription is set to cancel at period end (`POST /v2/account/billing/cancel`); `POST /v2/account/billing/resume` clears it and nothing else.

**`summary.pending_change.effective_at` string (date-time)**

The period end at which everything here takes effect.

**`summary.pending_change.changes` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval**

The scheduled set, one item per quantity. A cancellation is not an item here (see `cancel`).

**`summary.pending_change.changes[].kind` "plan"**

Always `plan`.

**`summary.pending_change.changes[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`summary.pending_change.changes[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`summary.pending_change.changes[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`summary.pending_change.changes[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`summary.pending_change.changes[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`summary.pending_change.changes[].addons` array of string**

The add-ons that will be removed.

**`summary.payment_method` BillPaymentMethod or null always present**

The default payment method; null when none is set.

**`summary.payment_method.id` string**

Stripe PaymentMethod id (`pm_...`).

**`summary.payment_method.brand` string**

Card brand as Stripe reports it, e.g. `visa`, `mastercard`.

**`summary.payment_method.last4` string**

Last four digits of the card number.

**`summary.payment_method.exp_month` integer**

Card expiry month. (1 to 12)

**`summary.payment_method.exp_year` integer**

Card expiry year (four digits).

**`summary.invoices` array of BillInvoice always present**

Up to the 12 most recent invoices, newest first.

**`summary.invoices[].id` string always present**

Stripe invoice id (`in_...`).

**`summary.invoices[].number` string or null always present**

The invoice number printed on it; null while a draft.

**`summary.invoices[].status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`summary.invoices[].total_cents` integer always present**

Invoice total in US cents.

**`summary.invoices[].amount_due_cents` integer always present**

Amount still owed, in US cents.

**`summary.invoices[].amount_paid_cents` integer always present**

Amount paid, in US cents.

**`summary.invoices[].created` string (date-time) always present**

When the invoice was created.

**`summary.invoices[].due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`summary.invoices[].period` object always present**

The service period the invoice covers.

**`summary.invoices[].period.start` string (date-time) always present**

Period start.

**`summary.invoices[].period.end` string (date-time) always present**

Period end.

**`summary.invoices[].hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`summary.invoices[].collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

**`summary.invoices[].description` string or null always present**

Free-text memo on the invoice (contract invoices), else null.

**`summary.invoices[].lines` array of BillInvoiceLine always present**

The invoice's lines.

**`summary.invoices[].lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`summary.invoices[].lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`summary.invoices[].lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`summary.invoices[].lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`contract_managed`](/docs/api/errors#code-contract-managed) (the organization is `billing_mode: invoiced`: its plan is managed under a contract by Data Tactical Group), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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) (`kind` is not one of `plan`, `counties`, `seats`, `addon_removal`), [`not_found`](/docs/api/errors#code-not-found) (the `X-DTG-Org` header names an organization the caller does not belong to).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`nothing_scheduled`](/docs/api/errors#code-nothing-scheduled) (nothing is scheduled for that quantity; the problem carries `kind`), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (another billing change for this organization is running; the request waited 15 seconds; retry).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`role_required`](/docs/api/errors#code-role-required) (403), [`contract_managed`](/docs/api/errors#code-contract-managed) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`nothing_scheduled`](/docs/api/errors#code-nothing-scheduled) (409), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (409). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/billing/cancel`

**Cancel the subscription at period end**

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

Sets the subscription to end at the current period end instead of renewing; the organization keeps everything until then and then drops to Free. Nothing is credited. Undo with `POST /v2/account/billing/resume`. No request body. Writes the audit row `billing.cancelled`. Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact). Billing changes for one organization are serialized: a second one waits for the first and, after 15 seconds, answers 409 [`billing_in_progress`](/docs/api/errors#code-billing-in-progress).

### Responses

#### `200` success

The billing summary after the change (`subscription.cancel_at_period_end` true, `pending_change.cancel` true).

As `application/json`.

**`gateway` "fake" | "test" | "live" always present**

Which payment gateway this server runs: `live` (Stripe live mode, production only), `test` (Stripe test mode) or `fake` (an in-process simulation used in development and tests). T153: the Stripe secret key's prefix decides between `test` and `live`.

**`publishable_key` string or null always present**

The Stripe publishable key to initialise Stripe.js and the Payment Element with.

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

`self_serve` (card subscription managed through these endpoints) or `invoiced` (a contract managed by Data Tactical Group: every self-serve change answers 403 [`contract_managed`](/docs/api/errors#code-contract-managed) ).

**`contract` object or null always present**

The contract terms for an `invoiced` organization; null for `self_serve`.

**`contract.expires_at` string (date-time) or null**

When the contract ends; null for no end date.

**`contract.notes` string or null**

DTG's notes on the contract.

**`plan` object always present**

The plan the organization holds now.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

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

The plan's display name (e.g. Local, Regional, National, Free).

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

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Active add-ons.

**`seats` object always present**

Seats: the plan's included seats plus purchased extra seats.

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

Seats the plan includes (the plan's allowance; see `GET /v2/plans`), raised to `grandfathered` while that is set.

**`seats.grandfathered` integer or null**

T163: an organization that had more seated members than its plan's new included count when Regional fell to 10 seats and National to 15 keeps that many included seats until its seated members fall to the plan's count or its plan changes. This is that count; null for every other organization.

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

Extra seats the organization holds beyond `included`. A scheduled decrease does not lower this until period end.

**`seats.billed_quantity` integer always present**

The seat quantity on the Stripe subscription (for an `invoiced` organization, the contracted extra seats; 0 with no subscription). Can differ from `purchased` briefly while a scheduled decrease has been sent to Stripe ahead of the renewal.

**`seats.seat_price_usd_month` number or null always present**

The plan's monthly price of one extra seat, in US dollars; null when the plan sells no extra seats.

**`seats.proration` "always\_invoice" always present**

How a seat increase is billed: always `always_invoice` (prorated to the rest of the period and charged to the card at purchase).

**`seats.proration_note` string always present**

A sentence stating the seat billing rule for display.

**`counties` object always present**

County slots: the plan's included counties plus paid county slots. The county SET is managed at `/v2/account/counties`; this is the billing quantity behind it.

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

Counties the plan includes; -1 means every county (National).

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

Paid county slots beyond `included`.

**`counties.billed_quantity` integer always present**

The county-slot quantity on the Stripe subscription (for an `invoiced` organization, the contracted slots; 0 with no subscription).

**`counties.active` integer always present**

Counties currently in the organization's active set, not counting a trial county.

**`counties.county_price_usd_month` number or null always present**

The plan's monthly price of one extra county slot, in US dollars; null when the plan sells no extra counties.

**`counties.proration` "always\_invoice" always present**

How a slot increase is billed: always `always_invoice` (prorated and charged at purchase).

**`counties.proration_note` string always present**

A sentence stating the county-slot billing rule for display.

**`counties.anchor_day` integer or null always present**

Day of the month of the organization's change window (the subscription's anchor date), on which county set changes land; null before a first subscription. (1 to 31)

**`subscription` object or null always present**

The current Stripe subscription (status active, trialing, past\_due or incomplete); null when there is none (Free, or contract-managed without one).

**`subscription.id` string**

Stripe subscription id (`sub_...`).

**`subscription.status` "active" | "trialing" | "past\_due" | "incomplete"**

`active`; `trialing`; `past_due` (a renewal was declined: L18 pauses county detail, CSV/XLSX export, live API keys and cluster editing at once, while the plan and its capacity stay held; pay with `POST /v2/account/billing/retry` after fixing the card); `incomplete` (the first payment has not succeeded).

**`subscription.current_period_start` string (date-time) or null**

Start of the current billing period.

**`subscription.current_period_end` string (date-time) or null**

End of the current billing period: where scheduled changes and a cancellation take effect.

**`subscription.renewal_date` string (date-time) or null**

When the subscription renews (the period end); null when it is set to cancel at period end.

**`subscription.cancel_at_period_end` boolean**

True when the subscription ends at `current_period_end` instead of renewing.

**`subscription.past_due_since` string (date-time) or null**

When the subscription first became past due; null when it is not past due.

**`subscription.dunning_grace_days` integer**

Days a past-due subscription has before it is cancelled and the organization drops to Free (the downgrade composite applies). Service is paused from the decline, not from the end of this window.

**`pending_change` BillPendingChange or null always present**

What is scheduled for the period end; null when nothing (and no cancellation) is scheduled.

**`pending_change.plan_code` string or null**

The plan the organization moves to at period end; null when no plan move is scheduled (an interval-only move leaves this null and sets `interval`).

**`pending_change.interval` "month" | "year" | null**

The interval the organization moves to at period end; null when no interval move is scheduled.

**`pending_change.seats_purchased` integer or null**

The purchased seat quantity from period end; null when no seat decrease is scheduled.

**`pending_change.counties_purchased` integer or null**

The paid county-slot quantity from period end; null when no slot release is scheduled.

**`pending_change.remove_addons` array of string**

Add-ons removed at period end; empty when none.

**`pending_change.cancel` boolean**

True when the subscription is set to cancel at period end (`POST /v2/account/billing/cancel`); `POST /v2/account/billing/resume` clears it and nothing else.

**`pending_change.effective_at` string (date-time)**

The period end at which everything here takes effect.

**`pending_change.changes` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval**

The scheduled set, one item per quantity. A cancellation is not an item here (see `cancel`).

**`pending_change.changes[].kind` "plan"**

Always `plan`.

**`pending_change.changes[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`pending_change.changes[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`pending_change.changes[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`pending_change.changes[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`pending_change.changes[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`pending_change.changes[].addons` array of string**

The add-ons that will be removed.

**`payment_method` BillPaymentMethod or null always present**

The default payment method; null when none is set.

**`payment_method.id` string**

Stripe PaymentMethod id (`pm_...`).

**`payment_method.brand` string**

Card brand as Stripe reports it, e.g. `visa`, `mastercard`.

**`payment_method.last4` string**

Last four digits of the card number.

**`payment_method.exp_month` integer**

Card expiry month. (1 to 12)

**`payment_method.exp_year` integer**

Card expiry year (four digits).

**`invoices` array of BillInvoice always present**

Up to the 12 most recent invoices, newest first.

**`invoices[].id` string always present**

Stripe invoice id (`in_...`).

**`invoices[].number` string or null always present**

The invoice number printed on it; null while a draft.

**`invoices[].status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`invoices[].total_cents` integer always present**

Invoice total in US cents.

**`invoices[].amount_due_cents` integer always present**

Amount still owed, in US cents.

**`invoices[].amount_paid_cents` integer always present**

Amount paid, in US cents.

**`invoices[].created` string (date-time) always present**

When the invoice was created.

**`invoices[].due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`invoices[].period` object always present**

The service period the invoice covers.

**`invoices[].period.start` string (date-time) always present**

Period start.

**`invoices[].period.end` string (date-time) always present**

Period end.

**`invoices[].hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`invoices[].collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

**`invoices[].description` string or null always present**

Free-text memo on the invoice (contract invoices), else null.

**`invoices[].lines` array of BillInvoiceLine always present**

The invoice's lines.

**`invoices[].lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`invoices[].lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`invoices[].lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`invoices[].lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`contract_managed`](/docs/api/errors#code-contract-managed) (the organization is `billing_mode: invoiced`: its plan is managed under a contract by Data Tactical Group), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 names an organization the caller does not belong to).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`not_subscribed`](/docs/api/errors#code-not-subscribed) (the organization has no active subscription; use `POST /v2/account/billing/subscribe`), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (another billing change for this organization is running; the request waited 15 seconds; retry).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`role_required`](/docs/api/errors#code-role-required) (403), [`contract_managed`](/docs/api/errors#code-contract-managed) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`not_subscribed`](/docs/api/errors#code-not-subscribed) (409), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (409). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/billing/resume`

**Withdraw a scheduled cancellation**

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

Ends a scheduled cancellation so the subscription renews. Touches no other scheduled change or its date. No request body. Writes the audit row `billing.resumed`. Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact). Billing changes for one organization are serialized: a second one waits for the first and, after 15 seconds, answers 409 [`billing_in_progress`](/docs/api/errors#code-billing-in-progress).

### Responses

#### `200` success

The billing summary after the change.

As `application/json`.

**`gateway` "fake" | "test" | "live" always present**

Which payment gateway this server runs: `live` (Stripe live mode, production only), `test` (Stripe test mode) or `fake` (an in-process simulation used in development and tests). T153: the Stripe secret key's prefix decides between `test` and `live`.

**`publishable_key` string or null always present**

The Stripe publishable key to initialise Stripe.js and the Payment Element with.

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

`self_serve` (card subscription managed through these endpoints) or `invoiced` (a contract managed by Data Tactical Group: every self-serve change answers 403 [`contract_managed`](/docs/api/errors#code-contract-managed) ).

**`contract` object or null always present**

The contract terms for an `invoiced` organization; null for `self_serve`.

**`contract.expires_at` string (date-time) or null**

When the contract ends; null for no end date.

**`contract.notes` string or null**

DTG's notes on the contract.

**`plan` object always present**

The plan the organization holds now.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

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

The plan's display name (e.g. Local, Regional, National, Free).

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

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Active add-ons.

**`seats` object always present**

Seats: the plan's included seats plus purchased extra seats.

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

Seats the plan includes (the plan's allowance; see `GET /v2/plans`), raised to `grandfathered` while that is set.

**`seats.grandfathered` integer or null**

T163: an organization that had more seated members than its plan's new included count when Regional fell to 10 seats and National to 15 keeps that many included seats until its seated members fall to the plan's count or its plan changes. This is that count; null for every other organization.

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

Extra seats the organization holds beyond `included`. A scheduled decrease does not lower this until period end.

**`seats.billed_quantity` integer always present**

The seat quantity on the Stripe subscription (for an `invoiced` organization, the contracted extra seats; 0 with no subscription). Can differ from `purchased` briefly while a scheduled decrease has been sent to Stripe ahead of the renewal.

**`seats.seat_price_usd_month` number or null always present**

The plan's monthly price of one extra seat, in US dollars; null when the plan sells no extra seats.

**`seats.proration` "always\_invoice" always present**

How a seat increase is billed: always `always_invoice` (prorated to the rest of the period and charged to the card at purchase).

**`seats.proration_note` string always present**

A sentence stating the seat billing rule for display.

**`counties` object always present**

County slots: the plan's included counties plus paid county slots. The county SET is managed at `/v2/account/counties`; this is the billing quantity behind it.

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

Counties the plan includes; -1 means every county (National).

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

Paid county slots beyond `included`.

**`counties.billed_quantity` integer always present**

The county-slot quantity on the Stripe subscription (for an `invoiced` organization, the contracted slots; 0 with no subscription).

**`counties.active` integer always present**

Counties currently in the organization's active set, not counting a trial county.

**`counties.county_price_usd_month` number or null always present**

The plan's monthly price of one extra county slot, in US dollars; null when the plan sells no extra counties.

**`counties.proration` "always\_invoice" always present**

How a slot increase is billed: always `always_invoice` (prorated and charged at purchase).

**`counties.proration_note` string always present**

A sentence stating the county-slot billing rule for display.

**`counties.anchor_day` integer or null always present**

Day of the month of the organization's change window (the subscription's anchor date), on which county set changes land; null before a first subscription. (1 to 31)

**`subscription` object or null always present**

The current Stripe subscription (status active, trialing, past\_due or incomplete); null when there is none (Free, or contract-managed without one).

**`subscription.id` string**

Stripe subscription id (`sub_...`).

**`subscription.status` "active" | "trialing" | "past\_due" | "incomplete"**

`active`; `trialing`; `past_due` (a renewal was declined: L18 pauses county detail, CSV/XLSX export, live API keys and cluster editing at once, while the plan and its capacity stay held; pay with `POST /v2/account/billing/retry` after fixing the card); `incomplete` (the first payment has not succeeded).

**`subscription.current_period_start` string (date-time) or null**

Start of the current billing period.

**`subscription.current_period_end` string (date-time) or null**

End of the current billing period: where scheduled changes and a cancellation take effect.

**`subscription.renewal_date` string (date-time) or null**

When the subscription renews (the period end); null when it is set to cancel at period end.

**`subscription.cancel_at_period_end` boolean**

True when the subscription ends at `current_period_end` instead of renewing.

**`subscription.past_due_since` string (date-time) or null**

When the subscription first became past due; null when it is not past due.

**`subscription.dunning_grace_days` integer**

Days a past-due subscription has before it is cancelled and the organization drops to Free (the downgrade composite applies). Service is paused from the decline, not from the end of this window.

**`pending_change` BillPendingChange or null always present**

What is scheduled for the period end; null when nothing (and no cancellation) is scheduled.

**`pending_change.plan_code` string or null**

The plan the organization moves to at period end; null when no plan move is scheduled (an interval-only move leaves this null and sets `interval`).

**`pending_change.interval` "month" | "year" | null**

The interval the organization moves to at period end; null when no interval move is scheduled.

**`pending_change.seats_purchased` integer or null**

The purchased seat quantity from period end; null when no seat decrease is scheduled.

**`pending_change.counties_purchased` integer or null**

The paid county-slot quantity from period end; null when no slot release is scheduled.

**`pending_change.remove_addons` array of string**

Add-ons removed at period end; empty when none.

**`pending_change.cancel` boolean**

True when the subscription is set to cancel at period end (`POST /v2/account/billing/cancel`); `POST /v2/account/billing/resume` clears it and nothing else.

**`pending_change.effective_at` string (date-time)**

The period end at which everything here takes effect.

**`pending_change.changes` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval**

The scheduled set, one item per quantity. A cancellation is not an item here (see `cancel`).

**`pending_change.changes[].kind` "plan"**

Always `plan`.

**`pending_change.changes[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`pending_change.changes[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`pending_change.changes[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`pending_change.changes[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`pending_change.changes[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`pending_change.changes[].addons` array of string**

The add-ons that will be removed.

**`payment_method` BillPaymentMethod or null always present**

The default payment method; null when none is set.

**`payment_method.id` string**

Stripe PaymentMethod id (`pm_...`).

**`payment_method.brand` string**

Card brand as Stripe reports it, e.g. `visa`, `mastercard`.

**`payment_method.last4` string**

Last four digits of the card number.

**`payment_method.exp_month` integer**

Card expiry month. (1 to 12)

**`payment_method.exp_year` integer**

Card expiry year (four digits).

**`invoices` array of BillInvoice always present**

Up to the 12 most recent invoices, newest first.

**`invoices[].id` string always present**

Stripe invoice id (`in_...`).

**`invoices[].number` string or null always present**

The invoice number printed on it; null while a draft.

**`invoices[].status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`invoices[].total_cents` integer always present**

Invoice total in US cents.

**`invoices[].amount_due_cents` integer always present**

Amount still owed, in US cents.

**`invoices[].amount_paid_cents` integer always present**

Amount paid, in US cents.

**`invoices[].created` string (date-time) always present**

When the invoice was created.

**`invoices[].due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`invoices[].period` object always present**

The service period the invoice covers.

**`invoices[].period.start` string (date-time) always present**

Period start.

**`invoices[].period.end` string (date-time) always present**

Period end.

**`invoices[].hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`invoices[].collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

**`invoices[].description` string or null always present**

Free-text memo on the invoice (contract invoices), else null.

**`invoices[].lines` array of BillInvoiceLine always present**

The invoice's lines.

**`invoices[].lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`invoices[].lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`invoices[].lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`invoices[].lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`contract_managed`](/docs/api/errors#code-contract-managed) (the organization is `billing_mode: invoiced`: its plan is managed under a contract by Data Tactical Group), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 names an organization the caller does not belong to).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`not_subscribed`](/docs/api/errors#code-not-subscribed) (the organization has no active subscription; use `POST /v2/account/billing/subscribe`), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (another billing change for this organization is running; the request waited 15 seconds; retry).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`role_required`](/docs/api/errors#code-role-required) (403), [`contract_managed`](/docs/api/errors#code-contract-managed) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`not_subscribed`](/docs/api/errors#code-not-subscribed) (409), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (409). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/billing/retry`

**Pay the open invoice now**

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

Pays the latest open, card-charged invoice of the current subscription against the default payment method now, instead of waiting for Stripe's retry schedule. A success brings the subscription out of `past_due` in this response, which lifts L18's pause at once. A decline is a 200 with `paid: false` and the subscription still `past_due`. No request body. Writes the audit row `billing.retried` (invoice, paid, amount due). Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact). Billing changes for one organization are serialized: a second one waits for the first and, after 15 seconds, answers 409 [`billing_in_progress`](/docs/api/errors#code-billing-in-progress).

### Responses

#### `200` success

The payment attempt's outcome.

As `application/json`.

**`paid` boolean always present**

True when the invoice is now paid. A decline is not an error: `paid` is false and the subscription stays `past_due`.

**`invoice` BillInvoice always present**

A Stripe invoice for the organization.

**`invoice.id` string always present**

Stripe invoice id (`in_...`).

**`invoice.number` string or null always present**

The invoice number printed on it; null while a draft.

**`invoice.status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`invoice.total_cents` integer always present**

Invoice total in US cents.

**`invoice.amount_due_cents` integer always present**

Amount still owed, in US cents.

**`invoice.amount_paid_cents` integer always present**

Amount paid, in US cents.

**`invoice.created` string (date-time) always present**

When the invoice was created.

**`invoice.due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`invoice.period` object always present**

The service period the invoice covers.

**`invoice.period.start` string (date-time) always present**

Period start.

**`invoice.period.end` string (date-time) always present**

Period end.

**`invoice.hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`invoice.collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

**`invoice.description` string or null always present**

Free-text memo on the invoice (contract invoices), else null.

**`invoice.lines` array of BillInvoiceLine always present**

The invoice's lines.

**`invoice.lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`invoice.lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`invoice.lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`invoice.lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

**`summary` BillSummary always present**

The organization's billing state as Kelpie's seats-and-billing screen shows it.

**`summary.gateway` "fake" | "test" | "live" always present**

Which payment gateway this server runs: `live` (Stripe live mode, production only), `test` (Stripe test mode) or `fake` (an in-process simulation used in development and tests). T153: the Stripe secret key's prefix decides between `test` and `live`.

**`summary.publishable_key` string or null always present**

The Stripe publishable key to initialise Stripe.js and the Payment Element with.

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

`self_serve` (card subscription managed through these endpoints) or `invoiced` (a contract managed by Data Tactical Group: every self-serve change answers 403 [`contract_managed`](/docs/api/errors#code-contract-managed) ).

**`summary.contract` object or null always present**

The contract terms for an `invoiced` organization; null for `self_serve`.

**`summary.contract.expires_at` string (date-time) or null**

When the contract ends; null for no end date.

**`summary.contract.notes` string or null**

DTG's notes on the contract.

**`summary.plan` object always present**

The plan the organization holds now.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

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

The plan's display name (e.g. Local, Regional, National, Free).

**`summary.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Active add-ons.

**`summary.seats` object always present**

Seats: the plan's included seats plus purchased extra seats.

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

Seats the plan includes (the plan's allowance; see `GET /v2/plans`), raised to `grandfathered` while that is set.

**`summary.seats.grandfathered` integer or null**

T163: an organization that had more seated members than its plan's new included count when Regional fell to 10 seats and National to 15 keeps that many included seats until its seated members fall to the plan's count or its plan changes. This is that count; null for every other organization.

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

Extra seats the organization holds beyond `included`. A scheduled decrease does not lower this until period end.

**`summary.seats.billed_quantity` integer always present**

The seat quantity on the Stripe subscription (for an `invoiced` organization, the contracted extra seats; 0 with no subscription). Can differ from `purchased` briefly while a scheduled decrease has been sent to Stripe ahead of the renewal.

**`summary.seats.seat_price_usd_month` number or null always present**

The plan's monthly price of one extra seat, in US dollars; null when the plan sells no extra seats.

**`summary.seats.proration` "always\_invoice" always present**

How a seat increase is billed: always `always_invoice` (prorated to the rest of the period and charged to the card at purchase).

**`summary.seats.proration_note` string always present**

A sentence stating the seat billing rule for display.

**`summary.counties` object always present**

County slots: the plan's included counties plus paid county slots. The county SET is managed at `/v2/account/counties`; this is the billing quantity behind it.

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

Counties the plan includes; -1 means every county (National).

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

Paid county slots beyond `included`.

**`summary.counties.billed_quantity` integer always present**

The county-slot quantity on the Stripe subscription (for an `invoiced` organization, the contracted slots; 0 with no subscription).

**`summary.counties.active` integer always present**

Counties currently in the organization's active set, not counting a trial county.

**`summary.counties.county_price_usd_month` number or null always present**

The plan's monthly price of one extra county slot, in US dollars; null when the plan sells no extra counties.

**`summary.counties.proration` "always\_invoice" always present**

How a slot increase is billed: always `always_invoice` (prorated and charged at purchase).

**`summary.counties.proration_note` string always present**

A sentence stating the county-slot billing rule for display.

**`summary.counties.anchor_day` integer or null always present**

Day of the month of the organization's change window (the subscription's anchor date), on which county set changes land; null before a first subscription. (1 to 31)

**`summary.subscription` object or null always present**

The current Stripe subscription (status active, trialing, past\_due or incomplete); null when there is none (Free, or contract-managed without one).

**`summary.subscription.id` string**

Stripe subscription id (`sub_...`).

**`summary.subscription.status` "active" | "trialing" | "past\_due" | "incomplete"**

`active`; `trialing`; `past_due` (a renewal was declined: L18 pauses county detail, CSV/XLSX export, live API keys and cluster editing at once, while the plan and its capacity stay held; pay with `POST /v2/account/billing/retry` after fixing the card); `incomplete` (the first payment has not succeeded).

**`summary.subscription.current_period_start` string (date-time) or null**

Start of the current billing period.

**`summary.subscription.current_period_end` string (date-time) or null**

End of the current billing period: where scheduled changes and a cancellation take effect.

**`summary.subscription.renewal_date` string (date-time) or null**

When the subscription renews (the period end); null when it is set to cancel at period end.

**`summary.subscription.cancel_at_period_end` boolean**

True when the subscription ends at `current_period_end` instead of renewing.

**`summary.subscription.past_due_since` string (date-time) or null**

When the subscription first became past due; null when it is not past due.

**`summary.subscription.dunning_grace_days` integer**

Days a past-due subscription has before it is cancelled and the organization drops to Free (the downgrade composite applies). Service is paused from the decline, not from the end of this window.

**`summary.pending_change` BillPendingChange or null always present**

What is scheduled for the period end; null when nothing (and no cancellation) is scheduled.

**`summary.pending_change.plan_code` string or null**

The plan the organization moves to at period end; null when no plan move is scheduled (an interval-only move leaves this null and sets `interval`).

**`summary.pending_change.interval` "month" | "year" | null**

The interval the organization moves to at period end; null when no interval move is scheduled.

**`summary.pending_change.seats_purchased` integer or null**

The purchased seat quantity from period end; null when no seat decrease is scheduled.

**`summary.pending_change.counties_purchased` integer or null**

The paid county-slot quantity from period end; null when no slot release is scheduled.

**`summary.pending_change.remove_addons` array of string**

Add-ons removed at period end; empty when none.

**`summary.pending_change.cancel` boolean**

True when the subscription is set to cancel at period end (`POST /v2/account/billing/cancel`); `POST /v2/account/billing/resume` clears it and nothing else.

**`summary.pending_change.effective_at` string (date-time)**

The period end at which everything here takes effect.

**`summary.pending_change.changes` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval**

The scheduled set, one item per quantity. A cancellation is not an item here (see `cancel`).

**`summary.pending_change.changes[].kind` "plan"**

Always `plan`.

**`summary.pending_change.changes[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`summary.pending_change.changes[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`summary.pending_change.changes[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`summary.pending_change.changes[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`summary.pending_change.changes[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`summary.pending_change.changes[].addons` array of string**

The add-ons that will be removed.

**`summary.payment_method` BillPaymentMethod or null always present**

The default payment method; null when none is set.

**`summary.payment_method.id` string**

Stripe PaymentMethod id (`pm_...`).

**`summary.payment_method.brand` string**

Card brand as Stripe reports it, e.g. `visa`, `mastercard`.

**`summary.payment_method.last4` string**

Last four digits of the card number.

**`summary.payment_method.exp_month` integer**

Card expiry month. (1 to 12)

**`summary.payment_method.exp_year` integer**

Card expiry year (four digits).

**`summary.invoices` array of BillInvoice always present**

Up to the 12 most recent invoices, newest first.

**`summary.invoices[].id` string always present**

Stripe invoice id (`in_...`).

**`summary.invoices[].number` string or null always present**

The invoice number printed on it; null while a draft.

**`summary.invoices[].status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`summary.invoices[].total_cents` integer always present**

Invoice total in US cents.

**`summary.invoices[].amount_due_cents` integer always present**

Amount still owed, in US cents.

**`summary.invoices[].amount_paid_cents` integer always present**

Amount paid, in US cents.

**`summary.invoices[].created` string (date-time) always present**

When the invoice was created.

**`summary.invoices[].due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`summary.invoices[].period` object always present**

The service period the invoice covers.

**`summary.invoices[].period.start` string (date-time) always present**

Period start.

**`summary.invoices[].period.end` string (date-time) always present**

Period end.

**`summary.invoices[].hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`summary.invoices[].collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

**`summary.invoices[].description` string or null always present**

Free-text memo on the invoice (contract invoices), else null.

**`summary.invoices[].lines` array of BillInvoiceLine always present**

The invoice's lines.

**`summary.invoices[].lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`summary.invoices[].lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`summary.invoices[].lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`summary.invoices[].lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`contract_managed`](/docs/api/errors#code-contract-managed) (the organization is `billing_mode: invoiced`: its plan is managed under a contract by Data Tactical Group), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 names an organization the caller does not belong to).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`not_subscribed`](/docs/api/errors#code-not-subscribed) (the organization has no active subscription; use `POST /v2/account/billing/subscribe`), [`no_open_invoice`](/docs/api/errors#code-no-open-invoice) (nothing is owed right now), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (another billing change for this organization is running; the request waited 15 seconds; retry).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`role_required`](/docs/api/errors#code-role-required) (403), [`contract_managed`](/docs/api/errors#code-contract-managed) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`not_subscribed`](/docs/api/errors#code-not-subscribed) (409), [`no_open_invoice`](/docs/api/errors#code-no-open-invoice) (409), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (409). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/seats`

**Set the purchased seat quantity**

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

Sets the number of extra seats bought beyond the plan's included seats (the whole target, not a delta). The same as `POST /v2/account/billing/change {seats}`: an increase applies now, prorated to the rest of the period and charged at once (`always_invoice`); a decrease is scheduled for period end, not credited, and the seats stay usable until then, when members beyond the seat count become free members. Price it first with `POST /v2/account/billing/preview {seats}`. Writes no audit row of its own. Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact). Billing changes for one organization are serialized: a second one waits for the first and, after 15 seconds, answers 409 [`billing_in_progress`](/docs/api/errors#code-billing-in-progress).

### Request body

Required; `application/json`.

**`quantity` integer required**

Extra seats wanted, 0 to 500. (0 to 500)

### Responses

#### `200` success

The change applied or scheduled.

As `application/json`.

**`applied` "immediately" | "period\_end" always present**

`immediately` when an increase was applied now (and charged), else `period_end` (the change, if any, is scheduled; also returned for a no-op).

**`replaced` BillPendingReplaced always present**

For each scheduled item a change would replace (a second change to the same quantity), the item it replaces, keyed by its kind (`plan`, `counties`, `seats`, `addon_removal`). An empty object when it replaces nothing.

**`replaced.plan` BillPendingItemPlan**

A scheduled plan or interval move, applied at period end.

**`replaced.plan.kind` "plan" always present**

Always `plan`.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`replaced.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`replaced.counties` BillPendingItemCounties**

A scheduled decrease of the paid county-slot quantity (a slot release), applied at period end, not credited; the slots stay usable until then.

**`replaced.counties.kind` "counties" always present**

Always `counties`.

**`replaced.counties.counties_purchased` integer always present**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`replaced.counties.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`replaced.seats` BillPendingItemSeats**

A scheduled decrease of purchased seats, applied at period end, not credited; the seats stay usable until then.

**`replaced.seats.kind` "seats" always present**

Always `seats`.

**`replaced.seats.seats_purchased` integer always present**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`replaced.seats.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`replaced.addon_removal` BillPendingItemAddonRemoval**

A scheduled add-on removal, applied at period end. When the API add-on leaves, the organization's live API keys are revoked at that moment; sandbox keys stay.

**`replaced.addon_removal.kind` "addon\_removal" always present**

Always `addon_removal`.

**`replaced.addon_removal.addons` array of string always present**

The add-ons that will be removed.

**`replaced.addon_removal.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview` object always present**

The preview the change was decided on.

**`preview.current` BillStateSnapshot always present**

A billing state: plan, interval, add-ons and purchased quantities.

**`preview.current.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.current.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.current.addons` array of string always present**

Add-ons.

**`preview.current.seats_purchased` number always present**

Purchased seats beyond the plan's included seats.

**`preview.current.counties_purchased` number always present**

Paid county slots beyond the plan's included counties.

**`preview.target` object always present**

The state the request asks for (unnamed quantities at their current values).

**`preview.target.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.target.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.target.addons` array of string always present**

Add-ons.

**`preview.target.seats_purchased` number always present**

Purchased seats beyond the plan's included seats.

**`preview.target.counties_purchased` number always present**

Paid county slots beyond the plan's included counties.

**`preview.kind` "upgrade" | "downgrade" | "mixed" | "none" always present**

`upgrade` (only increases: applied now), `downgrade` (only decreases: scheduled for period end), `mixed` (both: the increases now at the current plan and interval, the decreases scheduled), `none` (no change).

**`preview.effective` "immediately" | "period\_end" always present**

`immediately` when anything applies now (upgrade or mixed), else `period_end` (also for `none`).

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

Now for an immediate change, else the current period end.

**`preview.proration_behavior` "always\_invoice" | "none" always present**

`always_invoice` when the change applies now: the prorated amount is invoiced and charged to the card at once. `none` for a period-end change: nothing is charged now and nothing is credited.

**`preview.immediate_charge_cents` integer always present**

What the card is charged now, in US cents (the sum of the proration lines, never below 0); 0 for a period-end change. (at least 0)

**`preview.proration_lines` array of BillAmountLine always present**

The prorated lines charged now. When the API add-on is being added (term restart) these are every line of the restarted term.

**`preview.proration_lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`preview.proration_lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`preview.proration_lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`preview.term_restart` boolean always present**

True when the change adds the API add-on: the WHOLE subscription restarts its term today, every item moving to the new anchor together; the new term is charged now.

**`preview.credit_cents` integer or null always present**

Only when `term_restart` is true: the magnitude, in US cents, of any credit lines Stripe returns for unused time (commonly 0, because Stripe nets a continuing item into one line). Null otherwise.

**`preview.new_renewal_date` string (date-time) or null always present**

Only when `term_restart` is true: the renewal date after the restart. Null otherwise.

**`preview.next_invoice` object always present**

The next renewal invoice, priced on the period-end state (what this change asks for plus what is already scheduled). Empty (`total_cents` 0, no lines) on a term restart, where everything is due now.

**`preview.next_invoice.total_cents` integer always present**

Total of the next invoice in US cents.

**`preview.next_invoice.lines` array of BillAmountLine always present**

Its lines.

**`preview.next_invoice.lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`preview.next_invoice.lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`preview.next_invoice.lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`preview.unit_prices_cents` object always present**

Unit prices, in US cents per interval, for the target plan and interval, from the price catalog; 0 where the catalog holds no such price.

**`preview.unit_prices_cents.plan` integer always present**

The plan price.

**`preview.unit_prices_cents.seat` integer always present**

One extra seat.

**`preview.unit_prices_cents.county` integer always present**

One extra county slot; 0 when the plan sells none.

**`preview.unit_prices_cents.api_addon` integer always present**

The API add-on (0 on a monthly interval: it is sold annual only).

**`preview.counties_requiring_choice` object or null always present**

Set when the period-end county capacity is below the organization's active counties (counties already scheduled to leave are not counted): the change must then carry `surviving_fips`. Null otherwise.

**`preview.counties_requiring_choice.capacity` integer always present**

How many counties the period-end state holds (included plus paid slots).

**`preview.counties_requiring_choice.candidates` array of BillCountyCandidate always present**

The active counties to choose from, in KEEP order: the first `capacity` are the default choice.

**`preview.counties_requiring_choice.candidates[].fips` string always present**

Five-digit county FIPS code.

**`preview.counties_requiring_choice.candidates[].source` "manual" | "state" | "msa" always present**

How the county entered the active set: `manual` (picked), `state` or `msa` (added as part of a state or metro).

**`preview.counties_requiring_choice.candidates[].added_at` string (date-time) always present**

When it was added.

**`preview.counties_requiring_choice.candidates[].last_used` string (date-time) or null always present**

The organization's last data request touching this county in the past 90 days; null when none.

**`preview.county_slots_releasable` integer always present**

How many paid county slots the period-end state holds that the active set does not need (for example after a move to a plan with more included counties), so the customer could release them.

**`preview.seats_becoming_free_members` integer always present**

How many seated members exceed the period-end seat count and would become free members.

**`preview.scheduled` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval always present**

What is scheduled now, before this change.

**`preview.scheduled[].kind` "plan"**

Always `plan`.

**`preview.scheduled[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.scheduled[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.scheduled[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.scheduled[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.scheduled[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.scheduled[].addons` array of string**

The add-ons that will be removed.

**`preview.scheduled_after` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval always present**

What this change would leave scheduled (in a mixed change, the plan move appears here).

**`preview.scheduled_after[].kind` "plan"**

Always `plan`.

**`preview.scheduled_after[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.scheduled_after[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.scheduled_after[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.scheduled_after[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.scheduled_after[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.scheduled_after[].addons` array of string**

The add-ons that will be removed.

**`preview.replaces` BillPendingReplaced always present**

For each scheduled item a change would replace (a second change to the same quantity), the item it replaces, keyed by its kind (`plan`, `counties`, `seats`, `addon_removal`). An empty object when it replaces nothing.

**`preview.replaces.plan` BillPendingItemPlan**

A scheduled plan or interval move, applied at period end.

**`preview.replaces.plan.kind` "plan" always present**

Always `plan`.

**`preview.replaces.plan.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.replaces.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.replaces.counties` BillPendingItemCounties**

A scheduled decrease of the paid county-slot quantity (a slot release), applied at period end, not credited; the slots stay usable until then.

**`preview.replaces.counties.kind` "counties" always present**

Always `counties`.

**`preview.replaces.counties.counties_purchased` integer always present**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.replaces.counties.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview.replaces.seats` BillPendingItemSeats**

A scheduled decrease of purchased seats, applied at period end, not credited; the seats stay usable until then.

**`preview.replaces.seats.kind` "seats" always present**

Always `seats`.

**`preview.replaces.seats.seats_purchased` integer always present**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.replaces.seats.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview.replaces.addon_removal` BillPendingItemAddonRemoval**

A scheduled add-on removal, applied at period end. When the API add-on leaves, the organization's live API keys are revoked at that moment; sandbox keys stay.

**`preview.replaces.addon_removal.kind` "addon\_removal" always present**

Always `addon_removal`.

**`preview.replaces.addon_removal.addons` array of string always present**

The add-ons that will be removed.

**`preview.replaces.addon_removal.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`summary` BillSummary always present**

The organization's billing state as Kelpie's seats-and-billing screen shows it.

**`summary.gateway` "fake" | "test" | "live" always present**

Which payment gateway this server runs: `live` (Stripe live mode, production only), `test` (Stripe test mode) or `fake` (an in-process simulation used in development and tests). T153: the Stripe secret key's prefix decides between `test` and `live`.

**`summary.publishable_key` string or null always present**

The Stripe publishable key to initialise Stripe.js and the Payment Element with.

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

`self_serve` (card subscription managed through these endpoints) or `invoiced` (a contract managed by Data Tactical Group: every self-serve change answers 403 [`contract_managed`](/docs/api/errors#code-contract-managed) ).

**`summary.contract` object or null always present**

The contract terms for an `invoiced` organization; null for `self_serve`.

**`summary.contract.expires_at` string (date-time) or null**

When the contract ends; null for no end date.

**`summary.contract.notes` string or null**

DTG's notes on the contract.

**`summary.plan` object always present**

The plan the organization holds now.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

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

The plan's display name (e.g. Local, Regional, National, Free).

**`summary.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Active add-ons.

**`summary.seats` object always present**

Seats: the plan's included seats plus purchased extra seats.

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

Seats the plan includes (the plan's allowance; see `GET /v2/plans`), raised to `grandfathered` while that is set.

**`summary.seats.grandfathered` integer or null**

T163: an organization that had more seated members than its plan's new included count when Regional fell to 10 seats and National to 15 keeps that many included seats until its seated members fall to the plan's count or its plan changes. This is that count; null for every other organization.

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

Extra seats the organization holds beyond `included`. A scheduled decrease does not lower this until period end.

**`summary.seats.billed_quantity` integer always present**

The seat quantity on the Stripe subscription (for an `invoiced` organization, the contracted extra seats; 0 with no subscription). Can differ from `purchased` briefly while a scheduled decrease has been sent to Stripe ahead of the renewal.

**`summary.seats.seat_price_usd_month` number or null always present**

The plan's monthly price of one extra seat, in US dollars; null when the plan sells no extra seats.

**`summary.seats.proration` "always\_invoice" always present**

How a seat increase is billed: always `always_invoice` (prorated to the rest of the period and charged to the card at purchase).

**`summary.seats.proration_note` string always present**

A sentence stating the seat billing rule for display.

**`summary.counties` object always present**

County slots: the plan's included counties plus paid county slots. The county SET is managed at `/v2/account/counties`; this is the billing quantity behind it.

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

Counties the plan includes; -1 means every county (National).

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

Paid county slots beyond `included`.

**`summary.counties.billed_quantity` integer always present**

The county-slot quantity on the Stripe subscription (for an `invoiced` organization, the contracted slots; 0 with no subscription).

**`summary.counties.active` integer always present**

Counties currently in the organization's active set, not counting a trial county.

**`summary.counties.county_price_usd_month` number or null always present**

The plan's monthly price of one extra county slot, in US dollars; null when the plan sells no extra counties.

**`summary.counties.proration` "always\_invoice" always present**

How a slot increase is billed: always `always_invoice` (prorated and charged at purchase).

**`summary.counties.proration_note` string always present**

A sentence stating the county-slot billing rule for display.

**`summary.counties.anchor_day` integer or null always present**

Day of the month of the organization's change window (the subscription's anchor date), on which county set changes land; null before a first subscription. (1 to 31)

**`summary.subscription` object or null always present**

The current Stripe subscription (status active, trialing, past\_due or incomplete); null when there is none (Free, or contract-managed without one).

**`summary.subscription.id` string**

Stripe subscription id (`sub_...`).

**`summary.subscription.status` "active" | "trialing" | "past\_due" | "incomplete"**

`active`; `trialing`; `past_due` (a renewal was declined: L18 pauses county detail, CSV/XLSX export, live API keys and cluster editing at once, while the plan and its capacity stay held; pay with `POST /v2/account/billing/retry` after fixing the card); `incomplete` (the first payment has not succeeded).

**`summary.subscription.current_period_start` string (date-time) or null**

Start of the current billing period.

**`summary.subscription.current_period_end` string (date-time) or null**

End of the current billing period: where scheduled changes and a cancellation take effect.

**`summary.subscription.renewal_date` string (date-time) or null**

When the subscription renews (the period end); null when it is set to cancel at period end.

**`summary.subscription.cancel_at_period_end` boolean**

True when the subscription ends at `current_period_end` instead of renewing.

**`summary.subscription.past_due_since` string (date-time) or null**

When the subscription first became past due; null when it is not past due.

**`summary.subscription.dunning_grace_days` integer**

Days a past-due subscription has before it is cancelled and the organization drops to Free (the downgrade composite applies). Service is paused from the decline, not from the end of this window.

**`summary.pending_change` BillPendingChange or null always present**

What is scheduled for the period end; null when nothing (and no cancellation) is scheduled.

**`summary.pending_change.plan_code` string or null**

The plan the organization moves to at period end; null when no plan move is scheduled (an interval-only move leaves this null and sets `interval`).

**`summary.pending_change.interval` "month" | "year" | null**

The interval the organization moves to at period end; null when no interval move is scheduled.

**`summary.pending_change.seats_purchased` integer or null**

The purchased seat quantity from period end; null when no seat decrease is scheduled.

**`summary.pending_change.counties_purchased` integer or null**

The paid county-slot quantity from period end; null when no slot release is scheduled.

**`summary.pending_change.remove_addons` array of string**

Add-ons removed at period end; empty when none.

**`summary.pending_change.cancel` boolean**

True when the subscription is set to cancel at period end (`POST /v2/account/billing/cancel`); `POST /v2/account/billing/resume` clears it and nothing else.

**`summary.pending_change.effective_at` string (date-time)**

The period end at which everything here takes effect.

**`summary.pending_change.changes` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval**

The scheduled set, one item per quantity. A cancellation is not an item here (see `cancel`).

**`summary.pending_change.changes[].kind` "plan"**

Always `plan`.

**`summary.pending_change.changes[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`summary.pending_change.changes[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`summary.pending_change.changes[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`summary.pending_change.changes[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`summary.pending_change.changes[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`summary.pending_change.changes[].addons` array of string**

The add-ons that will be removed.

**`summary.payment_method` BillPaymentMethod or null always present**

The default payment method; null when none is set.

**`summary.payment_method.id` string**

Stripe PaymentMethod id (`pm_...`).

**`summary.payment_method.brand` string**

Card brand as Stripe reports it, e.g. `visa`, `mastercard`.

**`summary.payment_method.last4` string**

Last four digits of the card number.

**`summary.payment_method.exp_month` integer**

Card expiry month. (1 to 12)

**`summary.payment_method.exp_year` integer**

Card expiry year (four digits).

**`summary.invoices` array of BillInvoice always present**

Up to the 12 most recent invoices, newest first.

**`summary.invoices[].id` string always present**

Stripe invoice id (`in_...`).

**`summary.invoices[].number` string or null always present**

The invoice number printed on it; null while a draft.

**`summary.invoices[].status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`summary.invoices[].total_cents` integer always present**

Invoice total in US cents.

**`summary.invoices[].amount_due_cents` integer always present**

Amount still owed, in US cents.

**`summary.invoices[].amount_paid_cents` integer always present**

Amount paid, in US cents.

**`summary.invoices[].created` string (date-time) always present**

When the invoice was created.

**`summary.invoices[].due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`summary.invoices[].period` object always present**

The service period the invoice covers.

**`summary.invoices[].period.start` string (date-time) always present**

Period start.

**`summary.invoices[].period.end` string (date-time) always present**

Period end.

**`summary.invoices[].hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`summary.invoices[].collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

**`summary.invoices[].description` string or null always present**

Free-text memo on the invoice (contract invoices), else null.

**`summary.invoices[].lines` array of BillInvoiceLine always present**

The invoice's lines.

**`summary.invoices[].lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`summary.invoices[].lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`summary.invoices[].lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`summary.invoices[].lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

#### `400` error

Bad request. Codes: [`bad_request`](/docs/api/errors#code-bad-request) (`quantity` missing, not a number, or not an integer 0 to 500), [`surviving_counties_required`](/docs/api/errors#code-surviving-counties-required) (a county choice is owed; `capacity`, `candidates`; `errors` repeats it), [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (only when the period-end state would re-price the API add-on onto a non-annual interval), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON).

As `application/problem+json`.

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`contract_managed`](/docs/api/errors#code-contract-managed) (the organization is `billing_mode: invoiced`: its plan is managed under a contract by Data Tactical Group), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 names an organization the caller does not belong to).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`not_subscribed`](/docs/api/errors#code-not-subscribed) (the organization has no active subscription; use `POST /v2/account/billing/subscribe`), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (another billing change for this organization is running; the request waited 15 seconds; retry).

As `application/problem+json`.

#### `413` error

Payload too large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the JSON body is over 4 KB; the problem carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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), [`surviving_counties_required`](/docs/api/errors#code-surviving-counties-required) (400), [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`role_required`](/docs/api/errors#code-role-required) (403), [`contract_managed`](/docs/api/errors#code-contract-managed) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`not_subscribed`](/docs/api/errors#code-not-subscribed) (409), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (409), [`body_too_large`](/docs/api/errors#code-body-too-large) (413). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/county-slots`

**Set the paid county-slot quantity**

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

Sets the number of paid county slots beyond the plan's included counties (D44; the whole target, not a delta). The same as `POST /v2/account/billing/change {counties}`: an increase applies now, prorated and charged at once (`always_invoice`); a decrease is scheduled for period end, not credited, and the slots stay usable until then. The county SET is managed at `/v2/account/counties` (which answers [`confirm_purchase_required`](/docs/api/errors#code-confirm-purchase-required) when adding a county needs a slot bought); this is the billing quantity behind it. A decrease below the active counties needs a surviving-county choice, which this endpoint cannot carry: use `POST /v2/account/billing/change {counties, surviving_fips}`. Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact). Billing changes for one organization are serialized: a second one waits for the first and, after 15 seconds, answers 409 [`billing_in_progress`](/docs/api/errors#code-billing-in-progress).

### Request body

Required; `application/json`.

**`quantity` integer required**

Paid county slots wanted, 0 to 3194. (0 to 3194)

### Responses

#### `200` success

The change applied or scheduled.

As `application/json`.

**`applied` "immediately" | "period\_end" always present**

`immediately` when an increase was applied now (and charged), else `period_end` (the change, if any, is scheduled; also returned for a no-op).

**`replaced` BillPendingReplaced always present**

For each scheduled item a change would replace (a second change to the same quantity), the item it replaces, keyed by its kind (`plan`, `counties`, `seats`, `addon_removal`). An empty object when it replaces nothing.

**`replaced.plan` BillPendingItemPlan**

A scheduled plan or interval move, applied at period end.

**`replaced.plan.kind` "plan" always present**

Always `plan`.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`replaced.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`replaced.counties` BillPendingItemCounties**

A scheduled decrease of the paid county-slot quantity (a slot release), applied at period end, not credited; the slots stay usable until then.

**`replaced.counties.kind` "counties" always present**

Always `counties`.

**`replaced.counties.counties_purchased` integer always present**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`replaced.counties.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`replaced.seats` BillPendingItemSeats**

A scheduled decrease of purchased seats, applied at period end, not credited; the seats stay usable until then.

**`replaced.seats.kind` "seats" always present**

Always `seats`.

**`replaced.seats.seats_purchased` integer always present**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`replaced.seats.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`replaced.addon_removal` BillPendingItemAddonRemoval**

A scheduled add-on removal, applied at period end. When the API add-on leaves, the organization's live API keys are revoked at that moment; sandbox keys stay.

**`replaced.addon_removal.kind` "addon\_removal" always present**

Always `addon_removal`.

**`replaced.addon_removal.addons` array of string always present**

The add-ons that will be removed.

**`replaced.addon_removal.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview` object always present**

The preview the change was decided on.

**`preview.current` BillStateSnapshot always present**

A billing state: plan, interval, add-ons and purchased quantities.

**`preview.current.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.current.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.current.addons` array of string always present**

Add-ons.

**`preview.current.seats_purchased` number always present**

Purchased seats beyond the plan's included seats.

**`preview.current.counties_purchased` number always present**

Paid county slots beyond the plan's included counties.

**`preview.target` object always present**

The state the request asks for (unnamed quantities at their current values).

**`preview.target.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.target.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.target.addons` array of string always present**

Add-ons.

**`preview.target.seats_purchased` number always present**

Purchased seats beyond the plan's included seats.

**`preview.target.counties_purchased` number always present**

Paid county slots beyond the plan's included counties.

**`preview.kind` "upgrade" | "downgrade" | "mixed" | "none" always present**

`upgrade` (only increases: applied now), `downgrade` (only decreases: scheduled for period end), `mixed` (both: the increases now at the current plan and interval, the decreases scheduled), `none` (no change).

**`preview.effective` "immediately" | "period\_end" always present**

`immediately` when anything applies now (upgrade or mixed), else `period_end` (also for `none`).

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

Now for an immediate change, else the current period end.

**`preview.proration_behavior` "always\_invoice" | "none" always present**

`always_invoice` when the change applies now: the prorated amount is invoiced and charged to the card at once. `none` for a period-end change: nothing is charged now and nothing is credited.

**`preview.immediate_charge_cents` integer always present**

What the card is charged now, in US cents (the sum of the proration lines, never below 0); 0 for a period-end change. (at least 0)

**`preview.proration_lines` array of BillAmountLine always present**

The prorated lines charged now. When the API add-on is being added (term restart) these are every line of the restarted term.

**`preview.proration_lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`preview.proration_lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`preview.proration_lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`preview.term_restart` boolean always present**

True when the change adds the API add-on: the WHOLE subscription restarts its term today, every item moving to the new anchor together; the new term is charged now.

**`preview.credit_cents` integer or null always present**

Only when `term_restart` is true: the magnitude, in US cents, of any credit lines Stripe returns for unused time (commonly 0, because Stripe nets a continuing item into one line). Null otherwise.

**`preview.new_renewal_date` string (date-time) or null always present**

Only when `term_restart` is true: the renewal date after the restart. Null otherwise.

**`preview.next_invoice` object always present**

The next renewal invoice, priced on the period-end state (what this change asks for plus what is already scheduled). Empty (`total_cents` 0, no lines) on a term restart, where everything is due now.

**`preview.next_invoice.total_cents` integer always present**

Total of the next invoice in US cents.

**`preview.next_invoice.lines` array of BillAmountLine always present**

Its lines.

**`preview.next_invoice.lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`preview.next_invoice.lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`preview.next_invoice.lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`preview.unit_prices_cents` object always present**

Unit prices, in US cents per interval, for the target plan and interval, from the price catalog; 0 where the catalog holds no such price.

**`preview.unit_prices_cents.plan` integer always present**

The plan price.

**`preview.unit_prices_cents.seat` integer always present**

One extra seat.

**`preview.unit_prices_cents.county` integer always present**

One extra county slot; 0 when the plan sells none.

**`preview.unit_prices_cents.api_addon` integer always present**

The API add-on (0 on a monthly interval: it is sold annual only).

**`preview.counties_requiring_choice` object or null always present**

Set when the period-end county capacity is below the organization's active counties (counties already scheduled to leave are not counted): the change must then carry `surviving_fips`. Null otherwise.

**`preview.counties_requiring_choice.capacity` integer always present**

How many counties the period-end state holds (included plus paid slots).

**`preview.counties_requiring_choice.candidates` array of BillCountyCandidate always present**

The active counties to choose from, in KEEP order: the first `capacity` are the default choice.

**`preview.counties_requiring_choice.candidates[].fips` string always present**

Five-digit county FIPS code.

**`preview.counties_requiring_choice.candidates[].source` "manual" | "state" | "msa" always present**

How the county entered the active set: `manual` (picked), `state` or `msa` (added as part of a state or metro).

**`preview.counties_requiring_choice.candidates[].added_at` string (date-time) always present**

When it was added.

**`preview.counties_requiring_choice.candidates[].last_used` string (date-time) or null always present**

The organization's last data request touching this county in the past 90 days; null when none.

**`preview.county_slots_releasable` integer always present**

How many paid county slots the period-end state holds that the active set does not need (for example after a move to a plan with more included counties), so the customer could release them.

**`preview.seats_becoming_free_members` integer always present**

How many seated members exceed the period-end seat count and would become free members.

**`preview.scheduled` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval always present**

What is scheduled now, before this change.

**`preview.scheduled[].kind` "plan"**

Always `plan`.

**`preview.scheduled[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.scheduled[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.scheduled[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.scheduled[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.scheduled[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.scheduled[].addons` array of string**

The add-ons that will be removed.

**`preview.scheduled_after` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval always present**

What this change would leave scheduled (in a mixed change, the plan move appears here).

**`preview.scheduled_after[].kind` "plan"**

Always `plan`.

**`preview.scheduled_after[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.scheduled_after[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.scheduled_after[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.scheduled_after[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.scheduled_after[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.scheduled_after[].addons` array of string**

The add-ons that will be removed.

**`preview.replaces` BillPendingReplaced always present**

For each scheduled item a change would replace (a second change to the same quantity), the item it replaces, keyed by its kind (`plan`, `counties`, `seats`, `addon_removal`). An empty object when it replaces nothing.

**`preview.replaces.plan` BillPendingItemPlan**

A scheduled plan or interval move, applied at period end.

**`preview.replaces.plan.kind` "plan" always present**

Always `plan`.

**`preview.replaces.plan.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.replaces.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.replaces.counties` BillPendingItemCounties**

A scheduled decrease of the paid county-slot quantity (a slot release), applied at period end, not credited; the slots stay usable until then.

**`preview.replaces.counties.kind` "counties" always present**

Always `counties`.

**`preview.replaces.counties.counties_purchased` integer always present**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.replaces.counties.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview.replaces.seats` BillPendingItemSeats**

A scheduled decrease of purchased seats, applied at period end, not credited; the seats stay usable until then.

**`preview.replaces.seats.kind` "seats" always present**

Always `seats`.

**`preview.replaces.seats.seats_purchased` integer always present**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.replaces.seats.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview.replaces.addon_removal` BillPendingItemAddonRemoval**

A scheduled add-on removal, applied at period end. When the API add-on leaves, the organization's live API keys are revoked at that moment; sandbox keys stay.

**`preview.replaces.addon_removal.kind` "addon\_removal" always present**

Always `addon_removal`.

**`preview.replaces.addon_removal.addons` array of string always present**

The add-ons that will be removed.

**`preview.replaces.addon_removal.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`summary` BillSummary always present**

The organization's billing state as Kelpie's seats-and-billing screen shows it.

**`summary.gateway` "fake" | "test" | "live" always present**

Which payment gateway this server runs: `live` (Stripe live mode, production only), `test` (Stripe test mode) or `fake` (an in-process simulation used in development and tests). T153: the Stripe secret key's prefix decides between `test` and `live`.

**`summary.publishable_key` string or null always present**

The Stripe publishable key to initialise Stripe.js and the Payment Element with.

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

`self_serve` (card subscription managed through these endpoints) or `invoiced` (a contract managed by Data Tactical Group: every self-serve change answers 403 [`contract_managed`](/docs/api/errors#code-contract-managed) ).

**`summary.contract` object or null always present**

The contract terms for an `invoiced` organization; null for `self_serve`.

**`summary.contract.expires_at` string (date-time) or null**

When the contract ends; null for no end date.

**`summary.contract.notes` string or null**

DTG's notes on the contract.

**`summary.plan` object always present**

The plan the organization holds now.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

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

The plan's display name (e.g. Local, Regional, National, Free).

**`summary.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Active add-ons.

**`summary.seats` object always present**

Seats: the plan's included seats plus purchased extra seats.

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

Seats the plan includes (the plan's allowance; see `GET /v2/plans`), raised to `grandfathered` while that is set.

**`summary.seats.grandfathered` integer or null**

T163: an organization that had more seated members than its plan's new included count when Regional fell to 10 seats and National to 15 keeps that many included seats until its seated members fall to the plan's count or its plan changes. This is that count; null for every other organization.

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

Extra seats the organization holds beyond `included`. A scheduled decrease does not lower this until period end.

**`summary.seats.billed_quantity` integer always present**

The seat quantity on the Stripe subscription (for an `invoiced` organization, the contracted extra seats; 0 with no subscription). Can differ from `purchased` briefly while a scheduled decrease has been sent to Stripe ahead of the renewal.

**`summary.seats.seat_price_usd_month` number or null always present**

The plan's monthly price of one extra seat, in US dollars; null when the plan sells no extra seats.

**`summary.seats.proration` "always\_invoice" always present**

How a seat increase is billed: always `always_invoice` (prorated to the rest of the period and charged to the card at purchase).

**`summary.seats.proration_note` string always present**

A sentence stating the seat billing rule for display.

**`summary.counties` object always present**

County slots: the plan's included counties plus paid county slots. The county SET is managed at `/v2/account/counties`; this is the billing quantity behind it.

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

Counties the plan includes; -1 means every county (National).

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

Paid county slots beyond `included`.

**`summary.counties.billed_quantity` integer always present**

The county-slot quantity on the Stripe subscription (for an `invoiced` organization, the contracted slots; 0 with no subscription).

**`summary.counties.active` integer always present**

Counties currently in the organization's active set, not counting a trial county.

**`summary.counties.county_price_usd_month` number or null always present**

The plan's monthly price of one extra county slot, in US dollars; null when the plan sells no extra counties.

**`summary.counties.proration` "always\_invoice" always present**

How a slot increase is billed: always `always_invoice` (prorated and charged at purchase).

**`summary.counties.proration_note` string always present**

A sentence stating the county-slot billing rule for display.

**`summary.counties.anchor_day` integer or null always present**

Day of the month of the organization's change window (the subscription's anchor date), on which county set changes land; null before a first subscription. (1 to 31)

**`summary.subscription` object or null always present**

The current Stripe subscription (status active, trialing, past\_due or incomplete); null when there is none (Free, or contract-managed without one).

**`summary.subscription.id` string**

Stripe subscription id (`sub_...`).

**`summary.subscription.status` "active" | "trialing" | "past\_due" | "incomplete"**

`active`; `trialing`; `past_due` (a renewal was declined: L18 pauses county detail, CSV/XLSX export, live API keys and cluster editing at once, while the plan and its capacity stay held; pay with `POST /v2/account/billing/retry` after fixing the card); `incomplete` (the first payment has not succeeded).

**`summary.subscription.current_period_start` string (date-time) or null**

Start of the current billing period.

**`summary.subscription.current_period_end` string (date-time) or null**

End of the current billing period: where scheduled changes and a cancellation take effect.

**`summary.subscription.renewal_date` string (date-time) or null**

When the subscription renews (the period end); null when it is set to cancel at period end.

**`summary.subscription.cancel_at_period_end` boolean**

True when the subscription ends at `current_period_end` instead of renewing.

**`summary.subscription.past_due_since` string (date-time) or null**

When the subscription first became past due; null when it is not past due.

**`summary.subscription.dunning_grace_days` integer**

Days a past-due subscription has before it is cancelled and the organization drops to Free (the downgrade composite applies). Service is paused from the decline, not from the end of this window.

**`summary.pending_change` BillPendingChange or null always present**

What is scheduled for the period end; null when nothing (and no cancellation) is scheduled.

**`summary.pending_change.plan_code` string or null**

The plan the organization moves to at period end; null when no plan move is scheduled (an interval-only move leaves this null and sets `interval`).

**`summary.pending_change.interval` "month" | "year" | null**

The interval the organization moves to at period end; null when no interval move is scheduled.

**`summary.pending_change.seats_purchased` integer or null**

The purchased seat quantity from period end; null when no seat decrease is scheduled.

**`summary.pending_change.counties_purchased` integer or null**

The paid county-slot quantity from period end; null when no slot release is scheduled.

**`summary.pending_change.remove_addons` array of string**

Add-ons removed at period end; empty when none.

**`summary.pending_change.cancel` boolean**

True when the subscription is set to cancel at period end (`POST /v2/account/billing/cancel`); `POST /v2/account/billing/resume` clears it and nothing else.

**`summary.pending_change.effective_at` string (date-time)**

The period end at which everything here takes effect.

**`summary.pending_change.changes` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval**

The scheduled set, one item per quantity. A cancellation is not an item here (see `cancel`).

**`summary.pending_change.changes[].kind` "plan"**

Always `plan`.

**`summary.pending_change.changes[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`summary.pending_change.changes[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`summary.pending_change.changes[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`summary.pending_change.changes[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`summary.pending_change.changes[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`summary.pending_change.changes[].addons` array of string**

The add-ons that will be removed.

**`summary.payment_method` BillPaymentMethod or null always present**

The default payment method; null when none is set.

**`summary.payment_method.id` string**

Stripe PaymentMethod id (`pm_...`).

**`summary.payment_method.brand` string**

Card brand as Stripe reports it, e.g. `visa`, `mastercard`.

**`summary.payment_method.last4` string**

Last four digits of the card number.

**`summary.payment_method.exp_month` integer**

Card expiry month. (1 to 12)

**`summary.payment_method.exp_year` integer**

Card expiry year (four digits).

**`summary.invoices` array of BillInvoice always present**

Up to the 12 most recent invoices, newest first.

**`summary.invoices[].id` string always present**

Stripe invoice id (`in_...`).

**`summary.invoices[].number` string or null always present**

The invoice number printed on it; null while a draft.

**`summary.invoices[].status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`summary.invoices[].total_cents` integer always present**

Invoice total in US cents.

**`summary.invoices[].amount_due_cents` integer always present**

Amount still owed, in US cents.

**`summary.invoices[].amount_paid_cents` integer always present**

Amount paid, in US cents.

**`summary.invoices[].created` string (date-time) always present**

When the invoice was created.

**`summary.invoices[].due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`summary.invoices[].period` object always present**

The service period the invoice covers.

**`summary.invoices[].period.start` string (date-time) always present**

Period start.

**`summary.invoices[].period.end` string (date-time) always present**

Period end.

**`summary.invoices[].hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`summary.invoices[].collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

**`summary.invoices[].description` string or null always present**

Free-text memo on the invoice (contract invoices), else null.

**`summary.invoices[].lines` array of BillInvoiceLine always present**

The invoice's lines.

**`summary.invoices[].lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`summary.invoices[].lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`summary.invoices[].lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`summary.invoices[].lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

#### `400` error

Bad request. Codes: [`bad_request`](/docs/api/errors#code-bad-request) (`quantity` missing, not a number, or not an integer 0 to 3194), [`surviving_counties_required`](/docs/api/errors#code-surviving-counties-required) (`capacity`, `candidates`; `errors` repeats it) when the decrease leaves fewer places than active counties and no earlier choice fits), [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (only when the period-end state would re-price the API add-on onto a non-annual interval), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON).

As `application/problem+json`.

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`contract_managed`](/docs/api/errors#code-contract-managed) (the organization is `billing_mode: invoiced`: its plan is managed under a contract by Data Tactical Group), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 names an organization the caller does not belong to).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`not_subscribed`](/docs/api/errors#code-not-subscribed) (the organization has no active subscription; use `POST /v2/account/billing/subscribe`), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (another billing change for this organization is running; the request waited 15 seconds; retry).

As `application/problem+json`.

#### `413` error

Payload too large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the JSON body is over 4 KB; the problem carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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), [`surviving_counties_required`](/docs/api/errors#code-surviving-counties-required) (400), [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`role_required`](/docs/api/errors#code-role-required) (403), [`contract_managed`](/docs/api/errors#code-contract-managed) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`not_subscribed`](/docs/api/errors#code-not-subscribed) (409), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (409), [`body_too_large`](/docs/api/errors#code-body-too-large) (413). Every problem is a [problem document](/docs/api/errors).

## POST `/v2/account/addons`

**Buy the API add-on**

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

Adds the API add-on (live API keys) to the subscription. Sold only with an annual plan. Adding it mid-term restarts the whole subscription's term today: the new term is charged now and every item moves to the new renewal date. A declined card leaves the add-on ungranted (unlike other increases); check `summary.addons`. Price it first with `POST /v2/account/billing/preview {addons: ["api"]}`. Already holding it is a no-op. Writes no audit row of its own. Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact). Billing changes for one organization are serialized: a second one waits for the first and, after 15 seconds, answers 409 [`billing_in_progress`](/docs/api/errors#code-billing-in-progress).

### Request body

Required; `application/json`.

**`addon` "api" required**

The add-on to buy: only `api`.

### Responses

#### `200` success

The change applied.

As `application/json`.

**`applied` "immediately" | "period\_end" always present**

`immediately` when an increase was applied now (and charged), else `period_end` (the change, if any, is scheduled; also returned for a no-op).

**`replaced` BillPendingReplaced always present**

For each scheduled item a change would replace (a second change to the same quantity), the item it replaces, keyed by its kind (`plan`, `counties`, `seats`, `addon_removal`). An empty object when it replaces nothing.

**`replaced.plan` BillPendingItemPlan**

A scheduled plan or interval move, applied at period end.

**`replaced.plan.kind` "plan" always present**

Always `plan`.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`replaced.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`replaced.counties` BillPendingItemCounties**

A scheduled decrease of the paid county-slot quantity (a slot release), applied at period end, not credited; the slots stay usable until then.

**`replaced.counties.kind` "counties" always present**

Always `counties`.

**`replaced.counties.counties_purchased` integer always present**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`replaced.counties.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`replaced.seats` BillPendingItemSeats**

A scheduled decrease of purchased seats, applied at period end, not credited; the seats stay usable until then.

**`replaced.seats.kind` "seats" always present**

Always `seats`.

**`replaced.seats.seats_purchased` integer always present**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`replaced.seats.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`replaced.addon_removal` BillPendingItemAddonRemoval**

A scheduled add-on removal, applied at period end. When the API add-on leaves, the organization's live API keys are revoked at that moment; sandbox keys stay.

**`replaced.addon_removal.kind` "addon\_removal" always present**

Always `addon_removal`.

**`replaced.addon_removal.addons` array of string always present**

The add-ons that will be removed.

**`replaced.addon_removal.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview` object always present**

The preview the change was decided on.

**`preview.current` BillStateSnapshot always present**

A billing state: plan, interval, add-ons and purchased quantities.

**`preview.current.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.current.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.current.addons` array of string always present**

Add-ons.

**`preview.current.seats_purchased` number always present**

Purchased seats beyond the plan's included seats.

**`preview.current.counties_purchased` number always present**

Paid county slots beyond the plan's included counties.

**`preview.target` object always present**

The state the request asks for (unnamed quantities at their current values).

**`preview.target.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.target.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.target.addons` array of string always present**

Add-ons.

**`preview.target.seats_purchased` number always present**

Purchased seats beyond the plan's included seats.

**`preview.target.counties_purchased` number always present**

Paid county slots beyond the plan's included counties.

**`preview.kind` "upgrade" | "downgrade" | "mixed" | "none" always present**

`upgrade` (only increases: applied now), `downgrade` (only decreases: scheduled for period end), `mixed` (both: the increases now at the current plan and interval, the decreases scheduled), `none` (no change).

**`preview.effective` "immediately" | "period\_end" always present**

`immediately` when anything applies now (upgrade or mixed), else `period_end` (also for `none`).

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

Now for an immediate change, else the current period end.

**`preview.proration_behavior` "always\_invoice" | "none" always present**

`always_invoice` when the change applies now: the prorated amount is invoiced and charged to the card at once. `none` for a period-end change: nothing is charged now and nothing is credited.

**`preview.immediate_charge_cents` integer always present**

What the card is charged now, in US cents (the sum of the proration lines, never below 0); 0 for a period-end change. (at least 0)

**`preview.proration_lines` array of BillAmountLine always present**

The prorated lines charged now. When the API add-on is being added (term restart) these are every line of the restarted term.

**`preview.proration_lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`preview.proration_lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`preview.proration_lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`preview.term_restart` boolean always present**

True when the change adds the API add-on: the WHOLE subscription restarts its term today, every item moving to the new anchor together; the new term is charged now.

**`preview.credit_cents` integer or null always present**

Only when `term_restart` is true: the magnitude, in US cents, of any credit lines Stripe returns for unused time (commonly 0, because Stripe nets a continuing item into one line). Null otherwise.

**`preview.new_renewal_date` string (date-time) or null always present**

Only when `term_restart` is true: the renewal date after the restart. Null otherwise.

**`preview.next_invoice` object always present**

The next renewal invoice, priced on the period-end state (what this change asks for plus what is already scheduled). Empty (`total_cents` 0, no lines) on a term restart, where everything is due now.

**`preview.next_invoice.total_cents` integer always present**

Total of the next invoice in US cents.

**`preview.next_invoice.lines` array of BillAmountLine always present**

Its lines.

**`preview.next_invoice.lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`preview.next_invoice.lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`preview.next_invoice.lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`preview.unit_prices_cents` object always present**

Unit prices, in US cents per interval, for the target plan and interval, from the price catalog; 0 where the catalog holds no such price.

**`preview.unit_prices_cents.plan` integer always present**

The plan price.

**`preview.unit_prices_cents.seat` integer always present**

One extra seat.

**`preview.unit_prices_cents.county` integer always present**

One extra county slot; 0 when the plan sells none.

**`preview.unit_prices_cents.api_addon` integer always present**

The API add-on (0 on a monthly interval: it is sold annual only).

**`preview.counties_requiring_choice` object or null always present**

Set when the period-end county capacity is below the organization's active counties (counties already scheduled to leave are not counted): the change must then carry `surviving_fips`. Null otherwise.

**`preview.counties_requiring_choice.capacity` integer always present**

How many counties the period-end state holds (included plus paid slots).

**`preview.counties_requiring_choice.candidates` array of BillCountyCandidate always present**

The active counties to choose from, in KEEP order: the first `capacity` are the default choice.

**`preview.counties_requiring_choice.candidates[].fips` string always present**

Five-digit county FIPS code.

**`preview.counties_requiring_choice.candidates[].source` "manual" | "state" | "msa" always present**

How the county entered the active set: `manual` (picked), `state` or `msa` (added as part of a state or metro).

**`preview.counties_requiring_choice.candidates[].added_at` string (date-time) always present**

When it was added.

**`preview.counties_requiring_choice.candidates[].last_used` string (date-time) or null always present**

The organization's last data request touching this county in the past 90 days; null when none.

**`preview.county_slots_releasable` integer always present**

How many paid county slots the period-end state holds that the active set does not need (for example after a move to a plan with more included counties), so the customer could release them.

**`preview.seats_becoming_free_members` integer always present**

How many seated members exceed the period-end seat count and would become free members.

**`preview.scheduled` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval always present**

What is scheduled now, before this change.

**`preview.scheduled[].kind` "plan"**

Always `plan`.

**`preview.scheduled[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.scheduled[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.scheduled[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.scheduled[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.scheduled[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.scheduled[].addons` array of string**

The add-ons that will be removed.

**`preview.scheduled_after` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval always present**

What this change would leave scheduled (in a mixed change, the plan move appears here).

**`preview.scheduled_after[].kind` "plan"**

Always `plan`.

**`preview.scheduled_after[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.scheduled_after[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.scheduled_after[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.scheduled_after[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.scheduled_after[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.scheduled_after[].addons` array of string**

The add-ons that will be removed.

**`preview.replaces` BillPendingReplaced always present**

For each scheduled item a change would replace (a second change to the same quantity), the item it replaces, keyed by its kind (`plan`, `counties`, `seats`, `addon_removal`). An empty object when it replaces nothing.

**`preview.replaces.plan` BillPendingItemPlan**

A scheduled plan or interval move, applied at period end.

**`preview.replaces.plan.kind` "plan" always present**

Always `plan`.

**`preview.replaces.plan.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.replaces.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.replaces.counties` BillPendingItemCounties**

A scheduled decrease of the paid county-slot quantity (a slot release), applied at period end, not credited; the slots stay usable until then.

**`preview.replaces.counties.kind` "counties" always present**

Always `counties`.

**`preview.replaces.counties.counties_purchased` integer always present**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.replaces.counties.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview.replaces.seats` BillPendingItemSeats**

A scheduled decrease of purchased seats, applied at period end, not credited; the seats stay usable until then.

**`preview.replaces.seats.kind` "seats" always present**

Always `seats`.

**`preview.replaces.seats.seats_purchased` integer always present**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.replaces.seats.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview.replaces.addon_removal` BillPendingItemAddonRemoval**

A scheduled add-on removal, applied at period end. When the API add-on leaves, the organization's live API keys are revoked at that moment; sandbox keys stay.

**`preview.replaces.addon_removal.kind` "addon\_removal" always present**

Always `addon_removal`.

**`preview.replaces.addon_removal.addons` array of string always present**

The add-ons that will be removed.

**`preview.replaces.addon_removal.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`summary` BillSummary always present**

The organization's billing state as Kelpie's seats-and-billing screen shows it.

**`summary.gateway` "fake" | "test" | "live" always present**

Which payment gateway this server runs: `live` (Stripe live mode, production only), `test` (Stripe test mode) or `fake` (an in-process simulation used in development and tests). T153: the Stripe secret key's prefix decides between `test` and `live`.

**`summary.publishable_key` string or null always present**

The Stripe publishable key to initialise Stripe.js and the Payment Element with.

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

`self_serve` (card subscription managed through these endpoints) or `invoiced` (a contract managed by Data Tactical Group: every self-serve change answers 403 [`contract_managed`](/docs/api/errors#code-contract-managed) ).

**`summary.contract` object or null always present**

The contract terms for an `invoiced` organization; null for `self_serve`.

**`summary.contract.expires_at` string (date-time) or null**

When the contract ends; null for no end date.

**`summary.contract.notes` string or null**

DTG's notes on the contract.

**`summary.plan` object always present**

The plan the organization holds now.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

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

The plan's display name (e.g. Local, Regional, National, Free).

**`summary.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Active add-ons.

**`summary.seats` object always present**

Seats: the plan's included seats plus purchased extra seats.

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

Seats the plan includes (the plan's allowance; see `GET /v2/plans`), raised to `grandfathered` while that is set.

**`summary.seats.grandfathered` integer or null**

T163: an organization that had more seated members than its plan's new included count when Regional fell to 10 seats and National to 15 keeps that many included seats until its seated members fall to the plan's count or its plan changes. This is that count; null for every other organization.

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

Extra seats the organization holds beyond `included`. A scheduled decrease does not lower this until period end.

**`summary.seats.billed_quantity` integer always present**

The seat quantity on the Stripe subscription (for an `invoiced` organization, the contracted extra seats; 0 with no subscription). Can differ from `purchased` briefly while a scheduled decrease has been sent to Stripe ahead of the renewal.

**`summary.seats.seat_price_usd_month` number or null always present**

The plan's monthly price of one extra seat, in US dollars; null when the plan sells no extra seats.

**`summary.seats.proration` "always\_invoice" always present**

How a seat increase is billed: always `always_invoice` (prorated to the rest of the period and charged to the card at purchase).

**`summary.seats.proration_note` string always present**

A sentence stating the seat billing rule for display.

**`summary.counties` object always present**

County slots: the plan's included counties plus paid county slots. The county SET is managed at `/v2/account/counties`; this is the billing quantity behind it.

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

Counties the plan includes; -1 means every county (National).

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

Paid county slots beyond `included`.

**`summary.counties.billed_quantity` integer always present**

The county-slot quantity on the Stripe subscription (for an `invoiced` organization, the contracted slots; 0 with no subscription).

**`summary.counties.active` integer always present**

Counties currently in the organization's active set, not counting a trial county.

**`summary.counties.county_price_usd_month` number or null always present**

The plan's monthly price of one extra county slot, in US dollars; null when the plan sells no extra counties.

**`summary.counties.proration` "always\_invoice" always present**

How a slot increase is billed: always `always_invoice` (prorated and charged at purchase).

**`summary.counties.proration_note` string always present**

A sentence stating the county-slot billing rule for display.

**`summary.counties.anchor_day` integer or null always present**

Day of the month of the organization's change window (the subscription's anchor date), on which county set changes land; null before a first subscription. (1 to 31)

**`summary.subscription` object or null always present**

The current Stripe subscription (status active, trialing, past\_due or incomplete); null when there is none (Free, or contract-managed without one).

**`summary.subscription.id` string**

Stripe subscription id (`sub_...`).

**`summary.subscription.status` "active" | "trialing" | "past\_due" | "incomplete"**

`active`; `trialing`; `past_due` (a renewal was declined: L18 pauses county detail, CSV/XLSX export, live API keys and cluster editing at once, while the plan and its capacity stay held; pay with `POST /v2/account/billing/retry` after fixing the card); `incomplete` (the first payment has not succeeded).

**`summary.subscription.current_period_start` string (date-time) or null**

Start of the current billing period.

**`summary.subscription.current_period_end` string (date-time) or null**

End of the current billing period: where scheduled changes and a cancellation take effect.

**`summary.subscription.renewal_date` string (date-time) or null**

When the subscription renews (the period end); null when it is set to cancel at period end.

**`summary.subscription.cancel_at_period_end` boolean**

True when the subscription ends at `current_period_end` instead of renewing.

**`summary.subscription.past_due_since` string (date-time) or null**

When the subscription first became past due; null when it is not past due.

**`summary.subscription.dunning_grace_days` integer**

Days a past-due subscription has before it is cancelled and the organization drops to Free (the downgrade composite applies). Service is paused from the decline, not from the end of this window.

**`summary.pending_change` BillPendingChange or null always present**

What is scheduled for the period end; null when nothing (and no cancellation) is scheduled.

**`summary.pending_change.plan_code` string or null**

The plan the organization moves to at period end; null when no plan move is scheduled (an interval-only move leaves this null and sets `interval`).

**`summary.pending_change.interval` "month" | "year" | null**

The interval the organization moves to at period end; null when no interval move is scheduled.

**`summary.pending_change.seats_purchased` integer or null**

The purchased seat quantity from period end; null when no seat decrease is scheduled.

**`summary.pending_change.counties_purchased` integer or null**

The paid county-slot quantity from period end; null when no slot release is scheduled.

**`summary.pending_change.remove_addons` array of string**

Add-ons removed at period end; empty when none.

**`summary.pending_change.cancel` boolean**

True when the subscription is set to cancel at period end (`POST /v2/account/billing/cancel`); `POST /v2/account/billing/resume` clears it and nothing else.

**`summary.pending_change.effective_at` string (date-time)**

The period end at which everything here takes effect.

**`summary.pending_change.changes` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval**

The scheduled set, one item per quantity. A cancellation is not an item here (see `cancel`).

**`summary.pending_change.changes[].kind` "plan"**

Always `plan`.

**`summary.pending_change.changes[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`summary.pending_change.changes[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`summary.pending_change.changes[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`summary.pending_change.changes[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`summary.pending_change.changes[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`summary.pending_change.changes[].addons` array of string**

The add-ons that will be removed.

**`summary.payment_method` BillPaymentMethod or null always present**

The default payment method; null when none is set.

**`summary.payment_method.id` string**

Stripe PaymentMethod id (`pm_...`).

**`summary.payment_method.brand` string**

Card brand as Stripe reports it, e.g. `visa`, `mastercard`.

**`summary.payment_method.last4` string**

Last four digits of the card number.

**`summary.payment_method.exp_month` integer**

Card expiry month. (1 to 12)

**`summary.payment_method.exp_year` integer**

Card expiry year (four digits).

**`summary.invoices` array of BillInvoice always present**

Up to the 12 most recent invoices, newest first.

**`summary.invoices[].id` string always present**

Stripe invoice id (`in_...`).

**`summary.invoices[].number` string or null always present**

The invoice number printed on it; null while a draft.

**`summary.invoices[].status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`summary.invoices[].total_cents` integer always present**

Invoice total in US cents.

**`summary.invoices[].amount_due_cents` integer always present**

Amount still owed, in US cents.

**`summary.invoices[].amount_paid_cents` integer always present**

Amount paid, in US cents.

**`summary.invoices[].created` string (date-time) always present**

When the invoice was created.

**`summary.invoices[].due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`summary.invoices[].period` object always present**

The service period the invoice covers.

**`summary.invoices[].period.start` string (date-time) always present**

Period start.

**`summary.invoices[].period.end` string (date-time) always present**

Period end.

**`summary.invoices[].hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`summary.invoices[].collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

**`summary.invoices[].description` string or null always present**

Free-text memo on the invoice (contract invoices), else null.

**`summary.invoices[].lines` array of BillInvoiceLine always present**

The invoice's lines.

**`summary.invoices[].lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`summary.invoices[].lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`summary.invoices[].lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`summary.invoices[].lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

#### `400` error

Bad request. Codes: [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (the plan is not annual), [`bad_request`](/docs/api/errors#code-bad-request) (`addon` is not `api`), [`malformed_json`](/docs/api/errors#code-malformed-json) (the body is not valid JSON).

As `application/problem+json`.

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`contract_managed`](/docs/api/errors#code-contract-managed) (the organization is `billing_mode: invoiced`: its plan is managed under a contract by Data Tactical Group), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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 names an organization the caller does not belong to).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`not_subscribed`](/docs/api/errors#code-not-subscribed) (the organization has no active subscription; use `POST /v2/account/billing/subscribe`), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (another billing change for this organization is running; the request waited 15 seconds; retry).

As `application/problem+json`.

#### `413` error

Payload too large. Codes: [`body_too_large`](/docs/api/errors#code-body-too-large) (the JSON body is over 4 KB; the problem carries `limit_bytes`).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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: [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (400), [`bad_request`](/docs/api/errors#code-bad-request) (400), [`malformed_json`](/docs/api/errors#code-malformed-json) (400), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`role_required`](/docs/api/errors#code-role-required) (403), [`contract_managed`](/docs/api/errors#code-contract-managed) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`not_subscribed`](/docs/api/errors#code-not-subscribed) (409), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (409), [`body_too_large`](/docs/api/errors#code-body-too-large) (413). Every problem is a [problem document](/docs/api/errors).

## DELETE `/v2/account/addons/{addon}`

**Remove the API add-on at period end**

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

Schedules the API add-on's removal for the period end (never restarts the term, not credited; withdraw it with `DELETE /v2/account/billing/pending/addon_removal`). When the removal takes effect the organization's LIVE API keys are revoked; sandbox keys stay. Not holding it is a no-op. Writes no audit row of its own. Either credential (API key or session). Only the organization's owner or a member flagged as billing contact (an admin only when also a billing contact). Billing changes for one organization are serialized: a second one waits for the first and, after 15 seconds, answers 409 [`billing_in_progress`](/docs/api/errors#code-billing-in-progress).

### Parameters

**`addon` path · "api" required**

The add-on to remove: only `api`.

### Responses

#### `200` success

The removal scheduled.

As `application/json`.

**`applied` "immediately" | "period\_end" always present**

`immediately` when an increase was applied now (and charged), else `period_end` (the change, if any, is scheduled; also returned for a no-op).

**`replaced` BillPendingReplaced always present**

For each scheduled item a change would replace (a second change to the same quantity), the item it replaces, keyed by its kind (`plan`, `counties`, `seats`, `addon_removal`). An empty object when it replaces nothing.

**`replaced.plan` BillPendingItemPlan**

A scheduled plan or interval move, applied at period end.

**`replaced.plan.kind` "plan" always present**

Always `plan`.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`replaced.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`replaced.counties` BillPendingItemCounties**

A scheduled decrease of the paid county-slot quantity (a slot release), applied at period end, not credited; the slots stay usable until then.

**`replaced.counties.kind` "counties" always present**

Always `counties`.

**`replaced.counties.counties_purchased` integer always present**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`replaced.counties.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`replaced.seats` BillPendingItemSeats**

A scheduled decrease of purchased seats, applied at period end, not credited; the seats stay usable until then.

**`replaced.seats.kind` "seats" always present**

Always `seats`.

**`replaced.seats.seats_purchased` integer always present**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`replaced.seats.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`replaced.addon_removal` BillPendingItemAddonRemoval**

A scheduled add-on removal, applied at period end. When the API add-on leaves, the organization's live API keys are revoked at that moment; sandbox keys stay.

**`replaced.addon_removal.kind` "addon\_removal" always present**

Always `addon_removal`.

**`replaced.addon_removal.addons` array of string always present**

The add-ons that will be removed.

**`replaced.addon_removal.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview` object always present**

The preview the change was decided on.

**`preview.current` BillStateSnapshot always present**

A billing state: plan, interval, add-ons and purchased quantities.

**`preview.current.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.current.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.current.addons` array of string always present**

Add-ons.

**`preview.current.seats_purchased` number always present**

Purchased seats beyond the plan's included seats.

**`preview.current.counties_purchased` number always present**

Paid county slots beyond the plan's included counties.

**`preview.target` object always present**

The state the request asks for (unnamed quantities at their current values).

**`preview.target.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.target.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.target.addons` array of string always present**

Add-ons.

**`preview.target.seats_purchased` number always present**

Purchased seats beyond the plan's included seats.

**`preview.target.counties_purchased` number always present**

Paid county slots beyond the plan's included counties.

**`preview.kind` "upgrade" | "downgrade" | "mixed" | "none" always present**

`upgrade` (only increases: applied now), `downgrade` (only decreases: scheduled for period end), `mixed` (both: the increases now at the current plan and interval, the decreases scheduled), `none` (no change).

**`preview.effective` "immediately" | "period\_end" always present**

`immediately` when anything applies now (upgrade or mixed), else `period_end` (also for `none`).

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

Now for an immediate change, else the current period end.

**`preview.proration_behavior` "always\_invoice" | "none" always present**

`always_invoice` when the change applies now: the prorated amount is invoiced and charged to the card at once. `none` for a period-end change: nothing is charged now and nothing is credited.

**`preview.immediate_charge_cents` integer always present**

What the card is charged now, in US cents (the sum of the proration lines, never below 0); 0 for a period-end change. (at least 0)

**`preview.proration_lines` array of BillAmountLine always present**

The prorated lines charged now. When the API add-on is being added (term restart) these are every line of the restarted term.

**`preview.proration_lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`preview.proration_lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`preview.proration_lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`preview.term_restart` boolean always present**

True when the change adds the API add-on: the WHOLE subscription restarts its term today, every item moving to the new anchor together; the new term is charged now.

**`preview.credit_cents` integer or null always present**

Only when `term_restart` is true: the magnitude, in US cents, of any credit lines Stripe returns for unused time (commonly 0, because Stripe nets a continuing item into one line). Null otherwise.

**`preview.new_renewal_date` string (date-time) or null always present**

Only when `term_restart` is true: the renewal date after the restart. Null otherwise.

**`preview.next_invoice` object always present**

The next renewal invoice, priced on the period-end state (what this change asks for plus what is already scheduled). Empty (`total_cents` 0, no lines) on a term restart, where everything is due now.

**`preview.next_invoice.total_cents` integer always present**

Total of the next invoice in US cents.

**`preview.next_invoice.lines` array of BillAmountLine always present**

Its lines.

**`preview.next_invoice.lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`preview.next_invoice.lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`preview.next_invoice.lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`preview.unit_prices_cents` object always present**

Unit prices, in US cents per interval, for the target plan and interval, from the price catalog; 0 where the catalog holds no such price.

**`preview.unit_prices_cents.plan` integer always present**

The plan price.

**`preview.unit_prices_cents.seat` integer always present**

One extra seat.

**`preview.unit_prices_cents.county` integer always present**

One extra county slot; 0 when the plan sells none.

**`preview.unit_prices_cents.api_addon` integer always present**

The API add-on (0 on a monthly interval: it is sold annual only).

**`preview.counties_requiring_choice` object or null always present**

Set when the period-end county capacity is below the organization's active counties (counties already scheduled to leave are not counted): the change must then carry `surviving_fips`. Null otherwise.

**`preview.counties_requiring_choice.capacity` integer always present**

How many counties the period-end state holds (included plus paid slots).

**`preview.counties_requiring_choice.candidates` array of BillCountyCandidate always present**

The active counties to choose from, in KEEP order: the first `capacity` are the default choice.

**`preview.counties_requiring_choice.candidates[].fips` string always present**

Five-digit county FIPS code.

**`preview.counties_requiring_choice.candidates[].source` "manual" | "state" | "msa" always present**

How the county entered the active set: `manual` (picked), `state` or `msa` (added as part of a state or metro).

**`preview.counties_requiring_choice.candidates[].added_at` string (date-time) always present**

When it was added.

**`preview.counties_requiring_choice.candidates[].last_used` string (date-time) or null always present**

The organization's last data request touching this county in the past 90 days; null when none.

**`preview.county_slots_releasable` integer always present**

How many paid county slots the period-end state holds that the active set does not need (for example after a move to a plan with more included counties), so the customer could release them.

**`preview.seats_becoming_free_members` integer always present**

How many seated members exceed the period-end seat count and would become free members.

**`preview.scheduled` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval always present**

What is scheduled now, before this change.

**`preview.scheduled[].kind` "plan"**

Always `plan`.

**`preview.scheduled[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.scheduled[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.scheduled[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.scheduled[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.scheduled[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.scheduled[].addons` array of string**

The add-ons that will be removed.

**`preview.scheduled_after` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval always present**

What this change would leave scheduled (in a mixed change, the plan move appears here).

**`preview.scheduled_after[].kind` "plan"**

Always `plan`.

**`preview.scheduled_after[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.scheduled_after[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`preview.scheduled_after[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.scheduled_after[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.scheduled_after[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.scheduled_after[].addons` array of string**

The add-ons that will be removed.

**`preview.replaces` BillPendingReplaced always present**

For each scheduled item a change would replace (a second change to the same quantity), the item it replaces, keyed by its kind (`plan`, `counties`, `seats`, `addon_removal`). An empty object when it replaces nothing.

**`preview.replaces.plan` BillPendingItemPlan**

A scheduled plan or interval move, applied at period end.

**`preview.replaces.plan.kind` "plan" always present**

Always `plan`.

**`preview.replaces.plan.plan_code` string always present**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`preview.replaces.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`preview.replaces.counties` BillPendingItemCounties**

A scheduled decrease of the paid county-slot quantity (a slot release), applied at period end, not credited; the slots stay usable until then.

**`preview.replaces.counties.kind` "counties" always present**

Always `counties`.

**`preview.replaces.counties.counties_purchased` integer always present**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`preview.replaces.counties.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview.replaces.seats` BillPendingItemSeats**

A scheduled decrease of purchased seats, applied at period end, not credited; the seats stay usable until then.

**`preview.replaces.seats.kind` "seats" always present**

Always `seats`.

**`preview.replaces.seats.seats_purchased` integer always present**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`preview.replaces.seats.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`preview.replaces.addon_removal` BillPendingItemAddonRemoval**

A scheduled add-on removal, applied at period end. When the API add-on leaves, the organization's live API keys are revoked at that moment; sandbox keys stay.

**`preview.replaces.addon_removal.kind` "addon\_removal" always present**

Always `addon_removal`.

**`preview.replaces.addon_removal.addons` array of string always present**

The add-ons that will be removed.

**`preview.replaces.addon_removal.effective_at` string (date-time) or null always present**

When the change takes effect (the end of the current period). Same date for every scheduled item.

**`summary` BillSummary always present**

The organization's billing state as Kelpie's seats-and-billing screen shows it.

**`summary.gateway` "fake" | "test" | "live" always present**

Which payment gateway this server runs: `live` (Stripe live mode, production only), `test` (Stripe test mode) or `fake` (an in-process simulation used in development and tests). T153: the Stripe secret key's prefix decides between `test` and `live`.

**`summary.publishable_key` string or null always present**

The Stripe publishable key to initialise Stripe.js and the Payment Element with.

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

`self_serve` (card subscription managed through these endpoints) or `invoiced` (a contract managed by Data Tactical Group: every self-serve change answers 403 [`contract_managed`](/docs/api/errors#code-contract-managed) ).

**`summary.contract` object or null always present**

The contract terms for an `invoiced` organization; null for `self_serve`.

**`summary.contract.expires_at` string (date-time) or null**

When the contract ends; null for no end date.

**`summary.contract.notes` string or null**

DTG's notes on the contract.

**`summary.plan` object always present**

The plan the organization holds now.

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

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

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

The plan's display name (e.g. Local, Regional, National, Free).

**`summary.plan.interval` "month" | "year" always present**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

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

Active add-ons.

**`summary.seats` object always present**

Seats: the plan's included seats plus purchased extra seats.

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

Seats the plan includes (the plan's allowance; see `GET /v2/plans`), raised to `grandfathered` while that is set.

**`summary.seats.grandfathered` integer or null**

T163: an organization that had more seated members than its plan's new included count when Regional fell to 10 seats and National to 15 keeps that many included seats until its seated members fall to the plan's count or its plan changes. This is that count; null for every other organization.

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

Extra seats the organization holds beyond `included`. A scheduled decrease does not lower this until period end.

**`summary.seats.billed_quantity` integer always present**

The seat quantity on the Stripe subscription (for an `invoiced` organization, the contracted extra seats; 0 with no subscription). Can differ from `purchased` briefly while a scheduled decrease has been sent to Stripe ahead of the renewal.

**`summary.seats.seat_price_usd_month` number or null always present**

The plan's monthly price of one extra seat, in US dollars; null when the plan sells no extra seats.

**`summary.seats.proration` "always\_invoice" always present**

How a seat increase is billed: always `always_invoice` (prorated to the rest of the period and charged to the card at purchase).

**`summary.seats.proration_note` string always present**

A sentence stating the seat billing rule for display.

**`summary.counties` object always present**

County slots: the plan's included counties plus paid county slots. The county SET is managed at `/v2/account/counties`; this is the billing quantity behind it.

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

Counties the plan includes; -1 means every county (National).

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

Paid county slots beyond `included`.

**`summary.counties.billed_quantity` integer always present**

The county-slot quantity on the Stripe subscription (for an `invoiced` organization, the contracted slots; 0 with no subscription).

**`summary.counties.active` integer always present**

Counties currently in the organization's active set, not counting a trial county.

**`summary.counties.county_price_usd_month` number or null always present**

The plan's monthly price of one extra county slot, in US dollars; null when the plan sells no extra counties.

**`summary.counties.proration` "always\_invoice" always present**

How a slot increase is billed: always `always_invoice` (prorated and charged at purchase).

**`summary.counties.proration_note` string always present**

A sentence stating the county-slot billing rule for display.

**`summary.counties.anchor_day` integer or null always present**

Day of the month of the organization's change window (the subscription's anchor date), on which county set changes land; null before a first subscription. (1 to 31)

**`summary.subscription` object or null always present**

The current Stripe subscription (status active, trialing, past\_due or incomplete); null when there is none (Free, or contract-managed without one).

**`summary.subscription.id` string**

Stripe subscription id (`sub_...`).

**`summary.subscription.status` "active" | "trialing" | "past\_due" | "incomplete"**

`active`; `trialing`; `past_due` (a renewal was declined: L18 pauses county detail, CSV/XLSX export, live API keys and cluster editing at once, while the plan and its capacity stay held; pay with `POST /v2/account/billing/retry` after fixing the card); `incomplete` (the first payment has not succeeded).

**`summary.subscription.current_period_start` string (date-time) or null**

Start of the current billing period.

**`summary.subscription.current_period_end` string (date-time) or null**

End of the current billing period: where scheduled changes and a cancellation take effect.

**`summary.subscription.renewal_date` string (date-time) or null**

When the subscription renews (the period end); null when it is set to cancel at period end.

**`summary.subscription.cancel_at_period_end` boolean**

True when the subscription ends at `current_period_end` instead of renewing.

**`summary.subscription.past_due_since` string (date-time) or null**

When the subscription first became past due; null when it is not past due.

**`summary.subscription.dunning_grace_days` integer**

Days a past-due subscription has before it is cancelled and the organization drops to Free (the downgrade composite applies). Service is paused from the decline, not from the end of this window.

**`summary.pending_change` BillPendingChange or null always present**

What is scheduled for the period end; null when nothing (and no cancellation) is scheduled.

**`summary.pending_change.plan_code` string or null**

The plan the organization moves to at period end; null when no plan move is scheduled (an interval-only move leaves this null and sets `interval`).

**`summary.pending_change.interval` "month" | "year" | null**

The interval the organization moves to at period end; null when no interval move is scheduled.

**`summary.pending_change.seats_purchased` integer or null**

The purchased seat quantity from period end; null when no seat decrease is scheduled.

**`summary.pending_change.counties_purchased` integer or null**

The paid county-slot quantity from period end; null when no slot release is scheduled.

**`summary.pending_change.remove_addons` array of string**

Add-ons removed at period end; empty when none.

**`summary.pending_change.cancel` boolean**

True when the subscription is set to cancel at period end (`POST /v2/account/billing/cancel`); `POST /v2/account/billing/resume` clears it and nothing else.

**`summary.pending_change.effective_at` string (date-time)**

The period end at which everything here takes effect.

**`summary.pending_change.changes` array of BillPendingItemPlan or BillPendingItemCounties or BillPendingItemSeats or BillPendingItemAddonRemoval**

The scheduled set, one item per quantity. A cancellation is not an item here (see `cancel`).

**`summary.pending_change.changes[].kind` "plan"**

Always `plan`.

**`summary.pending_change.changes[].plan_code` string**

A plan code as `GET /v2/plans` lists it (`starter` is Local, `pro` is Regional, `national` is National; `free` is the unpaid tier and is never purchasable). The plans, their allowances and prices are in `GET /v2/plans`.

**`summary.pending_change.changes[].interval` "month" | "year"**

Billing interval: `month` (monthly) or `year` (annual). The API add-on is sold only with `year`.

**`summary.pending_change.changes[].effective_at` string (date-time) or null**

When the change takes effect: the end of the current subscription period. Every scheduled item shares this date. Null only in a preview's `scheduled_after` when nothing would be left scheduled.

**`summary.pending_change.changes[].counties_purchased` integer**

The paid county-slot quantity the organization will hold from `effective_at` (slots beyond the plan's included counties). (at least 0)

**`summary.pending_change.changes[].seats_purchased` integer**

The purchased (extra) seat quantity the organization will hold from `effective_at`, beyond the plan's included seats. (at least 0)

**`summary.pending_change.changes[].addons` array of string**

The add-ons that will be removed.

**`summary.payment_method` BillPaymentMethod or null always present**

The default payment method; null when none is set.

**`summary.payment_method.id` string**

Stripe PaymentMethod id (`pm_...`).

**`summary.payment_method.brand` string**

Card brand as Stripe reports it, e.g. `visa`, `mastercard`.

**`summary.payment_method.last4` string**

Last four digits of the card number.

**`summary.payment_method.exp_month` integer**

Card expiry month. (1 to 12)

**`summary.payment_method.exp_year` integer**

Card expiry year (four digits).

**`summary.invoices` array of BillInvoice always present**

Up to the 12 most recent invoices, newest first.

**`summary.invoices[].id` string always present**

Stripe invoice id (`in_...`).

**`summary.invoices[].number` string or null always present**

The invoice number printed on it; null while a draft.

**`summary.invoices[].status` "draft" | "open" | "paid" | "void" | "uncollectible" always present**

`open` is unpaid and collectable (a declined renewal stays `open` while the subscription is `past_due`; `POST /v2/account/billing/retry` pays it); `paid`; `void`; `uncollectible`; `draft` not yet finalized.

**`summary.invoices[].total_cents` integer always present**

Invoice total in US cents.

**`summary.invoices[].amount_due_cents` integer always present**

Amount still owed, in US cents.

**`summary.invoices[].amount_paid_cents` integer always present**

Amount paid, in US cents.

**`summary.invoices[].created` string (date-time) always present**

When the invoice was created.

**`summary.invoices[].due_date` string (date-time) or null always present**

Due date for a `send_invoice` (contract) invoice; null for card-charged invoices.

**`summary.invoices[].period` object always present**

The service period the invoice covers.

**`summary.invoices[].period.start` string (date-time) always present**

Period start.

**`summary.invoices[].period.end` string (date-time) always present**

Period end.

**`summary.invoices[].hosted_invoice_url` string (uri) or null always present**

Stripe's hosted page for viewing and paying the invoice; null when Stripe has none.

**`summary.invoices[].collection_method` "charge\_automatically" | "send\_invoice" always present**

`charge_automatically` (charged to the card on file: self-serve) or `send_invoice` (sent for payment by its due date: contract-managed organizations).

**`summary.invoices[].description` string or null always present**

Free-text memo on the invoice (contract invoices), else null.

**`summary.invoices[].lines` array of BillInvoiceLine always present**

The invoice's lines.

**`summary.invoices[].lines[].description` string always present**

The line's text as Stripe renders it (product, quantity, period).

**`summary.invoices[].lines[].amount_cents` integer always present**

Line amount in US cents; negative for a credit.

**`summary.invoices[].lines[].quantity` integer always present**

Quantity the line bills (1 for the plan and the add-on; the seat or county-slot count for those items).

**`summary.invoices[].lines[].proration` boolean always present**

True when the line is a proration (the part-period charge or credit of a mid-period change).

#### `400` error

Bad request. Codes: [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (only when a scheduled plan move would re-price the add-on onto a non-annual interval in the state this request produces).

As `application/problem+json`.

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no valid API key or session); [`key_revoked`](/docs/api/errors#code-key-revoked) (an API key that was revoked: `revoked_at`, and `reason` -- `holder`, `admin`, `member_removed`, `seat_removed`, `api_disabled`, `addon_ended`, [`account_closed`](/docs/api/errors#code-account-closed), [`org_closed`](/docs/api/errors#code-org-closed), `retired`, or null for a key revoked before T110 -- and a sentence saying when and why).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`role_required`](/docs/api/errors#code-role-required) (only the owner, or a member flagged as billing contact, may manage billing; an admin only when also a billing contact; the problem carries `required: [owner, billing_contact]`), [`contract_managed`](/docs/api/errors#code-contract-managed) (the organization is `billing_mode: invoiced`: its plan is managed under a contract by Data Tactical Group), [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed), [`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; `base_url` names 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) (no such add-on: `addon` is not `api`), [`not_found`](/docs/api/errors#code-not-found) (the `X-DTG-Org` header names an organization the caller does not belong to).

As `application/problem+json`.

#### `409` error

Conflict. Codes: [`not_subscribed`](/docs/api/errors#code-not-subscribed) (the organization has no active subscription; use `POST /v2/account/billing/subscribe`), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (another billing change for this organization is running; the request waited 15 seconds; retry).

As `application/problem+json`.

#### `429` error

rate\_limited (the per-minute ceiling, X-RateLimit-\* say which), points\_exhausted (the monthly points allowance; Retry-After to the end of the UTC month), server\_busy or sandbox\_busy (load; retry after Retry-After). A refused server\_busy request is not metered.

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: [`annual_plan_required`](/docs/api/errors#code-annual-plan-required) (400), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`account_closed`](/docs/api/errors#code-account-closed) (401), [`org_closed`](/docs/api/errors#code-org-closed) (401), [`role_required`](/docs/api/errors#code-role-required) (403), [`contract_managed`](/docs/api/errors#code-contract-managed) (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), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`not_subscribed`](/docs/api/errors#code-not-subscribed) (409), [`billing_in_progress`](/docs/api/errors#code-billing-in-progress) (409). Every problem is a [problem document](/docs/api/errors).

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