openapi: "3.1.0"
info:
  title: "Drawserv Lender API"
  version: "0.2.0"
  summary: "A documented, per-lender door into a construction-loan book: projects, draws, inspections, budgets, documents."
  description: |-
    The Drawserv Lender API lets a lender reach its own construction-loan book from its own systems: list projects, read draws and their evidence, read reviewed inspections and budgets, open a project, upload documents.

    **Status.** Every operation in this document answers on production and on the sandbox. A key is minted by Drawserv staff on request. Operations are added here as they are served. Nothing here is sensitive — it describes shapes, not data.

    ## Authentication

    Every request carries a key as a bearer token: `Authorization: Bearer dsk_live_…` on production, `dsk_test_…` on the sandbox. A key acts for a **lender**, optionally narrowed to one **department**, never for a person: it is minted by Drawserv staff, shown once, stored hashed, and revocable at once. A key carries **scopes** (below) as its ceiling; the lender and department it is bound to are its floor — a record outside them reads as `not_found`. A portal session token is refused: the API is not a second way to spend a login. A key may carry an IP allowlist and an expiry. Every request is written to a ledger (key, method, path, status, duration, address, request id) that staff can show the lender.

    | Scope | Grants |
    |---|---|
    | `projects:read` | Read projects, parties, budgets and released risk reports |
    | `draws:read` | Read draws, their lines, approvals and lien waivers |
    | `inspections:read` | Read reviewed inspections and their progress lines |
    | `documents:read` | Read document metadata and fetch short-lived download links |
    | `projects:write` | Create projects and update a pre-closing project's intake fields |
    | `documents:write` | Upload documents to a project |

    A new key starts with the read scopes (`projects:read`, `draws:read`, `inspections:read`, `documents:read`); a scope GET /me reports that no operation here needs is held in reserve.

    ## Reading

    List answers are paged: `page` (from 1) and `per_page` (at most 200), answered as `{ data: [...], page: { number, size, total } }`. Draw lists are read as one **slice** (`scope`: working, funded, declined, open-waivers) or one **window** (`since`, with `until` for history); the whole ledger is never answered at once. Projects take `updated_since`. Every answer carries `X-Request-Id`.

    Field names are the Drawserv data model's own, enum values are the application's proven vocabularies, and money is **dollars to the cent** as JSON numbers — never cents. Staff-only columns are not in any schema: the API shows a lender exactly what its portal shows, by the same rules.

    ## Errors

    Every non-2xx answer is `{ "error": { "code", "message", "requestId" } }`. The code is the contract; the message may change.

    | Code | HTTP | Meaning |
    |---|---|---|
    | `unauthorized` | 401 | The key is missing, malformed, revoked, expired, for the other environment, or a session token was sent |
    | `forbidden_scope` | 403 | The key lacks the scope the operation needs |
    | `not_found` | 404 | No such record in the caller's view — a record outside the key's lender or department reads as absent |
    | `validation_failed` | 400 | A parameter or body field is missing or malformed |
    | `rate_limited` | 429 | The key's request budget is spent; Retry-After says when to try again |
    | `conflict` | 409 | The write collides with the record's current state (a project no longer pre-closing) |
    | `internal` | 500 | Something failed on our side; the requestId names the ledger row to quote |

    ## Versioning

    `/v1` in the path. Changes are additive: a field is never renamed or re-typed, an enum value never changes meaning, and a retired enum value stays declared while a row may still carry it. A breaking change is `/v2` beside `/v1`, with twelve months' notice in the changelog.

    ## Sandbox

    `dsk_test_` keys work against `staging.app.drawserv.com`, a scrubbed copy of the book with a demo portfolio per lender. Nothing done there reaches production.

    ## Delivery

    Each operation carries the phase it shipped in (`x-drawserv-phase`). This document carries served operations only, so `x-drawserv-served` is true on every one; an operation joins it the day its door opens.

    | Operation | Phase |
    |---|---|
    | `GET /me` | P1 — the read door |
    | `GET /projects` | P1 — the read door |
    | `POST /projects` | P2 — writes and documents |
    | `GET /projects/{projectId}` | P1 — the read door |
    | `PATCH /projects/{projectId}/intake` | P2 — writes and documents |
    | `GET /projects/{projectId}/parties` | P1 — the read door |
    | `GET /projects/{projectId}/budget` | P1 — the read door |
    | `GET /projects/{projectId}/draws` | P1 — the read door |
    | `GET /projects/{projectId}/documents` | P2 — writes and documents |
    | `POST /projects/{projectId}/documents` | P2 — writes and documents |
    | `GET /risk-reports/{projectId}` | P1 — the read door |
    | `GET /draws` | P1 — the read door |
    | `GET /draws/{drawId}` | P1 — the read door |
    | `GET /inspections` | P1 — the read door |
    | `GET /inspections/{inspectionId}` | P1 — the read door |
    | `GET /documents/{documentId}` | P2 — writes and documents |

    _Drawserv is a product of Findlan Inc d/b/a Drawserv._
  contact:
    name: Drawserv
    url: "https://findlanassociates.com"
  x-drawserv-status: read-door-writes-and-documents-live
servers:
- url: "https://app.drawserv.com/api/v1"
  description: "Production — dsk_live_ keys"
- url: "https://staging.app.drawserv.com/api/v1"
  description: "Sandbox — dsk_test_ keys, against a scrubbed copy of the book"
security:
- apiKey: []
tags:
- name: Account
  description: "What a key is."
- name: Projects
  description: "Projects on the lender's book, their parties, budgets and risk reports."
- name: Draws
  description: "Draw requests and their evidence."
- name: Inspections
  description: "Reviewed site visits."
- name: Documents
  description: "Files the lender may read, and uploads."
