propraven-com Zoning API

Zoning from the jurisdiction that issues it: the raw code as published, overlays, and ordinance-parsed attributes with a citation per value, plus honest per-jurisdiction coverage. Account required.

Operations 3

GET /api/v1/parcels/{id}/zoning Zoning of a parcel, from the issuing jurisdiction #
GET /api/v1/zoning/search Screen parcels by zoning #
GET /api/v1/zoning/coverage Zoning coverage per jurisdiction #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/propraven-com:propraven-com-zoning-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

propraven-com-zoning-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: PropRaven Zoning API
  version: 1.2.1
  description: 'National property intelligence API: US parcels with ownership, valuation, permit, deed, risk and market data.


    **Base URL.** `https://propraven.com/api/v1` (every path below starts with `/api/v1`; `https://api.propraven.com/api/v1` serves the same API). The REST API is for server-side use: it sends no CORS headers, and API keys are secret.


    **Authentication.** `Authorization: Bearer pz_…`. Create a key in the dashboard. A few endpoints are key-optional (marked with an empty security requirement); anonymous calls to them are limited to 100 a day and 60 a minute per IP. The Free plan includes 1,000 API calls a month (60 a minute, 1,000 a day); `GET /api/v1/account/usage` reports your plan, period usage and limits.


    **Numbers.** Since API 1.2.0, every field this spec types as `number` or `integer` is a JSON number. (Before 1.2.0, values read from Postgres `numeric` and `bigint` columns, such as `total_assessed_value`, were sent as strings like "19388200.00".) Identifiers stay strings: `parcel_id`, `apn`, `county_fips`, `state_fips`, `zip`.


    **Errors.** Every error is an RFC 7807 problem (`application/problem+json`, schema `Problem`) with a stable `code`. Bad parameters are a 400 `invalid_parameter` naming each one in `errors`; an unknown endpoint is a 404 `not_found`; an unsupported method is a 405 with an `Allow` header; a query that runs out of time is a 504 `query_timeout` with `Retry-After`. Error bodies never contain database text. Paid endpoints answer an unpaid call with an x402 `402 Payment Required` envelope (`x402Version`, `accepts`).


    **Pagination.** List endpoints take `limit` and `offset` and return `{data, total, limit, offset}`; request the next page with `offset + limit` until a page is shorter than `limit` or `offset` reaches `total`. `GET /api/v1/search/full` uses a keyset cursor: pass the previous page''s `nextCursor` as `after` until `nextCursor` is null.


    **Rate limits.** Burst and daily windows by plan: Free 60 a minute / 1,000 a day; Developer 3,000 a minute / 100,000 a day; Team 12,000 a minute / 1,000,000 a day; key-optional endpoints called without a key, 60 a minute / 100 a day per IP. Authenticated responses carry `X-RateLimit-Limit` / `-Remaining` / `-Reset` (Unix seconds), all describing the window that will refuse you first. Every 429 and every 503 carries `Retry-After` (seconds): wait at least that long, then retry with backoff. A **429** means your plan''s window is used up (`rate_limit_exceeded`); or the key is cooling down for up to 15 minutes because its request rate spiked far above its own recent baseline (`key_cooldown`; it is not banned or revoked, and your other keys are unaffected); or one address failed authentication more than 60 times in a minute (`auth_failures_exceeded`). A **503** means PropRaven is briefly shedding load to protect the service (`load_shed`; calls without a key first, paid plans last) or the rate limiter is briefly unavailable. The MCP endpoint (`/mcp`) answers the same cases with the same status and `Retry-After`, plus a JSON-RPC error with code -32003.


    **SDKs.** Official clients generated from this spec: `@propraven/sdk` (npm), `propraven` (PyPI) and `github.com/jdw2111/propraven-go`. Each retries 429/503/504 with backoff, raises typed errors on the `Problem` shape, paginates, and verifies webhook signatures. The `x-sdk-group`, `x-sdk-method` and `x-sdk-pagination` extensions name each operation in the SDKs.'
  contact:
    name: PropRaven Support
    url: https://propraven.com/docs
    email: support@propraven.com
  license:
    name: Commercial
    url: https://propraven.com/terms#data-license
