---
title: "Keys & usage endpoints — API reference | Data Tactical Group"
route: "/docs/api/reference/keys-and-usage"
updated: "2026-10-23"
---

# Keys & usage

4 endpoints, generated from the API's own OpenAPI document. API keys (live and sandbox) and the month's usage.

## GET `/v2/account/usage`

**Month-to-date usage of the active organization**

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

Either credential (an API key or a session); resolves the caller's active organization (`X-DTG-Org`). Any role, with what they see by role (O63 S11, Q22): owners and admins get every member's row (`members_scope: organization`); anyone else the organization's totals and their own row only (`members_scope: self`). The UTC calendar month: API calls, CSV and XLSX exports, browsing, points against the organization's monthly points allowance (`GET /v2/plans`), a per-member breakdown and monthly active members. 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 usage.

As `application/json`.

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

The organization.

**`quota_scope` "organization" always present**

Always `organization`.

**`month` string always present**

The month counted, `YYYY-MM` (UTC).

**`api` object always present**

API-key data requests.

**`api.calls` integer always present**

Requests this month.

**`api.channel` "api\_key" always present**

Always `api_key`.

**`api.metered_by` "points" always present**

Always `points`: requests have no limit of their own.

**`csv` object always present**

CSV exports (the audit-trail download is not one).

**`csv.exports` integer always present**

Exports this month.

**`csv.quota` integer or null always present**

An export count limit; null (the usual case) when there is none.

**`csv.metered_by` "points" | "exports" always present**

`points` when there is no export count limit, `exports` when DTG re-armed one.

**`xlsx` object always present**

XLSX (as-shown) exports.

**`xlsx.exports` integer always present**

Exports this month.

**`xlsx.quota` null always present**

Always null: XLSX has no count limit.

**`xlsx.metered_by` "points" always present**

Always `points`.

**`ui` object always present**

Browser-session data requests.

**`ui.requests` integer always present**

Requests this month.

**`ui.metered` boolean always present**

Always false: browsing costs no points.

**`ui.note` string always present**

A sentence saying so.

**`points` object always present**

The meter that binds.

**`points.metered` integer always present**

Points (data values) emitted this month on the API, CSV and XLSX channels, for the caller's credential mode (a sandbox key reads the sandbox total).

**`points.quota` integer or null always present**

The organization's monthly points allowance (`GET /v2/plans`); null when unlimited.

**`points.channels` array of string always present**

Always `api`, `csv`, `xlsx`: the metered channels.

**`rate_limit_per_min` integer always present**

Requests a minute allowed across the organization.

**`mau` object always present**

Monthly active members (ORG\_LICENSING 8).

**`mau.definition` string always present**

What counts as active, in words.

**`mau.current_month` integer always present**

Distinct members active this month so far.

**`mau.registered_members` integer always present**

Active memberships now.

**`mau.ratio` number or null always present**

current\_month / registered\_members to 3 places; null when there are no members.

**`mau.history` array of object always present**

Earlier months, newest first, at most 12.

**`mau.history[].month` string always present**

`YYYY-MM`.

**`mau.history[].active_members` integer always present**

Active members that month.

**`members` array of AcctUsageMember always present**

Every active member, highest `points_all` first.

**`members[].membership_id` string (uuid) always present**

The membership id.

**`members[].email` string always present**

The member's email address.

**`members[].display_name` string or null always present**

The member's display name.

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

True when seated.

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

Role.

**`members[].last_active_at` string (date-time) or null always present**

The member's most recent successful data request, on any channel (updated at most every 5 minutes).

**`members[].api_calls` integer always present**

API-key data requests this month.

**`members[].csv_exports` integer always present**

CSV exports this month (the audit-trail download excluded).

**`members[].xlsx_exports` integer always present**

XLSX (as-shown) exports this month.

**`members[].ui_requests` integer always present**

Browser-session data requests this month (unmetered).

**`members[].points_metered` integer always present**

