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_required400- the API add-on is sold only with an annual plan. Sent by 7 endpoints, including POST /v2/account/billing/subscribe.
area_overlap400- two areas in a summed selection overlap;
pairnames them. Sent by 8 endpoints, including GET /v2/industry/series. area_sum_required400- a cluster or MSA beside other areas needs
sum=areas. Sent by 6 endpoints, including GET /v2/industry/series. bad_cluster_ref400- 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_code400- a code in a cluster is malformed. Sent by 2 endpoints, including POST /v2/account/clusters.
bad_request400- the request is malformed or a value is invalid;
detailanderrorssay which. Sent by 54 endpoints, including GET /healthz. bad_total400- the workbook
totalis notcluster:<id>of a code cluster. Sent by 6 endpoints, including GET /v2/industry/series. captcha_missing400- 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_cap400- the request asks for more than 400,000 cells (one series at one period), the one load guard for every credential.
cellsandcapsay by how much; narrow it or page it. Sent by 6 endpoints, including GET /v2/industry/series. chat_too_long400- a support assistant conversation over its message count, or a message over its length. Sent by POST /v2/support/chat.
codes_invalid400- a cluster's codes are missing or not a list of strings. Sent by 2 endpoints, including POST /v2/account/clusters.
codes_overlap400- a code cluster holds an ancestor and its descendant (they cannot be summed). Sent by 2 endpoints, including POST /v2/account/clusters.
column_not_filterable400- a workbook filter on a column that cannot be filtered (rank). Sent by 6 endpoints, including GET /v2/industry/series.
counties_outside_set400- a fips cluster names counties outside the organization's county set (
errorslists them). Sent by 2 endpoints, including POST /v2/account/clusters. events_invalid400- 400: a batch posted to
/v2/eventsis not 1 to 50 objects ofnamescreen_openedand a route familyroute; refused whole,indexnames the first bad entry. Sent by POST /v2/events. invalid_field400- a body field or query parameter that must be an id (UUID) or a date is not one;
fieldnames it. invalid_quantity400- 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_immutable400- a cluster's kind cannot be changed. Sent by PUT /v2/account/clusters/{id}.
kind_invalid400- a cluster kind other than naics, soc or fips. Sent by 3 endpoints, including GET /v2/account/clusters.
malformed_json400- the request body is not valid JSON, or uses an unsupported encoding. Sent by 40 endpoints, including PATCH /v2/account.
member_cap400- a cluster names more members than a cluster may hold. Sent by 2 endpoints, including POST /v2/account/clusters.
name_invalid400- a cluster name is missing, too long or not a string. Sent by 2 endpoints, including POST /v2/account/clusters.
password_too_long400- the password is longer than the maximum. Sent by POST /v2/invites/sign-up.
password_too_short400- the password is shorter than the minimum. Sent by 2 endpoints, including POST /v2/account/password.
public_email_domain400- 400 (T108: the problem's own code, with
domain; also repeated inerrors[]): a public email domain cannot be claimed by an organization. Sent by POST /v2/account/domains. repeated_parameter400- a query parameter given more than once. Sent by 18 endpoints, including GET /v2/vintages.
retired_msa400- the MSA has no counties at the latest mapping;
successorsnames where they went. Sent by 11 endpoints, including GET /v2/industry/series. series_cap400- more series than one request may name. Sent by 6 endpoints, including GET /v2/industry/series.
surviving_counties_required400- 400 (T108: the problem's own code, with
capacityandcandidates; also repeated inerrors[]): 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_codes400- a cluster names codes the current vintage does not have. Sent by 2 endpoints, including POST /v2/account/clusters.
unknown_column400- a workbook
columns,sortorfiltersname that is not a column. Sent by 6 endpoints, including GET /v2/industry/series. unknown_parameter400- a query parameter this route does not take (
parametersnames them). Sent by 19 endpoints, including GET /v2/vintages. workspace_invalid400- the workspace must be a JSON object. Sent by PUT /v2/account/workspace.
xlsx_column_ceiling400- the workbook would exceed the spreadsheet column ceiling. Sent by 6 endpoints, including GET /v2/industry/series.
xlsx_only_parameter400- 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_ceiling400- the workbook would exceed the spreadsheet row ceiling. Sent by 8 endpoints, including GET /v2/industry/series.
key_revoked401- 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. unauthenticated401- no credential, or one that is unknown or expired. Sent by 105 endpoints, including GET /v2/vintages.
payment_method_required402- 402 or 409: a card must be on file first. Sent by POST /v2/account/billing/subscribe.
payment_required402- 402, generic.
account_suspended403- 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/authincluded; sign-out, reading the session and the support ticket routes stay open, and sign-in succeeds. The key is refused, not revoked. Carriesreasonandsuspended_atwhere recorded. Sent by 97 endpoints, including GET /v2/vintages. api_not_enabled403- the member is not allowed to hold live API keys in this organization (
api_enabled). Sent by POST /v2/account/keys. bad_chat_token403- 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_token403- the support thread link is invalid or expired. Sent by 2 endpoints, including GET /v2/support/tickets/{id}.
captcha_failed403- the bot check failed. Sent by 5 endpoints, including POST /v2/invites/sign-up.
contract_managed403- 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_mismatch403- the signed-in account's address is not the one the invitation was sent to. Sent by POST /v2/invites/accept.
email_unverified403- the account's email address must be verified first. Sent by 4 endpoints, including POST /v2/account/keys.
forbidden403- the credential may not do this.
geography_excluded403- 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_membership403- the account has no active membership in an organization (or none matching
X-DTG-Org). Sent by 95 endpoints, including GET /v2/vintages. org_closed403- 403 or 409: the organization is closed; its former owner may reopen it. Sent by 99 endpoints, including GET /v2/vintages.
org_suspended403- the organization is suspended. Sent by 95 endpoints, including GET /v2/vintages.
owner_row_protected403- only an owner may change an owner's membership. Sent by PATCH /v2/account/members/{id}.
plan_required403- the organization's plan does not include this (
entitlementnames 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_required403- the caller's role (owner, admin, member) may not do this. Sent by 40 endpoints, including PATCH /v2/account/organization.
sandbox_read_only403- 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_required403- a DTG platform admin whose session has not passed the second-factor challenge; a code verified from that session admits it.
session_required403- 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_required403- 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 untilPOST /v2/account/legal/accept; the account-only routes stay open. Carriesterms_version,privacy_version(the versions in force) andaccept(the route). Sent by POST /v2/events. wrong_instance403- 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_urlnames the right one. Sent by 98 endpoints, including GET /v2/vintages. cluster_deleted404- 404 or 409: the cluster is deleted (restorable until
restorable_until, D70). Sent by 12 endpoints, including GET /v2/industry/series. cluster_not_found404- no such cluster in this organization. Sent by 12 endpoints, including GET /v2/industry/series.
not_found404- no such resource, route, or vintage id. Sent by 98 endpoints, including GET /v2/vintages.
nothing_scheduled404- there is no scheduled change of that kind to cancel. Sent by DELETE /v2/account/billing/pending/{kind}.
notification_not_found404- no such notification for this person. Sent by POST /v2/account/notifications/{id}/read.
region_retired404- the regions surface is retired; use counties and clusters. Sent by 13 endpoints, including GET /v2/industry/series.
staffing_not_in_vintage404- 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_superseded404- the vintage was replaced by a correction;
superseded_bynames it. Sent by 17 endpoints, including GET /v2/taxonomies/naics/levels. vintage_withdrawn404- the vintage was withdrawn and is not served;
currentnames the vintage to use. Sent by 17 endpoints, including GET /v2/taxonomies/naics/levels. account_closed409- 409: the account is closed; there is nothing to suspend. Sent by 66 endpoints, including GET /v2/vintages.
account_exists409- an account already exists for that address; sign in instead. Sent by POST /v2/invites/sign-up.
already_member409- the person is already a member of that organization. Sent by 2 endpoints, including POST /v2/invites/accept.
already_subscribed409- the organization already has a live subscription. Sent by POST /v2/account/billing/subscribe.
billing_in_progress409- another billing change for this organization is running; retry in a moment. Sent by 11 endpoints, including POST /v2/account/billing/subscribe.
capacity_exceeded409- the change would exceed the plan's county capacity. Sent by DELETE /v2/account/counties/changes/{id}.
closed_by_dtg409- 409 (O63 S11, AUTH-12): Data Tactical Group closed this organization; it reopens only through support.
cluster_members_taken409- another live cluster of this kind already has exactly these members (
existing). Sent by 3 endpoints, including POST /v2/account/clusters. cluster_name_taken409- another live cluster of this kind already has the name (
existing). Sent by 3 endpoints, including POST /v2/account/clusters. confirm_purchase_required409- the addition needs extra county slots; repeat it with
confirm_purchase: true(previewshows the charge). Sent by POST /v2/account/counties. conflict409- the request conflicts with the current state.
dns_txt_not_found409- the DNS TXT record for the domain claim was not found yet. Sent by POST /v2/account/domains/{id}/verify.
domain_verification_required409- the domain must be verified first. Sent by POST /v2/account/members/claim-ownership.
key_limit409- the per-member (or per-organization) active key limit for that mode is reached; revoke one first. Sent by POST /v2/account/keys.
last_owner409- the organization would be left with no owner; transfer ownership first (
organizationsnames them). Sent by 4 endpoints, including POST /v2/account/close. no_open_invoice409- there is no open invoice to retry. Sent by POST /v2/account/billing/retry.
no_seat_available409- 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_closed409- the organization is not closed. Sent by POST /v2/reopen/{id}.
not_subscribed409- the organization has no live self-serve subscription to change. Sent by 10 endpoints, including POST /v2/account/billing/preview.
org_name_taken409- another organization at the same verified domain carries the name. Sent by 3 endpoints, including PATCH /v2/account/organization.
owner_active409- the owner has been active recently, so an ownership claim cannot complete. Sent by POST /v2/account/members/claim-ownership.
password_already_set409- 409: the account already has a password; change it with the current one. Sent by POST /v2/account/password.
quantity_below_use409- 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_incomplete409- the card setup has not completed (3-D Secure pending or failed). Sent by POST /v2/account/billing/payment-method/confirm.
terms_version_changed409- 409: the versions sent to
POST /v2/account/legal/acceptare not the ones in force;terms_versionandprivacy_versionname them. Sent by POST /v2/account/legal/accept. trial_active409- a trial is already running. Sent by POST /v2/account/trial.
trial_cooldown409- A trial has already run for this person. Sent by POST /v2/account/trial.
gone410- 410, generic.
invite_expired410- the invitation link has expired; ask for a new one. Sent by 2 endpoints, including POST /v2/invites/accept.
body_too_large413- the request body is larger than the endpoint accepts (
limit_bytes). Sent by 36 endpoints, including PATCH /v2/account. workspace_too_large413- the workspace is larger than its limit (
max_bytes). Sent by PUT /v2/account/workspace. uri_too_long414- the URL is longer than the service accepts. Sent by 20 endpoints, including GET /healthz.
chat_rate_limited429- too many messages to the support assistant from this address, conversation or person;
Retry-Aftersays when to try again. Sent by POST /v2/support/chat. credential_failures429- 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_afterandRetry-Aftersay when. invitation_limit429- 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_exhausted429- the organization's monthly points allowance is used up for the credential's mode;
Retry-Aftercounts to the end of the UTC month. Sent by 8 endpoints, including GET /v2/industry/series. quota_exhausted429- a monthly CSV or API request quota re-armed for this organization by DTG is used up;
Retry-Aftercounts to the end of the UTC month. Sent by 8 endpoints, including GET /v2/industry/series. rate_limited429- the per-minute request ceiling for the credential (120, D79) or the organization (300, D45).
Retry-AfterandX-RateLimit-*say when. Sent by 58 endpoints, including GET /v2/vintages. sandbox_busy429- the sandbox is at its concurrency cap; retry after
Retry-After. Sent by 8 endpoints, including GET /v2/industry/series. server_busy429- 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_throttled429- too many sign-in attempts for this address (
limit,window_seconds,retry_after). Sent by POST /v2/auth/sign-in/email. ticket_rate_limited429- too many support tickets from this session or address today. Sent by POST /v2/support/tickets.
internal_error500- an unexpected error. Retrying the same request may not help; tell support. Sent by 20 endpoints, including GET /healthz.
pattern_missing500- 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.