paths:
  "/me":
    get:
      operationId: getMe
      tags:
      - Account
      summary: "Who am I"
      description: "The lender, the department and the key behind the bearer. Needs no scope; the first call to make with a new key."
      x-scope: null
      x-drawserv-phase: P1
      x-drawserv-served: true
      responses:
        "200":
          description: "The record"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Me"
                required:
                - data
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
      security:
      - apiKey: []
  "/projects":
    get:
      operationId: listProjects
      tags:
      - Projects
      summary: "List projects"
      description: "Every project in the key's view — the lender's book, or the department's — newest first."
      x-scope: "projects:read"
      x-drawserv-phase: P1
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/lifecycle"
      - "$ref": "#/components/parameters/department"
      - "$ref": "#/components/parameters/updated_since"
      - "$ref": "#/components/parameters/page"
      - "$ref": "#/components/parameters/per_page"
      responses:
        "200":
          description: "One page of the set"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Project"
                  page:
                    "$ref": "#/components/schemas/Page"
                required:
                - data
                - page
        "400":
          "$ref": "#/components/responses/ValidationFailed"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
    post:
      operationId: createProject
      tags:
      - Projects
      summary: "Create a project"
      description: "Opens a project on the lender's book in the Draft stage of intake, as the portal's New project form would. The answer is the project as the API reads it back."
      x-scope: "projects:write"
      x-drawserv-phase: P2
      x-drawserv-served: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ProjectCreate"
      responses:
        "201":
          description: Created
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Project"
                required:
                - data
        "400":
          "$ref": "#/components/responses/ValidationFailed"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "409":
          "$ref": "#/components/responses/Conflict"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
  "/projects/{projectId}":
    get:
      operationId: getProject
      tags:
      - Projects
      summary: "Get a project"
      x-scope: "projects:read"
      x-drawserv-phase: P1
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/projectId"
      responses:
        "200":
          description: "The record"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Project"
                required:
                - data
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
  "/projects/{projectId}/intake":
    patch:
      operationId: updateProjectIntake
      tags:
      - Projects
      summary: "Update a project's intake fields"
      description: "Changes the intake fields of a project that has not closed. Refused with conflict once the project is active."
      x-scope: "projects:write"
      x-drawserv-phase: P2
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/projectId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/IntakePatch"
      responses:
        "200":
          description: "The record"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Project"
                required:
                - data
        "400":
          "$ref": "#/components/responses/ValidationFailed"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Conflict"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
  "/projects/{projectId}/parties":
    get:
      operationId: listProjectParties
      tags:
      - Projects
      summary: "List a project's parties"
      description: "The borrower, the builder and any other party on the project, with their contact people. Party changes stay in the portal in v1."
      x-scope: "projects:read"
      x-drawserv-phase: P1
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/projectId"
      responses:
        "200":
          description: "One page of the set"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Party"
                  page:
                    "$ref": "#/components/schemas/Page"
                required:
                - data
                - page
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
  "/projects/{projectId}/budget":
    get:
      operationId: getProjectBudget
      tags:
      - Projects
      summary: "Get a project's budget"
      description: "The budget lines, the funding sources and the totals, computed on the server."
      x-scope: "projects:read"
      x-drawserv-phase: P1
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/projectId"
      responses:
        "200":
          description: "The record"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Budget"
                required:
                - data
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
  "/projects/{projectId}/draws":
    get:
      operationId: listProjectDraws
      tags:
      - Draws
      summary: "List a project's draws"
      description: "The project's draws, newest first, with the same slices and windows as the whole-book list."
      x-scope: "draws:read"
      x-drawserv-phase: P1
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/projectId"
      - "$ref": "#/components/parameters/scope"
      - "$ref": "#/components/parameters/since"
      - "$ref": "#/components/parameters/until"
      - "$ref": "#/components/parameters/page"
      - "$ref": "#/components/parameters/per_page"
      responses:
        "200":
          description: "One page of the set"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Draw"
                  page:
                    "$ref": "#/components/schemas/Page"
                required:
                - data
                - page
        "400":
          "$ref": "#/components/responses/ValidationFailed"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
  "/projects/{projectId}/documents":
    get:
      operationId: listProjectDocuments
      tags:
      - Documents
      summary: "List a project's documents"
      description: "The documents the lender may read on the project, newest first, each with a short-lived download link."
      x-scope: "documents:read"
      x-drawserv-phase: P2
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/projectId"
      - "$ref": "#/components/parameters/page"
      - "$ref": "#/components/parameters/per_page"
      responses:
        "200":
          description: "One page of the set"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Document"
                  page:
                    "$ref": "#/components/schemas/Page"
                required:
                - data
                - page
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
    post:
      operationId: createProjectDocument
      tags:
      - Documents
      summary: "Ask for an upload slot"
      description: "Step one of two: declare the file and get a signed URL to PUT its bytes to. The document row lands when the bytes do."
      x-scope: "documents:write"
      x-drawserv-phase: P2
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/projectId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/DocumentInput"
      responses:
        "201":
          description: Created
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/UploadSlot"
                required:
                - data
        "400":
          "$ref": "#/components/responses/ValidationFailed"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
  "/risk-reports/{projectId}":
    get:
      operationId: getRiskReport
      tags:
      - Projects
      summary: "Get a project's released risk report"
      description: "The stored report PDF, released while the project's approval stands; not_found before approval or after a later decision."
      x-scope: "projects:read"
      x-drawserv-phase: P1
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/projectId"
      responses:
        "200":
          description: "The record"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RiskReport"
                required:
                - data
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
  "/draws":
    get:
      operationId: listDraws
      tags:
      - Draws
      summary: "List draws"
      description: "Draws across the key's view, newest first. Ask for one slice (scope) or one window (since, until); the whole ledger is never answered at once."
      x-scope: "draws:read"
      x-drawserv-phase: P1
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/scope"
      - "$ref": "#/components/parameters/since"
      - "$ref": "#/components/parameters/until"
      - "$ref": "#/components/parameters/page"
      - "$ref": "#/components/parameters/per_page"
      responses:
        "200":
          description: "One page of the set"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Draw"
                  page:
                    "$ref": "#/components/schemas/Page"
                required:
                - data
                - page
        "400":
          "$ref": "#/components/responses/ValidationFailed"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
  "/draws/{drawId}":
    get:
      operationId: getDraw
      tags:
      - Draws
      summary: "Get a draw"
      x-scope: "draws:read"
      x-drawserv-phase: P1
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/drawId"
      responses:
        "200":
          description: "The record"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Draw"
                required:
                - data
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
  "/inspections":
    get:
      operationId: listInspections
      tags:
      - Inspections
      summary: "List inspections"
      description: "Reviewed inspections across the key's view, newest first."
      x-scope: "inspections:read"
      x-drawserv-phase: P1
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/project"
      - "$ref": "#/components/parameters/inspections_since"
      - "$ref": "#/components/parameters/page"
      - "$ref": "#/components/parameters/per_page"
      responses:
        "200":
          description: "One page of the set"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Inspection"
                  page:
                    "$ref": "#/components/schemas/Page"
                required:
                - data
                - page
        "400":
          "$ref": "#/components/responses/ValidationFailed"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
  "/inspections/{inspectionId}":
    get:
      operationId: getInspection
      tags:
      - Inspections
      summary: "Get an inspection"
      x-scope: "inspections:read"
      x-drawserv-phase: P1
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/inspectionId"
      responses:
        "200":
          description: "The record"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Inspection"
                required:
                - data
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
  "/documents/{documentId}":
    get:
      operationId: getDocument
      tags:
      - Documents
      summary: "Get a document"
      description: "The document's metadata and a fresh short-lived download link."
      x-scope: "documents:read"
      x-drawserv-phase: P2
      x-drawserv-served: true
      parameters:
      - "$ref": "#/components/parameters/documentId"
      responses:
        "200":
          description: "The record"
          headers:
            X-Request-Id:
              "$ref": "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Document"
                required:
                - data
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/ForbiddenScope"
        "404":
          "$ref": "#/components/responses/NotFound"
        "429":
          "$ref": "#/components/responses/RateLimited"
        "500":
          "$ref": "#/components/responses/Internal"
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: "dsk_live_… (production) or dsk_test_… (sandbox)"
      description: "A per-lender key, minted by Drawserv staff, shown once, revocable at once. The scope an operation needs is its `x-scope`; a key's scopes are listed in the Authentication section."
  headers:
    RequestId:
      description: "The request's id — quote it to support; it names the ledger row"
      schema:
        type: string
  parameters:
    projectId:
      name: projectId
      in: path
      required: true
      description: "A project's id"
      schema:
        type: string
        format: uuid
    drawId:
      name: drawId
      in: path
      required: true
      description: "A draw's id"
      schema:
        type: string
        format: uuid
    inspectionId:
      name: inspectionId
      in: path
      required: true
      description: "An inspection's id"
      schema:
        type: string
        format: uuid
    documentId:
      name: documentId
      in: path
      required: true
      description: "A document's id"
      schema:
        type: string
    page:
      name: page
      in: query
      required: false
      description: "The page to answer, from 1"
      schema:
        type: integer
        minimum: 1
        default: 1
    per_page:
      name: per_page
      in: query
      required: false
      description: "Rows per page, at most 200"
      schema:
        type: integer
        minimum: 1
        maximum: 200
        default: 50
    lifecycle:
      name: lifecycle
      in: query
      required: false
      description: "Only projects in this lifecycle"
      schema:
        type: string
        enum:
        - intake
        - active
        - completed
    department:
      name: department
      in: query
      required: false
      description: "Only projects in this department (LenderDepartment.id)"
      schema:
        type: string
    updated_since:
      name: updated_since
      in: query
      required: false
      description: "Only projects changed at or after this moment"
      schema:
        type: string
        format: date-time
    scope:
      name: scope
      in: query
      required: false
      description: "One slice of the ledger: working is every draw not yet funded or declined; funded and declined one outcome each; open-waivers the draws still carrying a lien waiver to sign"
      schema:
        type: string
        enum:
        - working
        - funded
        - declined
        - open-waivers
    since:
      name: since
      in: query
      required: false
      description: "A window of the ledger: every open draw whatever its age, plus every draw with a stage stamp at or after this moment"
      schema:
        type: string
        format: date-time
    until:
      name: until
      in: query
      required: false
      description: "With since: only stage stamps in [since, until) — an older window of history"
      schema:
        type: string
        format: date-time
    inspections_since:
      name: since
      in: query
      required: false
      description: "Only inspections reviewed at or after this moment"
      schema:
        type: string
        format: date-time
    project:
      name: project
      in: query
      required: false
      description: "Only inspections on this project"
      schema:
        type: string
        format: uuid
  responses:
    Unauthorized:
      description: "The key is missing, malformed, revoked, expired, for the other environment, or a session token was sent"
      headers:
        X-Request-Id:
          "$ref": "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    ForbiddenScope:
      description: "The key lacks the scope the operation needs"
      headers:
        X-Request-Id:
          "$ref": "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    NotFound:
      description: "No such record in the caller's view — a record outside the key's lender or department reads as absent"
      headers:
        X-Request-Id:
          "$ref": "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    ValidationFailed:
      description: "A parameter or body field is missing or malformed"
      headers:
        X-Request-Id:
          "$ref": "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    RateLimited:
      description: "The key's request budget is spent; Retry-After says when to try again"
      headers:
        X-Request-Id:
          "$ref": "#/components/headers/RequestId"
        Retry-After:
          description: "Seconds to wait before trying again"
          schema:
            type: integer
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    Conflict:
      description: "The write collides with the record's current state (a project no longer pre-closing)"
      headers:
        X-Request-Id:
          "$ref": "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
    Internal:
      description: "Something failed on our side; the requestId names the ledger row to quote"
      headers:
        X-Request-Id:
          "$ref": "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
  schemas:
    Page:
      type: object
      description: "Where a list answer sits in its set."
      properties:
        number:
          type: integer
          description: "The page answered, from 1"
        size:
          type: integer
          description: "Rows per page as applied (per_page, capped at 200)"
        total:
          type: integer
          description: "Rows in the whole set"
      required:
      - number
      - size
      - total
    Error:
      type: object
      description: "Every non-2xx answer. The code is the contract; the message is for a person; the requestId names the request's ledger row (also the X-Request-Id header) to quote to support."
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum:
              - unauthorized
              - forbidden_scope
              - not_found
              - validation_failed
              - rate_limited
              - conflict
              - internal
              description: "One of the closed set of codes"
            message:
              type: string
              description: "A sentence for a person; wording may change"
            requestId:
              type: string
              description: "The request's id, the same as the X-Request-Id header"
          required:
          - code
          - message
          - requestId
      required:
      - error
    Lender:
      type: object
      description: "The lending organisation a key acts for."
      properties:
        id:
          type: string
          description: "The lender's id"
          x-column: id
        name:
          type: string
          description: "The lender's name"
          x-column: name
        createdAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the lender was added to Drawserv"
          x-column: created_at
      required:
      - id
      - name
      x-graph-label: Lender
      x-table: lenders
    LenderDepartment:
      type: object
      description: "A department of the lender. A key bound to a department sees that department's projects; a key bound to the lender alone sees the whole book."
      properties:
        id:
          type: string
          description: "The department's id"
          x-column: id
        name:
          type: string
          description: "The department's name"
          x-column: name
        createdAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the department was added"
          x-column: created_at
      required:
      - id
      - name
      x-graph-label: LenderDepartment
      x-table: lender_departments
    ApiKey:
      type: object
      description: "The calling key, as the ledger knows it. The secret itself is never returned."
      properties:
        name:
          type: string
          description: "The display name given at creation (\"Acme Bank API — Treasury\")"
        prefix:
          type: string
          description: "The key's first six characters, to tell keys apart"
        environment:
          type: string
          enum:
          - live
          - test
          description: "live on production, test on the sandbox"
        scopes:
          type: array
          items:
            type: string
            enum:
            - "projects:read"
            - "draws:read"
            - "inspections:read"
            - "documents:read"
            - "events:read"
            - "projects:write"
            - "documents:write"
            - "webhooks:manage"
          description: "The key's scopes — the ceiling of what it may do"
        createdAt:
          type: string
          format: date-time
          description: "When the key was minted"
        expiresAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the key stops working; null for no expiry"
        lastUsedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "The newest request before this one"
      required:
      - name
      - prefix
      - environment
      - scopes
      - createdAt
    Me:
      type: object
      description: "What a caller is: the lender, the department (if the key is narrowed to one) and the key."
      properties:
        lender:
          "$ref": "#/components/schemas/Lender"
          description: "The lender the key acts for"
        department:
          anyOf:
          - "$ref": "#/components/schemas/LenderDepartment"
          - type: "null"
          description: "The department the key is narrowed to, or null for the whole book"
        key:
          "$ref": "#/components/schemas/ApiKey"
          description: "The calling key"
      required:
      - lender
      - department
      - key
    ProjectRef:
      type: object
      description: "Enough of a project to name and route it from a draw or inspection row."
      properties:
        id:
          type: string
          format: uuid
          description: "The project's id"
        address:
          type:
          - string
          - "null"
          description: "The one-line property address"
        borrower:
          type:
          - string
          - "null"
          description: "The borrower as named at intake"
        builder:
          type:
          - string
          - "null"
          description: "The builder (general contractor) as named at intake"
        lifecycleStatus:
          type: string
          enum:
          - intake
          - active
          - completed
          description: "The overall lifecycle"
      required:
      - id
      - address
      - lifecycleStatus
    Person:
      type: object
      description: "A contact person on a party."
      properties:
        name:
          type:
          - string
          - "null"
          description: "Full name"
        email:
          type:
          - string
          - "null"
          description: "Email address"
        phone:
          type:
          - string
          - "null"
          description: "Phone number, as entered"
    Project:
      type: object
      description: "One construction-loan project on the lender's book. lifecycleStatus is the overall lifecycle (intake → active → completed); intakeStage is the stage within intake and only means something while lifecycleStatus is intake. The four money figures (budgetTotal, fundedToDate, remainingToFund, constructionProgress) are computed on the server from the budget, the funded draws and the reviewed inspections — read them, never re-sum lines."
      properties:
        id:
          type: string
          format: uuid
          description: "The project's id"
          x-column: id
        lenderId:
          type: string
          description: "The lender the project is on the book of — always the key's lender"
          x-column: lender_id
        departmentId:
          type:
          - string
          - "null"
          description: "The department the project sits in; null for the whole institution"
          x-column: lender_department_id
        address:
          type:
          - string
          - "null"
          description: "The one-line property address"
          x-column: property_address
        streetAddress:
          type:
          - string
          - "null"
          description: "Street address"
          x-column: street_address
        city:
          type:
          - string
          - "null"
          description: City
          x-column: city
        state:
          type:
          - string
          - "null"
          description: "US state, full name (Maine)"
          x-column: state
        zipCode:
          type:
          - string
          - "null"
          description: "ZIP code"
          x-column: zip_code
        lifecycleStatus:
          type: string
          enum:
          - intake
          - active
          - completed
          description: "The overall lifecycle: intake → active → completed"
          x-column: lifecycle_status
        intakeStage:
          type: string
          enum:
          - Draft
          - "In Review"
          - Approved
          - Declined
          description: "The stage within intake; only meaningful while lifecycleStatus is intake"
          x-column: status
        paymentStatus:
          type:
          - string
          - "null"
          enum:
          - unpaid
          - processing
          - paid
          - waived
          description: "The project fee's billing state"
          x-column: payment_status
        constructionType:
          type:
          - string
          - "null"
          enum:
          - "New Construction"
          - Renovation
          - Modular
          - Commercial
          - Manufactured
          - "Modular / Manufactured"
          description: "Modular = factory-built sections set on a permanent foundation to the state/IRC residential code, financed like site-built; Manufactured = built to the federal HUD code on a permanent chassis, the mobile home; the retired Modular / Manufactured is one of the two, not yet reclassified — older rows may still carry Modular / Manufactured"
          x-column: construction_type
        sqft:
          type:
          - number
          - "null"
          description: "Total square footage"
          x-column: total_square_footage
        contractAmount:
          type:
          - number
          - "null"
          description: "The builder's contract or cost-plus estimate, dollars — the intake basis"
          x-column: contract_amount
        referenceCode:
          type:
          - string
          - "null"
          description: "An external reference or loan number, when one was recorded"
          x-column: reference_code
        closingDate:
          type:
          - string
          - "null"
          format: date
          description: "The loan's closing date, recorded when the project was activated; null for projects activated before it was tracked"
          x-column: closing_date
        expectedMaturityDate:
          type:
          - string
          - "null"
          format: date
          description: "The loan's expected maturity date"
          x-column: expected_maturity_date
        submittedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the project was submitted for review (intake)"
          x-column: submitted_at
        approvedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the project was approved (intake); the risk report is released from this moment"
          x-column: approved_at
        declinedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the project was declined (intake)"
          x-column: declined_at
        completedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the project was completed"
          x-column: completed_at
        budgetConfirmedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When staff confirmed the budget"
          x-column: budget_confirmed_at
        budgetConfirmedTotal:
          type:
          - number
          - "null"
          description: "The working-budget total stamped when the budget was confirmed, dollars"
          x-column: budget_confirmed_total
        projectFeeBilled:
          type:
          - number
          - "null"
          description: "The project fee as billed, dollars; null until billed"
          x-column: assessment_fee_billed
        latitude:
          type:
          - number
          - "null"
          description: "WGS-84 latitude of the property"
          x-column: latitude
        longitude:
          type:
          - number
          - "null"
          description: "WGS-84 longitude of the property"
          x-column: longitude
        createdAt:
          type: string
          format: date-time
          description: "When the project was created"
          x-column: created_at
        updatedAt:
          type: string
          format: date-time
          description: "When the project last changed"
          x-column: updated_at
        budgetTotal:
          type:
          - number
          - "null"
          x-unit: USD
          description: "Σ the budget lines, dollars; computed on the server"
          x-derived: rollup
        fundedToDate:
          type: number
          x-unit: USD
          description: "The pre-ledger funding epoch plus every funded draw's lines, dollars; computed on the server"
          x-derived: rollup
        remainingToFund:
          type:
          - number
          - "null"
          x-unit: USD
          description: "budgetTotal − fundedToDate, dollars; computed on the server"
          x-derived: rollup
        constructionProgress:
          type:
          - number
          - "null"
          x-unit: percent
          description: "Percent complete, 0–100, budget-weighted over each line's latest reviewed observation; null before a reviewed inspection"
          x-derived: rollup
        borrower:
          type:
          - string
          - "null"
          description: "The borrower as named at intake; the party records carry the people"
          x-column: borrower
        builder:
          type:
          - string
          - "null"
          description: "The builder (general contractor) as named at intake"
          x-column: contractor
        lender:
          "$ref": "#/components/schemas/Lender"
          description: "The lender, by name"
        department:
          anyOf:
          - "$ref": "#/components/schemas/LenderDepartment"
          - type: "null"
          description: "The department, by name, or null"
      required:
      - id
      - lenderId
      - lender
      - lifecycleStatus
      - intakeStage
      - createdAt
      - updatedAt
      - fundedToDate
      x-graph-label: Project
      x-table: projects
      x-redaction: PROJECT_STAFF_ONLY
    Party:
      type: object
      description: "A party on the project — the borrower, the builder, or another role — with its contact people."
      properties:
        id:
          type: string
          description: "The party record's id"
          x-column: id
        projectId:
          type: string
          format: uuid
          description: "The project"
          x-column: project_id
        stakeholderId:
          type:
          - string
          - "null"
          format: uuid
          description: "The cross-project identity (the same builder on several projects shares one); null while unlinked"
          x-column: stakeholder_id
        role:
          type: string
          x-known-values:
          - borrower
          - builder
          description: "The party's role on the project; borrower and builder are the two the app writes, other words are possible"
          x-column: role
        partyKind:
          type: string
          enum:
          - company
          - household
          description: "company = a business; household = an individual or family"
          x-column: party_kind
        companyName:
          type:
          - string
          - "null"
          description: "The company (empty for a household)"
          x-column: company_name
        contactName:
          type:
          - string
          - "null"
          description: "The primary contact's name"
          x-column: contact_name
        contactEmail:
          type:
          - string
          - "null"
          description: "The primary contact's email"
          x-column: contact_email
        contactPhone:
          type:
          - string
          - "null"
          description: "The primary contact's phone"
          x-column: contact_phone
        createdAt:
          type: string
          format: date-time
          description: "When the party was added"
          x-column: created_at
        people:
          type: array
          items:
            "$ref": "#/components/schemas/Person"
          description: "The contact people, first = primary"
          x-column: people
        stakeholderName:
          type:
          - string
          - "null"
          description: "The cross-project identity's canonical name; null while unlinked"
      required:
      - id
      - projectId
      - role
      - partyKind
      - people
      - createdAt
      x-table: project_contacts
      x-redaction: CONTACT_STAFF_ONLY
    BudgetLineItem:
      type: object
      description: "A line of the project's budget (the uses side). fundedToDate is computed on the server: the pre-ledger epoch plus every funded draw's amount on the line."
      properties:
        id:
          type: string
          description: "The line's id"
          x-column: id
        name:
          type: string
          description: "The line's name"
          x-column: name
        amount:
          type: number
          description: "The budgeted amount, dollars"
          x-column: amount
        epochFundedToDate:
          type:
          - number
          - "null"
          description: "Funded on the line before draws were recorded on the platform, dollars; a starting balance, not a running total"
          x-column: funded_to_date
        epochClosingDraw:
          type:
          - number
          - "null"
          description: "The line's dollars settled in the closing draw at intake; a starting balance, not a running total"
          x-column: closing_draw
        sortOrder:
          type: integer
          description: "Display order"
          x-column: sort_order
        createdAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the line was added"
          x-column: created_at
        updatedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the line last changed"
          x-column: updated_at
        currentProgress:
          type:
          - number
          - "null"
          x-unit: percent
          description: "The line's latest reviewed observation, 0–100; null when no reviewed inspection observed it"
          x-derived: rollup
        fundedToDate:
          type: number
          description: "Funded on the line to date, dollars, to the cent: epochFundedToDate plus epochClosingDraw plus funded draws' lines against it"
      required:
      - id
      - name
      - amount
      - fundedToDate
      - sortOrder
      x-graph-label: BudgetLineItem
      x-table: project_budget_lines
    FundingSource:
      type: object
      description: "Where the project's money comes from (the sources side): the loan, or equity. fundedToDate is computed on the server like a budget line's."
      properties:
        id:
          type: string
          description: "The source's id"
          x-column: id
        name:
          type: string
          description: "The source's name"
          x-column: name
        sourceType:
          type: string
          enum:
          - loan
          - equity
          description: "loan or equity"
          x-column: source_type
        amount:
          type: number
          description: "The committed amount, dollars"
          x-column: amount
        epochFundedToDate:
          type:
          - number
          - "null"
          description: "Drawn from the source before draws were recorded on the platform, dollars; a starting balance, not a running total"
          x-column: funded_to_date
        epochClosingDraw:
          type:
          - number
          - "null"
          description: "The source's dollars settled in the closing draw at intake; a starting balance, not a running total"
          x-column: closing_draw
        sortOrder:
          type: integer
          description: "Display order"
          x-column: sort_order
        createdAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the source was added"
          x-column: created_at
        updatedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the source last changed"
          x-column: updated_at
        fundedToDate:
          type: number
          description: "Drawn from the source to date, dollars, to the cent: epochFundedToDate plus epochClosingDraw plus funded draws' funding lines against it"
      required:
      - id
      - name
      - sourceType
      - amount
      - fundedToDate
      - sortOrder
      x-graph-label: FundingSource
      x-table: project_funding_sources
    Budget:
      type: object
      description: "The project's budget: its lines, its funding sources and the four totals the app computes from them."
      properties:
        projectId:
          type: string
          format: uuid
          description: "The project"
        confirmedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When staff confirmed the budget; null while it is still being assembled"
        lines:
          type: array
          items:
            "$ref": "#/components/schemas/BudgetLineItem"
          description: "The budget lines, in sort order"
        fundingSources:
          type: array
          items:
            "$ref": "#/components/schemas/FundingSource"
          description: "The funding sources, in sort order"
        totals:
          type: object
          properties:
            budgetTotal:
              type:
              - number
              - "null"
              description: "Σ the lines, dollars, to the cent; null until the budget has lines"
            fundedToDate:
              type: number
              description: "Funded to date across the lines, dollars, to the cent"
            remainingToFund:
              type:
              - number
              - "null"
              description: "budgetTotal − fundedToDate, dollars, to the cent; null until the budget has lines"
            constructionProgress:
              type:
              - number
              - "null"
              description: "Percent complete, 0–100, from the latest reviewed inspection; null before one"
          required:
          - fundedToDate
          - constructionProgress
      required:
      - projectId
      - lines
      - fundingSources
      - totals
    Draw:
      type: object
      description: "A draw request on a project. status is the current stage only (draft → pending → approved → funded, declined as the side exit); the stage stamps say what was ever reached. number is \"Draw 4\": the 1-based rank by creation within the project, computed on the server. requestedAmount is Σ the lines, computed on the server. Approvals that were revoked and lien waivers that were voided are not returned."
      properties:
        id:
          type: string
          format: uuid
          description: "The draw's id"
          x-column: id
        projectId:
          type: string
          format: uuid
          description: "The project"
          x-column: project_id
        status:
          type: string
          enum:
          - draft
          - pending
          - approved
          - funded
          - declined
          - inspection
          - review
          description: "The current stage: draft → pending → approved → funded, declined as the side exit — older rows may still carry inspection, review"
          x-column: status
        drawType:
          type: string
          enum:
          - standard
          - prepaid
          - closing
          description: "prepaid = the builder's spend before the loan; closing = disbursed when the loan closes; standard = a regular post-closing request"
          x-column: draw_type
        requestedByName:
          type:
          - string
          - "null"
          description: "Who requested the draw"
          x-column: requested_by_name
        createdAt:
          type: string
          format: date-time
          description: "When the draw was created (a draft)"
          x-column: created_at
        submittedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When it left draft — the request date"
          x-column: submitted_at
        approvedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When staff approved it"
          x-column: approved_at
        approvedByName:
          type:
          - string
          - "null"
          description: "Who approved it"
          x-column: approved_by_name
        declinedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When it was declined"
          x-column: declined_at
        declinedByName:
          type:
          - string
          - "null"
          description: "Who declined it"
          x-column: declined_by_name
        fundedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When it was marked funded — the draw's completion"
          x-column: funded_at
        fundedByName:
          type:
          - string
          - "null"
          description: "Who marked it funded"
          x-column: funded_by_name
        fundedVia:
          type:
          - string
          - "null"
          enum:
          - lender
          - staff
          description: "lender = the lender signed the funding; staff = recorded by staff; null until funded"
          x-column: funded_via
        inspectionWaivedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When staff waived the inspection requirement for this draw"
          x-column: inspection_waived_at
        inspectionWaivedByName:
          type:
          - string
          - "null"
          description: "Who waived it"
          x-column: inspection_waived_by_name
        lienWaiversWaivedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When staff waived the lien-waiver requirement for this draw (not a waiver's own state)"
          x-column: lien_waivers_waived_at
        lienWaiversWaivedByName:
          type:
          - string
          - "null"
          description: "Who waived it"
          x-column: lien_waivers_waived_by_name
        approvalsWaivedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When staff waived the party-approval requirement for this draw (not one approval's own state)"
          x-column: approvals_waived_at
        approvalsWaivedByName:
          type:
          - string
          - "null"
          description: "Who waived it"
          x-column: approvals_waived_by_name
        updatedAt:
          type: string
          format: date-time
          description: "When the draw last changed"
          x-column: updated_at
        number:
          type: integer
          description: "\"Draw 4\": the 1-based rank by creation within the project; computed on the server"
          x-derived: ordinal
        requestedAmount:
          type:
          - number
          - "null"
          x-unit: USD
          description: "Σ the lines, dollars; computed on the server (null while a draft has no lines)"
          x-derived: rollup
        declineReason:
          type:
          - string
          - "null"
          description: "Why it was declined"
          x-column: decline_reason
        fundedAmount:
          type:
          - number
          - "null"
          description: "The total the funding signature was for, dollars, to the cent; held while the draw stays funded"
          x-column: funded_amount
        fundedNote:
          type:
          - string
          - "null"
          description: "How the disbursement was confirmed, as written at funding"
          x-column: funded_note
        inspectionWaivedReason:
          type:
          - string
          - "null"
          description: "Why the inspection requirement was waived for this draw"
          x-column: inspection_waived_reason
        lienWaiversWaivedReason:
          type:
          - string
          - "null"
          description: "Why the lien-waiver requirement was waived for this draw"
          x-column: lien_waivers_waived_reason
        approvalsWaivedReason:
          type:
          - string
          - "null"
          description: "Why the party-approval requirement was waived for this draw"
          x-column: approvals_waived_reason
        project:
          "$ref": "#/components/schemas/ProjectRef"
          description: "The project, enough to name and route it"
        lines:
          type: array
          items:
            "$ref": "#/components/schemas/DrawLineItem"
          description: "The amounts requested per budget line"
        approvals:
          type: array
          items:
            "$ref": "#/components/schemas/DrawApproval"
          description: "The party approvals on the draw (revoked ones excluded)"
        lienWaivers:
          type: array
          items:
            "$ref": "#/components/schemas/LienWaiver"
          description: "The lien waivers requested on the draw (voided ones excluded)"
        inspections:
          type: array
          items:
            "$ref": "#/components/schemas/InspectionRef"
          description: "The reviewed inspections that are evidence for the draw — its own and any cited as shared evidence"
      required:
      - id
      - projectId
      - number
      - status
      - drawType
      - createdAt
      - updatedAt
      - project
      - lines
      - approvals
      - lienWaivers
      - inspections
      x-graph-label: Draw
      x-table: project_draws
    DrawLineItem:
      type: object
      description: "One draw's requested amount against one budget line. A budget line with no row here has nothing drawn on this draw."
      properties:
        id:
          type: string
          description: "The line's id"
          x-column: id
        budgetLineId:
          type: string
          description: "The budget line drawn against (BudgetLineItem.id)"
          x-column: budget_line_id
        amount:
          type: number
          description: "The amount requested on the line, dollars"
          x-column: amount
      required:
      - id
      - budgetLineId
      - amount
      x-graph-label: DrawLineItem
      x-table: project_draw_lines
    DrawApproval:
      type: object
      description: "A party's approval record on a draw: who still has to sign, who has signed, who was waived. state is computed per row. Revoked records are closed history and are not returned. The signature itself is not returned."
      properties:
        id:
          type: string
          description: "The approval record's id"
          x-column: id
        partyId:
          type:
          - string
          - "null"
          description: "The party asked (Party.id); null once that party was removed from the project"
          x-column: contact_id
        lastSentAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the approval request was last emailed or texted to the party; null if never sent"
          x-column: last_sent_at
        approvedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the party signed"
          x-column: approved_at
        approvedAmount:
          type:
          - number
          - "null"
          description: "The amount the signer consented to, dollars; null on records signed before it was stamped"
          x-column: approved_amount
        waivedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When staff waived this party's approval on the draw"
          x-column: waived_at
        waivedByName:
          type:
          - string
          - "null"
          description: "Who waived it"
          x-column: waived_by_name
        approverName:
          type:
          - string
          - "null"
          description: "The signer's name as typed at signing"
          x-column: approver_name
        signedVia:
          type:
          - string
          - "null"
          enum:
          - app
          - link
          description: "app = signed in the portal; link = signed through an emailed link"
          x-column: signed_via
        state:
          type: string
          enum:
          - revoked
          - signed
          - waived
          - awaiting
          description: "Computed per row: signed when approvedAt is set, else waived when waivedAt is set, else awaiting. Revoked records are not returned."
          x-derived: state
        partyName:
          type:
          - string
          - "null"
          description: "The party's name as recorded when asked"
          x-column: stakeholder_name
        contactName:
          type:
          - string
          - "null"
          description: "The person asked"
          x-column: contact_name
        approvedByName:
          type:
          - string
          - "null"
          description: "Who signed, as recorded"
          x-column: approved_by_name
        waivedReason:
          type:
          - string
          - "null"
          description: "Why staff waived this party's approval"
          x-column: waived_reason
      required:
      - id
      - state
      x-graph-label: DrawApproval
    LienWaiver:
      type: object
      description: "A lien waiver requested from a payee on a draw. state is computed per row: requested (open) or executed. Voided waivers are closed history and are not returned."
      properties:
        id:
          type: string
          description: "The waiver's id"
          x-column: id
        stakeholderId:
          type:
          - string
          - "null"
          format: uuid
          description: "The payee's cross-project identity; null once that identity was deleted"
          x-column: stakeholder_id
        amount:
          type: number
          description: "The amount waived, dollars"
          x-column: amount
        requestedAt:
          type: string
          format: date-time
          description: "When the waiver was requested"
          x-column: created_at
        lastSentAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the signature request was last emailed or texted to the payee; null if never sent"
          x-column: last_sent_at
        requestedByName:
          type:
          - string
          - "null"
          description: "Who requested it"
          x-column: requested_by_name
        signedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the payee signed"
          x-column: signed_at
        signedByName:
          type:
          - string
          - "null"
          description: "Who signed, as typed at signing"
          x-column: signed_by_name
        signedVia:
          type:
          - string
          - "null"
          enum:
          - app
          - link
          description: "app = signed in the portal; link = signed through an emailed link"
          x-column: signed_via
        state:
          type: string
          enum:
          - voided
          - executed
          - requested
          description: "Computed per row: executed when signedAt is set, else requested (open). Voided waivers are not returned."
          x-derived: state
        stakeholderName:
          type:
          - string
          - "null"
          description: "The payee's name as recorded"
          x-column: stakeholder_name
      required:
      - id
      - amount
      - state
      - requestedAt
      x-graph-label: LienWaiver
    InspectionRef:
      type: object
      description: "An inspection as a draw's evidence: its id and stage stamps."
      properties:
        id:
          type: string
          format: uuid
          description: "The inspection's id"
        status:
          type: string
          enum:
          - pending
          - completed
          - reviewed
          description: "Always reviewed on a draw's evidence list"
        completedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the visit was submitted"
        reviewedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When staff reviewed and released it"
      required:
      - id
      - status
    Inspection:
      type: object
      description: "A site visit on a project, released to the lender once staff have reviewed it — the API returns reviewed inspections only. origin tells an ordered inspector visit from a member's own site check."
      properties:
        id:
          type: string
          format: uuid
          description: "The inspection's id"
          x-column: id
        projectId:
          type: string
          format: uuid
          description: "The project"
          x-column: project_id
        drawId:
          type:
          - string
          - "null"
          format: uuid
          description: "The draw it was ordered on; null for a periodic visit"
          x-column: draw_id
        status:
          type: string
          enum:
          - pending
          - completed
          - reviewed
          description: "The current stage: pending → completed → reviewed. The API returns reviewed inspections only"
          x-column: status
        origin:
          type: string
          enum:
          - ordered
          - self
          description: "ordered = an inspector visit staff ordered; self = a site check a borrower or builder submitted themselves"
          x-column: origin
        createdByName:
          type:
          - string
          - "null"
          description: "Who submitted a site check; null on an ordered visit"
          x-column: created_by_name
        scheduledFor:
          type:
          - string
          - "null"
          format: date
          description: "The date the visit was scheduled for"
          x-column: scheduled_for
        completedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the visit was submitted — the inspection date"
          x-column: completed_at
        reviewedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When staff reviewed and released it"
          x-column: reviewed_at
        reviewedByName:
          type:
          - string
          - "null"
          description: "Who reviewed it"
          x-column: reviewed_by_name
        createdAt:
          type: string
          format: date-time
          description: "When the visit was ordered or started"
          x-column: created_at
        updatedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the inspection last changed"
          x-column: updated_at
        inspectorName:
          type:
          - string
          - "null"
          description: "The inspector, by name; null on a site check"
          x-column: inspector_name
        summary:
          type:
          - string
          - "null"
          description: "The inspector's field notes"
          x-column: summary
        requestedByName:
          type:
          - string
          - "null"
          description: "The staff member who asked a member for the visit, when one did"
          x-column: requested_by_name
        project:
          "$ref": "#/components/schemas/ProjectRef"
          description: "The project, enough to name and route it"
        drawNumber:
          type:
          - integer
          - "null"
          description: "The number of the draw it was ordered on (\"Draw 4\"); null for a periodic visit"
        linkedDrawIds:
          type: array
          items:
            type: string
            format: uuid
          description: "Further draws citing this visit as shared evidence"
        progress:
          type: array
          items:
            "$ref": "#/components/schemas/ProgressLineItem"
          description: "What the visit observed per budget line, recorded at review"
      required:
      - id
      - projectId
      - status
      - origin
      - createdAt
      - project
      - linkedDrawIds
      - progress
      x-graph-label: Inspection
      x-table: project_inspections
      x-redaction: INSPECTION_STAFF_ONLY
    ProgressLineItem:
      type: object
      description: "What one inspection observed on one budget line: the percent complete recorded at review."
      properties:
        id:
          type: string
          description: "The observation's id"
          x-column: id
        budgetLineId:
          type: string
          description: "The budget line observed (BudgetLineItem.id)"
          x-column: budget_line_id
        progress:
          type: number
          description: "Percent complete observed, 0–100"
          x-column: progress
        createdAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the observation was recorded"
          x-column: created_at
        updatedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When it last changed"
          x-column: updated_at
      required:
      - id
      - budgetLineId
      - progress
      x-graph-label: ProgressLineItem
      x-table: project_inspection_progress
    Document:
      type: object
      description: "A file the lender may read — a released risk report, a draw's documents, a reviewed inspection's photos, the lender's own uploads — with a short-lived download link."
      properties:
        id:
          type: string
          description: "The document's id"
          x-column: id
        projectId:
          type: string
          format: uuid
          description: "The project"
          x-column: project_id
        drawId:
          type:
          - string
          - "null"
          format: uuid
          description: "The draw it is attached to, if any"
          x-column: draw_id
        inspectionId:
          type:
          - string
          - "null"
          format: uuid
          description: "The inspection it is attached to, if any"
          x-column: inspection_id
        name:
          type: string
          description: "The file name"
          x-column: name
        mime:
          type:
          - string
          - "null"
          description: "The MIME type"
          x-column: mime_type
        size:
          type:
          - integer
          - "null"
          description: Bytes
          x-column: size
        parseStatus:
          type:
          - string
          - "null"
          enum:
          - pending
          - parsing
          - completed
          - failed
          - skipped
          description: "Where the document is in text extraction; a photo is skipped"
          x-column: parse_status
        uploadedByKind:
          type:
          - string
          - "null"
          enum:
          - staff
          - member
          - lender
          - inspector
          - system
          - other
          description: "Who uploaded it: staff, a portal member, the lender, an inspector, or the system"
          x-column: uploaded_by_kind
        createdAt:
          type: string
          format: date-time
          description: "When it was uploaded"
          x-column: created_at
        takenAt:
          type:
          - string
          - "null"
          format: date-time
          description: "A photo's shutter time, which can be hours before the upload; null for a document"
          x-column: taken_at
        updatedAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the record last changed"
          x-column: updated_at
        uploadedByName:
          type:
          - string
          - "null"
          description: "Who uploaded it, by name"
          x-column: uploaded_by_name
        url:
          type:
          - string
          - "null"
          format: uri
          description: "A signed download link, good for a few minutes; fetch the document again for a fresh one"
        urlExpiresAt:
          type:
          - string
          - "null"
          format: date-time
          description: "When the link stops working"
      required:
      - id
      - projectId
      - name
      - createdAt
      x-graph-label: Document
      x-table: files
    UploadSlot:
      type: object
      description: "Where to put a document's bytes. PUT the file to uploadUrl with the headers given; the document row lands when the bytes do and is handled as a portal upload is."
      properties:
        documentId:
          type: string
          description: "The id the document will have"
        uploadUrl:
          type: string
          format: uri
          description: "A signed single-use URL to PUT the bytes to"
        method:
          type: string
          enum:
          - PUT
          description: "Always PUT"
        headers:
          type: object
          description: "Headers to send with the PUT, as given"
        expiresAt:
          type: string
          format: date-time
          description: "When the slot closes unused"
      required:
      - documentId
      - uploadUrl
      - method
      - headers
      - expiresAt
    RiskReport:
      type: object
      description: "The released risk report for a project: the stored PDF, released while the project's approval stands."
      properties:
        projectId:
          type: string
          format: uuid
          description: "The project"
        approvedAt:
          type: string
          format: date-time
          description: "The approval the report belongs to"
        url:
          type: string
          format: uri
          description: "A signed download link, good for a few minutes"
        urlExpiresAt:
          type: string
          format: date-time
          description: "When the link stops working"
      required:
      - projectId
      - approvedAt
      - url
      - urlExpiresAt
    ProjectCreate:
      type: object
      description: "A new project on the lender's book: the fields the portal's New project form takes, less the contract amount and the square footage, which are set inside the platform (a body carrying contractAmount or sqft is refused; both are read on Project). Everything else — the lender, the stage, the lifecycle — is set on the server. Upload documents to the project after creating it."
      properties:
        streetAddress:
          type: string
          description: "Street address of the property"
        city:
          type: string
          description: City
        state:
          type: string
          description: "US state, full name (Maine)"
        zipCode:
          type: string
          description: "ZIP code"
        borrower:
          type:
          - string
          - "null"
          description: "The borrower's name"
        builder:
          type:
          - string
          - "null"
          description: "The builder (general contractor)'s name"
        constructionType:
          type:
          - string
          - "null"
          enum:
          - "New Construction"
          - Renovation
          - Modular
          - Commercial
          - Manufactured
          - "Modular / Manufactured"
          description: "The construction type"
        departmentId:
          type:
          - string
          - "null"
          description: "The department the project is filed under: the key's own, or the lender's only one. Optional — send it to assert the department, or leave it out. A key not bound to a department at a lender with two or more cannot create projects; bind the key first"
        parties:
          type:
          - array
          - "null"
          items:
            "$ref": "#/components/schemas/PartyInput"
          description: "The parties to seed; the borrower and the builder at least"
      required:
      - streetAddress
      - city
      - state
      - zipCode
    PartyInput:
      type: object
      description: "A party to add at creation."
      properties:
        role:
          type: string
          x-known-values:
          - borrower
          - builder
          description: "borrower or builder (another word is accepted and shown as given)"
        partyKind:
          type: string
          enum:
          - company
          - household
          description: "company or household"
        companyName:
          type:
          - string
          - "null"
          description: "The company (empty for a household)"
        contactName:
          type:
          - string
          - "null"
          description: "The primary contact's name"
        contactEmail:
          type:
          - string
          - "null"
          description: "The primary contact's email"
        contactPhone:
          type:
          - string
          - "null"
          description: "The primary contact's phone"
        people:
          type:
          - array
          - "null"
          items:
            "$ref": "#/components/schemas/Person"
          description: "Further contact people"
      required:
      - role
      - partyKind
    IntakePatch:
      type: object
      description: "Intake fields to change on a project that has not closed. Send only the fields to change; a field sent as null is cleared. The contract amount and the square footage are set inside the platform and cannot be changed here (a body carrying contractAmount or sqft is refused; both are read on Project). Refused with conflict once the project is active."
      properties:
        streetAddress:
          type:
          - string
          - "null"
          description: "Street address of the property"
        city:
          type:
          - string
          - "null"
          description: City
        state:
          type:
          - string
          - "null"
          description: "US state, full name"
        zipCode:
          type:
          - string
          - "null"
          description: "ZIP code"
        borrower:
          type:
          - string
          - "null"
          description: "The borrower's name"
        builder:
          type:
          - string
          - "null"
          description: "The builder's name"
        constructionType:
          type:
          - string
          - "null"
          enum:
          - "New Construction"
          - Renovation
          - Modular
          - Commercial
          - Manufactured
          - "Modular / Manufactured"
          description: "The construction type"
    DocumentInput:
      type: object
      description: "What an upload slot needs."
      properties:
        name:
          type: string
          description: "The file name, with its extension"
        mime:
          type: string
          description: "The MIME type"
        size:
          type: integer
          description: Bytes
        drawId:
          type:
          - string
          - "null"
          format: uuid
          description: "Attach to this draw"
      required:
      - name
      - mime
      - size
x-drawserv-scopes:
  "projects:read": "Read projects, parties, budgets and released risk reports"
  "draws:read": "Read draws, their lines, approvals and lien waivers"
  "inspections:read": "Read reviewed inspections and their progress lines"
  "documents:read": "Read document metadata and fetch short-lived download links"
  "projects:write": "Create projects and update a pre-closing project's intake fields"
  "documents:write": "Upload documents to a project"
x-drawserv-default-scopes:
- "projects:read"
- "draws:read"
- "inspections:read"
- "documents:read"
x-drawserv-error-codes:
  unauthorized: "The key is missing, malformed, revoked, expired, for the other environment, or a session token was sent"
  forbidden_scope: "The key lacks the scope the operation needs"
  not_found: "No such record in the caller's view — a record outside the key's lender or department reads as absent"
  validation_failed: "A parameter or body field is missing or malformed"
  rate_limited: "The key's request budget is spent; Retry-After says when to try again"
  conflict: "The write collides with the record's current state (a project no longer pre-closing)"
  internal: "Something failed on our side; the requestId names the ledger row to quote"
x-drawserv-phases:
  P1: "the read door"
  P2: "writes and documents"
x-drawserv-served-phases:
- P1
- P2
x-drawserv-schema-revision: 16