Points this member's API, CSV and XLSX requests emitted this month (live and sandbox together).

**`members[].points_all` integer always present**

Data values emitted on every channel, browsing included (informational; browsing is not metered).

**`members[].last_request_at` string (date-time) or null always present**

This member's latest recorded request this month.

#### `401` error

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

As `application/problem+json`.

#### `403` error

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

As `application/problem+json`.

#### `404` error

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

As `application/problem+json`.

#### `429` error

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

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

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

The per-minute request ceiling that applies.

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

Requests left in the current window.

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

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

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

## GET `/v2/account/keys`

**API keys and their caps**

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

Either credential (an API key or a session); resolves the caller's active organization (`X-DTG-Org`). Any role lists their own active keys; an owner or admin may list every key in the organization with `?all=true`. Each key carries its month-to-date points and calls. The secret is never returned.

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

**`all` query · "true" | "false"**

`true` to list every key in the organization (owner or admin). Any other value lists the caller's own.

### Responses

#### `200` success

The keys.

As `application/json`.

**`api_access` boolean always present**

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

**`api_enabled` boolean always present**

True when an owner or admin has granted this member API access.

**`api_keys_per_member` integer always present**

Active live keys a member may hold (2; 0 for an unseated member).

**`sandbox_keys_per_member` integer always present**

Active sandbox keys a member may hold (default 2), separate from the live cap.

**`my_active_keys` integer always present**

The caller's active live keys.

**`my_active_sandbox_keys` integer always present**

The caller's active sandbox keys.

**`active_keys` integer always present**

Active live keys in the whole organization.

**`active_sandbox_keys` integer always present**

Active sandbox keys in the whole organization.

**`live_base_url` string (uri) always present**

