# Nightboard Ops

> Florida municipal permit filings with DBPR and Sunbiz resolution. Anonymous reads stay open. A presented `X-API-Key` is metered.

## Specification

- OpenAPI: https://nightboardops.com/.well-known/openapi.json
- Interactive docs: https://nightboardops.com/docs
- Agent notes: https://nightboardops.com/agents.md
- Capability card: https://nightboardops.com/.well-known/agent-card.json

## Authentication

- Header: `X-API-Key`
- Trial token shape: `pk_live_` plus 64 hex characters
- Trial limit: 25 events, then HTTP 429 with `X-Stripe-Checkout-Url`
- Requests without a key are not metered

## Export

- `GET /api/v1/export/miami-dade` and `GET /api/v1/export/orlando`
- Query `csi_division`: `26` electrical, `22` plumbing, `01` or `06` building
- Query `format`: `zip` (default) or `json` (top 5 rows)
- Query `filed_after` and `filed_before`: inclusive `filed_at` bounds
- ZIP members are streamed. Zero valuations are omitted from webhook events

## Audit

- `GET /api/v1/audit/licenses` — DBPR status and 15/30/60 day expiration windows. Requires `audit_addon` or enterprise. Otherwise HTTP 403 `audit_addon_required`
- `GET /api/v1/audit/licenses/expiring?days=30&trade=EC` — paged licensees inside that window. Same add-on gate
- A sixth simultaneous request for one key or client address returns HTTP 429 `concurrency_limit_exceeded`
- `POST /api/v1/developer-keys` — email plus MSA `33100` (Miami) or `36740` (Orlando). Returns one 500-event trial key. A key scoped to the other MSA receives HTTP 403 `geographic_boundary_exceeded` on the Miami-Dade export
- `GET /api/v1/audit/valuations` — rolling 30, 60, and 90 day permit dollars per contractor, positive amounts only

## Other routes

- `GET /v1/telemetry` — filings, registry counts, watermarks, WAL status, outbox
- `GET /health`
- `POST /api/chat` — concierge. Returns 503 `gemini_unconfigured` when no key is configured on the host
- `POST /v1/webhooks/ingest` — HMAC-SHA256 `X-Signature`. Missing signature is 503. A bad digest is 401
- Outbound deliveries keep `X-Signature` and add `webhook-id`, `webhook-timestamp`, and `webhook-signature` (`v1,<hex>` of `id.timestamp.body`)
- `POST /v1/webhooks/ingest` accepts that same trio or legacy `X-Signature`. A `webhook-timestamp` more than 300 seconds off is HTTP 401
