canopy-reporting API Reference
On this page
Overview
Cross-link: canopy-reporting Data Model (#419)
Assembles federal SNAP/TANF/Medicaid reports from upstream service data. Does not access program databases directly (ADR-001 compliant) — instead queries canopy-persons, canopy-applications, canopy-snap, canopy-enrollment, canopy-renewals, canopy-tanf, canopy-medicaid, and canopy-caps via HTTP.
- Base URL
- Authentication
-
Bearer token (Keycloak RS256 JWT)
- Minimum role
-
supervisor
- Swagger UI
- Database
-
canopy_reporting
Receiver contract (OIDC S-reporting, #1438 / ADR-043 §C)
canopy-reporting is the fourteenth service on the ADR-043 receiver
contract (canopy_auth::ReceiverContract) — and the first whose
dominant route class is USER-ONLY:
-
ALL 21 supervisor report surfaces are
require_user_only(SUPERVISOR_OR_ABOVE): service bearers are 403 (service_class_on_user_only); under enforcement a broad-audience direct worker bearer is 403 (aud_not_exact), and only the exchanged user-context arm (exactaud=canopy-reporting, allowlisted azp, supervisor role) reaches them. Workers reach these surfaces via token exchange — there is no BFF sender today; the devstack suites ride jane.doe’s exchanged bearer. -
The three DUAL surfaces keep their service arms by design: the overpayments summary (the dashboard panel’s service-class sender) and the two org-visible runs reads (
authorize_runs_read— do not requester-scope). -
Azp allowlist:
canopy-web-exchangeronly.
Reporting-Access Audit Stream (#1404, Pub 1075 §9)
Every successful report generation, report/summary read, and CSV
extract on this surface stages one audit envelope through the ADR-018
outbox (ingested by canopy-security’s wildcard subscriber) via a
route-table middleware (src/audit.rs) — a new route cannot land
unclassified: an exhaustiveness test forces every OpenAPI (method, path)
into the audited table or the explicit exclusion list. Vocabulary:
reporting.report.generated (POST generate), reporting.report.accessed
(report/summary reads), reporting.extract.exported (CSV extracts — the
parsed export action also counts toward the seeded Bulk Data Access
rule). Payload carries report_family, accessed_by (the caller’s sub)
and user_role; the envelope asserts the family’s #1519 program stamp
(FNS-388/QC → snap, ACF-/WPR → tanf, T-MSIS/CMS- → medicaid; the
cross-program overpayment surfaces assert neutral). Fail-closed: if
staging fails the response is withheld (500) — the disclosure has not
crossed the wire, and a monitoring stream that drops silently is not a
control. Deliberately NOT audited (recorded in UNAUDITED_ROUTES with
the ruling): the run-status poll targets (GET /v1/reporting/runs,
GET /v1/reporting/runs/{id}) — run METADATA, not report content, and
auditing a dashboard poller would trip the 50/10min volume rule on
routine traffic. This is the emitter half of the #1302-seeded "Reporting
Extract Volume" detection rule.
Provisioning (both required — the live e2e test surfaced each): the
20261129000000 migration grants the app DB role the outbox INSERT the
#1456 least-privilege matrix deferred, and the broker user needs the
topic-write pattern in devstack/rabbitmq/definitions.json. The
definitions import only runs against a FRESH broker volume; on a running
devstack apply it live:
docker exec canopy-rabbitmq-1 rabbitmqctl set_topic_permissions -p / \
canopy-reporting canopy.events \
'^(reporting\.extract\.exported|reporting\.report\.accessed|reporting\.report\.generated)$' '.*'
Without the broker grant, staging still succeeds (fail-closed covers the INSERT, not the drainer) and events silt in the outbox with ACCESS_REFUSED retries — watch the outbox-backlog alert. The mq-topology lint cannot see these publish sites (#1530).
FNS-388 Monthly Participation Report
POST /v1/reporting/snap/fns-388
Enqueue a durable report run that generates the FNS-388 monthly report for a given month (#1202/#1203 MR5 — the run assembles active certifications, issuance totals, household counts, person demographics, and certification types from upstream services off the request path).
Request:
{
"report_month": "2026-03-01"
}
Any day of the month is accepted; the period is canonicalized to the month start.
Response (202): ReportRunAccepted — run_id, generation_id, poll_url — plus a Location header pointing at the poll URL. Poll GET /v1/reporting/runs/{id} until done, then read the published report from the GET endpoints below.
Response (409): the target month already has a queued/running run — the body carries THAT run’s ReportRunAccepted handle to poll.
Response (503): report runs disabled (RUNS_ENABLED=false) or the run queue at capacity — retry later.
The old synchronous 201 SnapMonthlyReport body is gone (pre-1.0, no shim); the type remains the GET response below.
Regulatory basis: 7 CFR 272.11 — monthly FNS-388 reporting.
FNS-7176 Quality Control Universe
POST /v1/reporting/snap/fns-388/{month}/submission
Advance the month’s FNS-388 submission lifecycle (#1335):
draft → final → submitted → accepted | rejected, with rejected → final
reopening resubmission. Acts on the month’s PUBLISHED generation’s row.
Guard: user-only, supervisor+. Audited as
reporting.report.submission_transitioned.
Request: SubmissionTransitionRequest — target_status
(final | submitted | accepted | rejected) and fns_confirmation_number
(required on accepted, rejected with 400 on any other target).
Semantics: submitted stamps submitted_at (each submitted edge —
a resubmission is a new filing, so the stamp reflects the CURRENT
submission) and makes the filing
IMMUTABLE to the #1202 promotion guard — a rerun for the month executes
but its promotion refuses (immutable_target), so a shipped federal
filing is never silently replaced; accepted stores the FNS
confirmation and keeps the block. Transitions are compare-and-set on the
row’s current status: an illegal edge is 409
(invalid_submission_transition), and a concurrent transition racing
this one is 409 (submission_transition_raced — re-read and re-decide).
Response (200): the transitioned SnapMonthlyReport with provenance.
POST /v1/reporting/snap/qc-universe
Enqueue a durable report run that generates the QC universe snapshot for a given date (#1202/#1203 MR5).
Request:
{
"snapshot_date": "2026-03-31"
}
The QC universe includes all active SNAP cases as of the snapshot date with 24 columns per the FNS-7176 specification.
Response (202): ReportRunAccepted — run_id, generation_id, poll_url — plus a Location header pointing at the poll URL. Poll GET /v1/reporting/runs/{id} until done, then read the published snapshot from the page/CSV endpoints below.
Response (409): the target date already has a queued/running run — the body carries THAT run’s ReportRunAccepted handle to poll.
Response (503): report runs disabled (RUNS_ENABLED=false) or the run queue at capacity — retry later.
The old synchronous 201 QcSnapshotGenerated body is DELETED (pre-1.0, no shim).
Regulatory basis: 7 CFR Part 275 — QC sampling and review.
GET /v1/reporting/snap/qc-universe/{date}
One keyset page of the QC universe for a specific date (format: YYYY-MM-DD), ordered by household_id ascending (#1221; ADR-001 Amendment 1 §B2).
Query parameters:
-
limit— page size; defaults to 50, clamped to 200. -
after_household_id— the prior page’snext_cursor.after_household_id; omit for the first page.
Since #1202/#1203 MR5 the page reads exclusively from the date’s PUBLISHED generation (staged/superseded/abandoned generations are invisible).
Response (200): SnapQcUniversePage — items (array of SnapQcUniverseEntry), next_cursor ({after_household_id} while a full page may have more, null at the end), total_in_scope (the authoritative generation-scope COUNT(*), populated only on the first page — the §B3 completeness tripwire: a page-looping extractor asserts drained rows equal it), and provenance (see "Report provenance" below).
Response (404): no published QC universe snapshot for the date. (Before MR5 an unknown date returned a 200 empty page.)
GET /v1/reporting/snap/qc-universe/{date}/csv
Export the QC universe as a CSV file (FNS-7176 column format) — rows of the date’s PUBLISHED generation; row data only, provenance rides the JSON page endpoint. A NULL abawd_household ("unverified legacy", plan D8) renders as an empty cell.
Response (200): CSV file (Content-Type: text/csv), streamed — the header first, then one chunk per keyset page, so peak service memory is O(page) regardless of snapshot size (#1221 / scale-audit H10). Inherent streaming caveat: a database error after the 200 header has gone out truncates the download mid-body rather than producing a 5xx.
Response (404): no published QC universe snapshot for the date. (Before MR5 an unknown date returned a 200 header-only CSV.)
ACF-199 Monthly TANF Case Snapshots
POST /v1/reporting/tanf/acf-199
Enqueue a durable report run that extracts one ACF-199 case snapshot per approved TANF determination for the month (#1202/#1203 MR6 — the run enriches each case with work-requirement, time-limit, work-activity, and CAPS childcare-funding data off the request path).
Request:
{
"report_month": "2026-03-01"
}
Any day of the month is accepted; the period is canonicalized to the month start.
Response (202): ReportRunAccepted — run_id, generation_id, poll_url — plus a Location header pointing at the poll URL. Poll GET /v1/reporting/runs/{id} until done, then read the published snapshots from the list/CSV endpoints below.
Response (409): the target month already has a queued/running run — the body carries THAT run’s ReportRunAccepted handle to poll.
Response (503): report runs disabled (RUNS_ENABLED=false) or the run queue at capacity — retry later.
The old synchronous 201 Acf199Generated body is DELETED (pre-1.0, no shim).
Regulatory basis: 45 CFR Part 265 — TANF data collection and reporting.
GET /v1/reporting/tanf/acf-199
List ACF-199 case snapshots from a PUBLISHED generation (#1202/#1203 MR6 — staged/superseded/abandoned generations are invisible).
Query parameters:
-
month(optional,YYYY-MM) — the report month. Omitted = the latest published generation. Bad format → 400. -
limit(optional) — page size; default 50, clamped to 200. -
after_report_month+after_case_id(optional, together) — the keyset cursor fromnext_cursor(#1334). Half-supplied cursor pair → 400 (a silently-restarted walk would look complete to a page-looping extractor).
Response (200): TanfAcf199SnapshotPage (#1334 — replaces the bare 100-row list) — items ordered by (report_month, case_id), next_cursor (null at the end), total_in_scope (first page only — the ADR-001 A1 §B3 completeness tripwire), and provenance (see "Report provenance" below).
Response (404): no published ACF-199 generation for the requested month — or, with month omitted, none published at all.
GET /v1/reporting/tanf/acf-199/csv
Export the resolved published generation’s ACF-199 snapshots as CSV. Same month query parameter and latest-published fallback as the list; row data only — provenance rides the JSON list endpoint. Page-walks the FULL generation (#1334 — the previous 100-row bound silently truncated larger months).
Response (200): CSV file (Content-Type: text/csv).
Response (404): no published ACF-199 generation for the month (or at all).
ACF-196 Quarterly TANF Financial Report
POST /v1/reporting/tanf/acf-196
Generate the ACF-196 quarterly stub — synchronous (local-DB aggregation over published ACF-199 generations, sub-second; plan D7/D8). Expenditure columns are zero pending real state-accounting wiring (partner-blocked).
Request: GenerateTanfQuarterlyRequest — fiscal_year, fiscal_quarter (federal fiscal year; Q1 = Oct–Dec of the prior calendar year).
Response (201): Acf196Generated — fiscal_year, fiscal_quarter, categories_generated, status. Since #1202/#1203 MR6 categories_generated is rows_affected()-honest: a rerun whose rows already exist reports 0, never a fabricated total.
Response (422): the fiscal quarter’s three months lack PUBLISHED ACF-199 generations (the plan-D2 published-input guard; the refusal names the missing months) — or an invalid fiscal_quarter (expected 1-4; previously a 500).
Regulatory basis: 45 CFR Part 265 — ACF-196 TANF financial report.
TANF Work Participation Rate (WPR)
POST /v1/reporting/tanf/wpr
Calculate the monthly all-family + two-parent Work Participation Rates from the month’s published ACF-199 generation — synchronous (plan D7/D8).
Request: GenerateTanfReportRequest — report_month. Any day of the month is accepted; the period is canonicalized to the month start.
Response (201): WprCalculated — report_month (normalized), all_family_rate, two_parent_rate, all_family_meets_target, two_parent_meets_target.
Response (422): the report month has no PUBLISHED ACF-199 generation (the plan-D2 published-input guard; names the missing month).
Regulatory basis: 45 CFR 261.21 / 261.32(c) — including the two-parent 55-hour childcare-funded standard (#1169).
T-MSIS Eligibility Extract
POST /v1/reporting/medicaid/tmsis
Enqueue a durable report run that extracts one T-MSIS eligibility row per approved Medicaid determination for the month, with the 38-COA coverage-group mapping, FPL computation, and citizenship enrichment off the request path (#1202/#1203 MR6).
Request:
{
"report_month": "2026-03-01"
}
Any day of the month is accepted; the period is canonicalized to the month start.
Response (202): ReportRunAccepted — run_id, generation_id, poll_url — plus a Location header pointing at the poll URL.
Response (409): the target month already has a queued/running run — the body carries THAT run’s handle.
Response (503): report runs disabled or the run queue at capacity.
The old synchronous 201 TmsisGenerated body is DELETED (pre-1.0, no shim). Dropped/degraded rows are counted, never silent (plan D6): orphan households are excluded and counted skipped_orphan; missing person facts ship citizenship_status = "unknown" (counted citizenship_degraded) and an honest NULL FPL (counted fpl_not_computable) — all visible in provenance and the run’s detail_counters.
Regulatory basis: 42 CFR Part 431 — T-MSIS state data submission.
GET /v1/reporting/medicaid/tmsis
List T-MSIS eligibility extracts from a PUBLISHED generation.
Query parameters:
-
month(optional,YYYY-MM) — the report month. Omitted = the latest published generation. Bad format → 400. -
limit(optional) — page size; default 50, clamped to 200. -
after_report_month+after_enrollment_id(optional, together) — the keyset cursor fromnext_cursor(#1334). Half-supplied cursor pair → 400 (a silently-restarted walk would look complete to a page-looping extractor).
Response (200): MedicaidTmsisExtractPage (#1334 — replaces the bare 100-row list; the wire sort changed from person_id to (report_month, enrollment_id), the generation’s total order) — items, next_cursor, total_in_scope (first page only), provenance.
Response (404): no published T-MSIS generation for the requested month (or at all).
GET /v1/reporting/medicaid/tmsis/csv
Export the resolved published generation’s T-MSIS extract as CSV. Same month parameter and latest-published fallback; row data only — provenance rides the JSON list endpoint. Streams the FULL generation as one RFC 4180 chunk per keyset page (#1454 removed the silent 100-row truncation; #1455 added real CSV quoting — embedded commas/quotes/newlines no longer corrupt rows). The JSON list endpoint pages on the same keyset since #1334.
Response (200): CSV file (Content-Type: text/csv), streamed. A database error after the 200 header truncates the download mid-stream (inherent streaming caveat, same as the FNS-7176 export) — it cannot become a 5xx.
Response (404): no published T-MSIS generation for the month (or at all).
CMS-64 Quarterly Expenditure Report
POST /v1/reporting/medicaid/cms-64
Aggregate CMS-64 enrollment counts + member-months from the quarter’s published T-MSIS generations — synchronous (plan D7/D8). Expenditure columns are NULL pending MMIS wiring (partner-blocked).
Request: GenerateMedicaidQuarterlyRequest — fiscal_year, fiscal_quarter.
Response (201): Cms64Generated — fiscal_year, fiscal_quarter, coverage_groups, status. Since #1202/#1203 MR6 coverage_groups is rows_affected()-honest: a rerun reports 0, never a fabricated total.
Response (422): the fiscal quarter’s three months lack PUBLISHED T-MSIS generations (names the missing months) — or an invalid fiscal_quarter (expected 1-4; previously a 500).
Regulatory basis: 42 CFR Part 430 — CMS-64 quarterly expense report.
CMS-416 Annual EPSDT Report
POST /v1/reporting/medicaid/cms-416
Enqueue a durable report run that builds the annual EPSDT age-band report for a report year (#1202/#1203 MR6).
Request:
{
"report_year": 2026
}
Response (202): ReportRunAccepted + Location header. Input pinning (plan D2): at enqueue the twelve months' published T-MSIS generation_id`s are recorded in the new generation’s `input_generation_ids; the universe drain filters generation_id = ANY(pinned), and the worker re-verifies the pins at every claim — a pin superseded/abandoned since enqueue finalizes the run error/stale_pins. CMS-416 is fully pinned and reproducible against published T-MSIS generations.
Response (409): the target year already has a queued/running run — the body carries THAT run’s handle.
Response (422): one or more months of the report year lack a PUBLISHED T-MSIS generation — the refusal names the missing months; generate + publish them first. (Also: a report year out of range.)
Response (503): report runs disabled or the run queue at capacity.
The old synchronous 201 Cms416Generated body is DELETED (pre-1.0, no shim).
Regulatory basis: 42 CFR 441.62 / SSA §1902(a)(43) — annual EPSDT participation report.
GET /v1/reporting/medicaid/cms-416
List CMS-416 age-band rows from a PUBLISHED generation.
Query parameters:
-
year(optional, integer) — the report year. Omitted = the latest published generation. Out-of-range year → 400. -
limit(optional) — page size; default 50, clamped to 200. -
after_report_year+after_age_group(optional, together) — the keyset cursor fromnext_cursor(#1334). Half-supplied cursor pair → 400 (a silently-restarted walk would look complete to a page-looping extractor).
Response (200): MedicaidCms416ReportPage (#1334) — items ordered by (report_year, age_group), next_cursor, total_in_scope (first page only), provenance. One generation holds at most the configured band set, so a single page is typical.
Response (404): no published CMS-416 generation for the requested year (or at all).
Cross-Program Overpayment Recovery Roll-Up
GET /v1/reporting/overpayments
Generate a CSV roll-up of overpayment claims + recoupment totals for one program (PAMMS 9000 / 7 CFR 273.18).
Minimum role: supervisor — via the exchanged user-context arm (#1438; user-only route: service-class tokens and, under enforcement, direct broad-audience worker bearers are 403).
Query parameters:
-
program(required) —snap/tanf/medicaid. -
status(optional) —open/in_repayment/closed/written_off. Omitted = no filter.
Behaviour: per ADR-001, canopy-reporting cannot read program-service DBs directly. Since #1222 the roll-up drains the target program service’s keyset GET /v1/overpayments page-by-page to exhaustion — each row already carries its server-side total_recouped_cents + outstanding_cents, so there are NO per-claim ledger calls (the old serial N+1 crossed gateway timeouts at ~3K claims) — and asserts the pulled count against total_in_scope (the §B3 completeness tripwire; a truncated universe fails the run). The supervisor-dashboard summary rides the same drain. Caseworker-supplied error_type is properly CSV-quoted (commas, embedded quotes, newlines all handled).
Response (200): CSV file (Content-Type: text/csv), 12 columns:
claim_id,person_id,household_id,determination_id,claim_amount_cents,
recouped_cents,outstanding_cents,status,claim_basis,error_type,
discovered_at,closed_at
Response (400): Unknown program.
GET /v1/reporting/overpayments/summary
Aggregated overpayment summary across all programs for the supervisor-dashboard overpayment-rollup panel (Stage 5 MR2 #496 FU-10).
Minimum role: supervisor (or service-class token).
Behaviour: per ADR-001, canopy-reporting cannot read program-service DBs directly. It composes via HTTP, fetching claims from each program service through the existing list_overpayment_claims client method + ledger view, then aggregates totals (no per-claim row data).
Response (200): OverpaymentsSummary — total_open_claims, total_closed_claims, total_outstanding_cents, total_recouped_cents, plus by_program array of ProgramOverpaymentTotals (per-program open_claims, closed_claims, outstanding_cents, recouped_cents).
Report Runs (#1202/#1203)
Durable report-run status surface (plan
report-run-generations D7).
MR4 shipped the READ surface + the run substrate; MR5 (SNAP) flipped the
two SNAP generate POSTs, and MR6 (TANF/Medicaid) the remaining three — all
five federal generate POSTs now enqueue and answer 202
ReportRunAccepted{run_id, generation_id, poll_url} (ACF-196 / CMS-64 /
WPR stay synchronous local aggregations). Operational guide:
the report-runs runbook.
Access (both endpoints): supervisor-or-above OR any service-class caller — an explicit OR. Runs are org-visible (a deliberate deviation from the #1205 chain-job requester-scoping: a hidden colleague run would make the 409 handles un-pollable).
GET /v1/reporting/runs/{id}
Poll one run by the id from the 202/409 enqueue handle.
Response (200): ReportRunStatus — run_id, generation_id,
report_kind (fns_388 | qc_7176 | acf_199 | tmsis | cms_416),
period_date, state (queued | running | done | error), attempts,
universe_total, processed_count, skipped_orphan_count,
degraded_count, detail_counters (named counts from the GENERATION row
— never decoded from run progress, so a malformed progress blob can never
500 a poll), error_code
(upstream_unavailable | universe_drift | contract_violation | stale_pins
| crashed | immutable_target), abandon_reason, requested_at,
finished_at, and result_url (set once done, pointing at the kind’s
read endpoint above).
Queued/running responses carry a Retry-After header (one worker tick,
whole seconds).
Response (404): unknown id — or an already-reaped terminal run
(indistinguishable by design; terminal run rows reap after
RUN_REAP_DAYS, provenance survives on the permanent generation row).
Report provenance
Since #1202/#1203 MR5 (SNAP) and MR6 (TANF/Medicaid) every
generation-scoped read body — FNS-388 get/list, the QC universe page, the
ACF-199 / T-MSIS / CMS-416 lists — carries a typed provenance object
from the resolved published report_generations row:
RunProvenance{generation_id, run_id, state, universe_total,
processed_count, skipped_orphan_count, degraded_count, extracted_at,
as_of}. run_id is null for the pre-pipeline legacy backfill
generations; state is always published on a read (readers never serve
any other state); extracted_at is the honesty stamp for the
as-of-extraction enrichment legs (see Known limitations); as_of is the
persons valid-time pin the run read under. Provenance survives run
reaping — it lives on the permanent generation row. The CSVs (QC,
ACF-199, T-MSIS) are row-data-only; provenance rides each kind’s JSON
page/list endpoint.
Known limitations
As-of-extraction enrichment (plan
report-run-generations
D2). Run universes are reproducible: phase 1 of every run materializes
the universe into report_run_universe and processing iterates that
snapshot, and the persons enrichment is pinned (as_of = the period end
for monthly kinds, the snapshot date for QC-7176 — surfaced as
provenance.as_of). The applications, enrollment-issuance, SNAP-ABAWD,
TANF (work-requirement / time-limit / work-activity), and CAPS
(childcare-authorization) enrichment legs have no valid-time corpus,
however: they are read as-of-extraction, so re-running the same period
after upstream edits can legitimately produce different enrichment values.
The generation’s extracted_at (surfaced in provenance) records the
read time. Valid-time corpora for those sources are filed follow-ups
(#1331 covers the ABAWD corpus). The exception is CMS-416, which has no
as-of-extraction leg at all: its inputs are pinned T-MSIS generation ids
recorded at enqueue, so a CMS-416 report is fully reproducible against its
published T-MSIS generations.
Federal-field dispositions (plan D8, resolved by #1333). Every formerly-silent approximation now has an explicit per-field disposition — sourced, NULL-as-unknown, or documented:
-
Sourced — T-MSIS
eligibility_start_date/eligibility_end_datecome from the determination’s own coverage window (the start falls back to the report month only when the wire carried no effective date);restricted_benefits_indicatorderives from the determination’sbenefit_type(family-planning-only and QMB-supplement coverage IS restricted scope; a missingbenefit_typemaps to unrestricted — restricted scope is asserted from positive evidence only). CMS-416total_member_monthsand CMS-64member_monthsare sourced from the run’s PINNED T-MSIS generations (#1585): one member-month = one DISTINCT(person, report_month)in scope — extract rows key on the enrollment (one per approved determination), so a same-month re-determination never double-counts a month — computed locally, no new upstream leg. A CMS-416 row still carries NULL when the figure is honestly unavailable (a run resumed from a pre-#1585 checkpoint, or months missing for a banded person) — never a fabricated value. -
NULL = unknown — ACF-199
months_other_states(no interstate TANF interchange source). A NULL is never a fabricated figure. -
Documented — CMS-416
eligible_for_screeningequalstotal_enrolled_childrenas this report’s operating identity: banded (under-21, non-CHIP) enrollees hold the 42 CFR 441.56 EPSDT entitlement, so the count is definitional here rather than an unmarked approximation; T-MSISmanaged_care_enrolledstaysfalse— canopy has no MCO/MMIS source (partner-blocked); ACF-199closure_reasonNULL means UNKNOWN (no case-closure leg exists), never "the case is open".
Error Codes
| Code | Meaning |
|---|---|
400 |
Invalid month/date/year format (or out-of-range year on the CMS-416 list), or unknown |
401 |
Missing or invalid JWT |
403 |
User-only surfaces (#1438): supervisor-or-above via the exchanged user-context arm — |
404 |
Report, snapshot, or run not found — for the generation-scoped reads: no PUBLISHED generation for the period, or none at all under the latest-published fallback (a reaped run is an indistinguishable 404) |
409 |
The target period already has a queued/running run — the body carries that run’s |
422 |
Unknown |
500 |
Upstream service failure during the synchronous overpayments roll-up / summary assembly — the only handlers that still call upstream services on the request path; the body is redacted ( |
503 |
Report runs disabled ( |
Upstream Service Clients
canopy-reporting queries these services via HTTP (configured by environment variables):
| Service | Env Var | Data Retrieved |
|---|---|---|
canopy-persons |
|
Household demographics |
canopy-applications |
|
Application dates and status |
canopy-snap |
|
ABAWD tracking data |
canopy-enrollment |
|
Benefit issuance records |
canopy-renewals |
|
Certification periods |
canopy-tanf |
|
TANF determinations, work requirements, time limits, work-activity summaries (ACF-199) |
canopy-medicaid |
|
Medicaid determinations with the 38-COA coverage-group mapping (T-MSIS) |
canopy-caps |
|
Active childcare authorizations — the WPR two-parent 55-hour standard discriminator (#1169) |