Where a live key is sent, e.g. `https://api.datatactical.group`. A sandbox key sent there is refused 403 [`wrong_instance`](/docs/api/errors#code-wrong-instance).

**`sandbox_base_url` string (uri) always present**

Where a sandbox key is sent, e.g. `https://api.datatactical.group/sandbox`. A live key or a session sent there is refused 403 [`wrong_instance`](/docs/api/errors#code-wrong-instance).

**`scope` "member" | "organization" always present**

`member` (the caller's own keys) or `organization` (every key, `?all=true`).

**`data` array of AcctKey always present**

The keys, newest first.

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

The key id.

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

The key's name.

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

The first 16 characters of the key, for recognising it (`dtg_live_...`, `dtg_dev_...` or `dtg_sandbox_...`). Display only.

**`data[].key_last4` string or null**

The key's last four characters, as the password-changed mail and notices name it; null for a key minted before T110.

**`data[].mode` "live" | "sandbox" always present**

`live` (real data, every gate) or `sandbox` (synthetic data only, D68).

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

When it was minted.

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

When it last authenticated a request; null when never.

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

Always null in this list (only active keys are listed).

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

The membership that holds it.

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

The holder's email address.

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

Points this key's requests emitted this month (UTC).

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

Requests made with this key this month (UTC).

#### `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), [`role_required`](/docs/api/errors#code-role-required) (the caller's role is not one of those named; the body carries `required`) with `?all=true` from a member.

As `application/problem+json`.

#### `404` error

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

As `application/problem+json`.

#### `429` error

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

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

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

The per-minute request ceiling that applies.

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

Requests left in the current window.

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

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

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

## POST `/v2/account/keys`

**Mint an API key**

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

Either credential (an API key or a session); resolves the caller's active organization (`X-DTG-Org`). Keys are per member and bound to the active organization. A LIVE key (default) needs the organization's API add-on, a seat, an admin's grant (`api_enabled`) and no past-due balance, within 2 active live keys per member (and an organization-wide guard of 100). Minting a live key writes a `key.created` audit row. A SANDBOX key (`mode: sandbox`, D68) needs only a verified email address, has its own cap, resolves synthetic data only, and writes no audit row. The secret is returned once.

### Parameters

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

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

### Request body

Optional; `application/json`.

**`name` string**

A name for the key; trimmed and cut to 80 characters. Default `API key` (live) or `Sandbox key`.

**`mode` "live" | "sandbox"**

`live` (default; also when omitted, null or empty) or `sandbox`. A sandbox key resolves synthetic data only, needs only a verified email address (no plan, add-on, seat or admin grant, and not paused by a past-due balance), and has its own cap.

### Responses

#### `201` success

The new key, with its secret.

As `application/json`.

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

The key id.

**`name` string always present**

The key's name.

**`mode` "live" | "sandbox" always present**

The key's mode.

**`key_prefix` string always present**

The first 16 characters, for recognising it later.

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

When it was minted.

**`key` string always present**

The full key. Send it as `Authorization: Bearer <key>`. It cannot be shown again.

**`sandbox` true**

Present (true) only on a sandbox key.

**`base_url` string (uri) always present**

The address this key is sent to: the live API for a live key, the sandbox API (`.../sandbox`) for a sandbox key.

**`note` string always present**

A reminder to store it now; for a sandbox key, that its data is synthetic and must never be published as data.

#### `400` error

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

As `application/problem+json`.

#### `401` error

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

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`no_membership`](/docs/api/errors#code-no-membership) (the account has no active membership in an active organization), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the signed-in person's own account is suspended by DTG; `reason` where recorded), [`org_suspended`](/docs/api/errors#code-org-suspended) (the selected organization is suspended by DTG; the body carries `organization {id, name}` and `reason`), [`org_closed`](/docs/api/errors#code-org-closed) (the selected organization is closed; the body carries `organization {id, name}`), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (a key of the mode this process does not serve: a sandbox key on the live API, or a live key on the sandbox; the body carries `expected` and `base_url`, the address that serves it), [`email_unverified`](/docs/api/errors#code-email-unverified) (a sandbox key needs a verified email address), [`key_limit`](/docs/api/errors#code-key-limit) (a cap is reached; the body carries `entitlement` and `active_keys`, see AcctKeyLimitProblem), [`plan_required`](/docs/api/errors#code-plan-required) (live key: the organization has no API add-on, `entitlement: api_access`; or its payment is past due, `entitlement: past_due`), [`api_not_enabled`](/docs/api/errors#code-api-not-enabled) (live key: no owner or admin has granted this member API access). A sandbox key: [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (it may write only clusters).

As `application/problem+json`.

#### `404` error

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

As `application/problem+json`.

#### `413` error

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

As `application/problem+json`.

#### `429` error

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

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

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

The per-minute request ceiling that applies.

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

Requests left in the current window.

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

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

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

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

**Revoke an API key**

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

Either credential (an API key or a session); resolves the caller's active organization (`X-DTG-Org`). A member revokes their own keys; an owner or admin may revoke any key in the organization. Takes effect at once on every server process, the live and the sandbox (each is told by the database when the revocation commits). Writes a `key.revoked` audit row.

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

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

The key id.

### Responses

#### `204` success

No Content.

#### `401` error

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

As `application/problem+json`.

#### `403` error

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

As `application/problem+json`.

#### `404` error

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

As `application/problem+json`.

#### `429` error

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

As `application/problem+json`.

**`Retry-After` header**

Seconds to wait before retrying.

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

The per-minute request ceiling that applies.

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

Requests left in the current window.

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

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

Problem codes: [`key_revoked`](/docs/api/errors#code-key-revoked) (401), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`no_membership`](/docs/api/errors#code-no-membership) (403), [`account_suspended`](/docs/api/errors#code-account-suspended) (403), [`org_suspended`](/docs/api/errors#code-org-suspended) (403), [`org_closed`](/docs/api/errors#code-org-closed) (403), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (403), [`sandbox_read_only`](/docs/api/errors#code-sandbox-read-only) (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).

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