{"openapi":"3.1.0","info":{"title":"TradesAPI","description":"Real-time contractor license verification across all 50 US states + DC, plus 8 metro areas. Send a license number + state + trade, get back a normalized JSON result with status, expiration, and disciplinary history.","version":"1.0.0"},"paths":{"/health":{"get":{"tags":["health"],"summary":"Health Check","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/status.json":{"get":{"tags":["status"],"summary":"Per-state operational status (JSON)","description":"Returns the current operational status of every state scraper, tagged with tier classification and coverage summary. Useful for uptime monitors and partner integrations. Public, no auth required.","operationId":"status_json_status_json_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/verify":{"get":{"tags":["verify"],"summary":"Verify a single contractor license","description":"Look up a license by state code, license number, and trade. Returns normalized JSON with status, expiration, and disciplinary history. Each successful verification consumes one credit.","operationId":"verify_license_verify_get","parameters":[{"name":"state","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":2,"description":"Two-letter state code","title":"State"},"description":"Two-letter state code"},{"name":"license","in":"query","required":true,"schema":{"type":"string","maxLength":50,"description":"License number","title":"License"},"description":"License number"},{"name":"trade","in":"query","required":false,"schema":{"type":"string","maxLength":20,"description":"Trade type","default":"general","title":"Trade"},"description":"Trade type"},{"name":"city","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"description":"Optional municipality slug (e.g. 'houston') for city-level licenses. Composed into a jurisdiction code like 'TX_houston'.","title":"City"},"description":"Optional municipality slug (e.g. 'houston') for city-level licenses. Composed into a jurisdiction code like 'TX_houston'."},{"name":"license_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"description":"Optional roster filter, currently only consumed by the MS scraper where MSBOC publishes parallel commercial + residential rosters. Accepted values: 'commercial', 'residential'. When omitted, MS performs an ordered lookup (commercial first, then residential, preferring an active row on collision). Other states ignore this.","title":"License Type"},"description":"Optional roster filter, currently only consumed by the MS scraper where MSBOC publishes parallel commercial + residential rosters. Accepted values: 'commercial', 'residential'. When omitted, MS performs an ordered lookup (commercial first, then residential, preferring an active row on collision). Other states ignore this."},{"name":"fresh","in":"query","required":false,"schema":{"type":"boolean","description":"Force a live scrape, bypassing both the Redis cache and the DB-first read layer. Costs 2 credits (a default request costs 1) — the premium buys a guaranteed live-portal read. Useful when the caller knows a license just changed and wants to refresh `license_current` immediately. (Pricing lock 2026-07-06; qualifier-credential short-circuits still serve from DB at 1 credit because the portal cannot answer for them.)","default":false,"title":"Fresh"},"description":"Force a live scrape, bypassing both the Redis cache and the DB-first read layer. Costs 2 credits (a default request costs 1) — the premium buys a guaranteed live-portal read. Useful when the caller knows a license just changed and wants to refresh `license_current` immediately. (Pricing lock 2026-07-06; qualifier-credential short-circuits still serve from DB at 1 credit because the portal cannot answer for them.)"},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LicenseResult"}}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/batch":{"post":{"tags":["verify"],"summary":"Batch verify up to 200 licenses","description":"Verify multiple licenses in a single request. Up to 200 per call. Partial failures don't block other items — each result is returned independently. Credits are charged only for successful verifications. For larger workloads, chunk into 200-item calls client-side.","operationId":"batch_verify_batch_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchResponse"}}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/batch/jobs":{"post":{"tags":["batch-jobs"],"summary":"Submit an asynchronous batch verification job","operationId":"submit_batch_job_batch_jobs_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchJobsRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchJobSubmitted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/batch/jobs/{job_id}":{"get":{"tags":["batch-jobs"],"summary":"Get Batch Job","operationId":"get_batch_job_batch_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchJobStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/batch/jobs/{job_id}/items":{"get":{"tags":["batch-jobs"],"summary":"Get Batch Job Items","operationId":"get_batch_job_items_batch_jobs__job_id__items_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BatchJobItemOut"},"title":"Response Get Batch Job Items Batch Jobs  Job Id  Items Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/search":{"get":{"tags":["search"],"summary":"Search for contractor licenses by name","description":"Find licenses matching a business or individual name in a given state. Useful when the license number is unknown. Costs 2 credits per search.","operationId":"search_by_name_search_get","parameters":[{"name":"state","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":2,"description":"Two-letter state code","title":"State"},"description":"Two-letter state code"},{"name":"name","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":200,"description":"Business or individual name","title":"Name"},"description":"Business or individual name"},{"name":"trade","in":"query","required":false,"schema":{"type":"string","maxLength":20,"description":"Trade type","default":"general","title":"Trade"},"description":"Trade type"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"Max results","default":20,"title":"Limit"},"description":"Max results"},{"name":"city","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"Optional municipality. When set, targets the composite jurisdiction '{state}_{city}' (e.g. state=TN&city=nashville → TN_nashville scraper) instead of the state-level scraper.","title":"City"},"description":"Optional municipality. When set, targets the composite jurisdiction '{state}_{city}' (e.g. state=TN&city=nashville → TN_nashville scraper) instead of the state-level scraper."},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/resolve":{"post":{"tags":["resolve"],"summary":"Resolve a name to licensed contractors","description":"Match a business or individual name (optionally with state, zip, phone, or address hints) against the bulk-fed license database. Returns ranked candidates with per-field match evidence and a confidence tier. Works across jurisdictions when `state` is omitted. Costs 2 credits per call.","operationId":"resolve_name_resolve_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveResponse"}}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Payment Required"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/states":{"get":{"tags":["public"],"summary":"List States","description":"Return every supported jurisdiction with metadata MCP clients and\nintegration code can consume directly.\n\nTop-level row (one per state) shape:\n    - code: registry key (e.g. \"TX\")\n    - name: full name (e.g. \"Texas\")\n    - portal_url: canonical state lookup portal\n    - supported_trades: trades observed in fixtures (best-effort; some\n      search-only states may return [])\n    - supports_name_search: whether `/search` works for this state\n    - status: \"healthy\" | \"degraded\" | \"maintenance\"\n    - municipalities: nested rows (same shape as state, with `city`\n      replacing `name`) for each municipal scraper registered under\n      this state. Empty list if this state has no city-level scrapers.","operationId":"list_states_states_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List States States Get"}}}}}}},"/billing/balance":{"get":{"tags":["billing"],"summary":"Check your remaining credit balance","description":"Returns the API key's current credit balance + cumulative usage.","operationId":"get_balance_billing_balance_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BalanceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/dashboard/api/export":{"get":{"tags":["export"],"summary":"Export Audit Log","description":"Stream the caller's verification history as CSV or NDJSON.\n\nAuth: dashboard session cookie (same as `/dashboard/api/history`).\nDate range: required, max 90-day window, `to` cannot be in the future.\nRate limit: 10 exports/day per customer.\nOwnership: derived from session owner email; `api_key_id` filter\n    narrows within the caller's owned keys, never broadens.","operationId":"export_audit_log_dashboard_api_export_get","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date-time","title":"From"}},{"name":"to","in":"query","required":true,"schema":{"type":"string","format":"date-time","title":"To"}},{"name":"format","in":"query","required":false,"schema":{"enum":["csv","json"],"type":"string","default":"csv","title":"Format"}},{"name":"api_key_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key Id"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"license","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"License"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"include_sandbox","in":"query","required":false,"schema":{"type":"boolean","description":"Include sandbox-scoped calls in the export. Sandbox rows are marked with raw_data['sandbox']=true (see app/sandbox/responder.py). Set false to exclude them from compliance / billing reports where test traffic should not be present.","default":true,"title":"Include Sandbox"},"description":"Include sandbox-scoped calls in the export. Sandbox rows are marked with raw_data['sandbox']=true (see app/sandbox/responder.py). Set false to exclude them from compliance / billing reports where test traffic should not be present."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/subscriptions":{"post":{"tags":["webhooks"],"summary":"Create Subscription","description":"Create a new webhook subscription.\n\nReturns 201 on a brand-new row, 200 on reactivation of a\npreviously soft-deleted same-URL row, 409 when an active duplicate\nURL exists (with ``existing_id`` so the client can PATCH instead),\nand 429 when the active-subscription quota is exhausted.\n\nEntitlement (#1388): when ``webhook_require_active_monitor`` is on,\na subscription's ``event_filter`` selects candidate events but does\nNOT by itself entitle delivery — the event is delivered only if this\nAPI key also holds an active monitor for the event's\n``(jurisdiction_code, license_number)``. Provision monitors for the\nlicenses you want change events on.","operationId":"create_subscription_webhooks_subscriptions_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreateResponse"}}}},"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreateResponse"}}},"description":"OK"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["webhooks"],"summary":"List Subscriptions","description":"List the caller's subscriptions, newest first.","operationId":"list_subscriptions_webhooks_subscriptions_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}},{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Inactive"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/subscriptions/{sub_id}":{"get":{"tags":["webhooks"],"summary":"Get Subscription","description":"Get a single subscription. 404 cross-tenant.","operationId":"get_subscription_webhooks_subscriptions__sub_id__get","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Sub Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["webhooks"],"summary":"Update Subscription","description":"Update url / event_filter / active / description.\n\nURL changes re-run validation. Setting ``active=true`` on a\nsoft-deleted row reactivates it (mirrors POST-soft-deleted-URL\nsemantics). PATCH never resets the signing secret — see\n``/rotate-secret`` for that.\n\nEntitlement (#1388): editing ``event_filter`` changes which events\nare candidates but does not grant delivery entitlement. When\n``webhook_require_active_monitor`` is on, delivery still requires an\nactive monitor owned by this API key for the event's\n``(jurisdiction_code, license_number)``.","operationId":"update_subscription_webhooks_subscriptions__sub_id__patch","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Sub Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["webhooks"],"summary":"Delete Subscription","description":"Soft-delete: ``active=false``. The row stays for audit history.","operationId":"delete_subscription_webhooks_subscriptions__sub_id__delete","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Sub Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/subscriptions/{sub_id}/rotate-secret":{"post":{"tags":["webhooks"],"summary":"Rotate Secret","description":"Rotate the signing secret. Old secret valid for the grace window.\n\n1. Generate new plaintext + new hash\n2. Move current ``signing_secret_hash`` to\n   ``signing_secret_hash_prev``\n3. Set new hash + ``secret_rotated_at = now()``\n4. Return plaintext ONCE","operationId":"rotate_secret_webhooks_subscriptions__sub_id__rotate_secret_post","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Sub Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRotateSecretResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/subscriptions/{sub_id}/test":{"post":{"tags":["webhooks"],"summary":"Test Subscription","description":"Fire a synthetic test event to the subscription's URL.\n\nIdempotent (no row created). The customer-side endpoint sees a\nreal signed POST so they can validate their integration end-to-end\nwithout needing a real license-status change.","operationId":"test_subscription_webhooks_subscriptions__sub_id__test_post","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Sub Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/deliveries":{"get":{"tags":["webhooks"],"summary":"List Deliveries","description":"List delivery attempts for one of the caller's subscriptions.\n\nOwnership-scoped: 404 if the subscription belongs to another tenant\n(matches the rest of /webhooks). Newest attempts first.","operationId":"list_deliveries_webhooks_deliveries_get","parameters":[{"name":"subscription_id","in":"query","required":true,"schema":{"type":"integer","minimum":1,"title":"Subscription Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/deliveries/{attempt_id}/replay":{"post":{"tags":["webhooks"],"summary":"Replay Delivery","description":"Manually re-enqueue a delivery attempt.\n\nRate-limited at 5/hour/subscription (Important #8). Useful for\ncustomer-side debugging — replay a DLQ row after fixing the\nreceiving endpoint.","operationId":"replay_delivery_webhooks_deliveries__attempt_id__replay_post","parameters":[{"name":"attempt_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Attempt Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookReplayResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/monitor":{"post":{"tags":["monitoring"],"summary":"Create Monitor","description":"Add one license to the watch list.\n\nReturns 201 on a brand-new row, 200 on idempotent re-POST (active\nduplicate) or reactivation (soft-deleted match), 400 on\nuncovered jurisdiction or invalid subscription IDs, and 429 when\nthe active-monitor quota is exhausted.\n\nThe watch is *inert* in Phase 1 — it will not fire any\nnotifications until the Phase 2 fan-out integration ships. Customers\ncan populate the watch list ahead of that flip without observable\nside effects.","operationId":"create_monitor_monitor_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorPublic"}}}},"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorPublic"}}},"description":"OK"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["monitoring"],"summary":"List Monitors","description":"List the caller's monitors, newest first.","operationId":"list_monitors_monitor_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}},{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Inactive"}},{"name":"jurisdiction","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jurisdiction"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/monitor/bulk":{"post":{"tags":["monitoring"],"summary":"Bulk Create","description":"Bulk-add monitors via a CSV body.\n\nBody: ``Content-Type: text/csv`` with a header row. Recognised\ncolumns (case-insensitive): ``jurisdiction``, ``license_number``,\noptional ``label``, optional ``trade`` (#1285 — routes the refresh\nsweep's re-scrape in trade-routing states). Extra columns are\nignored.\n\nReturns 200 always (per-row outcomes in the body), or 400 when:\n- the CSV is structurally malformed (missing required columns,\n  not parseable as CSV)\n- the row count exceeds ``settings.monitoring_bulk_csv_max_rows``\n\nPer-row outcomes: ``created`` (new row inserted), ``existed``\n(active duplicate — no-op), ``error`` (per-row reason), or\n``quota_exceeded`` (mid-batch cap hit; remainder short-circuits).\n\nAll rows commit under a SINGLE transaction. Per-row savepoints\nwould let one bad row's failure not impact the others — but for\nMVP we treat row-level errors as soft (recorded in outcomes), so\nno savepoint is needed.\n\nAudit log: ONE summary row per bulk call (Important 5), not N.","operationId":"bulk_create_monitor_bulk_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/monitor/usage":{"get":{"tags":["monitoring"],"summary":"Get Usage","description":"Current period usage + projected monthly cost.\n\nCost is computed from the live active count at request time —\nNOT from a billing snapshot. The Phase 4 cron stamps charges\nagainst ``MonitoredLicense.monthly_credits_charged`` at month-end;\nthis endpoint just shows the customer the running total.","operationId":"get_usage_monitor_usage_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/monitor/{monitor_id}":{"get":{"tags":["monitoring"],"summary":"Get Monitor","description":"Get a single monitor. 404 cross-tenant.","operationId":"get_monitor_monitor__monitor_id__get","parameters":[{"name":"monitor_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Monitor Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["monitoring"],"summary":"Update Monitor","description":"Update label / notify / active flag.","operationId":"update_monitor_monitor__monitor_id__patch","parameters":[{"name":"monitor_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Monitor Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorPublic"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["monitoring"],"summary":"Delete Monitor","description":"Soft-delete: ``active=false``. The row stays for audit history.","operationId":"delete_monitor_monitor__monitor_id__delete","parameters":[{"name":"monitor_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Monitor Id"}},{"name":"x-api-key","in":"header","required":false,"schema":{"type":"string","description":"API key for authentication","default":"","title":"X-Api-Key"},"description":"API key for authentication"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"BalanceResponse":{"properties":{"credit_balance":{"type":"integer","title":"Credit Balance"},"usage_count":{"type":"integer","title":"Usage Count"},"owner":{"type":"string","title":"Owner"}},"type":"object","required":["credit_balance","usage_count","owner"],"title":"BalanceResponse"},"BatchJobItemOut":{"properties":{"position":{"type":"integer","title":"Position"},"jurisdiction":{"type":"string","title":"Jurisdiction"},"license_number":{"type":"string","title":"License Number"},"trade":{"type":"string","title":"Trade"},"status":{"type":"string","title":"Status"},"result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"duplicate_positions":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Duplicate Positions"}},"type":"object","required":["position","jurisdiction","license_number","trade","status","result","error","duplicate_positions"],"title":"BatchJobItemOut"},"BatchJobStatus":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"total_items":{"type":"integer","title":"Total Items"},"succeeded_items":{"type":"integer","title":"Succeeded Items"},"failed_items":{"type":"integer","title":"Failed Items"},"pending_items":{"type":"integer","title":"Pending Items"},"credits_charged":{"type":"integer","title":"Credits Charged"},"created_at":{"type":"string","title":"Created At"},"started_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started At"},"finished_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finished At"}},"type":"object","required":["job_id","status","total_items","succeeded_items","failed_items","pending_items","credits_charged","created_at","started_at","finished_at"],"title":"BatchJobStatus"},"BatchJobSubmitted":{"properties":{"job_id":{"type":"string","title":"Job Id"},"status":{"type":"string","title":"Status"},"total_items":{"type":"integer","title":"Total Items"},"unique_items":{"type":"integer","title":"Unique Items"}},"type":"object","required":["job_id","status","total_items","unique_items"],"title":"BatchJobSubmitted"},"BatchJobsRequest":{"properties":{"licenses":{"items":{"$ref":"#/components/schemas/VerifyRequest"},"type":"array","minItems":1,"title":"Licenses"}},"type":"object","required":["licenses"],"title":"BatchJobsRequest"},"BatchRequest":{"properties":{"licenses":{"items":{"$ref":"#/components/schemas/VerifyRequest"},"type":"array","maxItems":200,"minItems":1,"title":"Licenses","description":"List of licenses to verify (max 200)"}},"type":"object","required":["licenses"],"title":"BatchRequest"},"BatchResponse":{"properties":{"total":{"type":"integer","title":"Total"},"succeeded":{"type":"integer","title":"Succeeded"},"failed":{"type":"integer","title":"Failed"},"results":{"items":{"$ref":"#/components/schemas/BatchResultItem"},"type":"array","title":"Results"},"credits_charged":{"type":"integer","title":"Credits Charged"}},"type":"object","required":["total","succeeded","failed","results","credits_charged"],"title":"BatchResponse"},"BatchResultItem":{"properties":{"result":{"anyOf":[{"$ref":"#/components/schemas/LicenseResult"},{"type":"null"}]},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","title":"BatchResultItem"},"BulkResponse":{"properties":{"total":{"type":"integer","title":"Total"},"created":{"type":"integer","title":"Created"},"existed":{"type":"integer","title":"Existed"},"error":{"type":"integer","title":"Error"},"quota_exceeded":{"type":"integer","title":"Quota Exceeded"},"insufficient_credits":{"type":"integer","title":"Insufficient Credits","default":0},"results":{"items":{"$ref":"#/components/schemas/BulkRowResult"},"type":"array","title":"Results"}},"type":"object","required":["total","created","existed","error","quota_exceeded","results"],"title":"BulkResponse"},"BulkRowResult":{"properties":{"row":{"type":"integer","title":"Row"},"outcome":{"type":"string","title":"Outcome"},"id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Id"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","required":["row","outcome"],"title":"BulkRowResult"},"ErrorResponse":{"properties":{"error":{"type":"string","title":"Error"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"type":"object","required":["error"],"title":"ErrorResponse"},"EventFilter":{"properties":{"jurisdictions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Jurisdictions"},"field_names":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Field Names"},"license_numbers":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"License Numbers"}},"type":"object","title":"EventFilter","description":"Customer-supplied event filter.\n\n``None`` and ``{}`` both mean \"match every event\". Each list field\nis independently optional — ``jurisdictions=[\"TX\"]`` with\n``field_names=None`` matches every TX event regardless of which\nfield changed. ``license_numbers`` is a literal list (case-\ninsensitive comparison happens server-side at fan-out time)."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"LicenseResult":{"properties":{"valid":{"type":"boolean","title":"Valid"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"license_number":{"type":"string","title":"License Number"},"trade":{"type":"string","title":"Trade"},"expiration":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiration"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"status_normalized":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Normalized","description":"Normalized status bucket across states: active | expired | suspended | revoked | probation | inactive | unknown. Null when the state's mapping doesn't cover this specific raw status. Raw `status` is always preserved."},"state":{"type":"string","title":"State"},"disciplinary_actions":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Disciplinary Actions"},"disciplinary_data_available":{"type":"boolean","title":"Disciplinary Data Available","description":"True iff this jurisdiction is one where we extract board disciplinary/enforcement actions. When False, an empty `disciplinary_actions` means 'not collected for this jurisdiction', NOT a verified-clean record — so absence of data is never mistaken for absence of disciplinary history.","default":false},"bond_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Bond Info"},"insurance_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Insurance Info"},"classifications":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Classifications"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"},"source_metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Source Metadata","description":"Per-source / per-program tags for downstream parity-audit + ER disambiguation. Example: {'program': 'dial_construction'} on Iowa DIAL Construction Contractor registrations distinguishes them from the EEB electrical and PMSB plumbing streams."},"cached":{"type":"boolean","title":"Cached","default":false},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Which read layer served this response: 'cache' (Redis), 'db' (license_current bulk feed), or 'scrape' (live portal). Null on legacy paths predating the cut-over (e.g. sandbox)."},"last_verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Verified At","description":"Timestamp of the underlying observation. Equal to `checked_at` for fresh scrapes; older than `checked_at` when served from DB."},"staleness_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Staleness Seconds","description":"Age of the underlying observation in seconds. Zero for fresh scrapes; >0 when served from DB."},"sandbox":{"type":"boolean","title":"Sandbox","description":"True iff this response came from the sandbox responder (sandbox-scoped API key). Sandbox responses are deterministic fixtures, not live data. Always false for production scrapes — clients can rely on this field to assert they're talking to the live API.","default":false},"verification_type":{"anyOf":[{"type":"string","enum":["license","registration"]},{"type":"null"}],"title":"Verification Type","description":"Disambiguates the legal weight of a positive (`valid=true`) result:\n  - 'license' → competency license (the holder has met the state's exam / experience / continuing-ed bar for the trade).\n  - 'registration' → an administrative registration only. The holder has filed paperwork and (typically) carries insurance / a bond / workers'-comp coverage, but the state has NOT certified trade competency. Examples: PA HICPA Home Improvement Contractor registration, MT ERD Contractor Registration / ICEC.\n  - null → not specified by the upstream scraper. Treat as 'license' for backward compatibility (this is the default for the 40+ scrapers that pre-date the field, all of which surface true competency licenses)."},"license_class":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"License Class","description":"The issuer's exact classification term, e.g. 'Master Electrician'."},"facets":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Facets","description":"Normalized trade/role/scope facets from app.license_class_facets."},"credential_role":{"anyOf":[{"type":"string","enum":["standalone","qualifier"]},{"type":"null"}],"title":"Credential Role","description":"Distinguishes a standalone operating license from an individual qualifier credential that requires linkage to a parent business license to be operationally valid. Today only IL 105* (Qualifying Party ROOFING CONTRACTOR) surfaces 'qualifier'; all other paths leave this null (treat as 'standalone' for backward compatibility)."},"qualifier_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Qualifier Note","description":"Human-readable note when credential_role='qualifier' — explains the credential class and operational scope (e.g. requires linkage to a parent business license). Null for standalone licenses."},"checked_at":{"type":"string","format":"date-time","title":"Checked At"},"has_disciplinary_action":{"type":"boolean","title":"Has Disciplinary Action","description":"Quick-check boolean: true iff disciplinary_actions is non-empty.\n\nComputed on every read/serialization so in-place mutation of\ndisciplinary_actions after construction can never leave this field\nstale. Cannot be set by the caller — derived is the source of truth.","readOnly":true}},"type":"object","required":["valid","license_number","trade","state","has_disciplinary_action"],"title":"LicenseResult"},"MonitorCreateRequest":{"properties":{"jurisdiction":{"type":"string","maxLength":32,"minLength":1,"title":"Jurisdiction"},"license_number":{"type":"string","maxLength":100,"minLength":1,"title":"License Number"},"label":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Label"},"notify":{"anyOf":[{"$ref":"#/components/schemas/NotifyConfig"},{"type":"null"}]},"trade":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Trade"}},"type":"object","required":["jurisdiction","license_number"],"title":"MonitorCreateRequest"},"MonitorListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/MonitorPublic"},"type":"array","title":"Items"},"total_active":{"type":"integer","title":"Total Active"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["items","total_active","limit","offset"],"title":"MonitorListResponse"},"MonitorPublic":{"properties":{"id":{"type":"integer","title":"Id"},"jurisdiction":{"type":"string","title":"Jurisdiction"},"license_number":{"type":"string","title":"License Number"},"trade":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trade"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"notify":{"anyOf":[{"$ref":"#/components/schemas/NotifyConfig"},{"type":"null"}]},"active":{"type":"boolean","title":"Active"},"watching_since":{"type":"string","format":"date-time","title":"Watching Since"},"last_event_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Event At"},"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id"}},"type":"object","required":["id","jurisdiction","license_number","trade","label","notify","active","watching_since","last_event_at","entity_id"],"title":"MonitorPublic"},"MonitorUpdateRequest":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Label"},"notify":{"anyOf":[{"$ref":"#/components/schemas/NotifyConfig"},{"type":"null"}]},"active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Active"}},"type":"object","title":"MonitorUpdateRequest"},"NotifyConfig":{"properties":{"webhook_subscription_ids":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Webhook Subscription Ids"},"email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Email"}},"type":"object","title":"NotifyConfig","description":"Notification preferences for a monitored license.\n\nBoth fields are independently optional. ``webhook_subscription_ids``\nreferences entries in ``webhook_subscription`` owned by the same\napi_key (cross-tenant + soft-deleted IDs rejected at validate\ntime). ``email`` adds an out-of-band Resend notification (Phase 2\nintegrates the email path)."},"ResolveCandidate":{"properties":{"license":{"$ref":"#/components/schemas/LicenseResult"},"stable_entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stable Entity Id"},"match_weight":{"type":"number","title":"Match Weight"},"match_probability":{"type":"number","title":"Match Probability"},"tier":{"type":"string","title":"Tier"},"comparison_vector":{"additionalProperties":true,"type":"object","title":"Comparison Vector"}},"type":"object","required":["license","match_weight","match_probability","tier","comparison_vector"],"title":"ResolveCandidate","description":"One ranked match: the license projected to the standard API shape\nplus the resolver's evidence."},"ResolveRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":2,"title":"Name"},"state":{"anyOf":[{"type":"string","maxLength":2,"minLength":2,"pattern":"^[A-Za-z]{2}$"},{"type":"null"}],"title":"State"},"city":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"City"},"zip":{"anyOf":[{"type":"string","maxLength":10},{"type":"null"}],"title":"Zip"},"phone":{"anyOf":[{"type":"string","maxLength":30},{"type":"null"}],"title":"Phone"},"address":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Address"},"limit":{"type":"integer","maximum":20.0,"minimum":1.0,"title":"Limit","default":10}},"type":"object","required":["name"],"title":"ResolveRequest","description":"POST /resolve body — a name-only (or name-plus-hints) record to\nmatch against `license_current` across jurisdictions.\n\n`state`/`city` narrow the search; when omitted the match runs\ncross-jurisdiction (the case live `/search` cannot serve). `phone`,\n`zip`, `address` are optional evidence that strengthens or weakens\ncandidates via the comparison vector — they never filter on their own."},"ResolveResponse":{"properties":{"name":{"type":"string","title":"Name"},"jurisdiction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jurisdiction"},"total_candidates":{"type":"integer","title":"Total Candidates"},"candidates":{"items":{"$ref":"#/components/schemas/ResolveCandidate"},"type":"array","title":"Candidates"},"model_version":{"type":"string","title":"Model Version"},"checked_at":{"type":"string","format":"date-time","title":"Checked At"}},"type":"object","required":["name","total_candidates","candidates","model_version"],"title":"ResolveResponse"},"SearchQuery":{"properties":{"state":{"type":"string","maxLength":2,"minLength":2,"pattern":"^[A-Za-z]{2}$","title":"State"},"city":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"City"},"name":{"type":"string","maxLength":200,"minLength":2,"title":"Name"},"trade":{"type":"string","maxLength":20,"minLength":1,"title":"Trade","default":"general"}},"type":"object","required":["state","name"],"title":"SearchQuery"},"SearchResponse":{"properties":{"query":{"$ref":"#/components/schemas/SearchQuery"},"total_results":{"type":"integer","title":"Total Results"},"results":{"items":{"$ref":"#/components/schemas/SearchResultItem"},"type":"array","title":"Results"},"cached":{"type":"boolean","title":"Cached","default":false},"checked_at":{"type":"string","format":"date-time","title":"Checked At"}},"type":"object","required":["query","total_results","results"],"title":"SearchResponse"},"SearchResultItem":{"properties":{"name":{"type":"string","title":"Name"},"license_number":{"type":"string","title":"License Number"},"trade":{"type":"string","title":"Trade"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"state":{"type":"string","title":"State"},"confidence":{"type":"number","title":"Confidence"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"}},"type":"object","required":["name","license_number","trade","state","confidence"],"title":"SearchResultItem"},"UsageResponse":{"properties":{"active_count":{"type":"integer","title":"Active Count"},"current_month":{"type":"string","title":"Current Month"},"projected_cost_credits":{"type":"integer","title":"Projected Cost Credits"},"projected_cost_cents":{"type":"integer","title":"Projected Cost Cents"},"projected_cost_usd":{"type":"string","title":"Projected Cost Usd"},"current_tier":{"type":"string","title":"Current Tier"},"next_tier_threshold":{"type":"integer","title":"Next Tier Threshold"},"supported_jurisdictions":{"items":{"type":"string"},"type":"array","title":"Supported Jurisdictions"}},"type":"object","required":["active_count","current_month","projected_cost_credits","projected_cost_cents","projected_cost_usd","current_tier","next_tier_threshold","supported_jurisdictions"],"title":"UsageResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerifyRequest":{"properties":{"state":{"type":"string","maxLength":2,"minLength":2,"pattern":"^[A-Za-z]{2}$","title":"State","description":"Two-letter state code"},"city":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"City","description":"Optional municipality slug (e.g. 'chicago') for city-level licenses. Composed into a jurisdiction code like 'IL_chicago'. None = state-level."},"license":{"type":"string","maxLength":50,"minLength":1,"title":"License","description":"License number to verify"},"trade":{"type":"string","maxLength":20,"minLength":1,"title":"Trade","description":"Trade type","default":"general"}},"type":"object","required":["state","license"],"title":"VerifyRequest"},"WebhookCreateRequest":{"properties":{"url":{"type":"string","maxLength":2048,"minLength":1,"title":"Url"},"event_filter":{"anyOf":[{"$ref":"#/components/schemas/EventFilter"},{"type":"null"}]},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description"}},"type":"object","required":["url"],"title":"WebhookCreateRequest"},"WebhookCreateResponse":{"properties":{"id":{"type":"integer","title":"Id"},"url":{"type":"string","title":"Url"},"active":{"type":"boolean","title":"Active"},"event_filter":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Event Filter"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"signing_secret_prefix":{"type":"string","title":"Signing Secret Prefix"},"secret_rotated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Secret Rotated At"},"last_success_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Success At"},"last_failure_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Failure At"},"consecutive_failures":{"type":"integer","title":"Consecutive Failures"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"signing_secret":{"type":"string","title":"Signing Secret"}},"type":"object","required":["id","url","active","event_filter","description","signing_secret_prefix","secret_rotated_at","last_success_at","last_failure_at","consecutive_failures","created_at","updated_at","signing_secret"],"title":"WebhookCreateResponse","description":"Create / reactivate response — plaintext secret returned ONCE."},"WebhookDeliveryAttemptPublic":{"properties":{"id":{"type":"integer","title":"Id"},"subscription_id":{"type":"integer","title":"Subscription Id"},"event_id":{"type":"integer","title":"Event Id"},"attempt_number":{"type":"integer","title":"Attempt Number"},"attempted_at":{"type":"string","format":"date-time","title":"Attempted At"},"status":{"type":"string","title":"Status"},"http_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Http Status"},"response_body_truncated":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Body Truncated"},"latency_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latency Ms"},"next_retry_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Retry At"}},"type":"object","required":["id","subscription_id","event_id","attempt_number","attempted_at","status","http_status","response_body_truncated","latency_ms","next_retry_at"],"title":"WebhookDeliveryAttemptPublic","description":"Public projection of a delivery attempt row for /deliveries.\n\nThe full row contains operational fields the customer doesn't need\n(subscription_id is implicit when filtering by it; latency_ms is\nsurfaced for transparency)."},"WebhookDeliveryListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookDeliveryAttemptPublic"},"type":"array","title":"Items"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["items","limit","offset"],"title":"WebhookDeliveryListResponse"},"WebhookListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookSubscriptionPublic"},"type":"array","title":"Items"},"total_active":{"type":"integer","title":"Total Active"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["items","total_active","limit","offset"],"title":"WebhookListResponse"},"WebhookReplayResponse":{"properties":{"new_attempt_id":{"type":"integer","title":"New Attempt Id"}},"type":"object","required":["new_attempt_id"],"title":"WebhookReplayResponse"},"WebhookRotateSecretResponse":{"properties":{"id":{"type":"integer","title":"Id"},"signing_secret":{"type":"string","title":"Signing Secret"},"signing_secret_prefix":{"type":"string","title":"Signing Secret Prefix"},"secret_rotated_at":{"type":"string","format":"date-time","title":"Secret Rotated At"},"previous_secret_valid_until":{"type":"string","format":"date-time","title":"Previous Secret Valid Until"}},"type":"object","required":["id","signing_secret","signing_secret_prefix","secret_rotated_at","previous_secret_valid_until"],"title":"WebhookRotateSecretResponse","description":"Rotate-secret response — plaintext secret returned ONCE."},"WebhookSubscriptionPublic":{"properties":{"id":{"type":"integer","title":"Id"},"url":{"type":"string","title":"Url"},"active":{"type":"boolean","title":"Active"},"event_filter":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Event Filter"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"signing_secret_prefix":{"type":"string","title":"Signing Secret Prefix"},"secret_rotated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Secret Rotated At"},"last_success_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Success At"},"last_failure_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Failure At"},"consecutive_failures":{"type":"integer","title":"Consecutive Failures"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","url","active","event_filter","description","signing_secret_prefix","secret_rotated_at","last_success_at","last_failure_at","consecutive_failures","created_at","updated_at"],"title":"WebhookSubscriptionPublic","description":"Subscription representation visible on GET / list endpoints.\n\nNotably absent: ``signing_secret``. Plaintext is returned exactly\nonce (in the create / rotate-secret responses below) and never\nre-fetchable."},"WebhookTestResponse":{"properties":{"delivered":{"type":"boolean","title":"Delivered"},"http_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Http Status"},"latency_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latency Ms"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"sent_payload_summary":{"additionalProperties":true,"type":"object","title":"Sent Payload Summary"}},"type":"object","required":["delivered","http_status","latency_ms","error","sent_payload_summary"],"title":"WebhookTestResponse","description":"Response from a synthetic ``POST /test`` invocation.\n\nNo ``WebhookDeliveryAttempt`` row is created — the synthetic payload\nis built and POSTed (or attempted) in the request lifecycle and the\noutcome is returned inline. The customer's endpoint logs WILL show\na request; our own delivery log won't."},"WebhookUpdateRequest":{"properties":{"url":{"anyOf":[{"type":"string","maxLength":2048,"minLength":1},{"type":"null"}],"title":"Url"},"event_filter":{"anyOf":[{"$ref":"#/components/schemas/EventFilter"},{"type":"null"}]},"active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Active"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description"}},"type":"object","title":"WebhookUpdateRequest"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for authenticated endpoints. Sign up at /auth/login to get one. Every successful verification consumes one credit."}}}}