{
  "openapi": "3.1.0",
  "info": {
    "title": "FloridaContractorCheck API",
    "version": "1.0.0",
    "summary": "Free Florida contractor license verification, plus an entitlement-scoped API for Growth subscribers.",
    "description": "Two surfaces. `/api/v1/verify` is free and public: one license per call, no list or search endpoint of any kind. Everything else requires a Growth subscriber's key and is clamped server-side to that subscriber's own territory, county cluster, inquiries and book.\n\nPERMIT COUNTS ARE FLOORS. Florida permit coverage is never complete, so a count is the minimum we can evidence, never the total filed. Render the `phrase` field as given.\n\nNeed statewide, multi-state or bulk permit data? That is PermitIntel (https://permitintel.ai), not this API. FCC sells a territory, not volume.",
    "contact": {
      "email": "info@floridacontractorcheck.com",
      "url": "https://floridacontractorcheck.com/developers"
    },
    "license": {
      "name": "Terms of Service",
      "url": "https://floridacontractorcheck.com/terms"
    }
  },
  "servers": [
    {
      "url": "https://floridacontractorcheck.com"
    }
  ],
  "externalDocs": {
    "description": "Developer docs",
    "url": "https://floridacontractorcheck.com/developers"
  },
  "security": [],
  "paths": {
    "/api/v1/verify": {
      "get": {
        "operationId": "verifyLicense",
        "summary": "Verify one Florida contractor license",
        "description": "Free. Keyless callers get 20 a day per IP at 30 a minute; a free developer key raises the daily allowance to 100. ONE license per call: there is no list, search or bulk variant of this endpoint.",
        "security": [],
        "parameters": [
          {
            "name": "license",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "DBPR license number, e.g. CCC1330911. The alpha prefix is optional; without it, a number matching several license types returns the candidates instead of guessing."
          }
        ],
        "responses": {
          "200": {
            "description": "A verification answer. `found:false` is a successful answer, not an error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                }
              }
            }
          },
          "400": {
            "description": "No license supplied, or fewer than three characters."
          },
          "429": {
            "description": "Allowance exhausted.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "operationId": "getMe",
        "summary": "This key's plan, scope, caps and credits",
        "description": "Free: costs no record credits.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Key identity and entitlement scope."
          },
          "401": {
            "description": "Key dead or license has no live plan."
          },
          "403": {
            "description": "Not a Growth plan."
          }
        }
      }
    },
    "/api/v1/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "Credits and rate state",
        "description": "Free: costs no record credits.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current usage."
          },
          "401": {
            "description": "Key dead."
          },
          "403": {
            "description": "Not a Growth plan."
          }
        }
      }
    },
    "/api/v1/territory": {
      "get": {
        "operationId": "listTerritory",
        "summary": "Your territory rows",
        "description": "The same rows the portal's territory view renders. A missing or stale build is reported as `state: not_built` / `state: stale` with `snapshot_at`, never as an empty territory.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Offset returned as next_cursor by the previous page."
          },
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "description": "Rows per page. Values above 100 are clamped to 100, not rejected."
          },
          {
            "name": "zip",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated ZIPs. ZIPs you do not own are ignored."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "newest",
                "lead_score"
              ],
              "default": "newest"
            },
            "description": "Row order. Default `newest` (the row’s own timestamp, descending, with a total tiebreak on id) because a polling trigger only ever reads the first page — a new row has to land there. `lead_score` restores the ranking the portal territory view uses; for a human reading by hand that is usually the one you want, since territory lists OVERDUE properties and newest-permit is close to the inverse of best-lead. An unknown value is refused with 400."
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "ISO-8601 date (2026-09-01) or instant (2026-09-01T14:30:00Z). INCLUSIVE lower bound on the row’s own timestamp (see `time_field`). A bare date means 00:00 UTC that day, so since=2026-09-01 includes the whole of the 1st. Rows with no timestamp are excluded while `since` is set and counted in `undated_excluded`. Unparseable values are refused with 400 — never silently ignored."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of territory rows.",
            "headers": {
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current minute."
              },
              "X-RateLimit-Day-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left today (UTC)."
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Record credits left this billing month."
              },
              "X-FCC-Preview": {
                "schema": {
                  "type": "string"
                },
                "description": "Present only when served by a non-production preview key."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "rows": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/TerritoryRow"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unparseable `since`, or an `order` this endpoint does not support. Refused rather than ignored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, revoked key, or the license no longer holds a live Growth plan."
          },
          "402": {
            "description": "This billing month's record credits are spent. Records already served this month remain free to re-read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Wrong plan, or every county asked for is outside the key's area.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Honour Retry-After.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/territory/export.csv": {
      "get": {
        "operationId": "exportTerritory",
        "summary": "Your territory as CSV",
        "description": "One export per key per UTC day. Rows consume record credits exactly as a page does. Each row carries an `fcc_ref` column.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "CSV.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Key dead."
          },
          "402": {
            "description": "Credits spent."
          },
          "403": {
            "description": "Not a Growth plan."
          },
          "409": {
            "description": "No territory view has been built for this license yet. Your daily export is not consumed."
          },
          "429": {
            "description": "Already exported today. Retry-After is the seconds to UTC midnight.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "zip",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma separated ZIPs to narrow to. ZIPs outside your territory are ignored, not refused."
          }
        ]
      }
    },
    "/api/v1/project-leads": {
      "get": {
        "operationId": "listProjectLeads",
        "summary": "Open masters in your county cluster",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Offset returned as next_cursor by the previous page."
          },
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "description": "Rows per page. Values above 100 are clamped to 100, not rejected."
          },
          {
            "name": "county",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated county keys (e.g. MIAMI_DADE,BROWARD). Counties outside the key's area are ignored, not trusted. If every county asked for is out of area the request is refused with 403 rather than answered with the home county."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "newest"
              ],
              "default": "newest"
            },
            "description": "Row order. Default `newest` (the row’s own timestamp, descending, with a total tiebreak on id) because a polling trigger only ever reads the first page — a new row has to land there.  An unknown value is refused with 400."
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "ISO-8601 date (2026-09-01) or instant (2026-09-01T14:30:00Z). INCLUSIVE lower bound on the row’s own timestamp (see `time_field`). A bare date means 00:00 UTC that day, so since=2026-09-01 includes the whole of the 1st. Rows with no timestamp are excluded while `since` is set and counted in `undated_excluded`. Unparseable values are refused with 400 — never silently ignored."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of project leads.",
            "headers": {
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current minute."
              },
              "X-RateLimit-Day-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left today (UTC)."
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Record credits left this billing month."
              },
              "X-FCC-Preview": {
                "schema": {
                  "type": "string"
                },
                "description": "Present only when served by a non-production preview key."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "rows": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ProjectLeadRow"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unparseable `since`, or an `order` this endpoint does not support. Refused rather than ignored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, revoked key, or the license no longer holds a live Growth plan."
          },
          "402": {
            "description": "This billing month's record credits are spent. Records already served this month remain free to re-read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Wrong plan, or every county asked for is outside the key's area.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Honour Retry-After.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/horizon": {
      "get": {
        "operationId": "listHorizon",
        "summary": "Pre-permit pipeline in your county cluster",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Offset returned as next_cursor by the previous page."
          },
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "description": "Rows per page. Values above 100 are clamped to 100, not rejected."
          },
          {
            "name": "county",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated county keys (e.g. MIAMI_DADE,BROWARD). Counties outside the key's area are ignored, not trusted. If every county asked for is out of area the request is refused with 403 rather than answered with the home county."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "newest",
                "nearest"
              ],
              "default": "newest"
            },
            "description": "Row order. Default `newest` (the row’s own timestamp, descending, with a total tiebreak on id) because a polling trigger only ever reads the first page — a new row has to land there. `nearest` restores the portal Horizon ranking (distance, then newest). `distance_mi` is on the row either way. An unknown value is refused with 400."
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "ISO-8601 date (2026-09-01) or instant (2026-09-01T14:30:00Z). INCLUSIVE lower bound on the row’s own timestamp (see `time_field`). A bare date means 00:00 UTC that day, so since=2026-09-01 includes the whole of the 1st. Rows with no timestamp are excluded while `since` is set and counted in `undated_excluded`. Unparseable values are refused with 400 — never silently ignored."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of Horizon signals.",
            "headers": {
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current minute."
              },
              "X-RateLimit-Day-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left today (UTC)."
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Record credits left this billing month."
              },
              "X-FCC-Preview": {
                "schema": {
                  "type": "string"
                },
                "description": "Present only when served by a non-production preview key."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "rows": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/HorizonRow"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unparseable `since`, or an `order` this endpoint does not support. Refused rather than ignored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, revoked key, or the license no longer holds a live Growth plan."
          },
          "402": {
            "description": "This billing month's record credits are spent. Records already served this month remain free to re-read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Wrong plan, or every county asked for is outside the key's area.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Honour Retry-After.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/inquiries": {
      "get": {
        "operationId": "listInquiries",
        "summary": "Homeowner inquiries sent to you",
        "description": "Costs no record credits: inquiries are free to subscribers in the portal and free here.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Offset returned as next_cursor by the previous page."
          },
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "description": "Rows per page. Values above 100 are clamped to 100, not rejected."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "newest"
              ],
              "default": "newest"
            },
            "description": "Row order. Default `newest` (the row’s own timestamp, descending, with a total tiebreak on id) because a polling trigger only ever reads the first page — a new row has to land there.  An unknown value is refused with 400."
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "ISO-8601 date (2026-09-01) or instant (2026-09-01T14:30:00Z). INCLUSIVE lower bound on the row’s own timestamp (see `time_field`). A bare date means 00:00 UTC that day, so since=2026-09-01 includes the whole of the 1st. Rows with no timestamp are excluded while `since` is set and counted in `undated_excluded`. Unparseable values are refused with 400 — never silently ignored."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of inquiries.",
            "headers": {
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current minute."
              },
              "X-RateLimit-Day-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left today (UTC)."
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Record credits left this billing month."
              },
              "X-FCC-Preview": {
                "schema": {
                  "type": "string"
                },
                "description": "Present only when served by a non-production preview key."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "rows": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/InquiryRow"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unparseable `since`, or an `order` this endpoint does not support. Refused rather than ignored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, revoked key, or the license no longer holds a live Growth plan."
          },
          "402": {
            "description": "This billing month's record credits are spent. Records already served this month remain free to re-read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Wrong plan, or every county asked for is outside the key's area.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Honour Retry-After.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/book": {
      "get": {
        "operationId": "listBook",
        "summary": "Your past customers whose system is due again",
        "description": "Paged from the UNLOCKED portion of your book only. The book releases over time, and the API sees exactly what the portal has unlocked.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Offset returned as next_cursor by the previous page."
          },
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "description": "Rows per page. Values above 100 are clamped to 100, not rejected."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "newest",
                "due"
              ],
              "default": "newest"
            },
            "description": "Row order. Default `newest` (the row’s own timestamp, descending, with a total tiebreak on id) because a polling trigger only ever reads the first page — a new row has to land there. `due` is the portal Book ranking. The release meter ALWAYS slices in due order first, so neither `order` nor `since` can unlock a home the portal has not unlocked. An unknown value is refused with 400."
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "ISO-8601 date (2026-09-01) or instant (2026-09-01T14:30:00Z). INCLUSIVE lower bound on the row’s own timestamp (see `time_field`). A bare date means 00:00 UTC that day, so since=2026-09-01 includes the whole of the 1st. Rows with no timestamp are excluded while `since` is set and counted in `undated_excluded`. Unparseable values are refused with 400 — never silently ignored."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of book rows.",
            "headers": {
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current minute."
              },
              "X-RateLimit-Day-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left today (UTC)."
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Record credits left this billing month."
              },
              "X-FCC-Preview": {
                "schema": {
                  "type": "string"
                },
                "description": "Present only when served by a non-production preview key."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "rows": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/BookRow"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unparseable `since`, or an `order` this endpoint does not support. Refused rather than ignored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, revoked key, or the license no longer holds a live Growth plan."
          },
          "402": {
            "description": "This billing month's record credits are spent. Records already served this month remain free to re-read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Wrong plan, or every county asked for is outside the key's area.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Honour Retry-After.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "summary": "Your webhook subscriptions",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Your hooks."
          },
          "401": {
            "description": "Key dead."
          },
          "403": {
            "description": "Not a Growth plan."
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "summary": "Subscribe to events",
        "description": "Deliveries are free: webhook events never consume record credits. Payloads are signed with HMAC-SHA256.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "territory.address.new",
                        "inquiry.new",
                        "project_lead.new",
                        "horizon.signal.new",
                        "contact.created",
                        "job.status_changed",
                        "estimate.sent",
                        "estimate.accepted",
                        "invoice.paid"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. The signing secret is returned once."
          },
          "400": {
            "description": "Bad URL or unknown event."
          },
          "401": {
            "description": "Key dead."
          },
          "403": {
            "description": "Not a Growth plan."
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Delete one of your hooks",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted."
          },
          "401": {
            "description": "Key dead."
          },
          "403": {
            "description": "Not yours, or not a Growth plan."
          }
        }
      }
    },
    "/api/mcp": {
      "post": {
        "operationId": "mcp",
        "summary": "Model Context Protocol (Streamable HTTP)",
        "description": "JSON-RPC 2.0. One tool: verify_florida_contractor. Manifest at /.well-known/mcp.json.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result."
          },
          "202": {
            "description": "Notification accepted."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/v1/contacts": {
      "get": {
        "operationId": "listContacts",
        "summary": "List your contacts",
        "description": "Cursor-paginated, newest first. Pass `next_cursor` back as `cursor` for the following page. The page key is `(created_at, id)` — a unique total order, so pages never overlap and a sync never invents a gap. Reading your own records costs no record credits.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque. Pass back `next_cursor` verbatim."
          },
          {
            "name": "contact_type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "list"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createContact",
        "summary": "Create a contact",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A required field is missing, or nothing in the body is writable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/contacts/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "operationId": "getContact",
        "summary": "One contact",
        "description": "Scoped to your license. An id belonging to another account answers 404, not 403 — we do not confirm that it exists.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateContact",
        "summary": "Update a contact",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Nothing in the body is writable on this resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs": {
      "get": {
        "operationId": "listJobs",
        "summary": "List your jobs",
        "description": "Cursor-paginated, newest first. Pass `next_cursor` back as `cursor` for the following page. The page key is `(created_at, id)` — a unique total order, so pages never overlap and a sync never invents a gap. Reading your own records costs no record credits.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque. Pass back `next_cursor` verbatim."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of jobs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "list"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Job"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createJob",
        "summary": "Create a job",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A required field is missing, or nothing in the body is writable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "operationId": "getJob",
        "summary": "One job",
        "description": "Scoped to your license. An id belonging to another account answers 404, not 403 — we do not confirm that it exists.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateJob",
        "summary": "Update a job",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Nothing in the body is writable on this resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/estimates": {
      "get": {
        "operationId": "listEstimates",
        "summary": "List your estimates",
        "description": "Cursor-paginated, newest first. Pass `next_cursor` back as `cursor` for the following page. The page key is `(created_at, id)` — a unique total order, so pages never overlap and a sync never invents a gap. Reading your own records costs no record credits.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque. Pass back `next_cursor` verbatim."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of estimates.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "list"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Estimate"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createEstimate",
        "summary": "Create a estimate",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EstimateWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Estimate"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A required field is missing, or nothing in the body is writable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/estimates/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "operationId": "getEstimate",
        "summary": "One estimate",
        "description": "Scoped to your license. An id belonging to another account answers 404, not 403 — we do not confirm that it exists.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Estimate"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateEstimate",
        "summary": "Update a estimate",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EstimateWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Estimate"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Nothing in the body is writable on this resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices": {
      "get": {
        "operationId": "listInvoices",
        "summary": "List your invoices",
        "description": "Cursor-paginated, newest first. Pass `next_cursor` back as `cursor` for the following page. The page key is `(created_at, id)` — a unique total order, so pages never overlap and a sync never invents a gap. Reading your own records costs no record credits.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque. Pass back `next_cursor` verbatim."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of invoices.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "list"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createInvoice",
        "summary": "Create a invoice",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A required field is missing, or nothing in the body is writable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "operationId": "getInvoice",
        "summary": "One invoice",
        "description": "Scoped to your license. An id belonging to another account answers 404, not 403 — we do not confirm that it exists.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateInvoice",
        "summary": "Update a invoice",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Nothing in the body is writable on this resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/events": {
      "get": {
        "operationId": "listEvents",
        "summary": "What has happened on your account",
        "description": "Read-only feed of CRM events, newest first. Free — events never consume record credits. Paginate with `after=<id>` (the feed key is a monotonic integer, so it needs no opaque cursor). This is also what a polling integration reads when a webhook subscription is lost.",
        "security": [
          {
            "ApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Return events with an id below this. Pass back `next_cursor`."
          },
          {
            "name": "event",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "contact.created",
                "job.status_changed",
                "estimate.sent",
                "estimate.accepted",
                "invoice.paid"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of events.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "type": "string",
                      "enum": [
                        "list"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "events_available": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "contact.created",
                          "job.status_changed",
                          "estimate.sent",
                          "estimate.accepted",
                          "invoice.paid"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, unknown key, or a revoked key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a Growth plan, or the key lacks the required scope (`insufficient_scope` names it).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. `Retry-After` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Metering is unreachable (fail-closed — nothing was charged), or the CRM is not provisioned on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "One key per account, created in Portal → Connect and shown once. Hashed at rest (sha256); we cannot show it again. Revoke it from the same screen — revoking is immediate and also happens automatically when the plan ends. Keys carry scopes, which narrow what the key may reach within your account but never widen it: contacts:read, contacts:write, jobs:read, jobs:write, estimates:read, estimates:write, invoices:read, invoices:write, events:read, webhooks:write. A key created without an explicit scope list carries every scope except `webhooks:write`."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "examples": [
              "plan_required",
              "credits_exhausted",
              "rate_limited",
              "key_revoked"
            ]
          },
          "message": {
            "type": "string"
          },
          "upgrade": {
            "type": "string"
          }
        }
      },
      "Page": {
        "type": "object",
        "properties": {
          "rows": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "next_cursor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Pass as ?cursor= for the next page. null means this was the last page."
          },
          "page_size": {
            "type": "integer"
          },
          "max_page_size": {
            "type": "integer"
          },
          "charged": {
            "type": "integer",
            "description": "Record credits this page consumed. Zero when every row had already been served to this key this billing month."
          },
          "order": {
            "type": "string",
            "description": "The order these rows are in. Default `newest`."
          },
          "order_options": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Every order this endpoint accepts on ?order=."
          },
          "time_field": {
            "type": "string",
            "description": "The row field `since` filters on and `newest` sorts on."
          },
          "since": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The `since` bound actually applied, echoed back so a client can confirm it was honoured rather than ignored."
          },
          "undated_excluded": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Rows withheld because they carry no value in time_field and therefore cannot be proven to fall after `since`. Only present when `since` was sent. They are fully visible without `since`."
          }
        },
        "description": "Every list endpoint returns this envelope. Three guarantees for polling clients: every row carries a stable unique `id`, rows are in a total documented order (newest-first by default), and `since` gives an inclusive lower bound on the row’s own timestamp. `since` narrows the set and `cursor` pages within it — they compose, and sending only `cursor` behaves exactly as before."
      },
      "Verification": {
        "type": "object",
        "properties": {
          "found": {
            "type": "boolean"
          },
          "license_number": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "business_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "license_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "license_type_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "expires": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "qualifier": {
            "type": [
              "string",
              "null"
            ]
          },
          "county": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "flags": {
            "type": "object",
            "properties": {
              "active": {
                "type": "boolean"
              },
              "lapsed": {
                "type": "boolean"
              },
              "expired": {
                "type": "boolean"
              },
              "impaired": {
                "type": "boolean"
              },
              "probation": {
                "type": "boolean"
              },
              "workers_comp_on_record": {
                "type": "boolean"
              },
              "workers_comp_source": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "general_liability_on_record": {
                "type": "boolean"
              },
              "claimed_on_fcc": {
                "type": "boolean"
              }
            }
          },
          "permits": {
            "type": "object",
            "description": "A FLOOR, never a total. Render `phrase` as given.",
            "properties": {
              "at_least": {
                "type": "integer"
              },
              "since": {
                "type": "string",
                "format": "date",
                "description": "Start of the counting window. It is a rolling 24 months, so it advances by one day every day and `at_least` moves with it. Do not treat it as a fixed period."
              },
              "county": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Named only when exactly one county is active in the window; otherwise null and `scope` is Florida."
              },
              "counties": {
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "type": "string"
                },
                "description": "null means the rollup was unavailable. That is not the same as an empty list."
              },
              "scope": {
                "type": "string"
              },
              "floor_framed": {
                "type": "boolean",
                "const": true
              },
              "phrase": {
                "type": "string"
              },
              "note": {
                "type": "string"
              }
            }
          },
          "profile_url": {
            "type": "string",
            "format": "uri"
          },
          "verified_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TerritoryRow": {
        "type": "object",
        "description": "One property in your territory. `id` is the normalized site address — the snapshot carries no surrogate key, so the address is the identity, and the same property costs one credit however its spelling changes.",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable unique id for this row. Dedup on this and nothing else: it does not change between polls, it is the same key your record credits are charged against, and it is never an array position."
          },
          "asset_class": {
            "type": [
              "string",
              "null"
            ],
            "description": "roof, hvac, water_heater, solar_ready, …"
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "confidence": {
            "type": [
              "string",
              "null"
            ]
          },
          "norm_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "jurisdiction": {
            "type": [
              "string",
              "null"
            ]
          },
          "site_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "zip": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_permit_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "The row’s own timestamp. `since` and the default newest-first order both use this field."
          },
          "years_overdue": {
            "type": [
              "integer",
              "null"
            ]
          },
          "owner_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner_mailing": {
            "type": [
              "string",
              "null"
            ]
          },
          "distance_mi": {
            "type": [
              "number",
              "null"
            ]
          },
          "roof_squares": {
            "type": [
              "number",
              "null"
            ]
          },
          "roof_squares_est": {
            "type": [
              "number",
              "null"
            ]
          },
          "lead_score": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Ranking used by order=lead_score and by the portal territory view."
          },
          "lead_tier": {
            "type": [
              "string",
              "null"
            ]
          },
          "just_value": {
            "type": [
              "integer",
              "null"
            ]
          },
          "value_per_sqft": {
            "type": [
              "number",
              "null"
            ]
          },
          "structure_age": {
            "type": [
              "integer",
              "null"
            ]
          },
          "owner_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "portfolio_size": {
            "type": [
              "integer",
              "null"
            ]
          },
          "out_of_state_owner": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "absentee_owner": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "hvhz": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "flood_sfha": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "fld_zone": {
            "type": [
              "string",
              "null"
            ]
          },
          "sunbiz_officer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lon": {
            "type": [
              "number",
              "null"
            ]
          }
        }
      },
      "ProjectLeadRow": {
        "type": "object",
        "description": "One master permit lead from the Bid Board, inside your county cluster. No owner or parcel fields: a master permit describes a GC’s open job, not a household.",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable unique id for this row. Dedup on this and nothing else: it does not change between polls, it is the same key your record credits are charged against, and it is never an array position."
          },
          "permit_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_key": {
            "type": [
              "string",
              "null"
            ]
          },
          "county": {
            "type": [
              "string",
              "null"
            ]
          },
          "jurisdiction": {
            "type": [
              "string",
              "null"
            ]
          },
          "site_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "gc_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "gc_license": {
            "type": [
              "string",
              "null"
            ]
          },
          "permit_value": {
            "type": [
              "number",
              "null"
            ]
          },
          "file_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "The row’s own timestamp. `since` and the newest-first order both use this field. A lead with no file_date is excluded when `since` is sent and shown when it is not."
          },
          "work_desc": {
            "type": [
              "string",
              "null"
            ]
          },
          "lead_class": {
            "type": [
              "string",
              "null"
            ]
          },
          "trades_filed": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "trades_open": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          }
        }
      },
      "HorizonRow": {
        "type": "object",
        "description": "One pre-permit development signal (rezoning, site plan, plat, variance) or development news item for your county cluster.",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable unique id for this row. Dedup on this and nothing else: it does not change between polls, it is the same key your record credits are charged against, and it is never an array position."
          },
          "headline": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "county": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "stage": {
            "type": [
              "string",
              "null"
            ]
          },
          "units": {
            "type": [
              "integer",
              "null"
            ]
          },
          "project_value": {
            "type": [
              "number",
              "null"
            ]
          },
          "developer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The row’s own timestamp. `since` and the default newest-first order both use this field."
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "source_kind": {
            "type": [
              "string",
              "null"
            ],
            "description": "agenda = published on a public meeting agenda; news = a development news story."
          },
          "distance_mi": {
            "type": [
              "number",
              "null"
            ],
            "description": "Miles from your base ZIP, or null when the city cannot be placed. order=nearest sorts on this; a null never renders as 0."
          }
        }
      },
      "InquiryRow": {
        "type": "object",
        "description": "One homeowner inquiry sent to your license. Free: inquiries cost no record credits here because they are free in the portal.",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable unique id for this row. Dedup on this and nothing else: it does not change between polls, it is the same key your record credits are charged against, and it is never an array position."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "zip_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The row’s own timestamp. `since` and the newest-first order both use this field."
          }
        }
      },
      "BookRow": {
        "type": "object",
        "description": "One of your own past customers whose system is due again. `id` is site_address|asset_class — the RPC returns no surrogate key. Homes whose asset class has already been re-permitted are excluded, so a superseded row is never served.",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable unique id for this row. Dedup on this and nothing else: it does not change between polls, it is the same key your record credits are charged against, and it is never an array position."
          },
          "site_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "site_city": {
            "type": [
              "string",
              "null"
            ]
          },
          "site_zip": {
            "type": [
              "string",
              "null"
            ]
          },
          "issue_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "The date of YOUR original permit at this address — the row’s own timestamp. `since` and order=newest both use this field."
          },
          "permit_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "asset_class": {
            "type": [
              "string",
              "null"
            ]
          },
          "years_since": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Years since your permit. The release meter ranks on this; order=due preserves that ranking."
          }
        }
      },
      "Contact": {
        "type": "object",
        "description": "The contractor's own contact record. ⛔ Carries no permit, territory or Horizon field: the v1 CRM namespace exposes the contractor's own business records and nothing sourced. Fields are whitelisted in src/lib/apiCrm.js and asserted by api-v1-crm.test.mjs.",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "contact"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "site_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "mailing_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "zip": {
            "type": [
              "string",
              "null"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "ContactWrite": {
        "type": "object",
        "required": [
          "display_name"
        ],
        "properties": {
          "display_name": {
            "type": "string"
          },
          "company_name": {
            "type": "string"
          },
          "contact_type": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "site_address": {
            "type": "string"
          },
          "mailing_address": {
            "type": "string"
          },
          "zip": {
            "type": "string"
          },
          "note": {
            "type": "string"
          }
        },
        "additionalProperties": false,
        "description": "Fields not listed here are IGNORED and reported back in `ignored_fields` — not silently dropped, and not an error. `license_number` is never writable: tenancy comes from the key."
      },
      "Job": {
        "type": "object",
        "description": "The contractor's own job record. ⛔ Carries no permit, territory or Horizon field: the v1 CRM namespace exposes the contractor's own business records and nothing sourced. Fields are whitelisted in src/lib/apiCrm.js and asserted by api-v1-crm.test.mjs.",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "job"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "next_action": {
            "type": [
              "string",
              "null"
            ]
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "site_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "zip": {
            "type": [
              "string",
              "null"
            ]
          },
          "counterparty_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "counterparty_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "counterparty_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "value_cents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Whole cents. Never a decimal on the wire."
          },
          "quote_amount_cents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Whole cents. Never a decimal on the wire."
          },
          "won_amount_cents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Whole cents. Never a decimal on the wire."
          },
          "lost_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "stage_entered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "JobWrite": {
        "type": "object",
        "required": [
          "title"
        ],
        "properties": {
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "next_action": {
            "type": "string"
          },
          "due_date": {
            "type": "string",
            "format": "date"
          },
          "site_address": {
            "type": "string"
          },
          "zip": {
            "type": "string"
          },
          "counterparty_name": {
            "type": "string"
          },
          "counterparty_phone": {
            "type": "string"
          },
          "counterparty_email": {
            "type": "string"
          },
          "value_cents": {
            "type": "integer",
            "description": "Whole cents. Never a decimal on the wire."
          },
          "quote_amount_cents": {
            "type": "integer",
            "description": "Whole cents. Never a decimal on the wire."
          },
          "won_amount_cents": {
            "type": "integer",
            "description": "Whole cents. Never a decimal on the wire."
          },
          "lost_reason": {
            "type": "string"
          },
          "note": {
            "type": "string"
          }
        },
        "additionalProperties": false,
        "description": "Fields not listed here are IGNORED and reported back in `ignored_fields` — not silently dropped, and not an error. `license_number` is never writable: tenancy comes from the key."
      },
      "Estimate": {
        "type": "object",
        "description": "The contractor's own estimate record. ⛔ Carries no permit, territory or Horizon field: the v1 CRM namespace exposes the contractor's own business records and nothing sourced. Fields are whitelisted in src/lib/apiCrm.js and asserted by api-v1-crm.test.mjs.",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "estimate"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "doc_number": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "signature_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "site_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "mailing_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "doc_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "tax_rate_pct": {
            "type": [
              "string",
              "null"
            ],
            "description": "A decimal string, not a number — cents are never lost to a float."
          },
          "customer_notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "subtotal": {
            "type": [
              "string",
              "null"
            ],
            "description": "A decimal string, not a number — cents are never lost to a float."
          },
          "tax_total": {
            "type": [
              "string",
              "null"
            ],
            "description": "A decimal string, not a number — cents are never lost to a float."
          },
          "total": {
            "type": [
              "string",
              "null"
            ],
            "description": "A decimal string, not a number — cents are never lost to a float."
          },
          "locked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "voided_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "EstimateWrite": {
        "type": "object",
        "required": [
          "customer_name"
        ],
        "properties": {
          "customer_name": {
            "type": "string"
          },
          "customer_email": {
            "type": "string"
          },
          "customer_phone": {
            "type": "string"
          },
          "site_address": {
            "type": "string"
          },
          "mailing_address": {
            "type": "string"
          },
          "doc_date": {
            "type": "string",
            "format": "date"
          },
          "tax_rate_pct": {
            "type": "string",
            "description": "A decimal string, not a number — cents are never lost to a float."
          },
          "customer_notes": {
            "type": "string"
          },
          "internal_notes": {
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        },
        "additionalProperties": false,
        "description": "Fields not listed here are IGNORED and reported back in `ignored_fields` — not silently dropped, and not an error. `license_number` is never writable: tenancy comes from the key."
      },
      "Invoice": {
        "type": "object",
        "description": "The contractor's own invoice record. ⛔ Carries no permit, territory or Horizon field: the v1 CRM namespace exposes the contractor's own business records and nothing sourced. Fields are whitelisted in src/lib/apiCrm.js and asserted by api-v1-crm.test.mjs.",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "invoice"
            ]
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "doc_number": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "signature_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "site_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "mailing_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "doc_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "payment_terms": {
            "type": [
              "string",
              "null"
            ]
          },
          "tax_rate_pct": {
            "type": [
              "string",
              "null"
            ],
            "description": "A decimal string, not a number — cents are never lost to a float."
          },
          "customer_notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "subtotal": {
            "type": [
              "string",
              "null"
            ],
            "description": "A decimal string, not a number — cents are never lost to a float."
          },
          "tax_total": {
            "type": [
              "string",
              "null"
            ],
            "description": "A decimal string, not a number — cents are never lost to a float."
          },
          "total": {
            "type": [
              "string",
              "null"
            ],
            "description": "A decimal string, not a number — cents are never lost to a float."
          },
          "amount_paid": {
            "type": [
              "string",
              "null"
            ],
            "description": "A decimal string, not a number — cents are never lost to a float."
          },
          "locked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "voided_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "InvoiceWrite": {
        "type": "object",
        "required": [
          "customer_name"
        ],
        "properties": {
          "customer_name": {
            "type": "string"
          },
          "customer_email": {
            "type": "string"
          },
          "customer_phone": {
            "type": "string"
          },
          "site_address": {
            "type": "string"
          },
          "mailing_address": {
            "type": "string"
          },
          "doc_date": {
            "type": "string",
            "format": "date"
          },
          "due_date": {
            "type": "string",
            "format": "date"
          },
          "payment_terms": {
            "type": "string"
          },
          "tax_rate_pct": {
            "type": "string",
            "description": "A decimal string, not a number — cents are never lost to a float."
          },
          "customer_notes": {
            "type": "string"
          },
          "internal_notes": {
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        },
        "additionalProperties": false,
        "description": "Fields not listed here are IGNORED and reported back in `ignored_fields` — not silently dropped, and not an error. `license_number` is never writable: tenancy comes from the key."
      },
      "Event": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "event"
            ]
          },
          "id": {
            "type": "integer",
            "description": "Also the `event_id` in a webhook payload. Dedupe on it — delivery is at-least-once."
          },
          "event": {
            "type": "string",
            "enum": [
              "contact.created",
              "job.status_changed",
              "estimate.sent",
              "estimate.accepted",
              "invoice.paid"
            ]
          },
          "resource_type": {
            "type": "string",
            "enum": [
              "contact",
              "job",
              "estimate",
              "invoice"
            ]
          },
          "resource_id": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "description": "The resource, serialized exactly as its GET would return it — the same function, so a push can never carry a field a GET would not."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  }
}
