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

# Industry data

4 endpoints, generated from the API's own OpenAPI document.

## GET `/v2/industry/series`

**Industry series**

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

Industry time series (QCEW vocabulary): one series per code x area x ownership over a range of periods, every period present (sparse means zero: a period with no source row is a true 0). Areas: a list gives one series per area; a lone `cluster:<id>` or `msa:<code>`, or `sum=areas`, gives one summed series (`meta.geography.region` says what is in it). Codes: a list or level gives one series per code; `cluster:<id>` or `sum=codes` gives one summed series. Summed values follow one rule set: values sum (in exact cents), disclosure takes the maximum, reliability is employment-weighted, the restated flags OR, avg\_pay is recomputed from the summed numerator and denominator, quarters\_present takes the minimum over members with data. Nation and state series are open to every account; county series need the county in the organization's active county set (a partial exclusion is counted in `meta.geography.excluded`). One guard of 400,000 cells per request, a cell being one series at one period; past it, page with `cursor` and `limit`. Callers: an API key (live or sandbox) or a session, any member role. A live API key needs an organization whose plan includes the API add-on (403 [`plan_required`](/docs/api/errors#code-plan-required), entitlement `api_access`) and that is not past due; a sandbox key needs neither and reads synthetic data. Gates, in order: authentication, the per-credential and per-organization rate ceilings, the global admission gate (429 [`server_busy`](/docs/api/errors#code-server-busy); a refused request is not metered), then the one entitlement gate (API access, vintage access, quarterly access, export access and the monthly points allowance). Metering: a JSON request on a SESSION is never metered; a JSON request on an API key, and every CSV or XLSX export on any credential, spends points from the organization's monthly points allowance (the plan's figure, `GET /v2/plans`; a sandbox key spends a separate sandbox allowance). During a trial, an export whose geography lies wholly inside the trial county is free of the points allowance and of the export entitlement. Side effects: one usage record per request (with the points emitted, the vintage read and the counties read), the member's last-active time, and, when a key is refused for its rate or the points allowance, a notice to the key's holder and the organization's admins at most once a day. A session request records the person's presence (it cancels an open ownership claim on an organization they own). Representations (spec sections 10 and 10a): JSON by default. CSV with `Accept: text/csv` or a `.csv` suffix on the path (/v2/industry/series.csv); the suffix wins over Accept. The as-shown XLSX workbook with `Accept: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` or an `.xlsx` suffix (/v2/industry/series.xlsx). CSV is long format, one line per series-point (the series' key columns, then the point's columns), headers exactly the JSON field names in JSON order, values exactly as JSON writes them (null as an empty field, booleans true/false), RFC 4180 quoting, CRLF, UTF-8 without a byte-order mark; a text cell a spreadsheet would read as a formula starts with `'`. A `#` comment preamble precedes the header: taigan-csv, endpoint, the sandbox warning (sandbox only), vintage, vintage\_label, source (the attribution to keep), generated\_at, granularity, provenance\_boundaries, geography, the region lines (summed areas only), count, disclosure and reliability (the key to the labels), encoding. The workbook has a `Table` sheet (full-precision values under display number formats, codes stored as text) and a `Source` sheet (attribution, vintage, the shape and grid state in words, and the key to disclosure and reliability). The as-shown parameters (`shape`, `by_period`, `columns`, `sort`, `filters`, `computed`, `total`) shape the workbook only and are 400 [`xlsx_only_parameter`](/docs/api/errors#code-xlsx-only-parameter) on JSON or CSV. Files are named `<domain>-<endpoint>-<vintage>-<UTC timestamp>.csv` (or `.xlsx`). Every problem, on any representation, is application/problem+json.

### Parameters

**`naics` query · string**

Comma list of NAICS platform codes (`481,482`), one series per code in the taxonomy's nested order; or `cluster:<id>`, one of the organization's naics code clusters, alone, summed into ONE series named with the cluster's name. With `sum=codes` any non-overlapping list sums into one series labelled `sum(<n>)`; an ancestor and its descendant together are refused. Default (neither `naics` nor `naics_level`): the level-0 total (`10`). Not with `naics_level`.

**`naics_level` query · string**

Every code at one hierarchy level (0 Total, 1 Domain, 2 Super-sector, 3 Sector, 4 to 7 the 3- to 6-digit codes); or `all`, every code at every level in nested order (a browse: parents and children together, never summed); or `leaf`, the codes that are the parent of no code. `all` and `leaf` need the plan's full depth. Not with `naics`. (pattern ^(\\d+|all|leaf)$)

**`areas` query · string**

Comma list of area codes ("00" nation, "22" state, "22073" county), state:XX (expands to the state's counties, INCLUDING its "Unknown Or Undefined" catch-all — O34), cluster:<id> (the caller's own fips-kind cluster), or msa:<code> (the MSA's member counties at the latest map, never a catch-all). T19: a cluster or an MSA ALONE is one summed series named by the cluster's name or the MSA's title; BESIDE other tokens it contributes its counties to the one summed area and requires sum=areas (else 400 area\_sum\_required). A summed selection may not overlap: 400 area\_overlap {pair}. A retired MSA is 400 retired\_msa naming its successors. areas=region:<id> is RETIRED (404 region\_retired, D44). The county universe is 3,194 areas: 3,144 counties and county-equivalents plus the 50 per-state catch-alls holding employment not attributable to a specific county — ordinary areas in the data, included in state and national totals, purchasable, but with no geographic extent (is\_catchall on /v2/account/counties governs presentation only: never in an msa: expansion, never a /map row, never a public data page).

**`region` query · string deprecated**

RETIRED: any value is 404 [`region_retired`](/docs/api/errors#code-region-retired). Use `areas=cluster:<id>`.

**`combine` query · "sum" deprecated**

Deprecated alias of `sum=areas`: sums the `areas` list into one series. Only `sum` is defined; not with `sum`.

**`sum` query · string**

Uniform summing (D37-FINAL): a comma-separated list of the axes to collapse — `codes`, `areas`, and (v0.11) `ownership`, which sums an explicit `ownership=` subset into ONE series labelled with the subset (e.g. `local,private`); with `order=ownership` it is a 400. Any NON-overlapping code set sums; an ancestor/descendant pair is a 400 naming the pair. cluster:<id> references imply summing. R5 semantics: values sum; disclosure max; reliability employment-weighted; restatement OR; avg\_pay/wage\_mean from summed numerator/denominator once-rounded; quarters\_present min over members with data.

**`ownership` query · string**

Comma list of `fed`, `state`, `local`, `private` (or the QCEW codes 1, 2, 3, 5). Default: one series summed across all four (`ownership: "all"`). An explicit list gives one unsummed series per ownership, or with `sum=ownership` one series summed over the subset.

**`granularity` query · "annual" | "quarterly"**

`annual` (default) or `quarterly`. Quarterly needs the plan's quarterly access (403 [`plan_required`](/docs/api/errors#code-plan-required), `quarterly_access`). (default "annual")

**`from` query · string**

Start period, inclusive — "2004" (annual) or "2004q1" (quarterly); must match granularity=. (pattern ^\\d{4}(q\[1-4\])?$)

**`to` query · string**

End period, inclusive — same format and granularity as from=. (pattern ^\\d{4}(q\[1-4\])?$)

**`measures` query · string**

Comma list of the measures to return, from `emp`, `estabs`, `earnings`, `avg_pay` (default all). Labels are always returned on industry points.

**`order` query · "naics" | "area" | "ownership"**

The major dimension of series ORDER: a sort key only, never a grouping; the code axis is never collapsed. `ownership` also splits the default summed ownership into one series per ownership. Not with `group`. (default "naics")

**`group` query · "naics" | "area" | "ownership" deprecated**

Deprecated alias of `order`; removal would be a new major version, announced first. Not with `order`.

**`strict` query · "true"**

`true`: any county the entitlement excludes makes the request 403 [`geography_excluded`](/docs/api/errors#code-geography-excluded) (listing up to 50) instead of a 200 with `meta.geography.excluded` above zero; while the organization is past due, 403 [`plan_required`](/docs/api/errors#code-plan-required) with `past_due` instead.

**`cursor` query · string**

Paging: the opaque `meta.pagination.next_cursor` of the previous page. A page is a slice of the series list in the order the response emits; the cell guard measures the page. Giving `cursor` or `limit` turns paging on; with neither the whole result comes in one response.

**`limit` query · integer**

Paging: series per page, 1 to 5,000 (1,000 when only `cursor` is given). Absent with no `cursor`: unpaged. (default 1000; 1 to 5000)

**`vintage` query · string**

The vintage to read (default: the current one). Pinning it makes the answer immutable and cacheable for a year. (pattern ^(\\d{8}|sandbox-\\d{4}\\.\\d+)$)

**`shape` query · "snapshot" | "by-period"**

XLSX only. `by-period`: one row per series with a column per period per point field (`emp_2019`, ..., `disclosure_2019`, `reliability_2019`). Anything else: the snapshot, one row per series showing its last period.

**`by_period` query · "1"**

XLSX only. `1` is the same as `shape=by-period`.

**`columns` query · string**

XLSX only. The table's columns in order, comma-separated: response fields, `title` (the code's name), the computed columns, and on /series `lq`. Default: everything. On industry sheets `disclosure` and `reliability` (and `lq_disclosure` beside `lq`) are always inserted after the values they qualify, whatever is named; those forced labels are not metered. An unknown name is 400 [`unknown_column`](/docs/api/errors#code-unknown-column).

**`sort` query · string**

XLSX only. `<column>:<asc|desc>` (default desc) on a snapshot value or a computed column; nulls last. (pattern ^\[^:\]+(:(asc|desc))?$)

**`filters` query · string**

XLSX only. Comma list of `<column>:<ge|gt|le|lt>:<number>`, or `disclosure:published` (keeps disclosure 0; in the by-period shape, every period exported must be published). A row with no value for a filtered column never passes; `rank` is not filterable.

**`computed` query · string**

XLSX only. Comma list of `emp_start` (first period's employment), `change_abs` (last minus first), `change_pct` ((last minus first) / first x 100; null when first is 0), `share` (last / sum of every returned row's last x 100, taken before filtering), `rank` (1, 2, 2, 4 over the filtered rows, highest first, on the sort column or employment). Only `emp_start` is metered.

**`total` query · string**

XLSX only, /series: `cluster:<id>`, a code cluster of this domain whose summed series is appended beneath the rows as the table's total row (`Total: <name>`), not filtered, sorted or ranked; metered as its values are. (pattern ^cluster:\[0-9a-fA-F-\]{36}$)

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

Session only: the organization the request acts in, when the person belongs to more than one (default: their own organization). An API key is bound to its organization; on a key this header must name that organization or be omitted. A malformed id, or an organization the caller is not an active member of, is 404 [`not_found`](/docs/api/errors#code-not-found).

### Responses

#### `200` success

The series.

As `application/json`, `text/csv`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`.

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

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

**`Content-Disposition` header**

CSV and XLSX only: `attachment; filename="<domain>-<endpoint>-<vintage>-<UTC timestamp>.csv"` (or `.xlsx`), e.g. `industry-series-20260913-20260927T184215Z.csv`.

**`Cache-Control` header**

CSV and XLSX only: `private, no-store`.

**`Access-Control-Expose-Headers` header**

CSV and XLSX only: `Content-Disposition`, so a cross-origin first-party app can read the file name.

**`data` array of DataSeriesIndustry always present**

The series (this page of them, when paged), in the order `order` sets. Empty only when a page or selection holds none.

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

The NAICS platform code as a string (`481111`), names on `GET /v2/taxonomies/naics`. On a summed code axis it is the label instead: the cluster's name for `naics=cluster:<id>`, or `sum(<n>)` for `sum=codes` over n codes.

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

The area: a FIPS code as a zero-padded string (`00` the nation, `22` a state's own row, `22073` a county; a code ending `999` is that state's catch-all area, `Unknown Or Undefined`, holding employment QCEW cannot place in a county: a real, purchasable area included in state and national totals, with no geographic extent). On a summed-area series it is the label instead: the cluster's name or the MSA's title when one was asked for alone, otherwise `custom region`; `meta.geography.region` lists what was summed. The response carries codes, not area names: names are on `GET /v2/taxonomies/areas`.

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

The ownership the series covers: `all` (the default: federal, state and local government and private summed), one of `fed`, `state`, `local`, `private` (an unsummed series, from an explicit `ownership=` list or `order=ownership`), or with `sum=ownership` the summed subset as tokens in code order, e.g. `local,private`.

**`data[].points` array of DataIndustryPoint always present**

One point per period of the requested range, in period order, every period present (dense).

**`data[].points[].period` string always present**

The period: `2021` (annual) or `2021q3` (quarterly), per `granularity`. (pattern ^\\d{4}(q\[1-4\])?$)

**`data[].points[].emp` number**

Average employment, in jobs covered by state unemployment-insurance programs: jobs, not people (a person with two covered jobs counts twice). A quarter is its three-month average; an annual value is the sum of its four quarters divided by 4. Present when `measures` includes emp (the default).

**`data[].points[].estabs` number**

Establishments: single economic units at one location, not firms (one firm may run many). Averaged over a year like emp (sum of quarters divided by 4), so an annual value can be fractional. QCEW publishes establishment counts even where employment is withheld, so establishments beside a small estimated employment mean real but limited activity. Present when `measures` includes estabs.

**`data[].points[].earnings` number**

Total wages paid in the period as reported for unemployment-insurance purposes (employee contributions and withheld taxes included; employer contributions excluded), in US dollars. An annual value is the SUM of its four quarters. Present when `measures` includes earnings.

**`data[].points[].avg_pay` number or null**

Pay per JOB for the period in US dollars (not an annual salary, not an hourly rate): earnings divided by employment, from the summed numerator and denominator and rounded once. Annual: the year's earnings divided by its average employment, which is QCEW's average annual pay. Null when employment is zero. Present when `measures` includes avg\_pay.

**`data[].points[].disclosure` 0 | 1 | 2 | 3 always present**

Industry only. Where this value came from: 0 = published by BLS in QCEW, as published; 1 = estimated by DTG where QCEW withheld (suppressed) the value to protect an employer's identity; 2 = carried forward past the latest QCEW release using the CES and SAE monthly surveys (their rate of change applied to DTG's base); 3 = projected. Suppression is a property of the QCEW source, not a judgement about the area. A summed value (a code set, a summed area, the default sum across ownerships, and on an annual point the year's four quarters) carries the HIGHEST code among the values summed, so an annual point whose year mixes published and projected quarters reports 3. A period with no source row is a true zero (sparse means zero) and reports 0.

**`data[].points[].reliability` number or null always present**

Industry only. 0 to 1, two decimals: how tightly the published QCEW record constrains this value. Published values (disclosure 0) score 1; an estimated value (disclosure 1) scores lower the wider the range of values the surrounding published totals leave open. Null where no score is computed at all: the value is carried forward or projected (disclosure 2 or 3, including an annual point whose year contains such a quarter), or there is no employment. A null is not a zero. A summed value's score is the employment-weighted mean of its parts, so it describes the total rather than its weakest member and a summed series can read 1.00 while carrying disclosure 1: the disclosure code is the authoritative published-or-estimated signal. Not a ranking of areas, not a confidence interval and not a margin of error. (0 to 1)

**`data[].points[].naics_restated` boolean always present**

Industry only. True where this value changed when the full series was restated onto this vintage's NAICS industry classification. Applied per value: a parent code is flagged only where its own value changed. Not a quality warning: it explains why a DTG figure can differ from what BLS published for that year at the time. A summed value is true if any part is. False on a period with no source row.

**`data[].points[].fips_restated` boolean always present**

Industry only. As naics\_restated, for county-definition (FIPS) restatement: true where this value changed when the series was restated onto the current county boundaries. A summed value is true if any part is.

**`data[].points[].quarters_present` integer**

Annual points only (absent on quarterly ones): how many of the year's quarters have a source row behind the annual figure; 4 is a complete year and 0 means the year is a true zero. On a summed series, the minimum over the summed members that have at least one source quarter in the year (a member with none is a true zero, not a partial year). (0 to 4)

**`meta` DataMeta always present**

The `meta` of every v2 envelope (spec section 3). It is written after `data` on the wire. Clients must tolerate unknown fields.

**`meta.vintage` string always present**

The vintage the answer was read from: the internal build id (`20260913`; a sandbox key's are `sandbox-2026.1`). Cite it and pass it back as `vintage=` to reproduce the answer exactly: a released vintage never changes. Its public name, such as `DTG 2026.3`, is the `label` on `GET /v2/vintages`.

**`meta.sandbox` true**

Present, and true, only when the answer was read from a sandbox vintage: every value is synthetic and must never be published. Absent otherwise.

**`meta.generated_at` string (date-time) always present**

When this response was produced, UTC, to the second (`2026-09-27T18:42:15Z`).

**`meta.granularity` "annual" | "quarterly"**

Data routes only: the period granularity of the points (on /map, `quarterly` when `qtr` was given). Absent on taxonomy responses.

**`meta.provenance_boundaries` DataProvenanceBoundaries always present**

Where the vintage's published record ends and estimation begins, recorded per vintage at build time and returned on every envelope. Compare a point's period against these ranges to know what kind of value it is. For OCCUPATION data this is the only provenance there is: occupation values carry no per-value label, so a period inside `actuals` is published-basis, inside `ces` carried forward, inside `projections` projected. Industry values also carry their own `disclosure` code, which is authoritative for the value. The ranges move with each release; read them, never hard-code them.

**`meta.provenance_boundaries.actuals` object always present**

Quarters published by BLS in QCEW (the Quarterly Census of Employment and Wages). Industry values here have disclosure 0 (published) or 1 (estimated by DTG where QCEW withheld the value).

**`meta.provenance_boundaries.actuals.from` string always present**

First quarter of the range, lower case, e.g. `2004q1`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.actuals.to` string always present**

Last quarter of the range, lower case, e.g. `2025q4`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.ces` object always present**

Quarters after the latest QCEW release, carried forward from the CES and SAE monthly employment surveys (their rate of change applied to DTG's base, not their levels). Industry values here have disclosure 2.

**`meta.provenance_boundaries.ces.from` string always present**

First quarter of the range, lower case, e.g. `2004q1`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.ces.to` string always present**

Last quarter of the range, lower case, e.g. `2025q4`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.projections` object always present**

Projected quarters, out to the end of the period axis. Industry values here have disclosure 3.

**`meta.provenance_boundaries.projections.from` string always present**

First quarter of the range, lower case, e.g. `2004q1`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.projections.to` string always present**

Last quarter of the range, lower case, e.g. `2025q4`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.geography` object**

Data routes only. Absent on taxonomy responses.

**`meta.geography.requested` integer always present**

/series and /lq: the number of distinct areas the request resolved to before entitlement (a `state:XX`, `msa:` or `cluster:` token counts its counties; a plain state code counts 1; no `areas` counts the nation as 1). /map: the counties of the `areas` restriction, or without one the counties with a row for this code and period (catch-alls never counted). (at least 0)

**`meta.geography.returned` integer always present**

Areas (on /map, counties) that passed entitlement and are in the answer. On a summed request these are the areas inside the one summed series. (at least 0)

**`meta.geography.excluded` integer always present**

Requested counties the organization's entitlement removed (outside the active county set, or every county while the subscription is past due). On /map a requested-versus-returned gap beyond `excluded` means counties with no data for the code and period, not an exclusion. (at least 0)

**`meta.geography.region` DataRegion**

Present only on a request whose areas were summed into one series (a lone `cluster:<id>` or `msa:<code>`, or `sum=areas` / `combine=sum`): what the summed area is made of. The CSV preamble prints it as `# region:` lines and the workbook's Source sheet as `Region` rows.

**`meta.geography.region.label` string always present**

The summed series' `area` label: the cluster's name or the MSA's title when one was asked for alone, otherwise `custom region`.

**`meta.geography.region.areas` array of DataRegionArea always present**

Each area as requested, in request order.

**`meta.geography.region.areas[].token` string always present**

The token exactly as requested: `22073`, `22`, `00`, `state:22`, `msa:35380`, `cluster:<id>`.

**`meta.geography.region.areas[].kind` "nation" | "state" | "county" | "stateAll" | "msa" | "cluster" always present**

What the token is: the nation (`00`), a state's own row (`22`), a county, `stateAll` (a `state:XX` token: every county of the state, its catch-all included), an MSA, or one of the organization's area clusters.

**`meta.geography.region.areas[].code` string always present**

The area's code: a FIPS code for a county or state (the 2-digit state code for `stateAll`), or the token itself, lower-cased, for `msa:<code>` and `cluster:<id>`.

**`meta.geography.region.areas[].name` string or null always present**

The area's name: the vintage taxonomy's title for a county or state, the MSA's title, or the cluster's name. Null where the taxonomy has none.

**`meta.geography.region.areas[].resolved` array of string always present**

The area codes this token contributed to the sum after entitlement. A county the plan does not cover appears in no list; `geography.excluded` counts it.

**`meta.geography.region.resolved` array of string always present**

Every area code summed, deduplicated, in request order, after entitlement.

**`meta.pagination` DataPagination always present**

**`meta.pagination.cursor` string or null always present**

The `cursor` this page was requested with; null for the first page or an unpaged request.

**`meta.pagination.next_cursor` string or null always present**

Pass as `cursor` to get the next page. Null on the last page and on every unpaged response, so null always means there is nothing more.

**`meta.pagination.count` integer always present**

Rows in `data` on this response: series on /series and /lq, rows on /map, items on a taxonomy. (at least 0)

#### `400` error

Bad request. Codes: [`unknown_parameter`](/docs/api/errors#code-unknown-parameter) (a query parameter this route does not take; `parameters[]` names them), [`repeated_parameter`](/docs/api/errors#code-repeated-parameter) (a parameter given more than once; `parameter`), [`bad_request`](/docs/api/errors#code-bad-request) (more than 50 query parameters); [`xlsx_only_parameter`](/docs/api/errors#code-xlsx-only-parameter) (an as-shown parameter on a JSON or CSV request; `parameters[]`); [`bad_request`](/docs/api/errors#code-bad-request) for a malformed `vintage` (a live key or a session takes `YYYYMMDD`, a sandbox key `sandbox-YYYY.N`; an id shaped for the other scope is refused the same way, so neither scope can probe the other); [`bad_request`](/docs/api/errors#code-bad-request) also for: a bad `granularity`; a `from` or `to` that is not `YYYY` or `YYYYqN`, a quarterly one without `granularity=quarterly`, one outside the vintage's period axis, or `from` after `to`; a bad `cursor`, a `cursor` past the end, or a `limit` outside 1 to 5,000; a bad `sum` token, or `sum` and `combine` together, or a `combine` other than `sum`; a malformed or unknown code, both `naics=` and `naics_level=`, a `naics_level` that is not an integer, `all` or `leaf`, a level with no codes, `sum=codes` with `naics_level=all` or `leaf`, an overlapping code set with `sum=codes` (the pair is named: "481 is contained in 48"), `cluster:<id>` not alone in `naics=`, or a malformed cluster reference; a bad `ownership` token; an unknown area, a malformed area code, a `state:XX` with no counties, a malformed `msa:` token, an unknown MSA, or a malformed `cluster:` token in `areas`; a bad `measures` entry; a bad `order`, or `order` and `group` together; `sum=ownership` with `order=ownership`; [`bad_cluster_ref`](/docs/api/errors#code-bad-cluster-ref) (a `cluster:` id that is not a 36-character UUID); [`area_sum_required`](/docs/api/errors#code-area-sum-required) (an `msa:` or `cluster:` token beside other areas without `sum=areas`; `token`); [`area_overlap`](/docs/api/errors#code-area-overlap) (a summed area list whose members overlap, D67; `pair`); [`retired_msa`](/docs/api/errors#code-retired-msa) (an MSA with no counties in the current map; `msa`, `retired_after`, `successors`); [`cell_cap`](/docs/api/errors#code-cell-cap) (the request, or the page, is more than 400,000 cells, a cell being one series at one period; `cells`, `cap`: narrow the areas, codes or period range, or page with `cursor` and `limit`); [`series_cap`](/docs/api/errors#code-series-cap) (more than the series cap per request, 10,000 by default; `series`, `cap`); XLSX only: [`unknown_column`](/docs/api/errors#code-unknown-column) (a `columns`, `sort` or `filters` name the table does not have; `column`, `parameter`), [`column_not_filterable`](/docs/api/errors#code-column-not-filterable) (a filter on `rank`), [`bad_total`](/docs/api/errors#code-bad-total) (a `total` that is not `cluster:<uuid>`), [`xlsx_row_ceiling`](/docs/api/errors#code-xlsx-row-ceiling) (more rows than a sheet holds; `rows`, `max_rows`, `alternatives`), [`xlsx_column_ceiling`](/docs/api/errors#code-xlsx-column-ceiling) (more columns than a sheet holds; `columns`, `max_columns`), [`bad_request`](/docs/api/errors#code-bad-request) for a malformed `sort` or `filters` entry.

As `application/problem+json`.

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no credential, or an API key or session that is unknown or expired); [`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 membership), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG; `reason` where recorded), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended; `organization` {id, name} and, where recorded, `reason`), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed; `organization`), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (a key of the mode this process does not serve, for example a sandbox key on a live-only process; `expected`, and `base_url` naming the address that serves it); [`plan_required`](/docs/api/errors#code-plan-required) with `entitlement` naming what the plan lacks: `api_access` (a live API key on an organization without the API add-on), `past_due` (the organization's subscription is past due: every request on a live API key, every CSV or XLSX export, and county detail are paused; nation and state data on a session are not), `vintage_access` (a non-current `vintage=` on a plan without prior vintages), `quarterly_access` (`granularity=quarterly` on a plan without quarterly data), `csv_access` (a CSV or XLSX export on a plan without export, or a trial export outside the trial county), `depth_cap_naics` (a code deeper than the plan's naics depth cap; `naics_level=all` and `leaf` need the full depth); [`geography_excluded`](/docs/api/errors#code-geography-excluded) (every requested county is outside the organization's entitlement, or any is with `strict=true`; `excluded_fips` lists up to 50).

As `application/problem+json`.

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `404` error

Not found. Codes: [`not_found`](/docs/api/errors#code-not-found) (no such vintage in the caller's scope, or an `X-DTG-Org` header naming an organization the caller does not belong to), [`vintage_superseded`](/docs/api/errors#code-vintage-superseded) (the pinned vintage was withdrawn and replaced by a correction; `superseded_by` names it; re-run against it), [`vintage_withdrawn`](/docs/api/errors#code-vintage-withdrawn) (the pinned vintage was withdrawn and no correction exists yet; `current` names the vintage to use), [`region_retired`](/docs/api/errors#code-region-retired) (`areas=region:<id>` or the `region` parameter is retired: use `cluster:<id>`; `replaced_by`), [`cluster_not_found`](/docs/api/errors#code-cluster-not-found) (a `cluster:<id>` that is not one of the organization's clusters of that kind), [`cluster_deleted`](/docs/api/errors#code-cluster-deleted) (the cluster was deleted; `cluster`, `restorable`, `restorable_until`).

As `application/problem+json`.

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `414` error

URI too long. Codes: [`uri_too_long`](/docs/api/errors#code-uri-too-long) (the request URL is longer than the configured maximum, 8,192 bytes by default).

As `application/problem+json`.

#### `429` error

Too many requests. Codes: [`rate_limited`](/docs/api/errors#code-rate-limited) (over the 120 requests a minute per credential or 300 a minute per organization; sandbox ceilings are lower; `X-RateLimit-*` say which), [`server_busy`](/docs/api/errors#code-server-busy) (the whole service is at capacity: every data slot and queue place is taken; `retry_after`; not metered, not a limit on the caller), [`sandbox_busy`](/docs/api/errors#code-sandbox-busy) (sandbox keys only: the sandbox's shared concurrent-query maximum is reached; `retry_after`), [`points_exhausted`](/docs/api/errors#code-points-exhausted) (the organization's monthly points allowance is spent; `Retry-After` counts to the end of the UTC month; checked on API-key and export requests, never on session JSON), [`quota_exhausted`](/docs/api/errors#code-quota-exhausted) (a monthly CSV or API request quota an operator has re-armed for this organization is spent; retired as a published limit). Every one carries `Retry-After`.

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

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `500` error

Server error. Codes: [`internal_error`](/docs/api/errors#code-internal-error) (for example a vintage file the server cannot read).

As `application/problem+json`.

Problem codes: [`unknown_parameter`](/docs/api/errors#code-unknown-parameter) (400), [`repeated_parameter`](/docs/api/errors#code-repeated-parameter) (400), [`bad_request`](/docs/api/errors#code-bad-request) (400), [`xlsx_only_parameter`](/docs/api/errors#code-xlsx-only-parameter) (400), [`bad_cluster_ref`](/docs/api/errors#code-bad-cluster-ref) (400), [`area_sum_required`](/docs/api/errors#code-area-sum-required) (400), [`area_overlap`](/docs/api/errors#code-area-overlap) (400), [`retired_msa`](/docs/api/errors#code-retired-msa) (400), [`cell_cap`](/docs/api/errors#code-cell-cap) (400), [`series_cap`](/docs/api/errors#code-series-cap) (400), [`unknown_column`](/docs/api/errors#code-unknown-column) (400), [`column_not_filterable`](/docs/api/errors#code-column-not-filterable) (400), [`bad_total`](/docs/api/errors#code-bad-total) (400), [`xlsx_row_ceiling`](/docs/api/errors#code-xlsx-row-ceiling) (400), [`xlsx_column_ceiling`](/docs/api/errors#code-xlsx-column-ceiling) (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), [`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), [`plan_required`](/docs/api/errors#code-plan-required) (403), [`geography_excluded`](/docs/api/errors#code-geography-excluded) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`vintage_superseded`](/docs/api/errors#code-vintage-superseded) (404), [`vintage_withdrawn`](/docs/api/errors#code-vintage-withdrawn) (404), [`region_retired`](/docs/api/errors#code-region-retired) (404), [`cluster_not_found`](/docs/api/errors#code-cluster-not-found) (404), [`cluster_deleted`](/docs/api/errors#code-cluster-deleted) (404), [`uri_too_long`](/docs/api/errors#code-uri-too-long) (414), [`rate_limited`](/docs/api/errors#code-rate-limited) (429), [`server_busy`](/docs/api/errors#code-server-busy) (429), [`sandbox_busy`](/docs/api/errors#code-sandbox-busy) (429), [`points_exhausted`](/docs/api/errors#code-points-exhausted) (429), [`quota_exhausted`](/docs/api/errors#code-quota-exhausted) (429), [`internal_error`](/docs/api/errors#code-internal-error) (500). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/industry/map`

**Industry choropleth values (annual or quarterly)**

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

Choropleth values for one NAICS platform code (or a summed code set) and one year or quarter: a row per state for every account, and a row per county the organization is entitled to that has data, in FIPS order. A /map row is never the nation and never a per-state catch-all county; catch-alls are dropped before the entitlement count, so they never read as excluded. Counties outside the active county set (or all counties while the organization is past due) are left out and counted in `meta.geography.excluded`; there is no 403 for them unless `strict=true` is sent, which turns any exclusion into 403 [`geography_excluded`](/docs/api/errors#code-geography-excluded) (as on /series). `areas` restricts the map to the union of its tokens' counties (then no state rows); nothing is summed across areas. Never paged; no cell guard applies. Each row carries disclosure and reliability for its value. As on /series and /lq, the code (each member of a summed set or cluster) must be within the plan's depth cap (403 [`plan_required`](/docs/api/errors#code-plan-required), entitlement `depth_cap_naics` or `depth_cap_soc`), and a `qtr` is a quarterly request: it needs the plan's quarterly access (403 [`plan_required`](/docs/api/errors#code-plan-required), entitlement `quarterly_access`). Callers: an API key (live or sandbox) or a session, any member role. A live API key needs an organization whose plan includes the API add-on (403 [`plan_required`](/docs/api/errors#code-plan-required), entitlement `api_access`) and that is not past due; a sandbox key needs neither and reads synthetic data. Gates, in order: authentication, the per-credential and per-organization rate ceilings, the global admission gate (429 [`server_busy`](/docs/api/errors#code-server-busy); a refused request is not metered), then the one entitlement gate (API access, vintage access, quarterly access, export access and the monthly points allowance). Metering: a JSON request on a SESSION is never metered; a JSON request on an API key, and every CSV or XLSX export on any credential, spends points from the organization's monthly points allowance (the plan's figure, `GET /v2/plans`; a sandbox key spends a separate sandbox allowance). During a trial, an export whose geography lies wholly inside the trial county is free of the points allowance and of the export entitlement. Side effects: one usage record per request (with the points emitted, the vintage read and the counties read), the member's last-active time, and, when a key is refused for its rate or the points allowance, a notice to the key's holder and the organization's admins at most once a day. A session request records the person's presence (it cancels an open ownership claim on an organization they own). Representations (spec sections 10 and 10a): JSON by default. CSV with `Accept: text/csv` or a `.csv` suffix on the path (/v2/industry/map.csv); the suffix wins over Accept. The as-shown XLSX workbook with `Accept: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` or an `.xlsx` suffix (/v2/industry/map.xlsx). CSV is long format, one line per row (`fips, value`, disclosure, reliability), headers exactly the JSON field names in JSON order, values exactly as JSON writes them (null as an empty field, booleans true/false), RFC 4180 quoting, CRLF, UTF-8 without a byte-order mark; a text cell a spreadsheet would read as a formula starts with `'`. A `#` comment preamble precedes the header: taigan-csv, endpoint, the sandbox warning (sandbox only), vintage, vintage\_label, source (the attribution to keep), generated\_at, granularity, provenance\_boundaries, geography, the region lines (summed areas only), count, disclosure and reliability (the key to the labels), encoding. The workbook has a `Table` sheet (full-precision values under display number formats, codes stored as text) and a `Source` sheet (attribution, vintage, the shape and grid state in words, and the key to disclosure and reliability). The as-shown parameters (`shape`, `by_period`, `columns`, `sort`, `filters`, `computed`, `total`) shape the workbook only and are 400 [`xlsx_only_parameter`](/docs/api/errors#code-xlsx-only-parameter) on JSON or CSV. Files are named `<domain>-<endpoint>-<vintage>-<UTC timestamp>.csv` (or `.xlsx`). Every problem, on any representation, is application/problem+json. The CSV of the trial county's own map (`areas` wholly inside the trial county) is trial-scoped.

### Parameters

**`naics` query · string required**

One NAICS platform code; or `cluster:<id>`, one of the organization's naics code clusters, summed per area; or with `sum=codes` a comma list of non-overlapping codes summed per area.

**`measure` query · "emp" | "estabs" | "earnings" | "avg\_pay"**

The one measure mapped (see DataIndustryPoint / DataOccupationPoint for meanings). (default "emp")

**`year` query · integer required**

The year, four digits, inside the vintage's period axis (`GET /v2/taxonomies/periods`). (1000 to 9999)

**`qtr` query · integer**

A quarter of `year`: the map then shows that quarter (`meta.granularity: quarterly`). Absent: the annual value. (1 to 4)

**`ownership` query · string**

Comma list of `fed`, `state`, `local`, `private` (or 1, 2, 3, 5): the ownerships summed into each value. Default all four.

**`areas` query · string**

Restrict the map to counties: any `areas` tokens (counties, a state code or `state:XX` for its counties, `msa:<code>`, `cluster:<id>`), several at once; the map shows the union of their counties, never summed, catch-alls never drawn. Tokens may not overlap (400 [`area_overlap`](/docs/api/errors#code-area-overlap) ). `00` alone means no restriction (the whole map); beside other tokens it overlaps them. `region:<id>` is 404 [`region_retired`](/docs/api/errors#code-region-retired).

**`sum` query · string**

`codes`: sum the comma list in `naics` into one value per area. `areas` is a 400 (a map is per area); `ownership` is accepted and has no effect (ownership is always summed here).

**`combine` query · "sum" deprecated**

Accepted for symmetry with /series; `combine=sum` is always a 400 here (a map is per area).

**`strict` query · "true"**

`true`: a county the organization's entitlement excludes is a 403 [`geography_excluded`](/docs/api/errors#code-geography-excluded) listing the excluded FIPS (cap 50), or the past-due refusal when the pause is the cause; without it they are left out and counted in `meta.geography.excluded`.

**`vintage` query · string**

The vintage to read (default: the current one). Pinning it makes the answer immutable and cacheable for a year. (pattern ^(\\d{8}|sandbox-\\d{4}\\.\\d+)$)

**`shape` query · "snapshot" | "by-period"**

XLSX only. `by-period`: one row per series with a column per period per point field (`emp_2019`, ..., `disclosure_2019`, `reliability_2019`). Anything else: the snapshot, one row per series showing its last period.

**`by_period` query · "1"**

XLSX only. `1` is the same as `shape=by-period`.

**`columns` query · string**

XLSX only. The table's columns in order, comma-separated: response fields, `title` (the code's name), the computed columns, and on /series `lq`. Default: everything. On industry sheets `disclosure` and `reliability` (and `lq_disclosure` beside `lq`) are always inserted after the values they qualify, whatever is named; those forced labels are not metered. An unknown name is 400 [`unknown_column`](/docs/api/errors#code-unknown-column).

**`sort` query · string**

XLSX only. `<column>:<asc|desc>` (default desc) on a snapshot value or a computed column; nulls last. (pattern ^\[^:\]+(:(asc|desc))?$)

**`filters` query · string**

XLSX only. Comma list of `<column>:<ge|gt|le|lt>:<number>`, or `disclosure:published` (keeps disclosure 0; in the by-period shape, every period exported must be published). A row with no value for a filtered column never passes; `rank` is not filterable.

**`computed` query · string**

XLSX only. Comma list of `emp_start` (first period's employment), `change_abs` (last minus first), `change_pct` ((last minus first) / first x 100; null when first is 0), `share` (last / sum of every returned row's last x 100, taken before filtering), `rank` (1, 2, 2, 4 over the filtered rows, highest first, on the sort column or employment). Only `emp_start` is metered.

**`total` query · string**

XLSX only, /series: `cluster:<id>`, a code cluster of this domain whose summed series is appended beneath the rows as the table's total row (`Total: <name>`), not filtered, sorted or ranked; metered as its values are. On /map and /lq it is 400 [`bad_total`](/docs/api/errors#code-bad-total). (pattern ^cluster:\[0-9a-fA-F-\]{36}$)

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

Session only: the organization the request acts in, when the person belongs to more than one (default: their own organization). An API key is bound to its organization; on a key this header must name that organization or be omitted. A malformed id, or an organization the caller is not an active member of, is 404 [`not_found`](/docs/api/errors#code-not-found).

### Responses

#### `200` success

The map rows.

As `application/json`, `text/csv`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`.

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

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

**`Content-Disposition` header**

CSV and XLSX only: `attachment; filename="<domain>-<endpoint>-<vintage>-<UTC timestamp>.csv"` (or `.xlsx`), e.g. `industry-series-20260913-20260927T184215Z.csv`.

**`Cache-Control` header**

CSV and XLSX only: `private, no-store`.

**`Access-Control-Expose-Headers` header**

CSV and XLSX only: `Content-Disposition`, so a cross-origin first-party app can read the file name.

**`data` array of DataMapRow always present**

State rows and entitled county rows, in FIPS order.

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

A 2-digit state code (a state row) or a 5-digit county code. Rows are in FIPS order, each state's row before its counties. Never the nation, and never a per-state catch-all county (O34: a choropleth has nowhere to draw it). With `areas=` only county rows are returned. (pattern ^\\d{2}(\\d{3})?$)

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

The selected `measure` for the area, summed across the ownership filter and any `sum=codes` code set: emp and estabs average the four quarters on an annual map (sum divided by 4); earnings sums them; avg\_pay is earnings over average employment, rounded once. Null for avg\_pay when employment is zero.

**`data[].disclosure` 0 | 1 | 2 | 3 always present**

The highest disclosure code among the cells summed into the value (the quarters of an annual map, the ownerships, a code set), by DataIndustryPoint.disclosure's meaning.

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

Employment-weighted reliability of the value, by DataIndustryPoint.reliability's rules; null when the value is carried forward or projected or there is no employment. (0 to 1)

**`meta` DataMeta always present**

The `meta` of every v2 envelope (spec section 3). It is written after `data` on the wire. Clients must tolerate unknown fields.

**`meta.vintage` string always present**

The vintage the answer was read from: the internal build id (`20260913`; a sandbox key's are `sandbox-2026.1`). Cite it and pass it back as `vintage=` to reproduce the answer exactly: a released vintage never changes. Its public name, such as `DTG 2026.3`, is the `label` on `GET /v2/vintages`.

**`meta.sandbox` true**

Present, and true, only when the answer was read from a sandbox vintage: every value is synthetic and must never be published. Absent otherwise.

**`meta.generated_at` string (date-time) always present**

When this response was produced, UTC, to the second (`2026-09-27T18:42:15Z`).

**`meta.granularity` "annual" | "quarterly"**

Data routes only: the period granularity of the points (on /map, `quarterly` when `qtr` was given). Absent on taxonomy responses.

**`meta.provenance_boundaries` DataProvenanceBoundaries always present**

Where the vintage's published record ends and estimation begins, recorded per vintage at build time and returned on every envelope. Compare a point's period against these ranges to know what kind of value it is. For OCCUPATION data this is the only provenance there is: occupation values carry no per-value label, so a period inside `actuals` is published-basis, inside `ces` carried forward, inside `projections` projected. Industry values also carry their own `disclosure` code, which is authoritative for the value. The ranges move with each release; read them, never hard-code them.

**`meta.provenance_boundaries.actuals` object always present**

Quarters published by BLS in QCEW (the Quarterly Census of Employment and Wages). Industry values here have disclosure 0 (published) or 1 (estimated by DTG where QCEW withheld the value).

**`meta.provenance_boundaries.actuals.from` string always present**

First quarter of the range, lower case, e.g. `2004q1`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.actuals.to` string always present**

Last quarter of the range, lower case, e.g. `2025q4`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.ces` object always present**

Quarters after the latest QCEW release, carried forward from the CES and SAE monthly employment surveys (their rate of change applied to DTG's base, not their levels). Industry values here have disclosure 2.

**`meta.provenance_boundaries.ces.from` string always present**

First quarter of the range, lower case, e.g. `2004q1`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.ces.to` string always present**

Last quarter of the range, lower case, e.g. `2025q4`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.projections` object always present**

Projected quarters, out to the end of the period axis. Industry values here have disclosure 3.

**`meta.provenance_boundaries.projections.from` string always present**

First quarter of the range, lower case, e.g. `2004q1`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.projections.to` string always present**

Last quarter of the range, lower case, e.g. `2025q4`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.geography` object**

Data routes only. Absent on taxonomy responses.

**`meta.geography.requested` integer always present**

/series and /lq: the number of distinct areas the request resolved to before entitlement (a `state:XX`, `msa:` or `cluster:` token counts its counties; a plain state code counts 1; no `areas` counts the nation as 1). /map: the counties of the `areas` restriction, or without one the counties with a row for this code and period (catch-alls never counted). (at least 0)

**`meta.geography.returned` integer always present**

Areas (on /map, counties) that passed entitlement and are in the answer. On a summed request these are the areas inside the one summed series. (at least 0)

**`meta.geography.excluded` integer always present**

Requested counties the organization's entitlement removed (outside the active county set, or every county while the subscription is past due). On /map a requested-versus-returned gap beyond `excluded` means counties with no data for the code and period, not an exclusion. (at least 0)

**`meta.geography.region` DataRegion**

Present only on a request whose areas were summed into one series (a lone `cluster:<id>` or `msa:<code>`, or `sum=areas` / `combine=sum`): what the summed area is made of. The CSV preamble prints it as `# region:` lines and the workbook's Source sheet as `Region` rows.

**`meta.geography.region.label` string always present**

The summed series' `area` label: the cluster's name or the MSA's title when one was asked for alone, otherwise `custom region`.

**`meta.geography.region.areas` array of DataRegionArea always present**

Each area as requested, in request order.

**`meta.geography.region.areas[].token` string always present**

The token exactly as requested: `22073`, `22`, `00`, `state:22`, `msa:35380`, `cluster:<id>`.

**`meta.geography.region.areas[].kind` "nation" | "state" | "county" | "stateAll" | "msa" | "cluster" always present**

What the token is: the nation (`00`), a state's own row (`22`), a county, `stateAll` (a `state:XX` token: every county of the state, its catch-all included), an MSA, or one of the organization's area clusters.

**`meta.geography.region.areas[].code` string always present**

The area's code: a FIPS code for a county or state (the 2-digit state code for `stateAll`), or the token itself, lower-cased, for `msa:<code>` and `cluster:<id>`.

**`meta.geography.region.areas[].name` string or null always present**

The area's name: the vintage taxonomy's title for a county or state, the MSA's title, or the cluster's name. Null where the taxonomy has none.

**`meta.geography.region.areas[].resolved` array of string always present**

The area codes this token contributed to the sum after entitlement. A county the plan does not cover appears in no list; `geography.excluded` counts it.

**`meta.geography.region.resolved` array of string always present**

Every area code summed, deduplicated, in request order, after entitlement.

**`meta.pagination` DataPagination always present**

**`meta.pagination.cursor` string or null always present**

The `cursor` this page was requested with; null for the first page or an unpaged request.

**`meta.pagination.next_cursor` string or null always present**

Pass as `cursor` to get the next page. Null on the last page and on every unpaged response, so null always means there is nothing more.

**`meta.pagination.count` integer always present**

Rows in `data` on this response: series on /series and /lq, rows on /map, items on a taxonomy. (at least 0)

#### `400` error

Bad request. Codes: [`unknown_parameter`](/docs/api/errors#code-unknown-parameter) (a query parameter this route does not take; `parameters[]` names them), [`repeated_parameter`](/docs/api/errors#code-repeated-parameter) (a parameter given more than once; `parameter`), [`bad_request`](/docs/api/errors#code-bad-request) (more than 50 query parameters); [`xlsx_only_parameter`](/docs/api/errors#code-xlsx-only-parameter) (an as-shown parameter on a JSON or CSV request; `parameters[]`); [`bad_request`](/docs/api/errors#code-bad-request) for a malformed `vintage` (a live key or a session takes `YYYYMMDD`, a sandbox key `sandbox-YYYY.N`; an id shaped for the other scope is refused the same way, so neither scope can probe the other); [`bad_request`](/docs/api/errors#code-bad-request) also for: a missing `naics`, several codes without `sum=codes`, a malformed or unknown code, an overlapping code set with `sum=codes`, or a malformed cluster reference; `sum=areas` or `combine=sum` (a map is per area); a bad `sum` token or `sum` with `combine`; a bad `measure`; a missing or malformed `year`, or one outside the vintage's period axis; a `qtr` other than 1 to 4; a bad `ownership` token; an unknown or malformed area token in `areas`; [`bad_cluster_ref`](/docs/api/errors#code-bad-cluster-ref) (a `cluster:` id that is not a 36-character UUID); [`area_overlap`](/docs/api/errors#code-area-overlap) (two `areas` tokens that share counties, D67; `pair`); [`retired_msa`](/docs/api/errors#code-retired-msa) (an MSA with no counties in the current map; `msa`, `retired_after`, `successors`); XLSX only: [`unknown_column`](/docs/api/errors#code-unknown-column) (a `columns`, `sort` or `filters` name the table does not have; `column`, `parameter`), [`column_not_filterable`](/docs/api/errors#code-column-not-filterable) (a filter on `rank`), [`bad_total`](/docs/api/errors#code-bad-total) (a `total` that is not `cluster:<uuid>`), [`xlsx_row_ceiling`](/docs/api/errors#code-xlsx-row-ceiling) (more rows than a sheet holds; `rows`, `max_rows`, `alternatives`), [`xlsx_column_ceiling`](/docs/api/errors#code-xlsx-column-ceiling) (more columns than a sheet holds; `columns`, `max_columns`), [`bad_request`](/docs/api/errors#code-bad-request) for a malformed `sort` or `filters` entry.

As `application/problem+json`.

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no credential, or an API key or session that is unknown or expired); [`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 membership), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG; `reason` where recorded), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended; `organization` {id, name} and, where recorded, `reason`), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed; `organization`), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (a key of the mode this process does not serve, for example a sandbox key on a live-only process; `expected`, and `base_url` naming the address that serves it); [`plan_required`](/docs/api/errors#code-plan-required) with `entitlement` naming what the plan lacks: `api_access` (a live API key on an organization without the API add-on), `past_due` (the organization's subscription is past due: every request on a live API key, every CSV or XLSX export, and county detail are paused; nation and state data on a session are not), `vintage_access` (a non-current `vintage=` on a plan without prior vintages), `quarterly_access` (a `qtr` on a plan without quarterly data), `depth_cap_naics` (a code deeper than the plan's depth cap), `csv_access` (a CSV or XLSX export on a plan without export, or a trial export outside the trial county); [`geography_excluded`](/docs/api/errors#code-geography-excluded) (`strict=true` and the entitlement excluded counties; `excluded_fips`, cap 50).

As `application/problem+json`.

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `404` error

Not found. Codes: [`not_found`](/docs/api/errors#code-not-found) (no such vintage in the caller's scope, or an `X-DTG-Org` header naming an organization the caller does not belong to), [`vintage_superseded`](/docs/api/errors#code-vintage-superseded) (the pinned vintage was withdrawn and replaced by a correction; `superseded_by` names it; re-run against it), [`vintage_withdrawn`](/docs/api/errors#code-vintage-withdrawn) (the pinned vintage was withdrawn and no correction exists yet; `current` names the vintage to use), [`region_retired`](/docs/api/errors#code-region-retired) (`areas=region:<id>` is retired: use `cluster:<id>`; `replaced_by`), [`cluster_not_found`](/docs/api/errors#code-cluster-not-found) (a `cluster:<id>` that is not one of the organization's clusters of that kind), [`cluster_deleted`](/docs/api/errors#code-cluster-deleted) (the cluster was deleted; `cluster`, `restorable`, `restorable_until`).

As `application/problem+json`.

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `414` error

URI too long. Codes: [`uri_too_long`](/docs/api/errors#code-uri-too-long) (the request URL is longer than the configured maximum, 8,192 bytes by default).

As `application/problem+json`.

#### `429` error

Too many requests. Codes: [`rate_limited`](/docs/api/errors#code-rate-limited) (over the 120 requests a minute per credential or 300 a minute per organization; sandbox ceilings are lower; `X-RateLimit-*` say which), [`server_busy`](/docs/api/errors#code-server-busy) (the whole service is at capacity: every data slot and queue place is taken; `retry_after`; not metered, not a limit on the caller), [`sandbox_busy`](/docs/api/errors#code-sandbox-busy) (sandbox keys only: the sandbox's shared concurrent-query maximum is reached; `retry_after`), [`points_exhausted`](/docs/api/errors#code-points-exhausted) (the organization's monthly points allowance is spent; `Retry-After` counts to the end of the UTC month; checked on API-key and export requests, never on session JSON), [`quota_exhausted`](/docs/api/errors#code-quota-exhausted) (a monthly CSV or API request quota an operator has re-armed for this organization is spent; retired as a published limit). Every one carries `Retry-After`.

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

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `500` error

Server error. Codes: [`internal_error`](/docs/api/errors#code-internal-error) (for example a vintage file the server cannot read).

As `application/problem+json`.

Problem codes: [`unknown_parameter`](/docs/api/errors#code-unknown-parameter) (400), [`repeated_parameter`](/docs/api/errors#code-repeated-parameter) (400), [`bad_request`](/docs/api/errors#code-bad-request) (400), [`xlsx_only_parameter`](/docs/api/errors#code-xlsx-only-parameter) (400), [`bad_cluster_ref`](/docs/api/errors#code-bad-cluster-ref) (400), [`area_overlap`](/docs/api/errors#code-area-overlap) (400), [`retired_msa`](/docs/api/errors#code-retired-msa) (400), [`unknown_column`](/docs/api/errors#code-unknown-column) (400), [`column_not_filterable`](/docs/api/errors#code-column-not-filterable) (400), [`bad_total`](/docs/api/errors#code-bad-total) (400), [`xlsx_row_ceiling`](/docs/api/errors#code-xlsx-row-ceiling) (400), [`xlsx_column_ceiling`](/docs/api/errors#code-xlsx-column-ceiling) (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), [`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), [`plan_required`](/docs/api/errors#code-plan-required) (403), [`geography_excluded`](/docs/api/errors#code-geography-excluded) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`vintage_superseded`](/docs/api/errors#code-vintage-superseded) (404), [`vintage_withdrawn`](/docs/api/errors#code-vintage-withdrawn) (404), [`region_retired`](/docs/api/errors#code-region-retired) (404), [`cluster_not_found`](/docs/api/errors#code-cluster-not-found) (404), [`cluster_deleted`](/docs/api/errors#code-cluster-deleted) (404), [`uri_too_long`](/docs/api/errors#code-uri-too-long) (414), [`rate_limited`](/docs/api/errors#code-rate-limited) (429), [`server_busy`](/docs/api/errors#code-server-busy) (429), [`sandbox_busy`](/docs/api/errors#code-sandbox-busy) (429), [`points_exhausted`](/docs/api/errors#code-points-exhausted) (429), [`quota_exhausted`](/docs/api/errors#code-quota-exhausted) (429), [`internal_error`](/docs/api/errors#code-internal-error) (500). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/industry/lq`

**Industry location quotients vs the nation**

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

Industry location quotients versus the nation, computed in one query: for each code x area and period, (area code employment / area total) / (nation code employment / nation total), the total being the level-0 code. Employment only; there is no measure choice. Ownership is a filter applied to all four terms and is always summed (an explicit `ownership` list narrows it; `sum=ownership` is accepted and changes nothing). Codes, areas, summing, entitlement, the cell guard and paging work exactly as on /series; a summed area is the deduplicated union of its counties versus the nation. Each point carries the area cell's own disclosure and reliability and the quotient's `lq_disclosure`. Callers: an API key (live or sandbox) or a session, any member role. A live API key needs an organization whose plan includes the API add-on (403 [`plan_required`](/docs/api/errors#code-plan-required), entitlement `api_access`) and that is not past due; a sandbox key needs neither and reads synthetic data. Gates, in order: authentication, the per-credential and per-organization rate ceilings, the global admission gate (429 [`server_busy`](/docs/api/errors#code-server-busy); a refused request is not metered), then the one entitlement gate (API access, vintage access, quarterly access, export access and the monthly points allowance). Metering: a JSON request on a SESSION is never metered; a JSON request on an API key, and every CSV or XLSX export on any credential, spends points from the organization's monthly points allowance (the plan's figure, `GET /v2/plans`; a sandbox key spends a separate sandbox allowance). During a trial, an export whose geography lies wholly inside the trial county is free of the points allowance and of the export entitlement. Side effects: one usage record per request (with the points emitted, the vintage read and the counties read), the member's last-active time, and, when a key is refused for its rate or the points allowance, a notice to the key's holder and the organization's admins at most once a day. A session request records the person's presence (it cancels an open ownership claim on an organization they own). Representations (spec sections 10 and 10a): JSON by default. CSV with `Accept: text/csv` or a `.csv` suffix on the path (/v2/industry/lq.csv); the suffix wins over Accept. The as-shown XLSX workbook with `Accept: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` or an `.xlsx` suffix (/v2/industry/lq.xlsx). CSV is long format, one line per series-point (`naics, area`, then the point's columns), headers exactly the JSON field names in JSON order, values exactly as JSON writes them (null as an empty field, booleans true/false), RFC 4180 quoting, CRLF, UTF-8 without a byte-order mark; a text cell a spreadsheet would read as a formula starts with `'`. A `#` comment preamble precedes the header: taigan-csv, endpoint, the sandbox warning (sandbox only), vintage, vintage\_label, source (the attribution to keep), generated\_at, granularity, provenance\_boundaries, geography, the region lines (summed areas only), count, disclosure and reliability (the key to the labels), lq\_disclosure, encoding. The workbook has a `Table` sheet (full-precision values under display number formats, codes stored as text) and a `Source` sheet (attribution, vintage, the shape and grid state in words, and the key to disclosure and reliability). The as-shown parameters (`shape`, `by_period`, `columns`, `sort`, `filters`, `computed`, `total`) shape the workbook only and are 400 [`xlsx_only_parameter`](/docs/api/errors#code-xlsx-only-parameter) on JSON or CSV. Files are named `<domain>-<endpoint>-<vintage>-<UTC timestamp>.csv` (or `.xlsx`). Every problem, on any representation, is application/problem+json.

### Parameters

**`naics` query · string**

Comma list of NAICS platform codes (`481,482`), one series per code in the taxonomy's nested order; or `cluster:<id>`, one of the organization's naics code clusters, alone, summed into ONE series named with the cluster's name. With `sum=codes` any non-overlapping list sums into one series labelled `sum(<n>)`; an ancestor and its descendant together are refused. Default (neither `naics` nor `naics_level`): the level-0 total (`10`). Not with `naics_level`.

**`naics_level` query · string**

Every code at one hierarchy level (0 Total, 1 Domain, 2 Super-sector, 3 Sector, 4 to 7 the 3- to 6-digit codes); or `all`, every code at every level in nested order (a browse: parents and children together, never summed); or `leaf`, the codes that are the parent of no code. `all` and `leaf` need the plan's full depth. Not with `naics`. (pattern ^(\\d+|all|leaf)$)

**`areas` query · string**

Comma list of area codes ("00" nation, "22" state, "22073" county), state:XX (expands to the state's counties, INCLUDING its "Unknown Or Undefined" catch-all — O34), cluster:<id> (the caller's own fips-kind cluster), or msa:<code> (the MSA's member counties at the latest map, never a catch-all). T19: a cluster or an MSA ALONE is one summed series named by the cluster's name or the MSA's title; BESIDE other tokens it contributes its counties to the one summed area and requires sum=areas (else 400 area\_sum\_required). A summed selection may not overlap: 400 area\_overlap {pair}. A retired MSA is 400 retired\_msa naming its successors. areas=region:<id> is RETIRED (404 region\_retired, D44). The county universe is 3,194 areas: 3,144 counties and county-equivalents plus the 50 per-state catch-alls holding employment not attributable to a specific county — ordinary areas in the data, included in state and national totals, purchasable, but with no geographic extent (is\_catchall on /v2/account/counties governs presentation only: never in an msa: expansion, never a /map row, never a public data page).

**`region` query · string deprecated**

RETIRED: any value is 404 [`region_retired`](/docs/api/errors#code-region-retired). Use `areas=cluster:<id>`.

**`combine` query · "sum" deprecated**

Deprecated alias of `sum=areas`. Only `sum` is defined; not with `sum`.

**`sum` query · string**

Comma list of `codes` and `areas` to sum (as on /series). `ownership` is accepted and has no effect: LQ ownership is always summed.

**`ownership` query · string**

Comma list of `fed`, `state`, `local`, `private` (or 1, 2, 3, 5): the ownerships every term is summed over. Default all four. Never splits the answer by ownership.

**`granularity` query · "annual" | "quarterly"**

`annual` (default) or `quarterly` (needs quarterly access). (default "annual")

**`from` query · string**

Start period, inclusive — "2004" (annual) or "2004q1" (quarterly); must match granularity=. (pattern ^\\d{4}(q\[1-4\])?$)

**`to` query · string**

End period, inclusive — same format and granularity as from=. (pattern ^\\d{4}(q\[1-4\])?$)

**`strict` query · "true"**

`true`: any excluded county makes the request 403 [`geography_excluded`](/docs/api/errors#code-geography-excluded).

**`cursor` query · string**

Paging: as on /series, a slice of the (code, area) list in response order; the cell guard measures the page. A page's values equal the same rows unpaged.

**`limit` query · integer**

Paging: series per page, 1 to 5,000 (1,000 when only `cursor` is given). Absent with no `cursor`: unpaged. (default 1000; 1 to 5000)

**`vintage` query · string**

The vintage to read (default: the current one). Pinning it makes the answer immutable and cacheable for a year. (pattern ^(\\d{8}|sandbox-\\d{4}\\.\\d+)$)

**`shape` query · "snapshot" | "by-period"**

XLSX only. `by-period`: one row per series with a column per period per point field (`emp_2019`, ..., `disclosure_2019`, `reliability_2019`). Anything else: the snapshot, one row per series showing its last period.

**`by_period` query · "1"**

XLSX only. `1` is the same as `shape=by-period`.

**`columns` query · string**

XLSX only. The table's columns in order, comma-separated: response fields, `title` (the code's name), the computed columns, and on /series `lq`. Default: everything. On industry sheets `disclosure` and `reliability` (and `lq_disclosure` beside `lq`) are always inserted after the values they qualify, whatever is named; those forced labels are not metered. An unknown name is 400 [`unknown_column`](/docs/api/errors#code-unknown-column).

**`sort` query · string**

XLSX only. `<column>:<asc|desc>` (default desc) on a snapshot value or a computed column; nulls last. (pattern ^\[^:\]+(:(asc|desc))?$)

**`filters` query · string**

XLSX only. Comma list of `<column>:<ge|gt|le|lt>:<number>`, or `disclosure:published` (keeps disclosure 0; in the by-period shape, every period exported must be published). A row with no value for a filtered column never passes; `rank` is not filterable.

**`computed` query · string**

XLSX only. Comma list of `emp_start` (first period's employment), `change_abs` (last minus first), `change_pct` ((last minus first) / first x 100; null when first is 0), `share` (last / sum of every returned row's last x 100, taken before filtering), `rank` (1, 2, 2, 4 over the filtered rows, highest first, on the sort column or employment). Only `emp_start` is metered.

**`total` query · string**

XLSX only, /series: `cluster:<id>`, a code cluster of this domain whose summed series is appended beneath the rows as the table's total row (`Total: <name>`), not filtered, sorted or ranked; metered as its values are. On /lq a valid one is 400. (pattern ^cluster:\[0-9a-fA-F-\]{36}$)

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

Session only: the organization the request acts in, when the person belongs to more than one (default: their own organization). An API key is bound to its organization; on a key this header must name that organization or be omitted. A malformed id, or an organization the caller is not an active member of, is 404 [`not_found`](/docs/api/errors#code-not-found).

### Responses

#### `200` success

The LQ series.

As `application/json`, `text/csv`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`.

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

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

**`Content-Disposition` header**

CSV and XLSX only: `attachment; filename="<domain>-<endpoint>-<vintage>-<UTC timestamp>.csv"` (or `.xlsx`), e.g. `industry-series-20260913-20260927T184215Z.csv`.

**`Cache-Control` header**

CSV and XLSX only: `private, no-store`.

**`Access-Control-Expose-Headers` header**

CSV and XLSX only: `Content-Disposition`, so a cross-origin first-party app can read the file name.

**`data` array of DataLqSeriesIndustry always present**

The LQ series (this page of them, when paged), by code then area.

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

The NAICS platform code as a string (`481111`), names on `GET /v2/taxonomies/naics`. On a summed code axis it is the label instead: the cluster's name for `naics=cluster:<id>`, or `sum(<n>)` for `sum=codes` over n codes.

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

The area: a FIPS code as a zero-padded string (`00` the nation, `22` a state's own row, `22073` a county; a code ending `999` is that state's catch-all area, `Unknown Or Undefined`, holding employment QCEW cannot place in a county: a real, purchasable area included in state and national totals, with no geographic extent). On a summed-area series it is the label instead: the cluster's name or the MSA's title when one was asked for alone, otherwise `custom region`; `meta.geography.region` lists what was summed. The response carries codes, not area names: names are on `GET /v2/taxonomies/areas`.

**`data[].points` array of DataLqPoint always present**

One point per period of the requested range, in period order, every period present (dense).

**`data[].points[].period` string always present**

The period: `2021` or `2021q3`, per `granularity`. (pattern ^\\d{4}(q\[1-4\])?$)

**`data[].points[].lq` number or null always present**

The location quotient, to 4 decimals (shown to 2). Above 1: the area is more concentrated in this code than the nation; below 1, less. Null when any denominator is zero (the area's total, the nation's code or the nation's total employment).

**`data[].points[].emp` number always present**

The area's own employment in the code for the period (the LQ's numerator), on /series' basis (annual = sum of quarters divided by 4).

**`data[].points[].disclosure` 0 | 1 | 2 | 3 always present**

The disclosure code of the area's own cell, the `emp` this point carries, exactly as /series labels that cell (see DataIndustryPoint.disclosure).

**`data[].points[].reliability` number or null always present**

The reliability of the area's own cell, by DataIndustryPoint.reliability's rules (null when carried forward, projected, or no employment). No reliability is given for the ratio itself. (0 to 1)

**`data[].points[].lq_disclosure` 0 | 1 | 2 | 3 always present**

The HIGHEST disclosure code among the four values the quotient is computed from (the area and the nation, in the code and in the total): an LQ is as estimated as its most estimated input. Use this, not `disclosure`, to qualify `lq`.

**`meta` DataMeta always present**

The `meta` of every v2 envelope (spec section 3). It is written after `data` on the wire. Clients must tolerate unknown fields.

**`meta.vintage` string always present**

The vintage the answer was read from: the internal build id (`20260913`; a sandbox key's are `sandbox-2026.1`). Cite it and pass it back as `vintage=` to reproduce the answer exactly: a released vintage never changes. Its public name, such as `DTG 2026.3`, is the `label` on `GET /v2/vintages`.

**`meta.sandbox` true**

Present, and true, only when the answer was read from a sandbox vintage: every value is synthetic and must never be published. Absent otherwise.

**`meta.generated_at` string (date-time) always present**

When this response was produced, UTC, to the second (`2026-09-27T18:42:15Z`).

**`meta.granularity` "annual" | "quarterly"**

Data routes only: the period granularity of the points (on /map, `quarterly` when `qtr` was given). Absent on taxonomy responses.

**`meta.provenance_boundaries` DataProvenanceBoundaries always present**

Where the vintage's published record ends and estimation begins, recorded per vintage at build time and returned on every envelope. Compare a point's period against these ranges to know what kind of value it is. For OCCUPATION data this is the only provenance there is: occupation values carry no per-value label, so a period inside `actuals` is published-basis, inside `ces` carried forward, inside `projections` projected. Industry values also carry their own `disclosure` code, which is authoritative for the value. The ranges move with each release; read them, never hard-code them.

**`meta.provenance_boundaries.actuals` object always present**

Quarters published by BLS in QCEW (the Quarterly Census of Employment and Wages). Industry values here have disclosure 0 (published) or 1 (estimated by DTG where QCEW withheld the value).

**`meta.provenance_boundaries.actuals.from` string always present**

First quarter of the range, lower case, e.g. `2004q1`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.actuals.to` string always present**

Last quarter of the range, lower case, e.g. `2025q4`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.ces` object always present**

Quarters after the latest QCEW release, carried forward from the CES and SAE monthly employment surveys (their rate of change applied to DTG's base, not their levels). Industry values here have disclosure 2.

**`meta.provenance_boundaries.ces.from` string always present**

First quarter of the range, lower case, e.g. `2004q1`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.ces.to` string always present**

Last quarter of the range, lower case, e.g. `2025q4`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.projections` object always present**

Projected quarters, out to the end of the period axis. Industry values here have disclosure 3.

**`meta.provenance_boundaries.projections.from` string always present**

First quarter of the range, lower case, e.g. `2004q1`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.projections.to` string always present**

Last quarter of the range, lower case, e.g. `2025q4`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.geography` object**

Data routes only. Absent on taxonomy responses.

**`meta.geography.requested` integer always present**

/series and /lq: the number of distinct areas the request resolved to before entitlement (a `state:XX`, `msa:` or `cluster:` token counts its counties; a plain state code counts 1; no `areas` counts the nation as 1). /map: the counties of the `areas` restriction, or without one the counties with a row for this code and period (catch-alls never counted). (at least 0)

**`meta.geography.returned` integer always present**

Areas (on /map, counties) that passed entitlement and are in the answer. On a summed request these are the areas inside the one summed series. (at least 0)

**`meta.geography.excluded` integer always present**

Requested counties the organization's entitlement removed (outside the active county set, or every county while the subscription is past due). On /map a requested-versus-returned gap beyond `excluded` means counties with no data for the code and period, not an exclusion. (at least 0)

**`meta.geography.region` DataRegion**

Present only on a request whose areas were summed into one series (a lone `cluster:<id>` or `msa:<code>`, or `sum=areas` / `combine=sum`): what the summed area is made of. The CSV preamble prints it as `# region:` lines and the workbook's Source sheet as `Region` rows.

**`meta.geography.region.label` string always present**

The summed series' `area` label: the cluster's name or the MSA's title when one was asked for alone, otherwise `custom region`.

**`meta.geography.region.areas` array of DataRegionArea always present**

Each area as requested, in request order.

**`meta.geography.region.areas[].token` string always present**

The token exactly as requested: `22073`, `22`, `00`, `state:22`, `msa:35380`, `cluster:<id>`.

**`meta.geography.region.areas[].kind` "nation" | "state" | "county" | "stateAll" | "msa" | "cluster" always present**

What the token is: the nation (`00`), a state's own row (`22`), a county, `stateAll` (a `state:XX` token: every county of the state, its catch-all included), an MSA, or one of the organization's area clusters.

**`meta.geography.region.areas[].code` string always present**

The area's code: a FIPS code for a county or state (the 2-digit state code for `stateAll`), or the token itself, lower-cased, for `msa:<code>` and `cluster:<id>`.

**`meta.geography.region.areas[].name` string or null always present**

The area's name: the vintage taxonomy's title for a county or state, the MSA's title, or the cluster's name. Null where the taxonomy has none.

**`meta.geography.region.areas[].resolved` array of string always present**

The area codes this token contributed to the sum after entitlement. A county the plan does not cover appears in no list; `geography.excluded` counts it.

**`meta.geography.region.resolved` array of string always present**

Every area code summed, deduplicated, in request order, after entitlement.

**`meta.pagination` DataPagination always present**

**`meta.pagination.cursor` string or null always present**

The `cursor` this page was requested with; null for the first page or an unpaged request.

**`meta.pagination.next_cursor` string or null always present**

Pass as `cursor` to get the next page. Null on the last page and on every unpaged response, so null always means there is nothing more.

**`meta.pagination.count` integer always present**

Rows in `data` on this response: series on /series and /lq, rows on /map, items on a taxonomy. (at least 0)

#### `400` error

Bad request. Codes: [`unknown_parameter`](/docs/api/errors#code-unknown-parameter) (a query parameter this route does not take; `parameters[]` names them), [`repeated_parameter`](/docs/api/errors#code-repeated-parameter) (a parameter given more than once; `parameter`), [`bad_request`](/docs/api/errors#code-bad-request) (more than 50 query parameters); [`xlsx_only_parameter`](/docs/api/errors#code-xlsx-only-parameter) (an as-shown parameter on a JSON or CSV request; `parameters[]`); [`bad_request`](/docs/api/errors#code-bad-request) for a malformed `vintage` (a live key or a session takes `YYYYMMDD`, a sandbox key `sandbox-YYYY.N`; an id shaped for the other scope is refused the same way, so neither scope can probe the other); [`bad_request`](/docs/api/errors#code-bad-request) also for: a bad `granularity`; a `from` or `to` that is not `YYYY` or `YYYYqN`, a quarterly one without `granularity=quarterly`, one outside the vintage's period axis, or `from` after `to`; a bad `cursor`, a `cursor` past the end, or a `limit` outside 1 to 5,000; a bad `sum` token, or `sum` and `combine` together, or a `combine` other than `sum`; a malformed or unknown code, both `naics=` and `naics_level=`, a `naics_level` that is not an integer, `all` or `leaf`, a level with no codes, `sum=codes` with `naics_level=all` or `leaf`, an overlapping code set with `sum=codes` (the pair is named: "481 is contained in 48"), `cluster:<id>` not alone in `naics=`, or a malformed cluster reference; a bad `ownership` token; an unknown area, a malformed area code, a `state:XX` with no counties, a malformed `msa:` token, an unknown MSA, or a malformed `cluster:` token in `areas`; [`bad_cluster_ref`](/docs/api/errors#code-bad-cluster-ref) (a `cluster:` id that is not a 36-character UUID); [`area_sum_required`](/docs/api/errors#code-area-sum-required) (an `msa:` or `cluster:` token beside other areas without `sum=areas`; `token`); [`area_overlap`](/docs/api/errors#code-area-overlap) (a summed area list whose members overlap, D67; `pair`); [`retired_msa`](/docs/api/errors#code-retired-msa) (an MSA with no counties in the current map; `msa`, `retired_after`, `successors`); [`cell_cap`](/docs/api/errors#code-cell-cap) (the request, or the page, is more than 400,000 cells, a cell being one series at one period; `cells`, `cap`: narrow the areas, codes or period range, or page with `cursor` and `limit`); [`series_cap`](/docs/api/errors#code-series-cap) (more than the series cap per request, 10,000 by default; `series`, `cap`); XLSX only: [`unknown_column`](/docs/api/errors#code-unknown-column) (a `columns`, `sort` or `filters` name the table does not have; `column`, `parameter`), [`column_not_filterable`](/docs/api/errors#code-column-not-filterable) (a filter on `rank`), [`bad_total`](/docs/api/errors#code-bad-total) (a `total` that is not `cluster:<uuid>`), [`xlsx_row_ceiling`](/docs/api/errors#code-xlsx-row-ceiling) (more rows than a sheet holds; `rows`, `max_rows`, `alternatives`), [`xlsx_column_ceiling`](/docs/api/errors#code-xlsx-column-ceiling) (more columns than a sheet holds; `columns`, `max_columns`), [`bad_request`](/docs/api/errors#code-bad-request) for a malformed `sort` or `filters` entry or a `total` on /lq.

As `application/problem+json`.

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no credential, or an API key or session that is unknown or expired); [`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 membership), [`account_suspended`](/docs/api/errors#code-account-suspended) (O56: the caller's own account is suspended by DTG; `reason` where recorded), [`org_suspended`](/docs/api/errors#code-org-suspended) (the organization is suspended; `organization` {id, name} and, where recorded, `reason`), [`org_closed`](/docs/api/errors#code-org-closed) (the organization is closed; `organization`), [`wrong_instance`](/docs/api/errors#code-wrong-instance) (a key of the mode this process does not serve, for example a sandbox key on a live-only process; `expected`, and `base_url` naming the address that serves it); [`plan_required`](/docs/api/errors#code-plan-required) with `entitlement` naming what the plan lacks: `api_access` (a live API key on an organization without the API add-on), `past_due` (the organization's subscription is past due: every request on a live API key, every CSV or XLSX export, and county detail are paused; nation and state data on a session are not), `vintage_access` (a non-current `vintage=` on a plan without prior vintages), `quarterly_access` (`granularity=quarterly` on a plan without quarterly data), `csv_access` (a CSV or XLSX export on a plan without export, or a trial export outside the trial county), `depth_cap_naics` (a code deeper than the plan's naics depth cap; `naics_level=all` and `leaf` need the full depth); [`geography_excluded`](/docs/api/errors#code-geography-excluded) (every requested county is outside the organization's entitlement, or any is with `strict=true`; `excluded_fips` lists up to 50).

As `application/problem+json`.

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `404` error

Not found. Codes: [`not_found`](/docs/api/errors#code-not-found) (no such vintage in the caller's scope, or an `X-DTG-Org` header naming an organization the caller does not belong to), [`vintage_superseded`](/docs/api/errors#code-vintage-superseded) (the pinned vintage was withdrawn and replaced by a correction; `superseded_by` names it; re-run against it), [`vintage_withdrawn`](/docs/api/errors#code-vintage-withdrawn) (the pinned vintage was withdrawn and no correction exists yet; `current` names the vintage to use), [`region_retired`](/docs/api/errors#code-region-retired) (`areas=region:<id>` or the `region` parameter is retired: use `cluster:<id>`; `replaced_by`), [`cluster_not_found`](/docs/api/errors#code-cluster-not-found) (a `cluster:<id>` that is not one of the organization's clusters of that kind), [`cluster_deleted`](/docs/api/errors#code-cluster-deleted) (the cluster was deleted; `cluster`, `restorable`, `restorable_until`).

As `application/problem+json`.

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `414` error

URI too long. Codes: [`uri_too_long`](/docs/api/errors#code-uri-too-long) (the request URL is longer than the configured maximum, 8,192 bytes by default).

As `application/problem+json`.

#### `429` error

Too many requests. Codes: [`rate_limited`](/docs/api/errors#code-rate-limited) (over the 120 requests a minute per credential or 300 a minute per organization; sandbox ceilings are lower; `X-RateLimit-*` say which), [`server_busy`](/docs/api/errors#code-server-busy) (the whole service is at capacity: every data slot and queue place is taken; `retry_after`; not metered, not a limit on the caller), [`sandbox_busy`](/docs/api/errors#code-sandbox-busy) (sandbox keys only: the sandbox's shared concurrent-query maximum is reached; `retry_after`), [`points_exhausted`](/docs/api/errors#code-points-exhausted) (the organization's monthly points allowance is spent; `Retry-After` counts to the end of the UTC month; checked on API-key and export requests, never on session JSON), [`quota_exhausted`](/docs/api/errors#code-quota-exhausted) (a monthly CSV or API request quota an operator has re-armed for this organization is spent; retired as a published limit). Every one carries `Retry-After`.

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

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `500` error

Server error. Codes: [`internal_error`](/docs/api/errors#code-internal-error) (for example a vintage file the server cannot read).

As `application/problem+json`.

Problem codes: [`unknown_parameter`](/docs/api/errors#code-unknown-parameter) (400), [`repeated_parameter`](/docs/api/errors#code-repeated-parameter) (400), [`bad_request`](/docs/api/errors#code-bad-request) (400), [`xlsx_only_parameter`](/docs/api/errors#code-xlsx-only-parameter) (400), [`bad_cluster_ref`](/docs/api/errors#code-bad-cluster-ref) (400), [`area_sum_required`](/docs/api/errors#code-area-sum-required) (400), [`area_overlap`](/docs/api/errors#code-area-overlap) (400), [`retired_msa`](/docs/api/errors#code-retired-msa) (400), [`cell_cap`](/docs/api/errors#code-cell-cap) (400), [`series_cap`](/docs/api/errors#code-series-cap) (400), [`unknown_column`](/docs/api/errors#code-unknown-column) (400), [`column_not_filterable`](/docs/api/errors#code-column-not-filterable) (400), [`bad_total`](/docs/api/errors#code-bad-total) (400), [`xlsx_row_ceiling`](/docs/api/errors#code-xlsx-row-ceiling) (400), [`xlsx_column_ceiling`](/docs/api/errors#code-xlsx-column-ceiling) (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), [`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), [`plan_required`](/docs/api/errors#code-plan-required) (403), [`geography_excluded`](/docs/api/errors#code-geography-excluded) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`vintage_superseded`](/docs/api/errors#code-vintage-superseded) (404), [`vintage_withdrawn`](/docs/api/errors#code-vintage-withdrawn) (404), [`region_retired`](/docs/api/errors#code-region-retired) (404), [`cluster_not_found`](/docs/api/errors#code-cluster-not-found) (404), [`cluster_deleted`](/docs/api/errors#code-cluster-deleted) (404), [`uri_too_long`](/docs/api/errors#code-uri-too-long) (414), [`rate_limited`](/docs/api/errors#code-rate-limited) (429), [`server_busy`](/docs/api/errors#code-server-busy) (429), [`sandbox_busy`](/docs/api/errors#code-sandbox-busy) (429), [`points_exhausted`](/docs/api/errors#code-points-exhausted) (429), [`quota_exhausted`](/docs/api/errors#code-quota-exhausted) (429), [`internal_error`](/docs/api/errors#code-internal-error) (500). Every problem is a [problem document](/docs/api/errors).

## GET `/v2/industry/staffing-pattern`

**Industries employing an occupation (both shares and job counts)**

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

The industries that employ one occupation, in one or more areas and one year (D99 Q3): for each industry, `share_of_industry` (the occupation's share of the industry's jobs, from the industry's staffing pattern), the job `count` (that share x the industry's employment), and `share_of_occupation` (the industry's count over the sum of the counts of the industries listed for the same area and ownership). An industry in which the occupation has no jobs is not listed. `occupation=` takes any SOC code; a code above the leaves sums the leaf occupations beneath it. The industries listed are one NAICS level (`naics_level`, default 4, the three-digit subsector) or a `naics=` list; they must not overlap, since an ancestor beside its descendant would count the occupation's jobs twice. Each row's `pattern_basis` says whether its pattern is regional or a national fill-in. Areas, entitlement and gates as on `/v2/occupation/series` (no new gate, D99 item 8): nation and state patterns are open to every account; a county needs the county in the organization's active county set (a partial exclusion is counted in `meta.geography.excluded`; `strict=true` makes it a 403). A state or the nation asked for directly serves its own staffing pattern (M5: it reproduces `/v2/occupation/series` exactly); any other area (a county, an MSA, a cluster, a summed list) is the blend of the staffing regions its counties lie in, weighted by DTG industry employment for the area, industry and year; `meta.staffing.patterns[].regions` names the regions, their weights and OEWS windows. Ownership: default `all`, the blend of the four ownership patterns weighted by the area's employment in each (no all-ownership pattern exists, M2); one ownership serves its own pattern; a list gives one pattern per ownership, or with `sum=ownership` their blend. Annual only, one `year` (default: the last year of published actuals). COUNTS: Industry-by-occupation job counts are DTG modelled estimates: the occupation's share of the industry, from the regionalised OEWS staffing pattern, applied to DTG's industry employment. They are not survey counts. A count is the share x the requested industry's own annual employment exactly as `/v2/industry/series` serves it, so an industry's counts sum to that figure within the rounding of each count to the cent. The complete pattern is served: unlike `/v2/occupation/series`, no occupation is dropped under a floor (Q5). An industry with employment in the area and year but no staffing pattern is a defect in the data release (the owner's rule, enforced at release by gate G1): the answer is 500 [`pattern_missing`](/docs/api/errors#code-pattern-missing), logged, never a partial pattern. A national fill-in (the pipeline copied the nation's pattern where the region had no survey data, M6) is served and labelled (`basis`, `note`, `national_fill_in`). Metering: a JSON request on a SESSION is never metered; on an API key, and every CSV or XLSX export, each value that shows non-zero at its display precision is one point (a share to 4 decimal places, a count to whole jobs); labels cost nothing. Representations: JSON by default; CSV with `Accept: text/csv` or a `.csv` suffix (long format: the row's fields as columns, values as JSON writes them; a `#` preamble with the vintage, the attribution, the year, the counts' sentence, the floor note and one `# pattern:` line per area and ownership); the workbook with the spreadsheet media type or an `.xlsx` suffix (a `Table` sheet with the code's title beside it, shares under a percentage format and counts under a whole-jobs format, and a `Source` sheet). The as-shown grid parameters of `/series` are not taken. Callers: an API key (live or sandbox) or a session, any member role; a live key needs the API add-on (403 [`plan_required`](/docs/api/errors#code-plan-required), `api_access`).

### Parameters

**`occupation` query · string required**

One SOC code (`29-1141`, `29-1140`, `29-0000`). Above the leaf level its shares and counts are the sums of the leaf occupations beneath it (`meta.staffing.occupation.leaves` counts them).

**`naics_level` query · string**

The industries listed: every NAICS code at one level (default `4`, the three-digit subsector), or `leaf`, the codes that are the parent of no code. `all` is refused: parents beside their children would count jobs twice. Not with `naics`. (pattern ^(\\d+|leaf)$)

**`naics` query · string**

The industries listed instead of a level: a comma list of public NAICS codes, none containing another (an ancestor and its descendant together are 400, the pair named). Not with `naics_level`.

**`areas` query · string**

Comma list of area codes ("00" nation, "22" state, "22073" county), state:XX (expands to the state's counties, INCLUDING its "Unknown Or Undefined" catch-all — O34), cluster:<id> (the caller's own fips-kind cluster), or msa:<code> (the MSA's member counties at the latest map, never a catch-all). T19: a cluster or an MSA ALONE is one summed series named by the cluster's name or the MSA's title; BESIDE other tokens it contributes its counties to the one summed area and requires sum=areas (else 400 area\_sum\_required). A summed selection may not overlap: 400 area\_overlap {pair}. A retired MSA is 400 retired\_msa naming its successors. areas=region:<id> is RETIRED (404 region\_retired, D44). The county universe is 3,194 areas: 3,144 counties and county-equivalents plus the 50 per-state catch-alls holding employment not attributable to a specific county — ordinary areas in the data, included in state and national totals, purchasable, but with no geographic extent (is\_catchall on /v2/account/counties governs presentation only: never in an msa: expansion, never a /map row, never a public data page).

**`sum` query · string**

Comma list of `areas` (sum the `areas` list into one area, whose pattern is the blend of its staffing regions; D67's overlap rule applies) and `ownership` (blend an explicit `ownership=` list into one pattern). `codes` is refused: a staffing pattern's rows are already parts of one whole.

**`ownership` query · string**

Comma list of `fed`, `state`, `local`, `private` (or 1, 2, 3, 5). Default: `all`, the blend of the four ownership patterns weighted by the area's employment in each (there is no all-ownership pattern, M2). A list gives one pattern per ownership; with `sum=ownership` their blend, labelled with the subset.

**`year` query · string**

The year, `YYYY`: the pattern that year and the industry's annual employment (the average of its four quarters) for the counts. Default: the last year of published actuals (`meta.provenance_boundaries.actuals.to`). Projection years are served (the pattern's last epoch runs to the end of the axis); quarters are not. (pattern ^\\d{4}$)

**`strict` query · "true"**

`true`: any county the entitlement excludes makes the request 403 [`geography_excluded`](/docs/api/errors#code-geography-excluded) instead of a 200 with `meta.geography.excluded` above zero.

**`vintage` query · string**

The vintage to read (default: the current one). A vintage built before the staffing patterns were added answers 404 [`staffing_not_in_vintage`](/docs/api/errors#code-staffing-not-in-vintage). (pattern ^(\\d{8}|sandbox-\\d{4}\\.\\d+)$)

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

Session only: the organization the request acts in, when the person belongs to more than one. An API key is bound to its organization.

### Responses

#### `200` success

The staffing pattern.

As `application/json`, `text/csv`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`.

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

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

**`Content-Disposition` header**

CSV and XLSX only: `attachment; filename="<domain>-staffing-pattern-<vintage>-<UTC timestamp>.csv"` (or `.xlsx`).

**`Cache-Control` header**

CSV and XLSX only: `private, no-store`.

**`Access-Control-Expose-Headers` header**

CSV and XLSX only: `Content-Disposition`.

**`data` array of DataIndustryStaffingRow always present**

The rows.

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

The NAICS code.

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

The area, as on `/v2/industry/series`.

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

`all`, one ownership, or a summed subset.

**`data[].share_of_industry` number always present**

The occupation's share of this industry's jobs (10 places on the wire, shown and metered to 4).

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

This industry's part of the occupation's jobs across the industries listed for the same area and ownership (sums to 1 over them).

**`data[].count` number always present**

The occupation's jobs in the industry, to the cent, shown and metered as whole jobs. Industry-by-occupation job counts are DTG modelled estimates: the occupation's share of the industry, from the regionalised OEWS staffing pattern, applied to DTG's industry employment. They are not survey counts.

**`data[].pattern_basis` "regional" | "national" | "partly national" always present**

A label (free): whether the industry's pattern here is regional or a national fill-in (M6).

**`meta` DataStaffingEnvelopeMeta always present**

The envelope's `meta` (spec section 3) with `staffing` beside it.

**`meta.vintage` string always present**

The vintage the answer was read from: the internal build id (`20260913`; a sandbox key's are `sandbox-2026.1`). Cite it and pass it back as `vintage=` to reproduce the answer exactly: a released vintage never changes. Its public name, such as `DTG 2026.3`, is the `label` on `GET /v2/vintages`.

**`meta.sandbox` true**

Present, and true, only when the answer was read from a sandbox vintage: every value is synthetic and must never be published. Absent otherwise.

**`meta.generated_at` string (date-time) always present**

When this response was produced, UTC, to the second (`2026-09-27T18:42:15Z`).

**`meta.granularity` "annual" | "quarterly"**

Data routes only: the period granularity of the points (on /map, `quarterly` when `qtr` was given). Absent on taxonomy responses.

**`meta.provenance_boundaries` DataProvenanceBoundaries always present**

Where the vintage's published record ends and estimation begins, recorded per vintage at build time and returned on every envelope. Compare a point's period against these ranges to know what kind of value it is. For OCCUPATION data this is the only provenance there is: occupation values carry no per-value label, so a period inside `actuals` is published-basis, inside `ces` carried forward, inside `projections` projected. Industry values also carry their own `disclosure` code, which is authoritative for the value. The ranges move with each release; read them, never hard-code them.

**`meta.provenance_boundaries.actuals` object always present**

Quarters published by BLS in QCEW (the Quarterly Census of Employment and Wages). Industry values here have disclosure 0 (published) or 1 (estimated by DTG where QCEW withheld the value).

**`meta.provenance_boundaries.actuals.from` string always present**

First quarter of the range, lower case, e.g. `2004q1`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.actuals.to` string always present**

Last quarter of the range, lower case, e.g. `2025q4`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.ces` object always present**

Quarters after the latest QCEW release, carried forward from the CES and SAE monthly employment surveys (their rate of change applied to DTG's base, not their levels). Industry values here have disclosure 2.

**`meta.provenance_boundaries.ces.from` string always present**

First quarter of the range, lower case, e.g. `2004q1`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.ces.to` string always present**

Last quarter of the range, lower case, e.g. `2025q4`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.projections` object always present**

Projected quarters, out to the end of the period axis. Industry values here have disclosure 3.

**`meta.provenance_boundaries.projections.from` string always present**

First quarter of the range, lower case, e.g. `2004q1`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.provenance_boundaries.projections.to` string always present**

Last quarter of the range, lower case, e.g. `2025q4`. (pattern ^\\d{4}q\[1-4\]$)

**`meta.geography` object**

Data routes only. Absent on taxonomy responses.

**`meta.geography.requested` integer always present**

/series and /lq: the number of distinct areas the request resolved to before entitlement (a `state:XX`, `msa:` or `cluster:` token counts its counties; a plain state code counts 1; no `areas` counts the nation as 1). /map: the counties of the `areas` restriction, or without one the counties with a row for this code and period (catch-alls never counted). (at least 0)

**`meta.geography.returned` integer always present**

Areas (on /map, counties) that passed entitlement and are in the answer. On a summed request these are the areas inside the one summed series. (at least 0)

**`meta.geography.excluded` integer always present**

Requested counties the organization's entitlement removed (outside the active county set, or every county while the subscription is past due). On /map a requested-versus-returned gap beyond `excluded` means counties with no data for the code and period, not an exclusion. (at least 0)

**`meta.geography.region` DataRegion**

Present only on a request whose areas were summed into one series (a lone `cluster:<id>` or `msa:<code>`, or `sum=areas` / `combine=sum`): what the summed area is made of. The CSV preamble prints it as `# region:` lines and the workbook's Source sheet as `Region` rows.

**`meta.geography.region.label` string always present**

The summed series' `area` label: the cluster's name or the MSA's title when one was asked for alone, otherwise `custom region`.

**`meta.geography.region.areas` array of DataRegionArea always present**

Each area as requested, in request order.

**`meta.geography.region.areas[].token` string always present**

The token exactly as requested: `22073`, `22`, `00`, `state:22`, `msa:35380`, `cluster:<id>`.

**`meta.geography.region.areas[].kind` "nation" | "state" | "county" | "stateAll" | "msa" | "cluster" always present**

What the token is: the nation (`00`), a state's own row (`22`), a county, `stateAll` (a `state:XX` token: every county of the state, its catch-all included), an MSA, or one of the organization's area clusters.

**`meta.geography.region.areas[].code` string always present**

The area's code: a FIPS code for a county or state (the 2-digit state code for `stateAll`), or the token itself, lower-cased, for `msa:<code>` and `cluster:<id>`.

**`meta.geography.region.areas[].name` string or null always present**

The area's name: the vintage taxonomy's title for a county or state, the MSA's title, or the cluster's name. Null where the taxonomy has none.

**`meta.geography.region.areas[].resolved` array of string always present**

The area codes this token contributed to the sum after entitlement. A county the plan does not cover appears in no list; `geography.excluded` counts it.

**`meta.geography.region.resolved` array of string always present**

Every area code summed, deduplicated, in request order, after entitlement.

**`meta.pagination` DataPagination always present**

**`meta.pagination.cursor` string or null always present**

The `cursor` this page was requested with; null for the first page or an unpaged request.

**`meta.pagination.next_cursor` string or null always present**

Pass as `cursor` to get the next page. Null on the last page and on every unpaged response, so null always means there is nothing more.

**`meta.pagination.count` integer always present**

Rows in `data` on this response: series on /series and /lq, rows on /map, items on a taxonomy. (at least 0)

**`meta.staffing` DataStaffingMeta**

`meta.staffing`: what the pattern is and where it came from.

**`meta.staffing.year` integer always present**

The year served.

**`meta.staffing.industry` object**

Forward endpoint: the industry asked for.

**`meta.staffing.industry.naics` string**

The NAICS code.

**`meta.staffing.industry.title` string or null**

Its title.

**`meta.staffing.occupation` object**

Reverse endpoint: the occupation asked for.

**`meta.staffing.occupation.soc` string**

The SOC code.

**`meta.staffing.occupation.title` string or null**

Its title.

**`meta.staffing.occupation.leaves` integer**

The leaf occupations its figures sum.

**`meta.staffing.industries` object**

Reverse endpoint: the list, as `naics_level` or `naics`.

**`meta.staffing.industries.naics_level` string**

The level listed.

**`meta.staffing.industries.naics` array of string**

The codes listed.

**`meta.staffing.soc_level` string**

Forward endpoint: `leaf`, `all` or the level.

**`meta.staffing.ownership_blend` string**

Present when a pattern blends ownerships: how (there is no all-ownership pattern).

**`meta.staffing.ep_base_year` integer or null**

The base year of the Employment Projections crosswalk the patterns were built with (inferred at build, M4).

**`meta.staffing.weights_source` string**

What the blends and the counts are weighted by.

**`meta.staffing.counts` string always present**

The counts' standing, in the owner's words: Industry-by-occupation job counts are DTG modelled estimates: the occupation's share of the industry, from the regionalised OEWS staffing pattern, applied to DTG's industry employment. They are not survey counts.

**`meta.staffing.floor` string always present**

The complete pattern is served; `/v2/occupation/series` applies a floor the pattern does not (Q5).

**`meta.staffing.share_display_decimals` integer**

The places a share is shown and metered at, as a fraction (4: a percentage to 2 places). A share that shows as 0 costs no point. The wire carries 10 places.

**`meta.staffing.share_of_occupation` string**

Reverse endpoint: what `share_of_occupation` is a share of.

**`meta.staffing.patterns` array of DataStaffingPattern always present**

One entry per area x ownership of the answer.

**`meta.staffing.patterns[].area` string always present**

The area, as the rows carry it.

**`meta.staffing.patterns[].ownership` string always present**

The ownership, as the rows carry it (`all`, one token, or a summed subset).

**`meta.staffing.patterns[].industry_employment` number**

Forward endpoint: the industry's employment in the area and year, the annual average to the cent, exactly as `/v2/industry/series` serves it. The rows' counts sum to it within rounding.

**`meta.staffing.patterns[].basis` "regional" | "national" | "partly national"**

Forward endpoint: `regional`, or `national` when the whole pattern is a national fill-in, or `partly national` when part of the industry's employment stands behind one.

**`meta.staffing.patterns[].note` string**

Present when `basis` is not `regional`: `pattern: national; no regional survey data for this industry in this area`, or the partly-national form with the share of the industry's employment.

**`meta.staffing.patterns[].industry_resolution` array of object**

Forward endpoint: how the NAICS code reached a pattern, per ownership with employment: `exact`; `inherited` with `inherited_from` (the NAICS code whose pattern a finer code took); `blended` with `blended_from` (the NAICS codes beneath it whose patterns were blended by employment).

**`meta.staffing.patterns[].industry_resolution[].ownership` string**

The ownership.

**`meta.staffing.patterns[].industry_resolution[].resolution` "exact" | "inherited" | "blended"**

How the code reached its pattern.

**`meta.staffing.patterns[].industry_resolution[].inherited_from` string**

The NAICS code whose pattern was taken.

**`meta.staffing.patterns[].industry_resolution[].blended_from` array of string**

The NAICS codes whose patterns were blended.

**`meta.staffing.patterns[].occupation_employment` number**

Reverse endpoint: the occupation's jobs summed over the industries listed (the denominator of `share_of_occupation`).

**`meta.staffing.patterns[].industries` integer**

Reverse endpoint: the rows this area and ownership produced.

**`meta.staffing.patterns[].inherited` array of object**

Reverse endpoint: the listed industries that took an ancestor's pattern.

**`meta.staffing.patterns[].inherited[].naics` string**

The listed code.

**`meta.staffing.patterns[].inherited[].ownership` string**

The ownership.

**`meta.staffing.patterns[].inherited[].inherited_from` string**

The code whose pattern it took.

**`meta.staffing.patterns[].national` array of string**

Reverse endpoint: the listed industries whose pattern is wholly or partly a national fill-in.

**`meta.staffing.patterns[].regions` array of DataStaffingRegion always present**

The staffing regions the pattern was read from.

**`meta.staffing.patterns[].regions[].region` string always present**

The staffing region's code in the source's numbering: `99` the nation, a two-digit code a state, a five-digit code a metropolitan area, a seven-digit code a state's nonmetropolitan area.

**`meta.staffing.patterns[].regions[].title` string or null always present**

The region's name.

**`meta.staffing.patterns[].regions[].type` "nation" | "state" | "metro" | "nonmetro" | null always present**

What kind of region it is.

**`meta.staffing.patterns[].regions[].ownership` string always present**

The ownership (`fed`, `state`, `local`, `private`) whose pattern this entry weighs.

**`meta.staffing.patterns[].regions[].weight` number**

Forward endpoint only: the jobs standing behind this region in this ownership (DTG industry employment, the year's annual average).

**`meta.staffing.patterns[].regions[].weight_share` number or null**

Forward endpoint only: this entry's part of the pattern's weight, 6 decimal places.

**`meta.staffing.patterns[].regions[].oews_window` string or null always present**

The OEWS survey years the pattern was fitted to (`2023-2025`), derived by the pipeline's own rule; several, comma-separated, when the region's industries differ.

**`meta.staffing.patterns[].regions[].national_fill_in` boolean always present**

True when the pattern (or part of it) is a national fill-in: the pipeline copied the nation's pattern because the region had no survey data for the industry (M6).

#### `400` error

Bad request. Codes: [`unknown_parameter`](/docs/api/errors#code-unknown-parameter) (`parameters[]`), [`repeated_parameter`](/docs/api/errors#code-repeated-parameter) (`parameter`), [`bad_request`](/docs/api/errors#code-bad-request) for: a missing or unknown `occupation`; `naics` and `naics_level` together; a malformed or unknown `naics` code; overlapping `naics` codes (the pair named); `naics_level=all`; a `naics_level` with no codes; a malformed `vintage`; a bad `ownership` token; a `sum` token other than `areas` or `ownership` (`sum=codes` does not apply); a `year` that is not `YYYY` or lies outside the vintage's staffing patterns; an unknown or malformed area, `state:XX`, `msa:` or `cluster:` token; [`bad_cluster_ref`](/docs/api/errors#code-bad-cluster-ref); [`area_sum_required`](/docs/api/errors#code-area-sum-required) (`token`); [`area_overlap`](/docs/api/errors#code-area-overlap) (`pair`); [`retired_msa`](/docs/api/errors#code-retired-msa); [`cell_cap`](/docs/api/errors#code-cell-cap) (`cells`, `cap`) or [`series_cap`](/docs/api/errors#code-series-cap) (`series`, `cap`): the request asks for more rows (areas x ownerships x rows each) than the one guard allows; XLSX only: [`xlsx_row_ceiling`](/docs/api/errors#code-xlsx-row-ceiling) (`rows`, `max_rows`, `alternatives`).

As `application/problem+json`.

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `401` error

Unauthenticated. Codes: [`unauthenticated`](/docs/api/errors#code-unauthenticated) (no credential, or an unknown or expired one); [`key_revoked`](/docs/api/errors#code-key-revoked) (`revoked_at`, `reason`).

As `application/problem+json`.

#### `403` error

Forbidden. Codes: [`no_membership`](/docs/api/errors#code-no-membership), [`account_suspended`](/docs/api/errors#code-account-suspended), [`org_suspended`](/docs/api/errors#code-org-suspended), [`org_closed`](/docs/api/errors#code-org-closed), [`wrong_instance`](/docs/api/errors#code-wrong-instance); [`plan_required`](/docs/api/errors#code-plan-required) with `entitlement`: `api_access`, `past_due`, `vintage_access`, `csv_access` (a CSV or XLSX export on a plan without export, or a trial export outside the trial county), `depth_cap_naics` or `depth_cap_soc` (a code or level deeper than the plan's cap); [`geography_excluded`](/docs/api/errors#code-geography-excluded) (every requested county is outside the organization's entitlement, or any is with `strict=true`; `excluded_fips`).

As `application/problem+json`.

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `404` error

Not found. Codes: [`not_found`](/docs/api/errors#code-not-found) (no such vintage in the caller's scope, or an `X-DTG-Org` organization the caller does not belong to), [`vintage_superseded`](/docs/api/errors#code-vintage-superseded) (`superseded_by`), [`vintage_withdrawn`](/docs/api/errors#code-vintage-withdrawn) (`current`), [`staffing_not_in_vintage`](/docs/api/errors#code-staffing-not-in-vintage) (the vintage was built before the staffing patterns were added: `vintage`), [`region_retired`](/docs/api/errors#code-region-retired), [`cluster_not_found`](/docs/api/errors#code-cluster-not-found), [`cluster_deleted`](/docs/api/errors#code-cluster-deleted).

As `application/problem+json`.

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `414` error

URI too long. Codes: [`uri_too_long`](/docs/api/errors#code-uri-too-long).

As `application/problem+json`.

#### `429` error

Too many requests. Codes: [`rate_limited`](/docs/api/errors#code-rate-limited), [`server_busy`](/docs/api/errors#code-server-busy) (`retry_after`; not metered), [`sandbox_busy`](/docs/api/errors#code-sandbox-busy) (`retry_after`), [`points_exhausted`](/docs/api/errors#code-points-exhausted) (the organization's monthly points allowance is spent; checked on API-key and export requests, never on session JSON), [`quota_exhausted`](/docs/api/errors#code-quota-exhausted). Every one carries `Retry-After`.

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

**`X-DTG-Sandbox` header**

Present, with the value `true`, on every response to a sandbox API key, success and problem alike; it is set as soon as the credential is known. Every value in a sandbox response is synthetic and must never be published as data. Absent for live keys and sessions.

#### `500` error

Server error. Codes: [`pattern_missing`](/docs/api/errors#code-pattern-missing) (an industry with employment in the area and year has no staffing pattern in this vintage: a defect in the data release, logged; no partial pattern is served; `vintage`), [`internal_error`](/docs/api/errors#code-internal-error).

As `application/problem+json`.

Problem codes: [`unknown_parameter`](/docs/api/errors#code-unknown-parameter) (400), [`repeated_parameter`](/docs/api/errors#code-repeated-parameter) (400), [`bad_request`](/docs/api/errors#code-bad-request) (400), [`bad_cluster_ref`](/docs/api/errors#code-bad-cluster-ref) (400), [`area_sum_required`](/docs/api/errors#code-area-sum-required) (400), [`area_overlap`](/docs/api/errors#code-area-overlap) (400), [`retired_msa`](/docs/api/errors#code-retired-msa) (400), [`cell_cap`](/docs/api/errors#code-cell-cap) (400), [`series_cap`](/docs/api/errors#code-series-cap) (400), [`xlsx_row_ceiling`](/docs/api/errors#code-xlsx-row-ceiling) (400), [`unauthenticated`](/docs/api/errors#code-unauthenticated) (401), [`key_revoked`](/docs/api/errors#code-key-revoked) (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), [`plan_required`](/docs/api/errors#code-plan-required) (403), [`geography_excluded`](/docs/api/errors#code-geography-excluded) (403), [`not_found`](/docs/api/errors#code-not-found) (404), [`vintage_superseded`](/docs/api/errors#code-vintage-superseded) (404), [`vintage_withdrawn`](/docs/api/errors#code-vintage-withdrawn) (404), [`staffing_not_in_vintage`](/docs/api/errors#code-staffing-not-in-vintage) (404), [`region_retired`](/docs/api/errors#code-region-retired) (404), [`cluster_not_found`](/docs/api/errors#code-cluster-not-found) (404), [`cluster_deleted`](/docs/api/errors#code-cluster-deleted) (404), [`uri_too_long`](/docs/api/errors#code-uri-too-long) (414), [`rate_limited`](/docs/api/errors#code-rate-limited) (429), [`server_busy`](/docs/api/errors#code-server-busy) (429), [`sandbox_busy`](/docs/api/errors#code-sandbox-busy) (429), [`points_exhausted`](/docs/api/errors#code-points-exhausted) (429), [`quota_exhausted`](/docs/api/errors#code-quota-exhausted) (429), [`pattern_missing`](/docs/api/errors#code-pattern-missing) (500), [`internal_error`](/docs/api/errors#code-internal-error) (500). Every problem is a [problem document](/docs/api/errors).

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