servers:
- url: https://propraven.com
  description: Production. Every path starts with /api/v1. (https://api.propraven.com serves the same API.)
security:
- bearerAuth: []
tags:
- name: Zoning
  description: 'Zoning from the jurisdiction that issues it: the raw code as published, overlays, and ordinance-parsed attributes with a citation per value, plus honest per-jurisdiction coverage. Account required.'
paths:
  /api/v1/parcels/{id}/zoning:
    get:
      operationId: getParcelZoning
      tags:
      - Zoning
      security:
      - bearerAuth: []
      summary: Zoning of a parcel, from the issuing jurisdiction
      description: 'The parcel''s zoning code exactly as the issuing jurisdiction published it, the issuing body (a city or town, not the county), overlays as a set, split zoning, and, where the ordinance has been parsed, the underwriting attributes (minimum lot area, height, FAR/density, setbacks, by-right multifamily, ADU), each with its ordinance section, verbatim quote and link in `attributes.citations`. Null is never zero or false. `{ zoning: null, reason: "not_collected" }` means the parcel''s jurisdiction has not been collected yet; it says nothing about whether the parcel is zoned. A research starting point, not a legal determination. Account required.'
      parameters:
      - name: id
        in: path
        required: true
        description: 'Parcel identifier: the canonical `state_fips:county_fips:parcel_id` form (e.g. `37:119:12104406`), the legacy 5-digit `county_fips5:parcel_id` form, or a PropRaven parcel UUID. URL-encode it.'
        schema:
          type: string
          example: 37:119:12104406
      responses:
        '200':
          description: The parcel's zoning, or not_collected.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                type: object
                required:
                - zoning
                properties:
                  zoning:
                    oneOf:
                    - $ref: '#/components/schemas/ParcelZoning'
                    - type: 'null'
                  reason:
                    type: string
                    enum:
                    - not_collected
                  coverage:
                    type: array
                    items:
                      $ref: '#/components/schemas/ZoningCoverageRow'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
        '404':
          $ref: '#/components/responses/NotFound'
      x-sdk-group: zoning
      x-sdk-method: parcel
  /api/v1/zoning/search:
    get:
      operationId: searchZoning
      tags:
      - Zoning
      security:
      - bearerAuth: []
      summary: Screen parcels by zoning
      description: Parcels by exact raw code (case-sensitive), base code, overlay, derived family, or parsed ordinance attribute. Attribute filters EXCLUDE parcels whose code is unparsed, so an empty result means unknown, not none; `dictionary_warning` and `coverage` say how much was parsed. Unknown parameters are a 400. Account required.
      parameters:
      - name: state_fips
        in: query
        schema:
          type: string
      - name: county_fips
        in: query
        schema:
          type: string
      - name: jurisdiction_id
        in: query
        schema:
          type: string
      - name: raw_code
        in: query
        schema:
          type: string
        description: Exact published code(s), comma-separated or repeated.
      - name: base_code
        in: query
        schema:
          type: string
      - name: family
        in: query
        schema:
          type: string
      - name: has_overlay
        in: query
        schema:
          type: string
      - name: without_overlay
        in: query
        schema:
          type: boolean
      - name: multifamily_by_right
        in: query
        schema:
          type: boolean
      - name: adu_allowed
        in: query
        schema:
          type: boolean
      - name: min_lot_area_sqft_max
        in: query
        schema:
          type: number
      - name: max_height_ft_min
        in: query
        schema:
          type: number
      - name: limit
        in: query
        schema:
          type: integer
          minimum: 1
          maximum: 500
          default: 50
      - name: offset
        in: query
        schema:
          type: integer
          minimum: 0
      responses:
        '200':
          description: Matching parcels with the coverage of the jurisdictions searched.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                type: object
                required:
                - results
                - count
                - limit
                - offset
                - coverage
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/ParcelZoning'
                  count:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
                  coverage:
                    type: array
                    items:
                      $ref: '#/components/schemas/ZoningCoverageRow'
                  dictionary_warning:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
      x-sdk-group: zoning
      x-sdk-method: search
  /api/v1/zoning/coverage:
    get:
      operationId: getZoningCoverage
      tags:
      - Zoning
      security:
      - bearerAuth: []
      summary: Zoning coverage per jurisdiction
      description: One row per registered zoning body with its honest `servingStatus` (served / partial / no_zoning / failed / registered) and reason, parcels matched against a measured denominator, distinct codes, overlays, and how many codes have parsed ordinance attributes (zeros included). Account required; the same table is on https://propraven.com/coverage.
      parameters:
      - name: state_fips
        in: query
        schema:
          type: string
      - name: jurisdiction_id
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Coverage rows and totals.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                type: object
                required:
                - coverage
                - totals
                properties:
                  coverage:
                    type: array
                    items:
                      $ref: '#/components/schemas/ZoningCoverageRow'
                  totals:
                    type: object
                    additionalProperties:
                      type:
                      - number
                      - 'null'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
      x-sdk-group: zoning
      x-sdk-method: coverage
components:
  headers:
    X-RateLimit-Reset:
      description: Unix timestamp (seconds) when the window described by X-RateLimit-Limit resets.
      schema:
        type: integer
        example: 1711670400
    Retry-After:
      description: Seconds to wait before retrying.
      schema:
        type: integer
        example: 5
    X-RateLimit-Remaining:
      description: Calls left in the window described by X-RateLimit-Limit.
      schema:
        type: integer
        example: 997
    X-Request-Id:
      description: Support handle for this request; equals the problem body's `request_id`. Sent on every error response, including 401.
      schema:
        type: string
    X-RateLimit-Limit:
      description: Size of the rate-limit window that will refuse you first — the per-minute burst window or the per-day window, whichever has fewer calls left. Limit, Remaining and Reset always describe that same window. Sent on every authenticated response (and on anonymous responses of key-optional endpoints).
      schema:
        type: integer
        example: 1000
  responses:
    NotFound:
      description: The resource (or the endpoint) does not exist.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://api.propraven.com/errors/not-found
            title: Not Found
            status: 404
            detail: No API endpoint at /api/v1/doesnotexist.
            code: not_found
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
    BadRequest:
      description: A query or body parameter is invalid. Nothing was read or charged.
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://api.propraven.com/errors/invalid-parameter
            title: Bad Request
            status: 400
            detail: 'limit: must be >= 1'
            code: invalid_parameter
            errors:
            - param: limit
              message: must be >= 1
    Unauthorized:
      description: Missing, malformed or invalid credential.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://api.propraven.com/errors/authentication-required
            title: Unauthorized
            status: 401
            detail: Provide an API key in the Authorization header ('Bearer pz_...') or sign in via the web dashboard.
            code: authentication_required
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
    InternalError:
      description: Unexpected server error. The body never carries database or driver text.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://api.propraven.com/errors/internal-error
            title: Internal Server Error
            status: 500
            detail: An internal error occurred.
            code: internal_error
            request_id: iad1::abc123
      headers:
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
    RateLimitExceeded:
      description: Rate limit exceeded.
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
        Retry-After:
          $ref: '#/components/headers/Retry-After'
        X-Request-Id:
          $ref: '#/components/headers/X-Request-Id'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://api.propraven.com/errors/rate-limit-exceeded
            title: Rate limit exceeded
            status: 429
            detail: Rate limit exceeded
            code: rate_limit_exceeded
  schemas:
    ZoningAttributes:
      type: object
      required:
      - attributeSource
      - citations
      description: 'The parsed ordinance dictionary for the code. EVERY value may be null: null means the ordinance has not been parsed for this code, or the text read does not state the value (or its quote did not verify). Null is never zero or false.'
      properties:
        minLotAreaSqft:
          type:
          - number
          - 'null'
        minLotWidthFt:
          type:
          - number
          - 'null'
        maxHeightFt:
          type:
          - number
          - 'null'
        maxStories:
          type:
          - number
          - 'null'
        maxFar:
          type:
          - number
          - 'null'
        maxDensityUnitsPerAcre:
          type:
          - number
          - 'null'
        maxLotCoveragePct:
          type:
          - number
          - 'null'
        setbackFrontFt:
          type:
          - number
          - 'null'
        setbackSideFt:
          type:
          - number
          - 'null'
        setbackRearFt:
          type:
          - number
          - 'null'
        minParkingPerUnit:
          type:
          - number
          - 'null'
        multifamilyByRight:
          type:
          - boolean
          - 'null'
        duplexByRight:
          type:
          - boolean
          - 'null'
        triplexQuadplexByRight:
          type:
          - boolean
          - 'null'
        aduAllowed:
          type:
          - boolean
          - 'null'
        shortTermRentalAllowed:
          type:
          - boolean
          - 'null'
        maxUnitsByRight:
          type:
          - number
          - 'null'
        permittedUseFamilies:
          type:
          - array
          - 'null'
          items:
            type: string
        conditionalUseFamilies:
          type:
          - array
          - 'null'
          items:
            type: string
        citations:
          type: object
          description: Attribute name (e.g. `min_lot_area_sqft`) -> every record read for it, each with its section, verbatim quote and link.
          additionalProperties:
            type: array
            items:
              $ref: '#/components/schemas/ZoningAttributeCitation'
        ordinanceVersion:
          type:
          - string
          - 'null'
        attributeSource:
          type: string
          enum:
          - ordinance_text
          - publisher_table
          - manual
          - none
        sourceCitation:
          type:
          - string
          - 'null'
        sourceUrl:
          type:
          - string
          - 'null'
        parsedAt:
          type:
          - string
          - 'null'
        reviewStatus:
          type: string
          enum:
          - unreviewed
          - machine
          - human_verified
          - disputed
    ZoningAttributeCitation:
      type: object
      required:
      - verified
      properties:
        value:
          type:
          - number
          - boolean
          - string
          - array
          - 'null'
          items:
            type: string
        qualifier:
          type:
          - string
          - 'null'
        section:
          type:
          - string
          - 'null'
          description: The ordinance's own label for the place, e.g. `Table 4-1`.
        quote:
          type:
          - string
          - 'null'
          description: Verbatim ordinance text, mechanically re-found in the authority's published text.
        url:
          type:
          - string
          - 'null'
          description: Deep link to the section in the authority's text.
        confidence:
          type:
          - number
          - 'null'
        verified:
          type: boolean
          description: True when the quote was found exactly once in the text read. Only verified values reach the typed attribute fields.
        note:
          type:
          - string
          - 'null'
    ParcelZoning:
      type: object
      required:
      - parcel
      - matched
      - rawCode
      - overlays
      - attributes
      - match
      properties:
        parcel:
          type: object
          properties:
            stateFips:
              type: string
            countyFips:
              type: string
            parcelId:
              type: string
        matched:
          type: boolean
          description: False when the parcel's centroid fell in no published district (a measurement, not an error).
        jurisdiction:
          type:
          - object
          - 'null'
          required:
          - jurisdictionId
          - name
          - type
          - stateFips
          properties:
            jurisdictionId:
              type: string
              description: Registry id of the zoning body, e.g. `nc-37119-charlotte`.
            name:
              type: string
            type:
              type: string
              enum:
              - city
              - town
              - village
              - borough
              - township
              - county
              - unincorporated
              - consolidated
              - special_district
            stateFips:
              type: string
            placeFips:
              type:
              - string
              - 'null'
            countyFips:
              type: array
              items:
                type: string
            zoningHomeUrl:
              type:
              - string
              - 'null'
        boundaryJurisdiction:
          type:
          - object
          - 'null'
          required:
          - jurisdictionId
          - name
          - type
          - stateFips
          properties:
            jurisdictionId:
              type: string
              description: Registry id of the zoning body, e.g. `nc-37119-charlotte`.
            name:
              type: string
            type:
              type: string
              enum:
              - city
              - town
              - village
              - borough
              - township
              - county
              - unincorporated
              - consolidated
              - special_district
            stateFips:
              type: string
            placeFips:
              type:
              - string
              - 'null'
            countyFips:
              type: array
              items:
                type: string
            zoningHomeUrl:
              type:
              - string
              - 'null'
        rawCode:
          type:
          - string
          - 'null'
          description: 'THE FACT: the zoning code exactly as the issuing jurisdiction published it.'
        baseCode:
          type:
          - string
          - 'null'
        codeName:
          type:
          - string
          - 'null'
        overlays:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              kind:
                type: string
              name:
                type:
                - string
                - 'null'
              restrictionSummary:
                type:
                - string
                - 'null'
        splitZoned:
          type:
          - boolean
          - 'null'
        altCodes:
          type: array
          items:
            type: string
        zonedSince:
          type:
          - string
          - 'null'
        match:
          type: object
          properties:
            method:
              type: string
            confidence:
              type:
              - number
              - 'null'
            overlaysAvailable:
              type: boolean
        attributes:
          $ref: '#/components/schemas/ZoningAttributes'
        attributesMatchedOn:
          type:
          - string
          - 'null'
          enum:
          - raw_code
          - base_code
          - null
        conditionalNote:
          type:
          - string
          - 'null'
        derivedFamily:
          type: object
          properties:
            family:
              type:
              - string
              - 'null'
            method:
              type: string
            confidence:
              type:
              - number
              - 'null'
            unresolvedReason:
              type: string
            evidence:
              type: string
        provenance:
          type: object
          properties:
            sourceUrl:
              type:
              - string
              - 'null'
            publisherRole:
              type:
              - string
              - 'null'
            harvestedAt:
              type:
              - string
              - 'null'
            epochVersion:
              type:
              - string
              - 'null'
    ZoningCoverageRow:
      type: object
      required:
      - jurisdictionId
      - jurisdictionName
      - servingStatus
      - parcelsMatched
      - matchRate
      - distinctCodes
      - codesWithAttributes
      - dictionaryCompleteness
      properties:
        jurisdictionId:
          type: string
        jurisdictionName:
          type: string
        jurisdictionType:
          type: string
        stateFips:
          type: string
        countyFips:
          type: array
          items:
            type: string
        servingStatus:
          type: string
          enum:
          - served
          - partial
          - no_zoning
          - failed
          - registered
          description: served = codes on parcels AND parsed ordinance attributes; partial = codes on parcels, attributes missing or incomplete; no_zoning = the body has no zoning ordinance; failed = could not be collected; registered = not yet attempted.
        statusReason:
          type:
          - string
          - 'null'
        statusAsOf:
          type:
          - string
          - 'null'
        parcelsInJurisdiction:
          type:
          - integer
          - 'null'
        denominatorMethod:
          type: string
          enum:
          - boundary_centroid
          - publisher_count
          - unknown
        parcelsMatched:
          type: integer
        parcelsUnmatched:
          type: integer
        matchRate:
          type:
          - number
          - 'null'
        distinctCodes:
          type: integer
        overlaysAvailable:
          type: boolean
        distinctOverlays:
          type: integer
        parcelsWithOverlay:
          type: integer
        parcelsSplitZoned:
          type:
          - integer
          - 'null'
        parcelsWithAttributes:
          type: integer
        parcelAttributeRate:
          type:
          - number
          - 'null'
        codesWithAttributes:
          type: integer
        codesMissingAttributes:
          type: integer
        dictionaryCompleteness:
          type:
          - number
          - 'null'
        primarySourceUrl:
          type:
          - string
          - 'null'
        lastHarvestedAt:
          type:
          - string
          - 'null'
    Problem:
      type: object
      description: 'THE error body of every /api/v1 endpoint (RFC 7807 problem details, served as `application/problem+json`). `code` is the stable machine-readable identifier (e.g. invalid_parameter, authentication_required, account_required, monthly_cap_reached, query_timeout, not_found, method_not_allowed); `detail` is human-readable and never contains database or driver text. Validation failures add `errors: [{param, message}]`. `request_id` identifies the request for support; every error response carries it, and the same value in the `X-Request-Id` header. Per-code members (e.g. `reason`, `retry_after`, `used` / `limit` / `plan` / `upgrade`, `allow`) sit beside the core members. Exception: an x402 `402 Payment Required` keeps the x402 protocol envelope (`x402Version`, `accepts`).'
      required:
      - type
      - title
      - status
      - detail
      - code
      properties:
        type:
          type: string
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        code:
          type: string
          description: Stable machine-readable code.
          example: invalid_parameter
        reason:
          type: string
          description: Finer reason for `code`, e.g. people_data_requires_account.
        errors:
          type: array
          description: Per-parameter validation failures (400 invalid_parameter).
          items:
            type: object
            required:
            - param
            - message
            properties:
              param:
                type: string
                example: limit
              message:
                type: string
                example: must be >= 1
        request_id:
          type: string
          description: Support handle for this request.
      additionalProperties: true
      example:
        type: https://api.propraven.com/errors/invalid-parameter
        title: Bad Request
        status: 400
        detail: 'limit: must be >= 1'
        code: invalid_parameter
        errors:
        - param: limit
          message: must be >= 1
        request_id: iad1::abc123
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: pz_*
      description: 'API key with pz_ prefix. Pass as: Authorization: Bearer pz_your_api_key'
    sessionCookie:
      type: apiKey
      in: cookie
      name: sb-<project-ref>-auth-token
      description: The signed-in web session (Supabase auth cookie) set by signing in at https://propraven.com/sign-in. Used only by account-scoped dashboard routes; an API key is not accepted on those routes.
externalDocs:
  description: 'Connect your agent to PropRaven: the MCP server, the discover -> preview -> pay (x402) flow, and pricing.'
  url: https://propraven.com/docs/agents