Every error is a problem document

Errors return application/problem+json with a stable code alongside the status. Branch on the code; the detail sentence is for people.

The error envelope

Every error from the API, on the JSON path and the CSV path alike, is a problem document with type, title, status, detail and code. The code is what to branch on; codes are never renamed. The type is the code as a URI, https://api.datatactical.group/problems/<code>, and does not change after launch either. A CSV or spreadsheet request never receives a CSV-shaped error. A request with no key:

Request

curl "https://api.datatactical.group/v2/industry/series?areas=22&naics=621&from=2024&to=2025"

Response · replayed 2026-10-05 against vintage 20260913.

HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json; charset=utf-8
​
{ "type": "https://api.datatactical.group/problems/unauthenticated",
    "title": "Unauthorized", "status": 401,
    "detail": "Provide an API key (Authorization: Bearer …) or a session.",
    "code": "unauthenticated" }

Many problems carry members of their own beside the code, naming what is at fault: parameters on an unknown parameter, cells and cap on a request over the cell ceiling, excluded_fips on a geography refusal, entitlement on a plan refusal, current or superseded_by on a withdrawn vintage, base_url on a key sent to the wrong address. The table at the end of this page lists every code; each endpoint’s page in the reference says which codes that endpoint can send.

A failure after a successful response has started cannot change its status: the connection is cut and the body ends early. A JSON body that does not parse, or a CSV that stops mid-row, is that case; retry the request.

Status codes

400 is a request the API will not interpret. The common ones: an unknown parameter (unknown_parameter), a parameter given twice (repeated_parameter), a spreadsheet-only parameter on a JSON or CSV request (xlsx_only_parameter), both naics and naics_level at once, a summed code list in which one code contains another (with sum=codes), a request over the ceiling of 400,000 cells (cell_cap) or over the series ceiling (series_cap). The rate limits page explains cells; paging is the way past the ceiling.

401 (unauthenticated) is a missing, unknown or expired key. A key that was revoked answers 401 key_revoked instead, with revoked_at, reason and a sentence saying when and why it was revoked.

403 is a valid key that may not do this. geography_excluded when every county in the request is outside the organization’s plan, or when any is and the request says strict=true. plan_required, with an entitlement naming what the plan lacks: api_access for a live key on an organization without the API add-on, past_due while a subscription is unpaid, vintage_access for a prior vintage on a plan without one. session_required for a change to the account made with a key: keys read, and changes are made in the app. wrong_instance for a sandbox key on the live address or a live key on the sandbox, with base_url naming the right one; sandbox_read_only for a sandbox key asked to change anything but a cluster.

404 is an unknown resource, including a cluster that is not your organization’s (cluster_not_found), a vintage that was withdrawn (vintage_withdrawn, with current) or replaced by a correction (vintage_superseded, with superseded_by).

409 is a state conflict on the account surface.

429 always carries Retry-After, in seconds. rate_limited is the per-minute request ceiling; points_exhausted is the organization’s monthly points allowance, and its Retry-After runs to the start of the next UTC month; server_busy (and sandbox_busy in the sandbox) means the service is at capacity and the request was not counted against you.

Retrying

Retry a 429 after the interval given and a 5xx with backoff. Do not retry a 400, 401, 403 or 404 unchanged; the code and the detail say what to change.

Every problem code

The 115 codes the API can send, with the status each comes with and what it means, as the API's OpenAPI document states them. A code is never renamed; a new one may be added, so treat a code you do not know by its status. Each endpoint's page in the reference names the codes it can send.

annual_plan_required 400
the API add-on is sold only with an annual plan. Sent by 7 endpoints, including POST /v2/account/billing/subscribe.
area_overlap 400
two areas in a summed selection overlap; pair names them. Sent by 8 endpoints, including GET /v2/industry/series.
area_sum_required 400
a cluster or MSA beside other areas needs sum=areas. Sent by 6 endpoints, including GET /v2/industry/series.
bad_cluster_ref 400
a cluster:<id> reference that is not a cluster of the kind the parameter takes. Sent by 8 endpoints, including GET /v2/industry/series.
bad_code 400
a code in a cluster is malformed. Sent by 2 endpoints, including POST /v2/account/clusters.
bad_request 400
the request is malformed or a value is invalid; detail and errors say which. Sent by 54 endpoints, including GET /healthz.
bad_total 400
the workbook total is not cluster:<id> of a code cluster. Sent by 6 endpoints, including GET /v2/industry/series.
captcha_missing 400
400 (T108: the problem's own code; also repeated in errors[]): the bot-check token (x-captcha-response) is missing. Sent by 5 endpoints, including POST /v2/invites/sign-up.
cell_cap 400
the request asks for more than 400,000 cells (one series at one period), the one load guard for every credential. cells and cap say by how much; narrow it or page it. Sent by 6 endpoints, including GET /v2/industry/series.
chat_too_long 400
a support assistant conversation over its message count, or a message over its length. Sent by POST /v2/support/chat.
codes_invalid 400
a cluster's codes are missing or not a list of strings. Sent by 2 endpoints, including POST /v2/account/clusters.
codes_overlap 400
a code cluster holds an ancestor and its descendant (they cannot be summed). Sent by 2 endpoints, including POST /v2/account/clusters.
column_not_filterable 400
a workbook filter on a column that cannot be filtered (rank). Sent by 6 endpoints, including GET /v2/industry/series.
counties_outside_set 400
a fips cluster names counties outside the organization's county set (errors lists them). Sent by 2 endpoints, including POST /v2/account/clusters.
events_invalid 400
400: a batch posted to /v2/events is not 1 to 50 objects of name screen_opened and a route family route; refused whole, index names the first bad entry. Sent by POST /v2/events.
invalid_field 400
a body field or query parameter that must be an id (UUID) or a date is not one; field names it.
invalid_quantity 400
400 (T98, BILL-13): a seat or county quantity that is not a whole number within the bounds (field, min, max), or extra counties on a plan that sells none. Nothing is stored or scheduled.
kind_immutable 400
a cluster's kind cannot be changed. Sent by PUT /v2/account/clusters/{id}.
kind_invalid 400
a cluster kind other than naics, soc or fips. Sent by 3 endpoints, including GET /v2/account/clusters.
malformed_json 400
the request body is not valid JSON, or uses an unsupported encoding. Sent by 40 endpoints, including PATCH /v2/account.
member_cap 400
a cluster names more members than a cluster may hold. Sent by 2 endpoints, including POST /v2/account/clusters.
name_invalid 400
a cluster name is missing, too long or not a string. Sent by 2 endpoints, including POST /v2/account/clusters.
password_too_long 400
the password is longer than the maximum. Sent by POST /v2/invites/sign-up.
password_too_short 400
the password is shorter than the minimum. Sent by 2 endpoints, including POST /v2/account/password.
public_email_domain 400
400 (T108: the problem's own code, with domain; also repeated in errors[]): a public email domain cannot be claimed by an organization. Sent by POST /v2/account/domains.
repeated_parameter 400
a query parameter given more than once. Sent by 18 endpoints, including GET /v2/vintages.
retired_msa 400
the MSA has no counties at the latest mapping; successors names where they went. Sent by 11 endpoints, including GET /v2/industry/series.
series_cap 400
more series than one request may name. Sent by 6 endpoints, including GET /v2/industry/series.
surviving_counties_required 400
400 (T108: the problem's own code, with capacity and candidates; also repeated in errors[]): a reduction leaves more counties than the new capacity; choose which survive (capacity, candidates). Sent by 3 endpoints, including POST /v2/account/billing/change.
unknown_codes 400
a cluster names codes the current vintage does not have. Sent by 2 endpoints, including POST /v2/account/clusters.
unknown_column 400
a workbook columns, sort or filters name that is not a column. Sent by 6 endpoints, including GET /v2/industry/series.
unknown_parameter 400
a query parameter this route does not take (parameters names them). Sent by 19 endpoints, including GET /v2/vintages.
workspace_invalid 400
the workspace must be a JSON object. Sent by PUT /v2/account/workspace.
xlsx_column_ceiling 400
the workbook would exceed the spreadsheet column ceiling. Sent by 6 endpoints, including GET /v2/industry/series.
xlsx_only_parameter 400
an as-shown workbook parameter (shape, by_period, columns, sort, filters, computed, total) on a JSON or CSV request. Sent by 6 endpoints, including GET /v2/industry/series.
xlsx_row_ceiling 400
the workbook would exceed the spreadsheet row ceiling. Sent by 8 endpoints, including GET /v2/industry/series.
key_revoked 401
an API key that was revoked; revoked_at, reason (why, or null before T110), and a sentence saying when and why. Sent by 100 endpoints, including GET /v2/vintages.
unauthenticated 401
no credential, or one that is unknown or expired. Sent by 105 endpoints, including GET /v2/vintages.
payment_method_required 402
402 or 409: a card must be on file first. Sent by POST /v2/account/billing/subscribe.
payment_required 402
402, generic.
account_suspended 403
403: DTG has suspended the signed-in person's own account (or the API key's holder's). Every route that authenticates answers it, the account-only routes and the session's own changes under /v2/auth included; sign-out, reading the session and the support ticket routes stay open, and sign-in succeeds. The key is refused, not revoked. Carries reason and suspended_at where recorded. Sent by 97 endpoints, including GET /v2/vintages.
api_not_enabled 403
the member is not allowed to hold live API keys in this organization (api_enabled). Sent by POST /v2/account/keys.
bad_chat_token 403
the support assistant conversation's token is invalid or expired, or its conversation already opened a ticket; start a new conversation. Sent by 2 endpoints, including POST /v2/support/chat.
bad_thread_token 403
the support thread link is invalid or expired. Sent by 2 endpoints, including GET /v2/support/tickets/{id}.
captcha_failed 403
the bot check failed. Sent by 5 endpoints, including POST /v2/invites/sign-up.
contract_managed 403
the organization's billing is managed by contract with DTG; self-serve billing changes are not available. Sent by 12 endpoints, including POST /v2/account/billing/subscribe.
email_mismatch 403
the signed-in account's address is not the one the invitation was sent to. Sent by POST /v2/invites/accept.
email_unverified 403
the account's email address must be verified first. Sent by 4 endpoints, including POST /v2/account/keys.
forbidden 403
the credential may not do this.
geography_excluded 403
with strict=true, areas outside the plan's geography were asked for (excluded_fips, at most 50). Sent by 8 endpoints, including GET /v2/industry/series.
no_membership 403
the account has no active membership in an organization (or none matching X-DTG-Org). Sent by 95 endpoints, including GET /v2/vintages.
org_closed 403
403 or 409: the organization is closed; its former owner may reopen it. Sent by 99 endpoints, including GET /v2/vintages.
org_suspended 403
the organization is suspended. Sent by 95 endpoints, including GET /v2/vintages.
owner_row_protected 403
only an owner may change an owner's membership. Sent by PATCH /v2/account/members/{id}.
plan_required 403
the organization's plan does not include this (entitlement names it: api_access, csv_access, vintage_access, quarterly_access, past_due, and the depth caps). Sent by 15 endpoints, including GET /v2/industry/series.
role_required 403
the caller's role (owner, admin, member) may not do this. Sent by 40 endpoints, including PATCH /v2/account/organization.
sandbox_read_only 403
a sandbox key reads, and may create and edit clusters; it changes nothing else (counties, billing, members, keys, the account). Use a live key or the app. Sent by 52 endpoints, including PATCH /v2/account.
second_factor_required 403
a DTG platform admin whose session has not passed the second-factor challenge; a code verified from that session admits it.
session_required 403
403 (O63 S11, AUTH-4): an API key reads data and account details but changes nothing on the account or organization; sign in. Sent by 3 endpoints, including POST /v2/account/password.
terms_acceptance_required 403
403 (T136; only while TERMS_AT_FIRST_SIGN_IN=on, off by default): a session of an account DTG staff set up (deploy.sh account, the house bootstrap) whose person has not yet accepted the Terms of Service and the Privacy Policy. Every route that resolves an organization answers it until POST /v2/account/legal/accept; the account-only routes stay open. Carries terms_version, privacy_version (the versions in force) and accept (the route). Sent by POST /v2/events.
wrong_instance 403
this process serves the other credential mode. A sandbox key belongs at https://api.datatactical.group/sandbox, a live key or a session at https://api.datatactical.group; base_url names the right one. Sent by 98 endpoints, including GET /v2/vintages.
cluster_deleted 404
404 or 409: the cluster is deleted (restorable until restorable_until, D70). Sent by 12 endpoints, including GET /v2/industry/series.
cluster_not_found 404
no such cluster in this organization. Sent by 12 endpoints, including GET /v2/industry/series.
not_found 404
no such resource, route, or vintage id. Sent by 98 endpoints, including GET /v2/vintages.
nothing_scheduled 404
there is no scheduled change of that kind to cancel. Sent by DELETE /v2/account/billing/pending/{kind}.
notification_not_found 404
no such notification for this person. Sent by POST /v2/account/notifications/{id}/read.
region_retired 404
the regions surface is retired; use counties and clusters. Sent by 13 endpoints, including GET /v2/industry/series.
staffing_not_in_vintage 404
the vintage was built before the staffing patterns were added; they are served from the first vintage built with them (vintage). Sent by 2 endpoints, including GET /v2/industry/staffing-pattern.
vintage_superseded 404
the vintage was replaced by a correction; superseded_by names it. Sent by 17 endpoints, including GET /v2/taxonomies/naics/levels.
vintage_withdrawn 404
the vintage was withdrawn and is not served; current names the vintage to use. Sent by 17 endpoints, including GET /v2/taxonomies/naics/levels.
account_closed 409
409: the account is closed; there is nothing to suspend. Sent by 66 endpoints, including GET /v2/vintages.
account_exists 409
an account already exists for that address; sign in instead. Sent by POST /v2/invites/sign-up.
already_member 409
the person is already a member of that organization. Sent by 2 endpoints, including POST /v2/invites/accept.
already_subscribed 409
the organization already has a live subscription. Sent by POST /v2/account/billing/subscribe.
billing_in_progress 409
another billing change for this organization is running; retry in a moment. Sent by 11 endpoints, including POST /v2/account/billing/subscribe.
capacity_exceeded 409
the change would exceed the plan's county capacity. Sent by DELETE /v2/account/counties/changes/{id}.
closed_by_dtg 409
409 (O63 S11, AUTH-12): Data Tactical Group closed this organization; it reopens only through support.
cluster_members_taken 409
another live cluster of this kind already has exactly these members (existing). Sent by 3 endpoints, including POST /v2/account/clusters.
cluster_name_taken 409
another live cluster of this kind already has the name (existing). Sent by 3 endpoints, including POST /v2/account/clusters.
confirm_purchase_required 409
the addition needs extra county slots; repeat it with confirm_purchase: true (preview shows the charge). Sent by POST /v2/account/counties.
conflict 409
the request conflicts with the current state.
dns_txt_not_found 409
the DNS TXT record for the domain claim was not found yet. Sent by POST /v2/account/domains/{id}/verify.
domain_verification_required 409
the domain must be verified first. Sent by POST /v2/account/members/claim-ownership.
key_limit 409
the per-member (or per-organization) active key limit for that mode is reached; revoke one first. Sent by POST /v2/account/keys.
last_owner 409
the organization would be left with no owner; transfer ownership first (organizations names them). Sent by 4 endpoints, including POST /v2/account/close.
no_open_invoice 409
there is no open invoice to retry. Sent by POST /v2/account/billing/retry.
no_seat_available 409
no free seat; buy a seat first. O63 S11 (BILL-8): an owner or admin always holds a seat and nobody is seated beyond the paid count, so promoting someone without a seat, an admin invitation and an ownership transfer to an unseated member need one too. Sent by PATCH /v2/account/members/{id}.
not_closed 409
the organization is not closed. Sent by POST /v2/reopen/{id}.
not_subscribed 409
the organization has no live self-serve subscription to change. Sent by 10 endpoints, including POST /v2/account/billing/preview.
org_name_taken 409
another organization at the same verified domain carries the name. Sent by 3 endpoints, including PATCH /v2/account/organization.
owner_active 409
the owner has been active recently, so an ownership claim cannot complete. Sent by POST /v2/account/members/claim-ownership.
password_already_set 409
409: the account already has a password; change it with the current one. Sent by POST /v2/account/password.
quantity_below_use 409
409: the extra seats asked for are fewer than the owners and admins the target plan's included seats cannot hold (field, min).
setup_intent_incomplete 409
the card setup has not completed (3-D Secure pending or failed). Sent by POST /v2/account/billing/payment-method/confirm.
terms_version_changed 409
409: the versions sent to POST /v2/account/legal/accept are not the ones in force; terms_version and privacy_version name them. Sent by POST /v2/account/legal/accept.
trial_active 409
a trial is already running. Sent by POST /v2/account/trial.
trial_cooldown 409
A trial has already run for this person. Sent by POST /v2/account/trial.
gone 410
410, generic.
invite_expired 410
the invitation link has expired; ask for a new one. Sent by 2 endpoints, including POST /v2/invites/accept.
body_too_large 413
the request body is larger than the endpoint accepts (limit_bytes). Sent by 36 endpoints, including PATCH /v2/account.
workspace_too_large 413
the workspace is larger than its limit (max_bytes). Sent by PUT /v2/account/workspace.
uri_too_long 414
the URL is longer than the service accepts. Sent by 20 endpoints, including GET /healthz.
chat_rate_limited 429
too many messages to the support assistant from this address, conversation or person; Retry-After says when to try again. Sent by POST /v2/support/chat.
credential_failures 429
429 (O63 S11, AUTH-6/ENT-8): this client address presented too many credentials that are not valid in the last minute; a credential the service already knows still works. retry_after and Retry-After say when.
invitation_limit 429
429 (O63 S11, KEL-8): the organization has sent its invitations for the day (25 on Free, 500 on a paid plan, resends included); limit, sent, remaining, retry_after. Nothing in the request was sent.
points_exhausted 429
the organization's monthly points allowance is used up for the credential's mode; Retry-After counts to the end of the UTC month. Sent by 8 endpoints, including GET /v2/industry/series.
quota_exhausted 429
a monthly CSV or API request quota re-armed for this organization by DTG is used up; Retry-After counts to the end of the UTC month. Sent by 8 endpoints, including GET /v2/industry/series.
rate_limited 429
the per-minute request ceiling for the credential (120, D79) or the organization (300, D45). Retry-After and X-RateLimit-* say when. Sent by 58 endpoints, including GET /v2/vintages.
sandbox_busy 429
the sandbox is at its concurrency cap; retry after Retry-After. Sent by 8 endpoints, including GET /v2/industry/series.
server_busy 429
the service is at capacity; retry after Retry-After. Not metered, and not a limit on the caller. Sent by 8 endpoints, including GET /v2/industry/series.
signin_throttled 429
too many sign-in attempts for this address (limit, window_seconds, retry_after). Sent by POST /v2/auth/sign-in/email.
ticket_rate_limited 429
too many support tickets from this session or address today. Sent by POST /v2/support/tickets.
internal_error 500
an unexpected error. Retrying the same request may not help; tell support. Sent by 20 endpoints, including GET /healthz.
pattern_missing 500
an industry with employment in the area and year has no staffing pattern in this vintage (D99, the owner's rule): a defect in the data release, logged, never a partial pattern; vintage. Sent by 2 endpoints, including GET /v2/industry/staffing-pattern.
unavailable 503
a dependency is unavailable (for example the bot check); retry later. Sent by 3 endpoints, including POST /v2/invites/sign-up.