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

# Occupation data

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

## GET `/v2/occupation/series`

**Occupation series**

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

Occupation time series (OEWS 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), wage\_mean is recomputed from the summed wage bill and employment, quarters\_present takes the minimum over members with data. Occupation values carry no per-value provenance: read `meta.provenance_boundaries`. 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/occupation/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/occupation/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, 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). 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

**`soc` query · string**

Comma list of SOC codes (`29-1141,29-1171`), one series per code in the taxonomy's nested order; or `cluster:<id>`, one of the organization's soc 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 `soc` nor `soc_level`): the level-0 total (`00-0000`). Not with `soc_level`.

**`soc_level` query · string**

Every code at one hierarchy level (0 Total, 1 Major group, 2 Minor group, 3 Broad occupation, 4 Detailed occupation); 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 `soc`. (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`, `wage_mean` (default all). Labels are always returned on industry points.

**`order` query · "soc" | "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 "soc")

**`group` query · "soc" | "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 DataSeriesOccupation always present**

The series (this page of them, when paged), in the order `order` sets.

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

The SOC code as a string (`29-1141`), names on `GET /v2/taxonomies/soc`. On a summed code axis it is the label instead: the cluster's name for `soc=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 DataOccupationPoint 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**

Occupation employment in jobs, on the same basis as industry employment (occupation employment is reconciled to DTG industry employment). A quarter is its average; an annual value is the sum of its quarters divided by 4. Present when `measures` includes emp.

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

Mean earnings per WORKER for the period, in US dollars: the cell's wage bill divided by its employment, rounded once (annual: close to the OEWS annual mean wage; quarterly: the quarter's earnings per worker). OEWS annualizes hourly pay at 2,080 hours; an hourly figure (annual divided by 2,080) is presentation arithmetic, not a measured hourly wage. Occupation wage totals do not sum to industry earnings, by construction; only employment reconciles. Null when employment is zero. Present when `measures` includes wage\_mean.

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

Annual points only: how many of the year's quarters have a source row (4 = complete year, 0 = a true-zero year). On a summed series, the minimum over the summed members that have data in the 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 `soc=` and `soc_level=`, a `soc_level` that is not an integer, `all` or `leaf`, a level with no codes, `sum=codes` with `soc_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 `soc=`, 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_soc` (a code deeper than the plan's soc depth cap; `soc_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/occupation/map`

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

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

Choropleth values for one SOC 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. Rows carry no labels. 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/occupation/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/occupation/map.xlsx). CSV is long format, one line per row (`fips, value`), 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, 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). 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

**`soc` query · string required**

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

**`measure` query · "emp" | "wage\_mean"**

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 `soc` 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 DataOccupationMapRow 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`: emp (annual: sum of quarters divided by 4) or wage\_mean (wage bill over employment, rounded once; null when employment is zero).

**`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 `soc`, 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_soc` (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/occupation/lq`

**Occupation location quotients vs the nation**

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

Occupation 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. Points carry no labels: read `meta.provenance_boundaries`. 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/occupation/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/occupation/lq.xlsx). CSV is long format, one line per series-point (`soc, 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, 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). 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

**`soc` query · string**

Comma list of SOC codes (`29-1141,29-1171`), one series per code in the taxonomy's nested order; or `cluster:<id>`, one of the organization's soc 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 `soc` nor `soc_level`): the level-0 total (`00-0000`). Not with `soc_level`.

**`soc_level` query · string**

Every code at one hierarchy level (0 Total, 1 Major group, 2 Minor group, 3 Broad occupation, 4 Detailed occupation); 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 `soc`. (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 DataLqSeriesOccupation always present**

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

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

The SOC code as a string (`29-1141`), names on `GET /v2/taxonomies/soc`. On a summed code axis it is the label instead: the cluster's name for `soc=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 DataOccupationLqPoint 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 vs the nation, to 4 decimals; null on any zero denominator.

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

The area's own employment in the occupation for the period (the numerator).

**`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 `soc=` and `soc_level=`, a `soc_level` that is not an integer, `all` or `leaf`, a level with no codes, `sum=codes` with `soc_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 `soc=`, 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_soc` (a code deeper than the plan's soc depth cap; `soc_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/occupation/staffing-pattern`

**Staffing pattern of an industry (occupations, shares and job counts)**

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

The occupations employed in one industry, in one or more areas and one year: each occupation's `share` of the industry's jobs and its job `count`. The pattern is the regionalised OEWS staffing pattern of DTG's occupation pipeline; the vintage carries the 832 leaf occupations (518 detailed, 314 broad where a broad occupation does not split), which sum to one; `soc_level` sums them up the SOC tree (levels 0 to 3 sum to one; level 4 holds only the detailed leaves, about 45% of an industry). `industry=` takes a public NAICS code only: a code finer than the pattern's industry inherits its pattern (`industry_resolution` names the code it took), a broader one blends the patterns beneath it by DTG employment. 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

**`industry` query · string required**

One public NAICS code (`622`, `622110`, `62`), as on `/v2/industry/series`. The pattern's internal industry vocabulary (EP) is never accepted or shown. (pattern ^\\d+$)

**`soc_level` query · string**

`leaf` (the default): the 832 leaf occupations, each at its most detailed code, which partition the industry. An integer: the leaves summed to that level of the SOC tree (0 Total, 1 Major group, 2 Minor group, 3 Broad, 4 Detailed: partial, a broad leaf has no detailed code). `all`: every code with leaves beneath it, nested, parents beside their children (never to be summed). `leaf` and `all` need the plan's full soc depth. (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).

**`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 DataOccupationStaffingRow always present**

The rows.

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

The SOC code (`29-1141`).

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

The area, as on `/v2/occupation/series` (a FIPS code, or a summed area's label).

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

`all`, one of `fed`, `state`, `local`, `private`, or a summed subset.

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

The occupation's share of the industry's jobs, a fraction (the leaves sum to 1), written to 10 decimal places. Shown and metered to 4 places (a percentage to 2): below 0.00005 it shows as 0 and costs no point.

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

The jobs: share x the industry's employment (`industry_employment`), to the cent, shown and metered as whole jobs (below 0.5 it costs no point). 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` 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 `industry`, a code that is not digits (an EP code is refused as unknown), an unknown NAICS code; a bad or empty `soc_level`; 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).
