Typed conditions, holds, workflow, and correction states
Added GET /api/v1/permits/{permit_number}/conditions for source-published holds, notices, and conditions of approval. Permit history now preserves portal workflow disciplines, actors, comments, named activities, and an explicit is_correction classification. EPIC-LA generic workflow activities are no longer mislabeled as inspections.
Migration: none. The endpoint and fields are additive. Treat observed=false as “condition detail not present in the latest source representation,” not proof that an agency has cleared every condition.
Additive
Canonical permit resources and field provenance
Added POST /api/v1/permit-resolutions and GET /api/v1/permits/{permit_id}. Canonical UUIDs are deterministic across formatting variants and include jurisdiction identity. Resource fields publish the source system and URL, observation time, normalization version, confidence, availability, and retained raw value where available.
Migration: deploy database migration 068 before the application release. The client contract is additive and existing permit-number endpoints remain supported. New integrations should persist permit_id, use status_url when the resource has not yet been populated, and treat not_observed as unknown rather than empty.
Additive
Official Python and TypeScript SDKs
Added the generated signedoff-api Python package and @signedoff/api TypeScript package. Both are pinned to the public OpenAPI 1.0 contract, include canonical permit resources and lifecycle endpoints, and configure API-key authentication at client creation.
Client action: optional. Existing direct HTTP integrations remain supported. Generated clients no longer expose a duplicate per-operation X-API-Key argument because the OpenAPI security scheme is now the single authentication contract.
Additive
Expanded AI-assistant tools
The remote MCP server now exposes twelve permit tools. In addition to status and jurisdiction lookup, agents can search permits, resolve canonical identity, read canonical resources, inspect inspections, conditions, and history, and create, remove, or list permit watches.
Client action: none. Existing tool names and behavior are unchanged. MCP clients discover the additive tools through tools/list; all calls continue through the REST API's authentication, rate limits, entitlement checks, and usage metering.
Geography and parcel-portfolio discovery
Additive
Exact source scope and up to 25 properties per search
GET /api/v1/permits/search and saved monitored queries now accept normalized City of Los Angeles and Los Angeles County scopes plus one to 25 repeated parcel APNs/AINs. City scope selects LADBS, county scope selects EPIC-LA's unincorporated records, and parcel portfolios are sent to each selected source as exact bounded filters. Parcel formatting removes spaces and hyphens and collapses duplicates.
Migration guidance
This is additive for valid requests. Requests that previously supplied city or county and received unsupported_filter may now use the documented Los Angeles names. Repeat parcel instead of issuing one search per property. contractor is now explicitly rejected with 400 unsupported_filter: current discovery feeds do not expose contractor identity, and SignedOff does not substitute a frozen historical dataset.
General new-permit event stream
Additive
First-observed permits without saved-query ownership
Paid live keys with the Permit Discovery add-on can now subscribe a webhook to permit_created. A shared baseline-first LADBS and EPIC-LA monitor records minimal tenant-neutral permit identities and emits one tenant-scoped canonical event per subscribed key when SignedOff first observes a later identity. The event is independent of customer saved-query execution and does not consume saved-query slots or API credits.
Migration guidance
This adds the permit_created webhook event enum. Existing integrations need no change unless they validate the enum exhaustively. To opt in, add it to an eligible webhook subscription and process each event_id idempotently. The initial source baseline emits no historical events; partial or capped reads emit nothing, and observed_at is a SignedOff observation time rather than an official filing timestamp.
Jurisdiction reliability signals unified
Operational
Lookup outcomes, depth, freshness, and upstream health
SignedOff now classifies supported-jurisdiction permit lookups as cache hits, live successes, stale fallbacks, confirmed not-found results, or upstream failures. Aggregate internal scorecards combine the retained 90-day outcomes with the existing field-coverage sample, tenant-neutral cache freshness, live source state, and overlapping incident history. Stale fallbacks, circuit-open failures, and timeouts are now included instead of disappearing from jurisdiction reliability measurement.
Migration guidance
No public request or response schema changed, and no client action is required. Existing retry, stale-data, and error semantics are unchanged; this release makes their aggregate operational measurement complete and explicit.
Jurisdiction demand votes normalized
Behavior
One authenticated vote per key and jurisdiction
Repeating POST /api/v1/jurisdictions/request with the same API key no longer inflates demand. unique_keys_count now reflects normalized authenticated-key votes, while anonymous website requests remain visible only in the aggregate request count. SignedOff uses these privacy-safe aggregates with active live-key base-plan list-price signals to prioritize coverage expansion internally; the signal is not recognized revenue or a roadmap commitment.
Migration guidance
No request or response schema changed. Clients that intentionally sent duplicates to raise a count should stop doing so. Continue reusing one Idempotency-Key for transport retries; a repeated logical request from the same API key now returns the current aggregate without another vote.
Saved discovery monitoring and criteria-match events
Added
Daily first-seen permit alerts
Paid live keys with the Permit Discovery add-on can create, list, pause, update, and delete monitored queries through /api/v1/saved-searches. The first successful evaluation establishes a no-event baseline; later first-seen permit identities append owner-scoped criteria_match events to the canonical feed and any subscribed webhooks. Partial source reads emit nothing and retry on the next daily evaluation.
Migration guidance
This is additive. Subscribe an existing webhook to criteria_match, or consume GET /api/v1/events; process events idempotently by event_id. Developer, Pro, and Enterprise caps are 3, 10, and 50 active saved queries. CRUD and scheduled evaluations consume no API credits. Changing criteria intentionally resets the baseline.
Operational data retention bounded
Policy
Usage, search, event, and webhook records
Published the API Data Retention & Deletion Policy. Detailed API usage logs and the discovery criteria stored on those rows now have a 90-day active-database window. Canonical events and terminal webhook delivery records, including stored delivery payloads, retain their 30-day windows; pending deliveries remain protected until they reach a terminal state.
Migration guidance
Export detailed call history, discovery criteria, event-feed entries, or terminal webhook attempt records before their stated windows expire if your integration needs longer history. No API response schema, quota, price, webhook retry, or billing behavior changed.
Enterprise service-level boundaries
Contract
Platform SLOs and municipal-source exclusions
Published the API Service Levels & Enterprise SLA Policy: a 99.9% monthly SignedOff-controlled API availability objective, 99% five-minute webhook first-attempt objective, and 99% rolling 26-hour watch-attempt objective, each with explicit measurement and eligibility rules.
Migration guidance
No endpoint behavior changed. Treat municipal portal failures separately from SignedOff platform availability, and do not treat a webhook attempt as successful processing by your receiver. Contractual targets, credits, response times, and remedies apply only when stated in a signed Enterprise order form.
Jurisdiction coverage transparency
Added
Field coverage and continuous data-release metadata
Added the public GET /api/v1/jurisdictions/coverage endpoint. It publishes typed API capabilities, rolling 90-day field fill rates, sample sizes, and successful observation bounds for every supported jurisdiction without exposing permit or customer identifiers.
Migration guidance
No existing response changed. Integrations may adopt the endpoint additively. Treat fill_rate: null with tier: "unknown" as no observed sample, not zero source coverage; observed rates are descriptive rather than availability guarantees.
Commercial-use contract published
Client action
API commercial-use and redistribution terms
Published the API Commercial Use & Redistribution Terms. They expressly permit ordinary internal and customer-facing commercial integrations while defining boundaries for caching, attribution, raw-record resale, bulk mirroring, pass-through API access, model training, source restrictions, deletion, and official-record reliance.
Migration guidance
Review the API Terms before your next API use. Existing endpoint behavior and plan quotas are unchanged. Contact support before offering raw-data feeds, bulk redistribution, white-label API access, sublicensing, or model-training uses.
API platform hardening baseline
Reliability
Per-jurisdiction source status and incident history
The public API source status page reports operational, degraded, unavailable, and not-yet-observed upstream permit sources by jurisdiction. The latest failed observation becomes degraded immediately; sustained failures open the breaker and a durable privacy-safe incident, while the next successful response restores operational status and resolves the incident across application deployments. A no-cache JSON representation is available at /developers/status.json.
Migration: none. Use the status page before retrying a jurisdiction-specific 503 jurisdiction_unavailable.
Contract tooling
Python and TypeScript SDK generation is CI-gated
Every public OpenAPI pull request must now generate and compile disposable Python and TypeScript client artifacts with pinned toolchains. Generator warnings, missing client surfaces, Python syntax failures, and strict TypeScript errors block the contract change before merge.
Migration: none. This validates SDK readiness but does not announce or publish official SDK packages.
Added
Official request retry and integration playbook
The developer guide now defines bounded exponential backoff with full jitter, Retry-After handling, retryable status classes, monthly-quota behavior, idempotent POST retries, asynchronous polling, and webhook reconciliation.
Migration: audit generic retry middleware. Limit ordinary reads to four total attempts, never tight-loop a monthly-quota 429, preserve the same key and byte-identical body for supported idempotent POSTs, and poll the stored operation Location instead of resubmitting an accepted force refresh.
Client action
Discovery cursors are signed, versioned, and short-lived
Discovery now returns an opaque HMAC-signed v1 cursor bound to the original filters and jurisdiction scope. Each cursor expires after 15 minutes. Pagination continues to query live upstream sources and is best-effort rather than snapshot-consistent.
Migration guidance
Treat pagination.next_cursor as opaque; do not decode, edit, or construct it.
Reuse the exact filters and jurisdiction scope from the first page.
On 400 invalid_cursor after expiry, restart from the first page without a cursor.
Do not persist cursors as durable checkpoints. Because sources are live, deduplicate records by jurisdiction and permit number across pages.
Client action
Discovery rejects unsupported city and county filters
GET /api/v1/permits/search now returns RFC 9457 400 unsupported_filter when city or county is supplied. These parameters were previously accepted but ignored.
Migration guidance
Remove city and county from discovery requests.
Use jurisdiction=ladbs for City of Los Angeles.
Use jurisdiction=epicla for unincorporated Los Angeles County.
Omit jurisdiction to query both currently supported discovery backends.
Added
Canonical events feed and manual webhook replay
Added the tenant-scoped, 30-day GET /api/v1/events feed and manual replay for retained terminal webhook deliveries. Replays preserve the source event_id and occurrence timestamp while receiving a fresh delivery_id.
Migration: none. Use the feed as the authoritative oldest-first reconciliation sequence.
Contract
Webhook delivery ordering is now explicit
Webhook delivery is at-least-once and arrival order is not guaranteed across retries or manual replays. Automatic retries keep the same delivery_id; a manual replay gets a new delivery ID while preserving event_id.
Migration: deduplicate by delivery ID, process idempotently, and reconcile source events by event ID through the events feed.
Reliability
Bounded concurrent webhook delivery
Independent webhook subscriptions now progress through five bounded workers. Attempts for one subscription remain serial, all network I/O stays outside database transactions, and due-attempt selection is deterministic.
Migration: none; this is an operational reliability improvement.
Added
Durable asynchronous force refresh
Paid live refreshes support Prefer: respond-async, return 202 Accepted, and expose durable owner-scoped polling through GET /api/v1/operations/{operation_id}.
Migration: optional. Existing synchronous calls remain supported; asynchronous callers should follow Location and honor Retry-After.
Added
Durable operation completion webhooks
Webhook subscriptions can include operation_completed. One owner-scoped canonical event is emitted after a durable operation reaches succeeded or failed, with the polling URL and terminal result or error in data. A durable outbox marker recovers interrupted enqueueing without duplicating the source event.
Migration: optional. Add operation_completed to an existing webhook subscription when push completion is preferred over polling.
This page begins the maintained public changelog. Earlier releases are not reconstructed here. Future externally visible API changes will be added in reverse chronological order with migration guidance.