Requirements API
Query published utility requirements and migrate from the retired legacy read.
Requirements API
There are two requirement endpoints. Use the published one.
| Endpoint | Status | Reads |
|---|---|---|
GET /api/v1/published-requirements | Current | The published requirement tier, minus utility-rejected rows |
GET /api/v1/requirements | Retired — returns 410 | No corpus read; use the published endpoint |
Query Published Requirements
GET /api/v1/published-requirements
Returns the published packet requirements for one utility — every published row except the ones a
utility reviewer has explicitly rejected. This is the replacement for the legacy requirements read.
Each returned row carries a reviewStatus of approved (utility reviewers signed off) or pending
(awaiting a current utility decision), so you can tell the two apart without guessing.
This matches what a packet deficiency report can cite: reports draw from the same all-except-rejected set, so the requirements list shows what a report may raise. Pending rows are labelled honestly rather than hidden.
Coverage Policy
Everything published except rejected rows, with honest gaps. This surface never substitutes something adjacent for the thing you asked for:
- Utility resolution is exact. The server matches on the utility name, then on its canonical
slug (
"Nashville Electric Service"→nashville-electric-service). There is no fuzzy match and no nearest-neighbour guess. - An unknown utility returns an empty result, not legacy rows and not a similar utility's rows.
- An uncovered category returns an empty result.
category=feesalways returns an empty set — the published store has no fee role, and unrelated checks are never relabelled as fee requirements. - Rejected rows are omitted server-side. A row a utility reviewer rejected never appears in any response, in any count, on any surface.
- Everything else is served, with its review status attached. An incomplete utility review no
longer empties the response. Rows still awaiting a decision return with
reviewStatus: "pending"; rows the utility signed off on return withreviewStatus: "approved". - A stale decision counts as pending re-review. When decision-relevant content changes after a
verdict, the row reverts to
reviewStatus: "pending"until a reviewer records a current decision. It is never silently dropped or hidden. - Rows whose published wording is still under review are withheld and counted. They appear in
totaland infilteredOutCount, never inrequirements. This is a separate mechanism fromreviewStatus.
Read message and summary before treating an empty page as "this utility has no requirements":
they distinguish nothing is technically published, nothing in scope survived rejection, and
published wording is still in review.
Query Params
utility(required): utility name or canonical slugstate(optional, exactly 2 characters): echoed in the response for context. No published rule is state-conditional any more (the state condition was retired on 2026-11-04), so it does not narrow the result.regionis accepted as a compatibility alias for callers migrating off the legacy endpoint.category(optional):site_controltechnical_specsformsfees(always empty — see coverage policy)
ixType(optional):Tfor transmission orDfor distributionlimit(optional): 1-200, defaults to 50offset(optional): defaults to 0
Example Request
curl "https://gridwonk.com/api/v1/published-requirements?utility=nashville-electric-service&state=TN&category=forms" \
-H "Authorization: Bearer $GRIDBOOST_API_KEY"Review-Incomplete Response
This is the durable response shape when the requested role has published rows whose utility review
is still in progress. The rows are served — releaseStatus reports review progress, not whether
anything came back. Counts depend on that utility's current review state.
{
"utility": "nashville-electric-service",
"state": "TN",
"category": "forms",
"count": 13,
"total": 13,
"source": "dynamic_extraction",
"releaseStatus": "review_incomplete",
"message": "13 published packet requirements for nashville-electric-service in this scope; 13 are awaiting a current utility review decision.",
"summary": "13 published requirements; 13 awaiting utility review.",
"filteredOutCount": 0,
"dedupedCount": 0,
"requirements": [
{
"id": "application-form:0:total_cost_missing",
"requirementText": "Application Form — Distribution: Total system cost provided.",
"validationStatus": "published",
"extractedAt": "",
"acceptedDocumentTypes": [],
"timingRequirements": [],
"citations": ["Total System Cost (Required)"],
"reviewStatus": "pending",
"sources": [
{
"title": "Utility application instructions",
"documentType": "procedure",
"pageRef": "2"
}
]
}
],
"sources": [],
"limit": 50,
"offset": 0
}As utility reviewers work through the scope, those rows flip to "reviewStatus": "approved" and
releaseStatus advances toward released. Rows the utility rejects disappear from requirements
entirely. Pending wording is withheld separately and counted in filteredOutCount, as described
above.
Response Fields
| Field | Type | Description |
|---|---|---|
utility | string | The utility name as provided in the query. |
state | string | null | The two-character state code echoed from the query, or null. It no longer narrows rows. |
category | string | null | Requirement category filter applied, or null if not filtered. |
count | number | Number of requirements returned on this page. |
total | number | Every served rule in scope, including rows beyond this page and rows withheld for pending wording. Rows awaiting a utility decision are included; only utility-rejected rows are excluded. |
source | string | "dynamic_extraction" for a live read of the published store. |
releaseStatus | string | Typed utility-review progress: released, partially_released, review_incomplete, or not_published. It is not a serving gate — review_incomplete still returns rows. Use it to describe review state, and per-row reviewStatus to judge an individual rule. |
message | string | Human-readable outcome — what was served, how much is awaiting a utility decision, wording still in review, or no technical publication. |
summary | string | One-line count and review-progress summary suitable for display. |
filteredOutCount | number | Served rows withheld because their published wording is still under review. Separate from reviewStatus, which marks rows that are served. |
dedupedCount | number | Always 0 today; the published reader performs no duplicate collapse. Present for contract compatibility. |
requirements | array | Requirement objects for this page (see below). |
sources | array | Deduplicated source list covering the returned page only. |
limit | number | Page size used for this response. |
offset | number | Offset used for this response. |
Requirement Object Fields
| Field | Type | Description |
|---|---|---|
id | string | Identifier of the rule within the current published set (role:index:findingId). Not guaranteed stable across republishes — do not key long-lived state on it. |
requirementText | string | Reviewed customer-facing wording, prefixed by its group heading ("<Role> — <Variant>: <rule>"). |
validationStatus | string | Always "published" for wire compatibility. Read reviewStatus for the utility-review state of the row. |
extractedAt | string | Always "". This surface does not expose an extraction timestamp; read reviewStatus for utility approval. |
acceptedDocumentTypes | string[] | Always empty on this surface. |
timingRequirements | string[] | Always empty on this surface. |
depositInLieuAllowed | boolean | Optional; present only when the published rule records it. |
citations | string[] | Quoted citation text supporting the rule. |
reviewStatus | string | "approved" — utility reviewers signed off on this row; or "pending" — awaiting a current utility decision, including a decision made stale by a later change. Rejected rows are never returned, so there is no third value. Absent on the deprecated legacy read. |
sources | array | Source documents for this rule: title, documentType: "procedure", and optional url / pageRef. |
Rate Limits
Standard API-key per-minute and daily limits apply, plus the daily unique-utility cap shared with
utility matching. Exceeding the cap returns RATE_LIMITED; you can still re-query utilities you
have already accessed that day. Unlike the legacy endpoint, this read emits no webhook event —
a read is not an extraction.
Other Bindings
| Surface | Binding |
|---|---|
| SDK | client.queryPublishedRequirements(options) — strict QueryPublishedRequirementsResult type |
| CLI | gridwonk requirements published --utility <name> |
| MCP | query_published_requirements tool, and the gridwonk://utilities/{utility}/requirements resource |
The SDK and CLI default to the canonical https://gridwonk.com API origin; use a base URL override
only for an approved alternate environment.
The SDK method calls this endpoint directly. The CLI wraps that SDK method and deliberately does not pre-resolve the utility name — resolution stays exact and server-side, so an unknown utility comes back as an honest empty answer rather than a guessed neighbour.
The MCP tool takes the same arguments and, in stateful sessions, inherits utility and state
from the active work item when they are omitted. See MCP API.
Query Requirements (legacy — retired)
GET /api/v1/requirements
Retired. Production no longer reads the frozen legacy
ix_requirementscorpus through this route. UseGET /api/v1/published-requirementsinstead.Authenticated calls now return typed
410 Gone/ENDPOINT_GONEwith the replacement path.
Deprecation Signals (historical notice)
Before retirement, handler-produced responses carried these migration signals. The current 410
keeps the two headers and the replacement body field:
| Signal | Value |
|---|---|
Deprecation header | true |
Link header | </api/v1/published-requirements>; rel="successor-version" |
replacement body field | "/api/v1/published-requirements" |
The replacement field mirrors the headers for clients that only parse bodies. Authentication and
per-minute rate-limit rejections can still occur in the API-key wrapper before the retired handler,
so those wrapper responses do not carry the retirement headers. The RFC 8594 Sunset value was
announced as 2026-08-11T19:00:00Z and remains on the handler's 410 response.
Retirement Stages
| Stage | State today | What changes |
|---|---|---|
| 1. Deprecation signals | Complete | Deprecation: true, Link rel="successor-version", and the replacement body field announced the replacement before retirement. |
| 2. Sunset announcement | Complete | The retirement instant was 2026-08-11T19:00:00Z. |
3. 410 Gone | Live since 2026-08-11 | The endpoint no longer reads the legacy corpus and returns typed 410. |
The retirement stages are complete. The response is now:
{
"error": {
"code": "ENDPOINT_GONE",
"message": "This endpoint has been retired. Use GET /api/v1/published-requirements for published Packet Intake requirements."
},
"replacement": "/api/v1/published-requirements",
"requestId": "req_xxx"
}The legacy corpus is never read on this route and the requirements.extracted webhook event stops
firing for this endpoint. See ENDPOINT_GONE for the error
contract and a handling snippet.
Migration notes:
regionbecomesstateon the replacement (the replacement also acceptsregionas an alias).limitrises from a maximum of 50 to a maximum of 200.- The replacement emits no
requirements.extractedwebhook event. - The response shape changes: see Response Fields above.
Historical Current Procedure Enforcement
This section records how the legacy endpoint behaved before retirement. No current route can
restore this behavior. The published endpoint has no shadow/enforcement mode: it serves the Packet
Intake store and exposes each row's utility-review state as approved or pending.
The ENFORCE_CURRENT_PROCEDURE_REQUIREMENTS flag controls whether legacy customer-facing reads filter to only current usable requirements:
| Mode | Behavior |
|---|---|
Shadow mode (false) | Returns the existing displayable extracted corpus so rollout does not hide utilities before review cleanup is complete. The message field may include shadow diagnostics, such as how many returned matches would remain customer-usable after enforcement. |
Enforcement mode (true) | Returns only requirements that pass the current-procedure usability check: extractionOutcome: "requirements_found", validationStatus: "validated", linked documentId, linked procedure document with approved/manual-override discovery and completed digitization, and matching source hash when both hashes exist. |
Deprecated, rejected, pending-review, missing-source, and source-hash-mismatched requirements can still appear in admin review or migration tooling, but they are not customer-usable API results when enforcement is on.
Historical Query Params
These parameters describe the removed success handler. The permanent 410 tombstone does not parse them.
utility(required)region(optional, 2-char)category(optional):site_controltechnical_specsformsfees
ixType(optional):Tfor transmission orDfor distributionlimit(optional): 1-50, defaults to 50offset(optional): defaults to 0
Example Request (retired path)
curl "https://gridwonk.com/api/v1/requirements?utility=duke-energy®ion=NC&category=site_control" \
-H "Authorization: Bearer $GRIDBOOST_API_KEY"Historical Success Response
{
"utility": "duke-energy",
"state": "NC",
"category": "site_control",
"requirements": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"category": "site_control",
"key": "lease_agreement",
"value": {
"description": "Executed lease or ownership documentation for the project site",
"required": true,
"stage": "application"
},
"spec": {
"site_control_requirements": {
"interpreted_summary": "Executed lease or ownership documentation for the project site"
}
},
"requirementText": "Executed lease or ownership documentation for the project site",
"ixType": "D",
"extractedAt": "2026-02-15T08:30:00.000Z",
"validationStatus": "validated"
},
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"category": "site_control",
"key": "proof_of_ownership",
"value": {
"description": "Deed or title documentation proving site ownership or control",
"required": true,
"stage": "application"
},
"spec": null,
"requirementText": "Deed or title documentation proving site ownership or control",
"ixType": "D",
"extractedAt": "2026-02-15T08:30:00.000Z",
"validationStatus": "validated"
}
],
"count": 2,
"total": 12,
"limit": 2,
"offset": 0,
"source": "dynamic_extraction",
"message": "Successfully loaded 2 of 12 requirement(s) from IX Requirements Agent database."
}Response Fields (legacy)
| Field | Type | Description |
|---|---|---|
utility | string | The utility name as provided in the query. |
state | string | null | Two-character state/region code, or null if not filtered. |
category | string | null | Requirement category filter applied, or null if not filtered. |
requirements | array | Array of matching requirement objects (see below). |
count | number | Number of requirements returned in this page. |
total | number | Total matching rows before pagination. |
limit | number | Page size used for this response. |
offset | number | Offset used for this response. |
source | string | Data source: "dynamic_extraction" when requirements are found in the database, "fallback_static" when no results are available. |
message | string | Human-readable summary of the query result. |
Requirement Object Fields (legacy)
Each entry in the requirements array contains:
| Field | Type | Description |
|---|---|---|
id | string | UUID of the requirement record. |
category | string | Requirement category (site_control, technical_specs, forms, fees). |
key | string | Machine-readable requirement identifier (e.g., lease_agreement). |
value | object | Structured data extracted from the source document. Contents vary by requirement type. |
spec | object | null | Full rule-pack style requirement object when available, including site-control detail. |
requirementText | string | null | Human-readable requirement text derived from spec or value. |
ixType | "T" | "D" | null | Transmission/distribution applicability when known. |
extractedAt | string | ISO 8601 timestamp of when the requirement was extracted. |
validationStatus | string | Current validation state, usually "pending", "validated", or "disputed". Customer-facing enforcement requires "validated". |
SDK and CLI Retirement
The TypeScript SDK client.queryRequirements() method and the CLI requirements query /
requirements list aliases have been removed. Use queryPublishedRequirements() and
gridwonk requirements published.
Notes (legacy)
- The retired route emits no
requirements.extractedwebhook event. The published endpoint also has no read-side webhook. - The former success handler shared the daily unique-utility cap used by utility matching. The 410 guard runs before that check.
- Results reflect the frozen extracted requirement corpus. The utility name is normalized to slug format (e.g., "Duke Energy" becomes
duke-energy) with fuzzy matching as a fallback. The published endpoint removes that fuzzy fallback deliberately. - When no requirements are found, the response returns
count: 0,source: "fallback_static", and an emptyrequirementsarray. - In shadow mode,
source: "dynamic_extraction"can include pending, deprecated-source, or missing-source rows from the existing corpus. Treat the shadow diagnostic as the customer-usable count forecast, not as the returned count.