Skip to main content
GET
Get a permit by canonical_uid
The per-permit detail endpoint. Returns a single fully enriched PermitResponse row by its stable cross-jurisdictional canonical_uid. The shape returned is the same PermitResponse schema as /v1/permits, with one difference: the nested pluto object (PLUTO parcel-data presence + DQ status) is populated here, NULL on the list endpoint — see the asymmetry note.

Authentication

Authorization: Bearer <your-api-key> — see Authentication.

The canonical_uid path parameter

canonical_uid is the stable cross-jurisdictional permit identifier:
The source_permit_id portion routinely contains characters that are reserved or unsafe in a URL path segment.

Encoding requirement — clients MUST encodeURIComponent

Every client MUST percent-encode the canonical_uid before placing it in the path (JavaScript: encodeURIComponent(canonical_uid); Python: urllib.parse.quote(canonical_uid, safe="")). Four hostile shape classes appear in real canonical_uid values: Worked examples:
Skipping the encode is the most common integration mistake: an unencoded / will be parsed as a path-segment break, and an unencoded | or space is invalid in a URL path segment per RFC 3986. The server returns 400 Malformed canonical_uid for the shapes it can detect at parse time, and falls through to 404 for shapes that produce a valid-looking but non-matching key.

Response shape

Identical to PermitResponse on /v1/permits — 58 fields including the enrichment set (cohort, dq_flags, top-level PLUTO-derived fields, description_llm_enriched). The differences on this endpoint are:
  1. pluto is populated when the row’s parcel_apn matches a PLUTO bbl row (NYC only today). For NYC rows where the parcel does not match a PLUTO row, AND for all non-NYC rows, pluto remains null.
  2. The response body is the single row object — not wrapped in items: [...] — and there is no pagination envelope.
Elided fields above resolve from the same PermitResponse schema — see the listing endpoint’s response shape for the full field list.

The nested pluto object semantics

pluto reports the PLUTO join + per-flag DQ status, not the PLUTO values themselves (those are denormalized into top-level fields like year_built, building_class).
  • is_placeholderyear_built flagged as a placeholder in PLUTO.
  • oobyear_built flagged as out-of-bounds in PLUTO.
Per the SD-039 semantic, both default to false when the join landed but the upstream PLUTO DQ flag was NULL (the “rule has not flagged this value” meaning). pluto: null (the field itself) means the join didn’t land at all — either non-NYC, or no parcel_apnbbl match.

Errors

All error responses follow RFC 9457 problem+json — see Errors for the envelope.

400 Invalid canonical_uid

Returned when the path segment can be parsed but doesn’t conform to the {jurisdiction_id}:{source_permit_id} shape (e.g. missing the : separator, or jurisdiction_id is non-numeric).

404 Permit not found

Returned when the canonical_uid is well-formed but no matching permit exists.

422 Validation Error

Returned when the canonical_uid is path-parameter-invalid (rare — typically caught at the framework layer before the endpoint runs).

Quotas

Quotas are tier-based and apply to every successful request against this endpoint. See Rate limits for the matrix
  • the X-RateLimit-* response headers + the 429 problem+json shape.

Example request

JavaScript / TypeScript:
Python: