{
  "openapi": "3.1.0",
  "info": {
    "title": "HL Hunt Underwriting Partner API",
    "version": "0.1.0",
    "description": "GENERATED DOCUMENT -- DO NOT EDIT BY HAND. Produced by `npx tsx scripts/generate-openapi.ts` from the /v1 route sources: paths and methods from the route files on disk, scopes from each route's requireApiKey() call, request bodies from the zod schemas the routes parse with, responses from the return types of the `serialize*` functions they call, and error codes from STATUS_BY_CODE. Where a route validates its body with hand-written checks instead of a zod schema, this document says so on the operation rather than guessing a field list.\n\nMONEY AND UNITS. Every monetary field on this API is a whole number of DOLLARS, never cents: $10,378.00 is 10378. Amounts below one dollar are not representable and are refused rather than rounded. The one column stored in cents is `BankTransaction.amount`, and the read path converts it, so `GET /v1/bank-link/{id}/transactions` publishes dollars like every other endpoint. Rates are percentages: 11.8 means 11.8 %."
  },
  "servers": [
    {
      "url": "/api",
      "description": "Relative to the deployment host; the route tree is mounted at /api."
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "webhooks": {
    "application.received": {
      "post": {
        "summary": "A new application has been accepted and queued for underwriting. The payload is the case summary.",
        "description": "A new application has been accepted and queued for underwriting. The payload is the case summary. Delivered to every endpoint subscribed to this event, in the mode of the key that registered it. Up to 8 attempts: the first immediately, then after 1m, 5m, 30m, 2h, 6h, 12h, 24h. Respond 2xx to acknowledge; any other status, or no response within 10 seconds, schedules the next attempt. After the last one the delivery is marked exhausted and is not retried.",
        "parameters": [
          {
            "name": "X-Webhook-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 over \"<timestamp>:<raw body>\", keyed with the signing secret returned once when the endpoint was registered. Format: timestamp=<unix seconds>,sig=<hex>. Verify against the RAW bytes you received — re-serialising the JSON changes the digest. Reject a delivery whose timestamp is more than 300 seconds from your own clock. Every retry is signed FRESH, so a retry's timestamp is the moment it was sent, not the moment the event occurred.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Unique id for this event."
                  },
                  "type": {
                    "type": "string",
                    "const": "application.received"
                  },
                  "livemode": {
                    "type": "boolean",
                    "description": "True for a live-mode event, false for test mode."
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "live",
                      "test"
                    ]
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the event occurred, not when this attempt was sent."
                  },
                  "data": {
                    "type": "object",
                    "description": "The resource as the matching read endpoint serializes it."
                  }
                },
                "required": [
                  "id",
                  "type",
                  "livemode",
                  "environment",
                  "created_at",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Acknowledged. The delivery is complete."
          }
        }
      }
    },
    "application.decided": {
      "post": {
        "summary": "A binding decision was recorded on a case — approved or declined. The payload is the case summary carrying the new status.",
        "description": "A binding decision was recorded on a case — approved or declined. The payload is the case summary carrying the new status. Delivered to every endpoint subscribed to this event, in the mode of the key that registered it. Up to 8 attempts: the first immediately, then after 1m, 5m, 30m, 2h, 6h, 12h, 24h. Respond 2xx to acknowledge; any other status, or no response within 10 seconds, schedules the next attempt. After the last one the delivery is marked exhausted and is not retried.",
        "parameters": [
          {
            "name": "X-Webhook-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 over \"<timestamp>:<raw body>\", keyed with the signing secret returned once when the endpoint was registered. Format: timestamp=<unix seconds>,sig=<hex>. Verify against the RAW bytes you received — re-serialising the JSON changes the digest. Reject a delivery whose timestamp is more than 300 seconds from your own clock. Every retry is signed FRESH, so a retry's timestamp is the moment it was sent, not the moment the event occurred.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Unique id for this event."
                  },
                  "type": {
                    "type": "string",
                    "const": "application.decided"
                  },
                  "livemode": {
                    "type": "boolean",
                    "description": "True for a live-mode event, false for test mode."
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "live",
                      "test"
                    ]
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the event occurred, not when this attempt was sent."
                  },
                  "data": {
                    "type": "object",
                    "description": "The resource as the matching read endpoint serializes it."
                  }
                },
                "required": [
                  "id",
                  "type",
                  "livemode",
                  "environment",
                  "created_at",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Acknowledged. The delivery is complete."
          }
        }
      }
    },
    "application.needs_review": {
      "post": {
        "summary": "A case was routed to a human instead of being decided. The payload is the case summary.",
        "description": "A case was routed to a human instead of being decided. The payload is the case summary. Delivered to every endpoint subscribed to this event, in the mode of the key that registered it. Up to 8 attempts: the first immediately, then after 1m, 5m, 30m, 2h, 6h, 12h, 24h. Respond 2xx to acknowledge; any other status, or no response within 10 seconds, schedules the next attempt. After the last one the delivery is marked exhausted and is not retried.",
        "parameters": [
          {
            "name": "X-Webhook-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 over \"<timestamp>:<raw body>\", keyed with the signing secret returned once when the endpoint was registered. Format: timestamp=<unix seconds>,sig=<hex>. Verify against the RAW bytes you received — re-serialising the JSON changes the digest. Reject a delivery whose timestamp is more than 300 seconds from your own clock. Every retry is signed FRESH, so a retry's timestamp is the moment it was sent, not the moment the event occurred.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Unique id for this event."
                  },
                  "type": {
                    "type": "string",
                    "const": "application.needs_review"
                  },
                  "livemode": {
                    "type": "boolean",
                    "description": "True for a live-mode event, false for test mode."
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "live",
                      "test"
                    ]
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the event occurred, not when this attempt was sent."
                  },
                  "data": {
                    "type": "object",
                    "description": "The resource as the matching read endpoint serializes it."
                  }
                },
                "required": [
                  "id",
                  "type",
                  "livemode",
                  "environment",
                  "created_at",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Acknowledged. The delivery is complete."
          }
        }
      }
    },
    "application.analysis_failed": {
      "post": {
        "summary": "Underwriting analysis exhausted its retries for a case. The case still exists; nothing was decided.",
        "description": "Underwriting analysis exhausted its retries for a case. The case still exists; nothing was decided. Delivered to every endpoint subscribed to this event, in the mode of the key that registered it. Up to 8 attempts: the first immediately, then after 1m, 5m, 30m, 2h, 6h, 12h, 24h. Respond 2xx to acknowledge; any other status, or no response within 10 seconds, schedules the next attempt. After the last one the delivery is marked exhausted and is not retried.",
        "parameters": [
          {
            "name": "X-Webhook-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 over \"<timestamp>:<raw body>\", keyed with the signing secret returned once when the endpoint was registered. Format: timestamp=<unix seconds>,sig=<hex>. Verify against the RAW bytes you received — re-serialising the JSON changes the digest. Reject a delivery whose timestamp is more than 300 seconds from your own clock. Every retry is signed FRESH, so a retry's timestamp is the moment it was sent, not the moment the event occurred.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Unique id for this event."
                  },
                  "type": {
                    "type": "string",
                    "const": "application.analysis_failed"
                  },
                  "livemode": {
                    "type": "boolean",
                    "description": "True for a live-mode event, false for test mode."
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "live",
                      "test"
                    ]
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the event occurred, not when this attempt was sent."
                  },
                  "data": {
                    "type": "object",
                    "description": "The resource as the matching read endpoint serializes it."
                  }
                },
                "required": [
                  "id",
                  "type",
                  "livemode",
                  "environment",
                  "created_at",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Acknowledged. The delivery is complete."
          }
        }
      }
    },
    "batch.completed": {
      "post": {
        "summary": "Every application in a batch has terminated, whether it succeeded or failed. Fires exactly once per batch. The payload is the batch with its per-element outcomes.",
        "description": "Every application in a batch has terminated, whether it succeeded or failed. Fires exactly once per batch. The payload is the batch with its per-element outcomes. Delivered to every endpoint subscribed to this event, in the mode of the key that registered it. Up to 8 attempts: the first immediately, then after 1m, 5m, 30m, 2h, 6h, 12h, 24h. Respond 2xx to acknowledge; any other status, or no response within 10 seconds, schedules the next attempt. After the last one the delivery is marked exhausted and is not retried.",
        "parameters": [
          {
            "name": "X-Webhook-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 over \"<timestamp>:<raw body>\", keyed with the signing secret returned once when the endpoint was registered. Format: timestamp=<unix seconds>,sig=<hex>. Verify against the RAW bytes you received — re-serialising the JSON changes the digest. Reject a delivery whose timestamp is more than 300 seconds from your own clock. Every retry is signed FRESH, so a retry's timestamp is the moment it was sent, not the moment the event occurred.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Unique id for this event."
                  },
                  "type": {
                    "type": "string",
                    "const": "batch.completed"
                  },
                  "livemode": {
                    "type": "boolean",
                    "description": "True for a live-mode event, false for test mode."
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "live",
                      "test"
                    ]
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the event occurred, not when this attempt was sent."
                  },
                  "data": {
                    "type": "object",
                    "description": "The resource as the matching read endpoint serializes it."
                  }
                },
                "required": [
                  "id",
                  "type",
                  "livemode",
                  "environment",
                  "created_at",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Acknowledged. The delivery is complete."
          }
        }
      }
    },
    "document.analyzed": {
      "post": {
        "summary": "An uploaded document finished analysis. The payload is the document with its verification result.",
        "description": "An uploaded document finished analysis. The payload is the document with its verification result. Delivered to every endpoint subscribed to this event, in the mode of the key that registered it. Up to 8 attempts: the first immediately, then after 1m, 5m, 30m, 2h, 6h, 12h, 24h. Respond 2xx to acknowledge; any other status, or no response within 10 seconds, schedules the next attempt. After the last one the delivery is marked exhausted and is not retried.",
        "parameters": [
          {
            "name": "X-Webhook-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 over \"<timestamp>:<raw body>\", keyed with the signing secret returned once when the endpoint was registered. Format: timestamp=<unix seconds>,sig=<hex>. Verify against the RAW bytes you received — re-serialising the JSON changes the digest. Reject a delivery whose timestamp is more than 300 seconds from your own clock. Every retry is signed FRESH, so a retry's timestamp is the moment it was sent, not the moment the event occurred.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Unique id for this event."
                  },
                  "type": {
                    "type": "string",
                    "const": "document.analyzed"
                  },
                  "livemode": {
                    "type": "boolean",
                    "description": "True for a live-mode event, false for test mode."
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "live",
                      "test"
                    ]
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the event occurred, not when this attempt was sent."
                  },
                  "data": {
                    "type": "object",
                    "description": "The resource as the matching read endpoint serializes it."
                  }
                },
                "required": [
                  "id",
                  "type",
                  "livemode",
                  "environment",
                  "created_at",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Acknowledged. The delivery is complete."
          }
        }
      }
    },
    "bank_link.connected": {
      "post": {
        "summary": "An applicant completed a bank connection and transactions are available. The payload is the bank link status.",
        "description": "An applicant completed a bank connection and transactions are available. The payload is the bank link status. Delivered to every endpoint subscribed to this event, in the mode of the key that registered it. Up to 8 attempts: the first immediately, then after 1m, 5m, 30m, 2h, 6h, 12h, 24h. Respond 2xx to acknowledge; any other status, or no response within 10 seconds, schedules the next attempt. After the last one the delivery is marked exhausted and is not retried.",
        "parameters": [
          {
            "name": "X-Webhook-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 over \"<timestamp>:<raw body>\", keyed with the signing secret returned once when the endpoint was registered. Format: timestamp=<unix seconds>,sig=<hex>. Verify against the RAW bytes you received — re-serialising the JSON changes the digest. Reject a delivery whose timestamp is more than 300 seconds from your own clock. Every retry is signed FRESH, so a retry's timestamp is the moment it was sent, not the moment the event occurred.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Unique id for this event."
                  },
                  "type": {
                    "type": "string",
                    "const": "bank_link.connected"
                  },
                  "livemode": {
                    "type": "boolean",
                    "description": "True for a live-mode event, false for test mode."
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "live",
                      "test"
                    ]
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the event occurred, not when this attempt was sent."
                  },
                  "data": {
                    "type": "object",
                    "description": "The resource as the matching read endpoint serializes it."
                  }
                },
                "required": [
                  "id",
                  "type",
                  "livemode",
                  "environment",
                  "created_at",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Acknowledged. The delivery is complete."
          }
        }
      }
    }
  },
  "paths": {
    "/v1/applicants": {
      "get": {
        "operationId": "get_applicants",
        "tags": [
          "applicants"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "applicants:read"
        ],
        "x-route-file": "app/api/v1/applicants/route.ts",
        "summary": "the organization's applicant directory, paginated.",
        "x-rate-limit-per-minute": 200,
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 25,
              "maximum": 100
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "last_activity",
                "name",
                "date_added"
              ]
            },
            "x-derived-from": "oneOf()"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "inactive"
              ]
            },
            "x-derived-from": "oneOf()"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "individual",
                "business"
              ]
            },
            "x-derived-from": "oneOf()"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SerializedApplicant"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "number"
                        },
                        "per_page": {
                          "type": "number"
                        },
                        "total": {
                          "type": "number"
                        },
                        "total_pages": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "page",
                        "per_page",
                        "total",
                        "total_pages"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/applicants/{id}": {
      "get": {
        "operationId": "get_applicants_by_id",
        "tags": [
          "applicants"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "applicants:read"
        ],
        "x-route-file": "app/api/v1/applicants/[id]/route.ts",
        "summary": "one applicant from the directory.",
        "x-rate-limit-per-minute": 200,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedApplicant"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "patch_applicants_by_id",
        "tags": [
          "applicants"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "applicants:write"
        ],
        "x-route-file": "app/api/v1/applicants/[id]/route.ts",
        "summary": "update an applicant's contact details. ═══════════════════════════════════════════════════════════════════════════",
        "x-rate-limit-per-minute": 100,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "requestBody": {
          "required": true,
          "x-schema-source": "accepted-params-list",
          "description": "Field NAMES are derived from the route's ACCEPTED_PARAMS guard and are exact; any other key is a 400. Per-field types are enforced by hand-written checks and are not derivable, so none is published here.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "name": {
                    "x-derivation": "name-only"
                  },
                  "phone": {
                    "x-derivation": "name-only"
                  }
                }
              }
            }
          }
        },
        "x-refused-parameters": {
          "email": "'email' is not accepted: the applicant id is DERIVED from the lowercased email (apl_ + a hash of your organization id and that address), so changing it would change the id in the URL of this very request. It would not rename the applicant either — cases that cannot be rewritten keep the old address, leaving one person split across two ids with partial history under each. To onboard a corrected address, submit an application under it with POST /v1/underwrite.",
          "applicant_email": "'applicant_email' is not accepted; see 'email', which is not accepted either: the applicant id is derived from the email address.",
          "status": "'status' is not accepted: it is DERIVED, not stored. An applicant is 'active' when any of their cases was updated in the last 90 days and 'inactive' otherwise, and there is no column to set. Updating a case's contact details is itself activity.",
          "type": "'type' is not accepted: the applicant type (individual or business) is an UNDERWRITING INPUT — it selects the rules an application is analyzed under — not a contact detail. Changing it on an analyzed case would mean the decision was made under rules the record no longer claims.",
          "applicant_type": "'applicant_type' is not accepted; the parameter is named 'type', and it is not accepted either: the applicant type is an underwriting input, not a contact detail.",
          "credit_score": "'credit_score' is not accepted: it is read from the bureau report on the applicant's most recent case carrying one. Nothing may assert a score.",
          "total_applications": "'total_applications' is not accepted: it is a count of the applicant's cases.",
          "active_loans": "'active_loans' is not accepted: it is a count of the applicant's open loans.",
          "total_borrowed": "'total_borrowed' is not accepted: it is a sum over the applicant's originated loans.",
          "assigned_to": "'assigned_to' is not accepted: an underwriter is assigned to a CASE, not to an applicant. This endpoint does not address individual cases.",
          "assignedTo": "'assignedTo' is not accepted: an underwriter is assigned to a CASE, not to an applicant.",
          "id": "'id' is not accepted: the applicant is taken from the URL path, and the id is derived rather than assigned.",
          "applicant_id": "'applicant_id' is not accepted: the applicant is taken from the URL path.",
          "case_id": "'case_id' is not accepted: this endpoint updates the applicant, which is every one of their cases that can still be changed. To address one case, use the case endpoints.",
          "idempotency_key": "'idempotency_key' is not accepted as a body field: send it as the 'Idempotency-Key' HTTP header."
        },
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedApplicantUpdate"
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: conflict, idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/applicants/{id}/send-link": {
      "post": {
        "operationId": "post_applicants_by_id_send_link",
        "tags": [
          "applicants"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "applicants:write"
        ],
        "x-route-file": "app/api/v1/applicants/[id]/send-link/route.ts",
        "summary": "mint an applicant-facing link, chosen by the `link_type` discriminator, and (for a live key) email it.",
        "x-rate-limit-per-minute": 50,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "requestBody": {
          "required": true,
          "x-schema-source": "zod",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "link_type": {
                    "type": "string",
                    "enum": [
                      "application",
                      "bank_link"
                    ],
                    "description": "Which link to mint: an application invitation, or a bank-verification link."
                  },
                  "product_slug": {
                    "type": "string",
                    "description": "Required when link_type is \"application\" — the product the invitation is for, as listed by GET /v1/products. Refused for \"bank_link\"."
                  }
                },
                "required": [
                  "link_type"
                ]
              }
            }
          }
        },
        "x-refused-parameters": {
          "email": "'email' is not accepted: the link is delivered to the applicant email on record.",
          "applicant_email": "'applicant_email' is not accepted: the link is delivered to the applicant email on record.",
          "applicant_phone": "'applicant_phone' is not accepted: SMS delivery is not available.",
          "channel": "'channel' is not accepted: links are delivered by email only, and only for a live-mode key.",
          "message": "'message' is not accepted: custom message text is not available.",
          "expires_in": "'expires_in' is not accepted: links expire 7 days after creation."
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedSendLink"
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: conflict, idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bank-link": {
      "post": {
        "operationId": "post_bank_link",
        "tags": [
          "bank-link"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "bank_link:write"
        ],
        "x-route-file": "app/api/v1/bank-link/route.ts",
        "summary": "mint a bank-verification portal link for an existing case.",
        "x-rate-limit-per-minute": 100,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "requestBody": {
          "required": true,
          "x-schema-source": "zod",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "case_id": {
                    "type": "string",
                    "minLength": 1,
                    "description": "The case to mint the link for, as returned by POST /v1/underwrite."
                  }
                },
                "required": [
                  "case_id"
                ]
              }
            }
          }
        },
        "x-refused-parameters": {
          "applicant_email": "'applicant_email' is not accepted: the link is delivered to the applicant email on the case record.",
          "applicant_phone": "'applicant_phone' is not accepted: SMS delivery is not available.",
          "redirect_url": "'redirect_url' is not accepted: post-connection redirects are not available.",
          "expires_in": "'expires_in' is not accepted: links expire 7 days after creation."
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedBankLink"
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bank-link/{id}/status": {
      "get": {
        "operationId": "get_bank_link_by_id_status",
        "tags": [
          "bank-link"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "cases:read"
        ],
        "x-route-file": "app/api/v1/bank-link/[id]/status/route.ts",
        "summary": "the state of one bank-verification request, plus the account summary it produced.",
        "x-rate-limit-per-minute": 200,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedBankLinkStatus"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bank-link/{id}/transactions": {
      "get": {
        "operationId": "get_bank_link_by_id_transactions",
        "tags": [
          "bank-link"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "cases:read"
        ],
        "x-route-file": "app/api/v1/bank-link/[id]/transactions/route.ts",
        "summary": "the ingested bank transactions for the case behind one bank-verification request, paginated.",
        "x-rate-limit-per-minute": 100,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "x-derived-from": "dayParam()"
          },
          {
            "name": "date_to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "x-derived-from": "dayParam()"
          },
          {
            "name": "min_amount",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 25,
              "maximum": 100
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "x-derived-from": "searchParams.get"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SerializedBankTransaction"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "number"
                        },
                        "per_page": {
                          "type": "number"
                        },
                        "total": {
                          "type": "number"
                        },
                        "total_pages": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "page",
                        "per_page",
                        "total",
                        "total_pages"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases": {
      "get": {
        "operationId": "get_cases",
        "tags": [
          "cases"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "cases:read"
        ],
        "x-route-file": "app/api/v1/cases/route.ts",
        "summary": "the organization's underwriting cases, paginated.",
        "x-rate-limit-per-minute": 200,
        "parameters": [
          {
            "name": "applicant_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "individual",
                "business"
              ]
            },
            "x-derived-from": "oneOf()"
          },
          {
            "name": "date_from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "x-derived-from": "dayBound()"
          },
          {
            "name": "date_to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "x-derived-from": "dayBound()"
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "x-derived-from": "oneOf()"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 25,
              "maximum": 100
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "product",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 3025
            },
            "x-derived-from": "boundedString()"
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "x-derived-from": "boundedString()"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "updated_at",
                "amount",
                "status"
              ]
            },
            "x-derived-from": "oneOf()"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "approved",
                "declined",
                "review",
                "invited",
                "all"
              ]
            },
            "x-derived-from": "oneOf()"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SerializedCaseSummary"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "number"
                        },
                        "per_page": {
                          "type": "number"
                        },
                        "total": {
                          "type": "number"
                        },
                        "total_pages": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "page",
                        "per_page",
                        "total",
                        "total_pages"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases/{id}": {
      "get": {
        "operationId": "get_cases_by_id",
        "tags": [
          "cases"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "cases:read"
        ],
        "x-route-file": "app/api/v1/cases/[id]/route.ts",
        "summary": "the full underwriting report for one case.",
        "x-rate-limit-per-minute": 200,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedCaseDetail"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases/{id}/decision": {
      "post": {
        "operationId": "post_cases_by_id_decision",
        "tags": [
          "cases"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "cases:decide"
        ],
        "x-route-file": "app/api/v1/cases/[id]/decision/route.ts",
        "summary": "record a binding credit decision, or send the case to a human.",
        "x-rate-limit-per-minute": 50,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "requestBody": {
          "required": true,
          "x-schema-source": "zod",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "decision": {
                    "type": "string",
                    "enum": [
                      "approve",
                      "approved",
                      "decline",
                      "declined",
                      "review"
                    ],
                    "description": "approve, decline or review; the past-tense spellings are aliases. Matched case-insensitively."
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "description": "The justification. Recorded as the override reason on an approval or decline, and as the review note on a review."
                  },
                  "conditions": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 500
                    },
                    "maxItems": 20,
                    "description": "Only with `approve`. Each is stored as a required prior-to-funding condition."
                  },
                  "approved_amount": {
                    "anyOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "Only with `approve`. WHOLE DOLLARS, never cents: greater than zero and at most 2147483647, as a number or a numeric string."
                  }
                },
                "required": [
                  "decision",
                  "reason"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedCaseDecision"
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases/{id}/documents": {
      "get": {
        "operationId": "get_cases_by_id_documents",
        "tags": [
          "cases"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "documents:read"
        ],
        "x-route-file": "app/api/v1/cases/[id]/documents/route.ts",
        "summary": "the files attached to one case, paginated.",
        "x-rate-limit-per-minute": 200,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 25,
              "maximum": 100
            },
            "x-derived-from": "searchParams.get"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SerializedDocument"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "number"
                        },
                        "per_page": {
                          "type": "number"
                        },
                        "total": {
                          "type": "number"
                        },
                        "total_pages": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "page",
                        "per_page",
                        "total",
                        "total_pages"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "post_cases_by_id_documents",
        "tags": [
          "cases"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "documents:write"
        ],
        "x-route-file": "app/api/v1/cases/[id]/documents/route.ts",
        "summary": "attach a file to a case. Multipart, with one `file` part of at most 25 MiB (26,214,400 bytes) plus the string fields below. De-duplication is by content hash and is PER CASE: the same bytes uploaded twice to one case are refused with 409 `conflict`, and `details.existing_document_id` names the copy. The same bytes on a DIFFERENT case in your organization are accepted — a voided check, an ID scan or a signed application legitimately belongs on more than one case for the same merchant. Nothing is written on the refused path. A `financial` document also starts an analysis of the case; several of them queue one run, not one each.",
        "x-rate-limit-per-minute": 50,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "requestBody": {
          "required": true,
          "x-schema-source": "zod",
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "x-max-bytes": 26214400,
                    "x-derived-from": "multipartUploadCodec()"
                  },
                  "category": {
                    "type": "string",
                    "enum": [
                      "stipulation",
                      "contract",
                      "id_verification",
                      "financial",
                      "insurance",
                      "legal",
                      "other"
                    ],
                    "description": "Defaults to `other`. Matched case-insensitively; the label spelling (\"ID Verification\") is accepted too."
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Display name. Defaults to the uploaded filename."
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Alias of `name`, read only when `name` is absent."
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedDocument"
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: conflict, idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases/{id}/reanalyze": {
      "post": {
        "operationId": "post_cases_by_id_reanalyze",
        "tags": [
          "cases"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "cases:write"
        ],
        "x-route-file": "app/api/v1/cases/[id]/reanalyze/route.ts",
        "summary": "queue a fresh AI analysis of an existing case. Always 202. A case whose run is already pending or in flight is not queued a second time; that run is returned instead with `already_queued` true, so retrying never stacks duplicate analyses. An already-decided case CAN be re-analyzed: the analysis is evidence, not a verdict, so a new run is recorded and the existing decision and case status are left untouched.",
        "x-rate-limit-per-minute": 20,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "responses": {
          "202": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedAnalysisJob"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/cases/{id}/report/pdf": {
      "get": {
        "operationId": "get_cases_by_id_report_pdf",
        "tags": [
          "cases"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "cases:read"
        ],
        "x-route-file": "app/api/v1/cases/[id]/report/pdf/route.ts",
        "summary": "the underwriting report as a PDF document.",
        "x-rate-limit-per-minute": 50,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/collections": {
      "get": {
        "operationId": "get_collections",
        "tags": [
          "collections"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "collections:read"
        ],
        "x-route-file": "app/api/v1/collections/route.ts",
        "summary": "the organization's delinquent accounts, paginated.",
        "x-rate-limit-per-minute": 200,
        "parameters": [
          {
            "name": "max_dpd",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "x-derived-from": "dpdParam()"
          },
          {
            "name": "min_dpd",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "x-derived-from": "dpdParam()"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 25,
              "maximum": 100
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "severity",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "30_day",
                "60_day",
                "90_plus",
                "charge_off"
              ]
            },
            "x-derived-from": "oneOf()"
          },
          {
            "name": "stage",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "current",
                "grace",
                "early",
                "mid",
                "late",
                "legal",
                "charge_off",
                "recovery"
              ]
            },
            "x-derived-from": "oneOf()"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "resolved",
                "charged_off",
                "recovered",
                "closed"
              ]
            },
            "x-derived-from": "oneOf()"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SerializedCollectionCase"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "number"
                        },
                        "per_page": {
                          "type": "number"
                        },
                        "total": {
                          "type": "number"
                        },
                        "total_pages": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "page",
                        "per_page",
                        "total",
                        "total_pages"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/collections/{id}/activity": {
      "post": {
        "operationId": "post_collections_by_id_activity",
        "tags": [
          "collections"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "collections:write"
        ],
        "x-route-file": "app/api/v1/collections/[id]/activity/route.ts",
        "summary": "log a contact attempt against a delinquent account.",
        "x-rate-limit-per-minute": 100,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "requestBody": {
          "required": true,
          "x-schema-source": "zod",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "call",
                      "sms",
                      "email",
                      "letter",
                      "visit",
                      "promise_to_pay",
                      "dispute",
                      "note"
                    ]
                  },
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "connected",
                      "voicemail",
                      "no_answer",
                      "promise_to_pay",
                      "broken_promise",
                      "dispute",
                      "refused",
                      "partial_payment",
                      "other"
                    ]
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 2000
                  }
                },
                "required": [
                  "type",
                  "outcome"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "x-refused-parameters": {
          "next_action_date": "'next_action_date' is not accepted: logging an activity records the contact and stamps last_contact_date; it cannot schedule a follow-up. No endpoint sets next_action_date today.",
          "performed_by": "'performed_by' is not accepted: an activity is always attributed to the API key that logged it.",
          "performedBy": "'performedBy' is not accepted: an activity is always attributed to the API key that logged it.",
          "performed_at": "'performed_at' is not accepted: the activity is timestamped when it is recorded.",
          "collection_case_id": "'collection_case_id' is not accepted: the case is taken from the URL path.",
          "case_id": "'case_id' is not accepted: the case is taken from the URL path.",
          "result": "'result' is not accepted; the parameter is named 'outcome'."
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedCollectionActivity"
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/collections/{id}/escalate": {
      "post": {
        "operationId": "post_collections_by_id_escalate",
        "tags": [
          "collections"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "collections:write"
        ],
        "x-route-file": "app/api/v1/collections/[id]/escalate/route.ts",
        "summary": "escalate a delinquent account to legal collections, or request its charge-off.",
        "x-rate-limit-per-minute": 20,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "requestBody": {
          "required": true,
          "x-schema-source": "zod",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "legal",
                      "charge_off"
                    ]
                  }
                },
                "required": [
                  "type"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "x-refused-parameters": {
          "amount": "'amount' is not accepted: the charge-off balance is DERIVED from the loan's remaining balance plus the interest and late fees on its due, unsettled installments. A caller-supplied write-off amount is never trusted. The derived figures are returned under 'charge_off'.",
          "balance": "'balance' is not accepted: the charge-off balance is DERIVED from the loan's remaining balance plus the interest and late fees on its due, unsettled installments. A caller-supplied write-off amount is never trusted. The derived figures are returned under 'charge_off'.",
          "charge_off_amount": "'charge_off_amount' is not accepted: the charge-off balance is DERIVED from the loan's remaining balance plus the interest and late fees on its due, unsettled installments. A caller-supplied write-off amount is never trusted. The derived figures are returned under 'charge_off'.",
          "principal_amount": "'principal_amount' is not accepted: the charge-off balance is DERIVED from the loan's remaining balance plus the interest and late fees on its due, unsettled installments. A caller-supplied write-off amount is never trusted. The derived figures are returned under 'charge_off'.",
          "principalAmount": "'principalAmount' is not accepted: the charge-off balance is DERIVED from the loan's remaining balance plus the interest and late fees on its due, unsettled installments. A caller-supplied write-off amount is never trusted. The derived figures are returned under 'charge_off'.",
          "interest_amount": "'interest_amount' is not accepted: the charge-off balance is DERIVED from the loan's remaining balance plus the interest and late fees on its due, unsettled installments. A caller-supplied write-off amount is never trusted. The derived figures are returned under 'charge_off'.",
          "interestAmount": "'interestAmount' is not accepted: the charge-off balance is DERIVED from the loan's remaining balance plus the interest and late fees on its due, unsettled installments. A caller-supplied write-off amount is never trusted. The derived figures are returned under 'charge_off'.",
          "late_fee_amount": "'late_fee_amount' is not accepted: the charge-off balance is DERIVED from the loan's remaining balance plus the interest and late fees on its due, unsettled installments. A caller-supplied write-off amount is never trusted. The derived figures are returned under 'charge_off'.",
          "lateFeeAmount": "'lateFeeAmount' is not accepted: the charge-off balance is DERIVED from the loan's remaining balance plus the interest and late fees on its due, unsettled installments. A caller-supplied write-off amount is never trusted. The derived figures are returned under 'charge_off'.",
          "total_amount": "'total_amount' is not accepted: the charge-off balance is DERIVED from the loan's remaining balance plus the interest and late fees on its due, unsettled installments. A caller-supplied write-off amount is never trusted. The derived figures are returned under 'charge_off'.",
          "totalAmount": "'totalAmount' is not accepted: the charge-off balance is DERIVED from the loan's remaining balance plus the interest and late fees on its due, unsettled installments. A caller-supplied write-off amount is never trusted. The derived figures are returned under 'charge_off'.",
          "amount_cents": "'amount_cents' is not accepted: the charge-off balance is DERIVED from the loan's remaining balance plus the interest and late fees on its due, unsettled installments. A caller-supplied write-off amount is never trusted. The derived figures are returned under 'charge_off'. Every amount on this API is in WHOLE DOLLARS.",
          "approve": "'approve' is not accepted: this endpoint REQUESTS a charge-off. Approving one posts a write-off journal entry and is a separate, human step.",
          "auto_approve": "'auto_approve' is not accepted: this endpoint REQUESTS a charge-off. Approving one posts a write-off journal entry and is a separate, human step.",
          "approved_by": "'approved_by' is not accepted: this endpoint REQUESTS a charge-off and does not approve one.",
          "stage": "'stage' is not accepted: the stage an escalation moves the case to is determined by 'type'. Escalation only moves a case forward along the delinquency ladder.",
          "status": "'status' is not accepted: the case's resulting status is determined by the escalation and is returned on the response.",
          "performed_by": "'performed_by' is not accepted: an escalation is always attributed to the API key that performed it.",
          "performedBy": "'performedBy' is not accepted: an escalation is always attributed to the API key that performed it.",
          "requested_by": "'requested_by' is not accepted: an escalation is always attributed to the API key that performed it.",
          "collection_case_id": "'collection_case_id' is not accepted: the case is taken from the URL path.",
          "case_id": "'case_id' is not accepted: the case is taken from the URL path.",
          "loan_id": "'loan_id' is not accepted: the loan is the one the collection case in the URL path belongs to.",
          "notes": "'notes' is not accepted: an escalation records no free-text note. Use POST /v1/collections/:id/activity to log commentary against the case.",
          "reason": "'reason' is not accepted: an escalation records no free-text reason. Use POST /v1/collections/:id/activity to log commentary against the case.",
          "idempotency_key": "'idempotency_key' is not accepted as a body field: send it as the 'Idempotency-Key' HTTP header."
        },
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedEscalation"
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: conflict, idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/documents/{id}/verify": {
      "patch": {
        "operationId": "patch_documents_by_id_verify",
        "tags": [
          "documents"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "documents:write"
        ],
        "x-route-file": "app/api/v1/documents/[id]/verify/route.ts",
        "summary": "move a document through its review lifecycle: verified, rejected, back to pending_review, or expired.",
        "x-rate-limit-per-minute": 100,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "requestBody": {
          "required": true,
          "x-schema-source": "zod",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "verified",
                      "rejected",
                      "pending_review",
                      "expired"
                    ],
                    "description": "The target state. Matched case-insensitively; the stored label spelling (\"Pending Review\") is accepted too."
                  },
                  "rejection_reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 1000,
                    "description": "Only with `rejected`. Echoed back as `rejection_reason` on the document."
                  }
                },
                "required": [
                  "status"
                ]
              }
            }
          }
        },
        "x-refused-parameters": {
          "notes": "'notes' is not accepted on this endpoint."
        },
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedDocument"
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/portfolio": {
      "get": {
        "operationId": "get_portfolio",
        "tags": [
          "portfolio"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "portfolio:read"
        ],
        "x-route-file": "app/api/v1/portfolio/route.ts",
        "summary": "the organization's portfolio summary and trailing six-month repayment series.",
        "x-rate-limit-per-minute": 200,
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedPortfolio"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/portfolio/loans": {
      "get": {
        "operationId": "get_portfolio_loans",
        "tags": [
          "portfolio"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "portfolio:read"
        ],
        "x-route-file": "app/api/v1/portfolio/loans/route.ts",
        "summary": "the organization's funded loans, filtered, sorted and paginated.",
        "x-rate-limit-per-minute": 200,
        "parameters": [
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            },
            "x-derived-from": "oneOf()"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 25,
              "maximum": 100
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "balance",
                "next_due",
                "rate",
                "originated"
              ]
            },
            "x-derived-from": "oneOf()"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "delinquent",
                "defaulted",
                "paid_off",
                "pending_disbursement",
                "cancelled"
              ]
            },
            "x-derived-from": "oneOf()"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SerializedLoan"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "number"
                        },
                        "per_page": {
                          "type": "number"
                        },
                        "total": {
                          "type": "number"
                        },
                        "total_pages": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "page",
                        "per_page",
                        "total",
                        "total_pages"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/portfolio/loans/{id}": {
      "get": {
        "operationId": "get_portfolio_loans_by_id",
        "tags": [
          "portfolio"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "portfolio:read"
        ],
        "x-route-file": "app/api/v1/portfolio/loans/[id]/route.ts",
        "summary": "one funded loan and its repayment schedule.",
        "x-rate-limit-per-minute": 200,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedLoanDetail"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/portfolio/loans/{id}/payments": {
      "post": {
        "operationId": "post_portfolio_loans_by_id_payments",
        "tags": [
          "portfolio"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "portfolio:write"
        ],
        "x-route-file": "app/api/v1/portfolio/loans/[id]/payments/route.ts",
        "summary": "record a payment against a loan.",
        "x-rate-limit-per-minute": 100,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "requestBody": {
          "required": true,
          "x-schema-source": "zod",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10000000
                  },
                  "method": {
                    "type": "string",
                    "enum": [
                      "ach",
                      "check",
                      "cash",
                      "card",
                      "wire",
                      "money_order",
                      "other"
                    ]
                  },
                  "reference": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "date": {
                    "type": "string"
                  }
                },
                "required": [
                  "amount",
                  "method"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "x-refused-parameters": {
          "loan_id": "'loan_id' is not accepted: the loan is taken from the URL path.",
          "loanId": "'loanId' is not accepted: the loan is taken from the URL path.",
          "recorded_by": "'recorded_by' is not accepted: a payment is always attributed to the API key that recorded it.",
          "recordedBy": "'recordedBy' is not accepted: a payment is always attributed to the API key that recorded it.",
          "amount_cents": "'amount_cents' is not accepted, and 'amount' is NOT cents: every amount on this API is in WHOLE DOLLARS. $10,378.00 is 10378, not 1037800. Amounts below one dollar cannot be recorded.",
          "principal_applied": "'principal_applied' is not accepted: allocation is computed by the servicing waterfall (late fees, then interest, then principal) and is returned on the response.",
          "interest_applied": "'interest_applied' is not accepted: allocation is computed by the servicing waterfall (late fees, then interest, then principal) and is returned on the response.",
          "late_fee_applied": "'late_fee_applied' is not accepted: allocation is computed by the servicing waterfall (late fees, then interest, then principal) and is returned on the response.",
          "overpayment": "'overpayment' is not accepted: any amount left after the waterfall is computed and returned on the response.",
          "new_balance": "'new_balance' is not accepted: the resulting balance is computed from the payment and returned on the response.",
          "status": "'status' is not accepted: a recorded payment is always 'applied'. Reversing a payment is a separate operation.",
          "scheduled_payment_id": "'scheduled_payment_id' is not accepted: the payment is applied to the loan's earliest open installment automatically.",
          "idempotency_key": "'idempotency_key' is not accepted as a body field: send it as the 'Idempotency-Key' HTTP header."
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedPayment"
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: conflict, idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/portfolio/loans/{id}/schedule": {
      "get": {
        "operationId": "get_portfolio_loans_by_id_schedule",
        "tags": [
          "portfolio"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "portfolio:read"
        ],
        "x-route-file": "app/api/v1/portfolio/loans/[id]/schedule/route.ts",
        "summary": "the amortization schedule, as its own document, with the running balance.",
        "x-rate-limit-per-minute": 200,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedLoanSchedule"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/products": {
      "get": {
        "operationId": "get_products",
        "tags": [
          "products"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "products:read"
        ],
        "x-route-file": "app/api/v1/products/route.ts",
        "x-rate-limit-per-minute": 500,
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedProduct"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/underwrite": {
      "post": {
        "operationId": "post_underwrite",
        "tags": [
          "underwrite"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "underwrite:write"
        ],
        "x-route-file": "app/api/v1/underwrite/route.ts",
        "summary": "submit an application for underwriting. Your organization must have an APPROVED business verification. Until it does, every submission is refused with 403 `forbidden` and no case is created; the message says whether a verification has never been submitted or was rejected, because the remedy differs. Verification is submitted and reviewed outside the API — ask your HL Hunt contact if you are unsure of your organization's status.",
        "x-rate-limit-per-minute": 100,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "requestBody": {
          "required": true,
          "x-schema-source": "zod",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "product_slug": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The product's slug, as listed by GET /v1/products. Required unless `product` is sent."
                  },
                  "product": {
                    "description": "Alias of `product_slug`: a string, read only when `product_slug` is absent. Ignored, whatever its type, when `product_slug` is sent."
                  },
                  "applicant": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Full name. Required unless `first_name` and/or `last_name` is sent."
                      },
                      "first_name": {
                        "description": "With `last_name`, the split spelling of `name`: a string; a non-string part is ignored."
                      },
                      "last_name": {
                        "description": "With `first_name`, the split spelling of `name`: a string; a non-string part is ignored."
                      },
                      "email": {
                        "type": "string",
                        "minLength": 1
                      },
                      "type": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "Individual",
                          "Business",
                          "individual",
                          "business",
                          null
                        ],
                        "description": "`Individual` or `Business`, case-insensitive. Defaults from the product's category when omitted. Any other value is a 400 naming applicant.type."
                      },
                      "phone": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "ein": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Employer Identification Number, `XX-XXXXXXX` or nine digits. Business applicants only. Returned on the case as `applicant.ein`; sending it here is what makes that field populate, and it is also written into `application_data.ein`."
                      },
                      "owner": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "properties": {
                          "first_name": {
                            "type": "string",
                            "minLength": 1,
                            "description": "Owner's legal first name."
                          },
                          "last_name": {
                            "type": "string",
                            "minLength": 1,
                            "description": "Owner's legal last name."
                          },
                          "ssn": {
                            "type": "string",
                            "pattern": "^(\\d{3}-?\\d{2}-?\\d{4}|\\d{4})$",
                            "description": "Full SSN (`123-45-6789` or `123456789`) or just the last four. THE FULL VALUE IS NEVER STORED: it is reduced to the last four at the request boundary and the remainder is discarded, because the bureau integration identifies on last-four plus name, date of birth and address. Never returned by any endpoint."
                          },
                          "date_of_birth": {
                            "type": "string",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                            "description": "`YYYY-MM-DD`. Required by most bureaus to resolve an identity."
                          },
                          "ownership_percent": {
                            "type": "number",
                            "minimum": 0,
                            "maximum": 100,
                            "description": "0-100. The share of the business this owner holds."
                          },
                          "title": {
                            "type": "string",
                            "description": "Role at the business, e.g. `CEO`."
                          },
                          "address": {
                            "type": "string",
                            "description": "Home street address."
                          },
                          "city": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string",
                            "minLength": 2,
                            "maxLength": 2,
                            "description": "Two-letter US state code."
                          },
                          "postal_code": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "first_name",
                          "last_name"
                        ],
                        "description": "The primary owner or personal guarantor. Typed because credit and background checks run against these values — do not put them in `application_data`. `ssn` is encrypted at rest and never returned."
                      }
                    },
                    "required": [
                      "email"
                    ]
                  },
                  "metadata": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": {},
                    "description": "Arbitrary key-value pairs for your own reference; stored and returned verbatim."
                  },
                  "consent": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "credit_check": {
                        "type": "boolean",
                        "description": "True when the applicant authorized a consumer credit report on the owner named in `applicant.owner`. This is the permissible-purpose assertion the bureau pull relies on."
                      },
                      "background_check": {
                        "type": "boolean",
                        "description": "True when the applicant authorized identity and background verification."
                      },
                      "terms": {
                        "type": "boolean",
                        "description": "True when the applicant accepted your terms of service."
                      },
                      "accuracy": {
                        "type": "boolean",
                        "description": "True when the applicant attested that the information supplied is accurate."
                      },
                      "agreement_url": {
                        "type": "string",
                        "description": "Link to the exact agreement text the applicant accepted. Stored for audit."
                      },
                      "accepted_text_version": {
                        "type": "string",
                        "description": "Your version identifier for that text, e.g. `esign-v3`. Stored for audit."
                      }
                    },
                    "description": "The applicant's authorizations. PRESENCE of the object is what records consent; `credit_check` is what a bureau pull requires. The timestamp and the caller's IP are recorded server-side and cannot be supplied."
                  },
                  "requested_amount": {
                    "anyOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "WHOLE DOLLARS, never cents: 0 to 2147483647, as a number or a numeric string. Required unless `amount` is sent."
                  },
                  "amount": {
                    "description": "Alias of `requested_amount`: a number or a numeric string, read only when `requested_amount` is absent. Ignored, whatever its type, when `requested_amount` is sent."
                  },
                  "application_data": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": {},
                    "description": "The product's form fields, read by the analysis. Every value is stored as a string; a nested object or array is JSON-encoded."
                  },
                  "bank_link": {
                    "description": "The boolean `true` also mints a bank-verification link and returns it as `bank_link_url`. Any other value (including the string \"true\") requests no link."
                  },
                  "bank_verification": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "plaid",
                      "statements",
                      "none",
                      null
                    ],
                    "description": "How this platform proves the applicant's banking. `plaid` mints a bank-verification link and is the same as `bank_link: true`. `statements` means you upload the bank statements yourself to POST /v1/cases/{id}/documents with category `financial`: no link is minted, no email is sent, and the case waits for those files rather than being analyzed immediately with nothing to analyze — the first `financial` upload starts the analysis. `none` underwrites on credit and background evidence alone. Defaults to `plaid` when `bank_link` is true, otherwise `none`. Sending this together with `bank_link` is accepted when the two agree; a contradiction (for example `statements` beside `bank_link: true`) is refused with 400 `validation_error` naming `bank_verification`, rather than one silently winning."
                  }
                },
                "required": [
                  "applicant"
                ],
                "x-required-one-of": [
                  {
                    "fields": [
                      "product_slug",
                      "product"
                    ],
                    "note": "Send one of these. `product_slug` is the canonical spelling; `product` is accepted as an alias and is ignored when `product_slug` is present."
                  },
                  {
                    "fields": [
                      "requested_amount",
                      "amount"
                    ],
                    "note": "Send one of these, in whole dollars. `requested_amount` is the canonical spelling; `amount` is accepted as an alias and is ignored when `requested_amount` is present."
                  },
                  {
                    "fields": [
                      "applicant.name",
                      [
                        "applicant.first_name",
                        "applicant.last_name"
                      ]
                    ],
                    "note": "Send the applicant's full `name`, or the split `first_name` and `last_name`. When `name` is present the split fields are ignored."
                  }
                ]
              }
            }
          }
        },
        "x-refused-parameters": {
          "webhook_url": "'webhook_url' is not accepted: there is no per-submission webhook destination. Register an endpoint ONCE with POST /v1/webhooks and it receives every event you subscribed to, in this key's mode. This field was previously accepted and ignored, which is why it is now refused rather than silently dropped.",
          "webhookUrl": "'webhookUrl' is not accepted: there is no per-submission webhook destination. Register an endpoint ONCE with POST /v1/webhooks. The parameter is spelled 'webhook_url', and it is not accepted either.",
          "callback_url": "'callback_url' is not accepted: there is no per-submission webhook destination. Register an endpoint ONCE with POST /v1/webhooks.",
          "idempotency_key": "'idempotency_key' is not accepted as a body field: send it as the 'Idempotency-Key' HTTP header."
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedUnderwriteSubmission"
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/underwrite/batch": {
      "post": {
        "operationId": "post_underwrite_batch",
        "tags": [
          "underwrite"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "underwrite:write"
        ],
        "x-route-file": "app/api/v1/underwrite/batch/route.ts",
        "summary": "submit up to 50 applications in one request.",
        "x-rate-limit-per-minute": 10,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "requestBody": {
          "required": true,
          "x-schema-source": "zod",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "applications": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "product_slug": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The product's slug, as listed by GET /v1/products. Required unless `product` is sent."
                        },
                        "product": {
                          "description": "Alias of `product_slug`: a string, read only when `product_slug` is absent. Ignored, whatever its type, when `product_slug` is sent."
                        },
                        "applicant": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Full name. Required unless `first_name` and/or `last_name` is sent."
                            },
                            "first_name": {
                              "description": "With `last_name`, the split spelling of `name`: a string; a non-string part is ignored."
                            },
                            "last_name": {
                              "description": "With `first_name`, the split spelling of `name`: a string; a non-string part is ignored."
                            },
                            "email": {
                              "type": "string",
                              "minLength": 1
                            },
                            "type": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "Individual",
                                "Business",
                                "individual",
                                "business",
                                null
                              ],
                              "description": "`Individual` or `Business`, case-insensitive. Defaults from the product's category when omitted. Any other value is a 400 naming applicant.type."
                            },
                            "phone": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "ein": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Employer Identification Number, `XX-XXXXXXX` or nine digits. Business applicants only. Returned on the case as `applicant.ein`; sending it here is what makes that field populate, and it is also written into `application_data.ein`."
                            },
                            "owner": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "first_name": {
                                  "type": "string",
                                  "minLength": 1,
                                  "description": "Owner's legal first name."
                                },
                                "last_name": {
                                  "type": "string",
                                  "minLength": 1,
                                  "description": "Owner's legal last name."
                                },
                                "ssn": {
                                  "type": "string",
                                  "pattern": "^(\\d{3}-?\\d{2}-?\\d{4}|\\d{4})$",
                                  "description": "Full SSN (`123-45-6789` or `123456789`) or just the last four. THE FULL VALUE IS NEVER STORED: it is reduced to the last four at the request boundary and the remainder is discarded, because the bureau integration identifies on last-four plus name, date of birth and address. Never returned by any endpoint."
                                },
                                "date_of_birth": {
                                  "type": "string",
                                  "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                  "description": "`YYYY-MM-DD`. Required by most bureaus to resolve an identity."
                                },
                                "ownership_percent": {
                                  "type": "number",
                                  "minimum": 0,
                                  "maximum": 100,
                                  "description": "0-100. The share of the business this owner holds."
                                },
                                "title": {
                                  "type": "string",
                                  "description": "Role at the business, e.g. `CEO`."
                                },
                                "address": {
                                  "type": "string",
                                  "description": "Home street address."
                                },
                                "city": {
                                  "type": "string"
                                },
                                "state": {
                                  "type": "string",
                                  "minLength": 2,
                                  "maxLength": 2,
                                  "description": "Two-letter US state code."
                                },
                                "postal_code": {
                                  "type": "string"
                                }
                              },
                              "required": [
                                "first_name",
                                "last_name"
                              ],
                              "description": "The primary owner or personal guarantor. Typed because credit and background checks run against these values — do not put them in `application_data`. `ssn` is encrypted at rest and never returned."
                            }
                          },
                          "required": [
                            "email"
                          ]
                        },
                        "metadata": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {},
                          "description": "Arbitrary key-value pairs for your own reference; stored and returned verbatim."
                        },
                        "consent": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "credit_check": {
                              "type": "boolean",
                              "description": "True when the applicant authorized a consumer credit report on the owner named in `applicant.owner`. This is the permissible-purpose assertion the bureau pull relies on."
                            },
                            "background_check": {
                              "type": "boolean",
                              "description": "True when the applicant authorized identity and background verification."
                            },
                            "terms": {
                              "type": "boolean",
                              "description": "True when the applicant accepted your terms of service."
                            },
                            "accuracy": {
                              "type": "boolean",
                              "description": "True when the applicant attested that the information supplied is accurate."
                            },
                            "agreement_url": {
                              "type": "string",
                              "description": "Link to the exact agreement text the applicant accepted. Stored for audit."
                            },
                            "accepted_text_version": {
                              "type": "string",
                              "description": "Your version identifier for that text, e.g. `esign-v3`. Stored for audit."
                            }
                          },
                          "description": "The applicant's authorizations. PRESENCE of the object is what records consent; `credit_check` is what a bureau pull requires. The timestamp and the caller's IP are recorded server-side and cannot be supplied."
                        },
                        "requested_amount": {
                          "anyOf": [
                            {
                              "type": "number"
                            },
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "WHOLE DOLLARS, never cents: 0 to 2147483647, as a number or a numeric string. Required unless `amount` is sent."
                        },
                        "amount": {
                          "description": "Alias of `requested_amount`: a number or a numeric string, read only when `requested_amount` is absent. Ignored, whatever its type, when `requested_amount` is sent."
                        },
                        "application_data": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {},
                          "description": "The product's form fields, read by the analysis. Every value is stored as a string; a nested object or array is JSON-encoded."
                        },
                        "bank_link": {
                          "description": "The boolean `true` also mints a bank-verification link and returns it as `bank_link_url`. Any other value (including the string \"true\") requests no link."
                        },
                        "bank_verification": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "plaid",
                            "statements",
                            "none",
                            null
                          ],
                          "description": "How this platform proves the applicant's banking. `plaid` mints a bank-verification link and is the same as `bank_link: true`. `statements` means you upload the bank statements yourself to POST /v1/cases/{id}/documents with category `financial`: no link is minted, no email is sent, and the case waits for those files rather than being analyzed immediately with nothing to analyze — the first `financial` upload starts the analysis. `none` underwrites on credit and background evidence alone. Defaults to `plaid` when `bank_link` is true, otherwise `none`. Sending this together with `bank_link` is accepted when the two agree; a contradiction (for example `statements` beside `bank_link: true`) is refused with 400 `validation_error` naming `bank_verification`, rather than one silently winning."
                        }
                      },
                      "required": [
                        "applicant"
                      ],
                      "x-required-one-of": [
                        {
                          "fields": [
                            "product_slug",
                            "product"
                          ],
                          "note": "Send one of these. `product_slug` is the canonical spelling; `product` is accepted as an alias and is ignored when `product_slug` is present."
                        },
                        {
                          "fields": [
                            "requested_amount",
                            "amount"
                          ],
                          "note": "Send one of these, in whole dollars. `requested_amount` is the canonical spelling; `amount` is accepted as an alias and is ignored when `requested_amount` is present."
                        },
                        {
                          "fields": [
                            "applicant.name",
                            [
                              "applicant.first_name",
                              "applicant.last_name"
                            ]
                          ],
                          "note": "Send the applicant's full `name`, or the split `first_name` and `last_name`. When `name` is present the split fields are ignored."
                        }
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 50,
                    "description": "Each element is the body of POST /v1/underwrite. Order is preserved."
                  }
                },
                "required": [
                  "applications"
                ]
              }
            }
          }
        },
        "x-refused-parameters": {
          "webhook_url": "'webhook_url' is not accepted: there is no per-submission webhook destination. Register an endpoint ONCE with POST /v1/webhooks and it receives every event you subscribed to, in this key's mode. This field was previously accepted and ignored, which is why it is now refused rather than silently dropped.",
          "webhookUrl": "'webhookUrl' is not accepted: there is no per-submission webhook destination. Register an endpoint ONCE with POST /v1/webhooks. The parameter is spelled 'webhook_url', and it is not accepted either.",
          "callback_url": "'callback_url' is not accepted: there is no per-submission webhook destination. Register an endpoint ONCE with POST /v1/webhooks.",
          "idempotency_key": "'idempotency_key' is not accepted as a body field: send it as the 'Idempotency-Key' HTTP header."
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedBatch"
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/underwrite/batch/{id}": {
      "get": {
        "operationId": "get_underwrite_batch_by_id",
        "tags": [
          "underwrite"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "underwrite:write",
          "cases:read"
        ],
        "x-route-file": "app/api/v1/underwrite/batch/[id]/route.ts",
        "x-rate-limit-per-minute": 200,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SerializedBatch"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "operationId": "get_webhooks",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "webhooks:manage"
        ],
        "x-route-file": "app/api/v1/webhooks/route.ts",
        "summary": "the endpoints this key's organization has registered.",
        "x-rate-limit-per-minute": 200,
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "x-derived-from": "searchParams.get"
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 25,
              "maximum": 100
            },
            "x-derived-from": "searchParams.get"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SerializedWebhookSubscription"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "number"
                        },
                        "per_page": {
                          "type": "number"
                        },
                        "total": {
                          "type": "number"
                        },
                        "total_pages": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "page",
                        "per_page",
                        "total",
                        "total_pages"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "post_webhooks",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "webhooks:manage"
        ],
        "x-route-file": "app/api/v1/webhooks/route.ts",
        "summary": "register one, and disclose its signing secret ONCE.",
        "x-rate-limit-per-minute": 20,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Replays the first response for a repeated request. Reusing a key with a different body is `idempotency_key_reused` (409), never a silent replay."
          }
        ],
        "requestBody": {
          "required": true,
          "x-schema-source": "zod",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "The https:// endpoint to deliver to. http://, private hosts and IP literals are refused."
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "application.received",
                        "application.decided",
                        "application.needs_review",
                        "application.analysis_failed",
                        "batch.completed",
                        "document.analyzed",
                        "bank_link.connected"
                      ]
                    },
                    "minItems": 1,
                    "description": "Event types to subscribe to. At least one. Duplicates are collapsed, not refused."
                  },
                  "products": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string"
                    },
                    "description": "Product slugs this endpoint should hear about. Omit or send an empty array to hear about every product — the default. Naming products matters when several teams integrate under one organization: without a filter, this endpoint also receives the other lines' events. At most 60, and slugs are not checked against the catalog, so a typo produces a quiet endpoint rather than a failed registration."
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Your own label for this endpoint, at most 200 characters after trimming."
                  }
                },
                "required": [
                  "url",
                  "events"
                ]
              }
            }
          }
        },
        "x-refused-parameters": {
          "secret": "'secret' is not accepted: the signing secret is generated server-side (32 bytes of CSPRNG entropy) and returned exactly once, on this response. A caller-chosen secret would let an endpoint be signed with a low-entropy value, and would travel through request logs and the idempotency record on its way here.",
          "signing_secret": "'signing_secret' is not accepted: the signing secret is generated server-side and returned exactly once, on this response.",
          "signingSecret": "'signingSecret' is not accepted: the signing secret is generated server-side and returned exactly once, on this response.",
          "mode": "'mode' is not accepted: a subscription is always created in the mode of the API key that registered it, and only ever receives that mode's events. Use a live key to register a live endpoint.",
          "livemode": "'livemode' is not accepted: a subscription is always created in the mode of the API key that registered it.",
          "status": "'status' is not accepted: a subscription is created active. Its status is set by delivery outcomes (failing) and by DELETE (removed).",
          "enabled": "'enabled' is not accepted: a subscription is created active. Remove it with DELETE /v1/webhooks/:id.",
          "active": "'active' is not accepted: a subscription is created active. Remove it with DELETE /v1/webhooks/:id.",
          "id": "'id' is not accepted: the subscription id is assigned by the platform and returned on this response.",
          "org_id": "'org_id' is not accepted: a subscription always belongs to the organization the API key belongs to.",
          "api_key_id": "'api_key_id' is not accepted: the registering key is recorded automatically and is not caller-supplied.",
          "webhook_url": "'webhook_url' is not the parameter name on this endpoint; send 'url'.",
          "idempotency_key": "'idempotency_key' is not accepted as a body field: send it as the 'Idempotency-Key' HTTP header."
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SerializedWebhookSubscription"
                    },
                    {
                      "type": "object",
                      "required": [
                        "secret"
                      ],
                      "properties": {
                        "secret": {
                          "x-derivation": "name-only"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "error.code is one of: validation_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "error.code is one of: idempotency_key_reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "delete": {
        "operationId": "delete_webhooks_by_id",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "apiKey": []
          }
        ],
        "x-required-scopes": [
          "webhooks:manage"
        ],
        "x-route-file": "app/api/v1/webhooks/[id]/route.ts",
        "summary": "retire a registered endpoint.",
        "x-rate-limit-per-minute": 20,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Success."
          },
          "401": {
            "description": "error.code is one of: unauthorized.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "error.code is one of: forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "error.code is one of: not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "error.code is one of: rate_limit_exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "error.code is one of: internal_error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key, sent as `Authorization: Bearer <key>`. Every operation additionally requires a scope from its `x-required-scopes`, which is what the route passes to `requireApiKey`. Where that list has more than one entry, ANY ONE of them authorizes the call — they are alternatives, not requirements. Scopes are not expressed in `security` because OpenAPI only carries scopes for oauth2 and openIdConnect schemes. Key scope vocabulary: products:read, underwrite:write, cases:read, cases:write, cases:decide, bank_link:write, documents:read, documents:write, portfolio:read, portfolio:write, collections:read, collections:write, applicants:read, applicants:write, webhooks:manage."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "request_id"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "conflict",
                  "forbidden",
                  "idempotency_key_reused",
                  "internal_error",
                  "not_found",
                  "rate_limit_exceeded",
                  "unauthorized",
                  "validation_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "param": {
                "type": "string",
                "description": "The public name of the request field the error is about, when there is one."
              },
              "details": {
                "type": "object",
                "description": "Machine-readable context, present only on the errors that carry it."
              },
              "request_id": {
                "type": "string"
              }
            }
          }
        }
      },
      "SerializedAmortizationRow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "description": "Business dates: bare `YYYY-MM-DD`, never an instant."
          },
          "paid_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount": {
            "type": "number"
          },
          "principal": {
            "type": "number"
          },
          "interest": {
            "type": "number"
          },
          "late_fee": {
            "type": "number"
          },
          "paid_amount": {
            "type": "number"
          },
          "status": {
            "type": "string"
          },
          "days_overdue": {
            "type": "number"
          },
          "remaining_principal": {
            "type": "number",
            "description": "Whole dollars: the contractual principal still outstanding after this\ninstallment is paid as scheduled. It moves with the schedule, not with\nmoney received, so it differs from `current_balance` on the loan whenever\nan installment is paid late, short or not at all."
          }
        },
        "required": [
          "id",
          "due_date",
          "paid_date",
          "amount",
          "principal",
          "interest",
          "late_fee",
          "paid_amount",
          "status",
          "days_overdue",
          "remaining_principal"
        ]
      },
      "SerializedAnalysisJob": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "case_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "`pending`, `running`, `succeeded` or `failed`."
          },
          "kind": {
            "type": "string",
            "description": "`analyze` for the run queued at submission, `reanalyze` for a re-run."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "already_queued": {
            "type": "boolean",
            "description": "True when this call did NOT queue new work because a run was already\npending or in flight, and the job below is that existing run. Re-running a\ncase that is already queued is a no-op, not a second run — otherwise a\npartner polling and retrying would stack duplicate analyses of one case."
          }
        },
        "required": [
          "id",
          "case_id",
          "status",
          "kind",
          "created_at",
          "already_queued"
        ],
        "description": "A queued analysis, as `POST /v1/cases/{id}/reanalyze` returns it.\n\nDeliberately thin. `ApplicationJob` also carries `attempts`, `maxAttempts`,\n`lockedBy`, `lockedAt`, `nextAttemptAt` and `lastError` — a worker lease and a\nstack trace. None of that is a partner's business: `lockedBy` names internal\ninfrastructure and `lastError` is an unbounded upstream message that may quote\napplicant data back. A partner needs to know the run was accepted and how to\nfollow it, which is the case id and the job's own state."
      },
      "SerializedApplicant": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "`apl_…` — stable for a given applicant email within your organization."
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "`individual` | `business`"
          },
          "email": {
            "type": "string"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "credit_score": {
            "type": [
              "number",
              "null"
            ],
            "description": "Bureau score on the most recent case carrying a credit report; null when none."
          },
          "total_applications": {
            "type": "number"
          },
          "active_loans": {
            "type": "number"
          },
          "total_borrowed": {
            "type": "number",
            "description": "Whole dollars: the original principal of every loan made to this applicant, summed."
          },
          "status": {
            "type": "string",
            "description": "`active` | `inactive`"
          },
          "last_activity": {
            "type": [
              "string",
              "null"
            ]
          },
          "date_added": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "type",
          "email",
          "phone",
          "credit_score",
          "total_applications",
          "active_loans",
          "total_borrowed",
          "status",
          "last_activity",
          "date_added"
        ]
      },
      "SerializedApplicantUpdate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "`apl_…` — stable for a given applicant email within your organization."
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "`individual` | `business`"
          },
          "email": {
            "type": "string"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "credit_score": {
            "type": [
              "number",
              "null"
            ],
            "description": "Bureau score on the most recent case carrying a credit report; null when none."
          },
          "total_applications": {
            "type": "number"
          },
          "active_loans": {
            "type": "number"
          },
          "total_borrowed": {
            "type": "number",
            "description": "Whole dollars: the original principal of every loan made to this applicant, summed."
          },
          "status": {
            "type": "string",
            "description": "`active` | `inactive`"
          },
          "last_activity": {
            "type": [
              "string",
              "null"
            ]
          },
          "date_added": {
            "type": [
              "string",
              "null"
            ]
          },
          "cases_updated": {
            "type": "number",
            "description": "Cases whose contact details were rewritten. Always at least 1 — a request\nthat would update none is a 409, never a 200 with a zero here."
          },
          "cases_preserved": {
            "type": "number",
            "description": "Cases left untouched because they carry a decision or evidence."
          }
        },
        "required": [
          "id",
          "name",
          "type",
          "email",
          "phone",
          "credit_score",
          "total_applications",
          "active_loans",
          "total_borrowed",
          "status",
          "last_activity",
          "date_added",
          "cases_updated",
          "cases_preserved"
        ],
        "description": "The `PATCH /v1/applicants/:id` response: the applicant as they now stand,\nPLUS what the fan-out actually did.\n\nTHE TWO COUNTS ARE THE POINT. An applicant is N cases, and this endpoint\nwrites only the ones that carry no underwriting decision, no originated\nloan and no pulled credit or background evidence — the rest are left exactly\nas the decision was made on them. A response that reported only the new\ncontact details would be true and still misleading: a partner correcting a\nphone number across an applicant with eight decided cases and one live one\nhas changed one row, and needs to be able to see that.\n\n`cases_preserved` is deliberately not called \"skipped\" or \"failed\". Nothing\nfailed; those rows were protected, which is the behaviour, not a shortfall."
      },
      "SerializedBankLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "case_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "case_id",
          "status",
          "url",
          "expires_at"
        ]
      },
      "SerializedBankLinkStatus": {
        "type": "object",
        "properties": {
          "link_id": {
            "type": "string"
          },
          "case_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "opened_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "connected_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "bank_connected": {
            "type": "boolean"
          },
          "institution": {
            "type": [
              "string",
              "null"
            ]
          },
          "account_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "account_last4": {
            "type": [
              "string",
              "null"
            ]
          },
          "accounts": {
            "type": "number",
            "description": "1 when a bank account is connected, otherwise 0. A case holds at most one linked account."
          },
          "analysis": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "current_balance": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Null when no balance was measured. Never estimated, never zero-filled."
                  },
                  "avg_monthly_deposits": {
                    "type": "number"
                  },
                  "avg_monthly_withdrawals": {
                    "type": "number"
                  },
                  "monthly_net_cash_flow": {
                    "type": "number"
                  },
                  "lowest_balance_30d": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "ALWAYS NULL. The platform does not compute a 30-day minimum balance and\nnever publishes an estimate in its place."
                  },
                  "nsf_events_90d": {
                    "type": "number"
                  },
                  "transaction_count": {
                    "type": "number"
                  }
                },
                "required": [
                  "current_balance",
                  "avg_monthly_deposits",
                  "avg_monthly_withdrawals",
                  "monthly_net_cash_flow",
                  "lowest_balance_30d",
                  "nsf_events_90d",
                  "transaction_count"
                ]
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "link_id",
          "case_id",
          "status",
          "created_at",
          "opened_at",
          "connected_at",
          "expires_at",
          "bank_connected",
          "institution",
          "account_type",
          "account_last4",
          "accounts",
          "analysis"
        ]
      },
      "SerializedBankTransaction": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "date": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": "string"
          },
          "amount": {
            "type": "number",
            "description": "Whole dollars, like every amount on this API."
          },
          "type": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "merchant_name": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "date",
          "description",
          "amount",
          "type",
          "category",
          "merchant_name"
        ]
      },
      "SerializedBatch": {
        "type": "object",
        "properties": {
          "batch_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "`pending` until every item has terminated, then `completed`. There is no intermediate value."
          },
          "total": {
            "type": "number",
            "description": "Items submitted. Immutable: a batch never grows."
          },
          "succeeded": {
            "type": "number",
            "description": "Terminated successfully."
          },
          "failed": {
            "type": "number",
            "description": "Terminated in failure — at creation, or after the analysis gave up."
          },
          "pending": {
            "type": "number",
            "description": "Still outstanding. `total - succeeded - failed`."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null until the batch completes; set once, by the fence that emits the event."
          },
          "applications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SerializedBatchItem"
            },
            "description": "PER-ITEM OUTCOMES, not just a count. A batch that reported only totals\ncould say \"12 submitted, 3 failed\" and leave a partner to diff their own\nrequest against `GET /v1/cases` to discover WHICH three — which defeats the\npurpose of handing back a batch id instead of 50 case ids."
          }
        },
        "required": [
          "batch_id",
          "status",
          "total",
          "succeeded",
          "failed",
          "pending",
          "created_at",
          "completed_at",
          "applications"
        ]
      },
      "SerializedBatchItem": {
        "type": "object",
        "properties": {
          "index": {
            "type": "number"
          },
          "case_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null ONLY for an item that never reached a case — i.e. one that failed at\ncreation. A `pending` or `succeeded` item always has one."
          },
          "status": {
            "type": "string",
            "description": "`pending` | `succeeded` | `failed`."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "A short sentence saying why the item failed, or null. Never a stack trace."
          }
        },
        "required": [
          "index",
          "case_id",
          "status",
          "error"
        ],
        "description": "ONE submitted application's outcome within a batch.\n\n`index` IS THE PARTNER'S HANDLE. They sent an ordered array; this is how a\nresult is matched back to the element that produced it. It is not decorative\n— without it a partner holding 50 case ids and 50 submissions has no way to\npair them except by guessing from applicant names, which are not unique."
      },
      "SerializedCaseDecision": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "declined",
              "review",
              "invited"
            ]
          },
          "decision": {
            "type": [
              "string",
              "null"
            ],
            "description": "The recorded outcome, or null. Null is the review case: a review records\nno outcome, and `status` (`review`) is the whole answer."
          },
          "decided_by": {
            "type": "string",
            "description": "`api_key:<key id>` — the API key that recorded the decision."
          },
          "decided_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "approved_amount": {
            "type": [
              "number",
              "null"
            ],
            "description": "Whole dollars. Null when the decision carries no approved amount."
          },
          "interest_rate": {
            "type": [
              "number",
              "null"
            ]
          },
          "term_months": {
            "type": [
              "number",
              "null"
            ]
          },
          "monthly_payment": {
            "type": [
              "number",
              "null"
            ]
          },
          "decline_reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "conditions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Echoed back only when the caller sent conditions; [] otherwise."
          }
        },
        "required": [
          "id",
          "status",
          "decision",
          "decided_by",
          "decided_at",
          "approved_amount",
          "interest_rate",
          "term_months",
          "monthly_payment",
          "decline_reasons",
          "conditions"
        ]
      },
      "SerializedCaseDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "declined",
              "review",
              "invited"
            ]
          },
          "product": {
            "type": "string"
          },
          "applicant": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "type": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ein": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "name",
              "type",
              "email",
              "phone",
              "ein"
            ]
          },
          "amount": {
            "type": "number"
          },
          "ai_analysis": {
            "type": "object",
            "properties": {
              "risk_score": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The risk score, or null when no analysis has run. Null is never a score:\na measured 0, the best possible score, is published as 0."
              },
              "risk_level": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Null when no analysis has run — in exactly the cases `risk_score` is null."
              },
              "confidence": {
                "type": "number"
              },
              "recommendation": {
                "type": "string"
              },
              "summary": {
                "type": "string"
              },
              "conditions": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "risk_factors": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/SerializedCaseRiskFactor"
                }
              },
              "run_number": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Null when no analysis has run. Treat it as the presence signal for the\nwhole `ai_analysis` block: `risk_score` and `risk_level` are null exactly\nwhen it is."
              }
            },
            "required": [
              "risk_score",
              "risk_level",
              "confidence",
              "recommendation",
              "summary",
              "conditions",
              "risk_factors",
              "run_number"
            ]
          },
          "credit": {
            "type": "object",
            "properties": {
              "score": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The bureau score, or null when no credit report is on file for the case.\nNull is never a score: a measured 0 is published as 0. The counts and\npercentages beside it are 0 when no report exists."
              },
              "provider": {
                "type": "string"
              },
              "report_date": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "accounts": {
                "type": "number"
              },
              "accounts_closed": {
                "type": "number"
              },
              "total_debt": {
                "type": "number"
              },
              "utilization": {
                "type": "number"
              },
              "payment_history": {
                "type": "number"
              },
              "derogatory_marks": {
                "type": "number"
              },
              "hard_inquiries": {
                "type": "number"
              },
              "public_records": {
                "type": "number"
              },
              "collections": {
                "type": "number"
              },
              "oldest_account_years": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "score",
              "provider",
              "report_date",
              "accounts",
              "accounts_closed",
              "total_debt",
              "utilization",
              "payment_history",
              "derogatory_marks",
              "hard_inquiries",
              "public_records",
              "collections",
              "oldest_account_years"
            ]
          },
          "bank_data": {
            "type": "object",
            "properties": {
              "connected": {
                "type": "boolean"
              },
              "provider": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "source": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "plaid",
                  "statements",
                  "manual",
                  null
                ],
                "description": "How the bank evidence arrived: `plaid` (open banking), `statements`\n(uploaded and read by the platform), `manual`, or null when there is\nnone. `connected` is true only for `plaid`. A partner that reads\n`connected: false` as \"no bank evidence\" shows a reviewer \"No bank\nconnected\" over a case built on three months of statements — this field\nis what lets them tell the two apart."
              },
              "deposit_trend_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Statement path only; null / 0 for a Plaid link. `deposit_trend_pct` is the\n% change in monthly deposits from the first statement read to the last.\n`stacked_lenders` counts distinct lenders with recurring debits — 2 or\nmore means the applicant is already servicing multiple advances."
              },
              "stacked_lenders": {
                "type": "number"
              },
              "periods_analyzed": {
                "type": "number"
              },
              "institution": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "account_type": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "account_last4": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "accounts": {
                "type": "number"
              },
              "total_balance": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "NULL when the platform holds no measured balance for the account —\nbecause the provider returned none, or because no bank was ever linked.\nNEVER estimated, and never zero-filled: a $0 balance is a measurement\n(\"this account is empty\") and absence is not."
              },
              "avg_monthly_deposits": {
                "type": "number"
              },
              "avg_monthly_withdrawals": {
                "type": "number"
              },
              "monthly_net_cash_flow": {
                "type": "number"
              },
              "lowest_balance_30d": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "ALWAYS NULL. The platform does not compute a 30-day minimum balance and\nnever publishes an estimate in its place."
              },
              "nsf_events_90d": {
                "type": "number",
                "description": "Non-sufficient-funds events in the trailing 90 days — the window the data covers."
              },
              "recent_transactions": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "date": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "description": {
                      "type": "string"
                    },
                    "amount": {
                      "type": "number"
                    },
                    "type": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "date",
                    "description",
                    "amount",
                    "type",
                    "category"
                  ]
                }
              }
            },
            "required": [
              "connected",
              "provider",
              "source",
              "deposit_trend_pct",
              "stacked_lenders",
              "periods_analyzed",
              "institution",
              "account_type",
              "account_last4",
              "accounts",
              "total_balance",
              "avg_monthly_deposits",
              "avg_monthly_withdrawals",
              "monthly_net_cash_flow",
              "lowest_balance_30d",
              "nsf_events_90d",
              "recent_transactions"
            ]
          },
          "background": {
            "type": "object",
            "properties": {
              "performed": {
                "type": "boolean"
              },
              "identity_verified": {
                "type": "boolean"
              },
              "ssn4_match": {
                "type": "boolean"
              },
              "address_match": {
                "type": "boolean"
              },
              "ofac_clear": {
                "type": "boolean"
              },
              "criminal_records": {
                "type": "number"
              },
              "civil_judgments": {
                "type": "number"
              },
              "bankruptcies": {
                "type": "number"
              },
              "tax_liens": {
                "type": "number"
              }
            },
            "required": [
              "performed",
              "identity_verified",
              "ssn4_match",
              "address_match",
              "ofac_clear",
              "criminal_records",
              "civil_judgments",
              "bankruptcies",
              "tax_liens"
            ]
          },
          "decision": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "outcome": {
                    "type": "string"
                  },
                  "decided_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "approved_amount": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "interest_rate": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "term_months": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "monthly_payment": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "decline_reasons": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "counter_amount": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "counter_rate": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "outcome",
                  "decided_at",
                  "approved_amount",
                  "interest_rate",
                  "term_months",
                  "monthly_payment",
                  "decline_reasons",
                  "counter_amount",
                  "counter_rate"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "application_data": {
            "type": "object"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "decided_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "status",
          "product",
          "applicant",
          "amount",
          "ai_analysis",
          "credit",
          "bank_data",
          "background",
          "decision",
          "application_data",
          "created_at",
          "updated_at",
          "decided_at"
        ]
      },
      "SerializedCaseRiskFactor": {
        "type": "object",
        "properties": {
          "factor": {
            "type": "string"
          },
          "impact": {
            "type": "string",
            "enum": [
              "positive",
              "negative"
            ]
          }
        },
        "required": [
          "factor",
          "impact"
        ]
      },
      "SerializedCaseSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "applicant": {
            "type": "string"
          },
          "applicant_type": {
            "type": "string"
          },
          "product": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "declined",
              "review",
              "invited"
            ]
          },
          "risk_score": {
            "type": [
              "number",
              "null"
            ],
            "description": "The latest analysis run's risk score, or null when no analysis has run.\nNull means \"not gathered yet\", never a score; a measured 0 (the best\npossible score) is published as 0."
          },
          "credit_score": {
            "type": [
              "number",
              "null"
            ],
            "description": "The applicant's bureau score, or null when no credit report is on file."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "applicant",
          "applicant_type",
          "product",
          "amount",
          "status",
          "risk_score",
          "credit_score",
          "created_at",
          "updated_at"
        ]
      },
      "SerializedChargeOffRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "`pending` until a human approves it. See the escalation doc comment."
          },
          "requested_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "INSTANT — `ChargeOff.chargeOffDate` is `@default(now())`."
          },
          "principal_amount": {
            "type": "number",
            "description": "WHOLE DOLLARS, all four. DERIVED server-side, never caller-supplied (D5)."
          },
          "interest_amount": {
            "type": "number"
          },
          "late_fee_amount": {
            "type": "number"
          },
          "total_amount": {
            "type": "number"
          }
        },
        "required": [
          "id",
          "status",
          "requested_at",
          "principal_amount",
          "interest_amount",
          "late_fee_amount",
          "total_amount"
        ]
      },
      "SerializedCollectionActivity": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "collection_case_id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "One of `call`, `sms`, `email`, `letter`, `visit`, `promise_to_pay`, `dispute`, `note`."
          },
          "outcome": {
            "type": [
              "string",
              "null"
            ],
            "description": "One of `connected`, `voicemail`, `no_answer`, `promise_to_pay`,\n`broken_promise`, `dispute`, `other`, or null. An outcome recorded as\n`refused` or `partial_payment` is returned as `other`."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "As stored, including the `[outcome: …]` marker when one was added."
          },
          "performed_by": {
            "type": "string",
            "description": "`api_key:<key id>` — the API key that recorded the activity. Not settable in the request."
          },
          "performed_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "collection_case_id",
          "type",
          "outcome",
          "notes",
          "performed_by",
          "performed_at"
        ]
      },
      "SerializedCollectionCase": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "loan_id": {
            "type": "string"
          },
          "borrower": {
            "type": "string"
          },
          "borrower_type": {
            "type": "string",
            "description": "`individual` | `business`"
          },
          "product": {
            "type": "string",
            "description": "The product's display name, not its slug."
          },
          "status": {
            "type": "string",
            "description": "The COLLECTION case's status: `active` | `resolved` | `charged_off` | `recovered` | `closed`."
          },
          "stage": {
            "type": "string",
            "description": "The delinquency stage: `current` | `grace` | `early` | `mid` | `late` | `legal` | `charge_off` | `recovery`."
          },
          "loan_status": {
            "type": "string",
            "description": "The LOAN's own status (`current`, `late`, `default`, …). A different axis from `status`."
          },
          "days_past_due": {
            "type": "number",
            "description": "The collection case's own counter, maintained by the daily engine."
          },
          "days_late": {
            "type": "number",
            "description": "The loan's own days-late counter, maintained by servicing; `days_past_due` is copied from it daily."
          },
          "principal_amount": {
            "type": "number",
            "description": "Whole dollars: the loan's original principal."
          },
          "remaining_balance": {
            "type": "number",
            "description": "Whole dollars: the loan's remaining balance. Not the amount overdue."
          },
          "promise_to_pay_amount": {
            "type": [
              "number",
              "null"
            ],
            "description": "Whole dollars, or null when no promise to pay stands."
          },
          "promise_to_pay_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "assigned_to": {
            "type": [
              "string",
              "null"
            ],
            "description": "The assigned collector's internal user id, or null when unassigned."
          },
          "last_contact_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "next_action_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "loan_id",
          "borrower",
          "borrower_type",
          "product",
          "status",
          "stage",
          "loan_status",
          "days_past_due",
          "days_late",
          "principal_amount",
          "remaining_balance",
          "promise_to_pay_amount",
          "promise_to_pay_date",
          "assigned_to",
          "last_contact_date",
          "next_action_date",
          "created_at",
          "updated_at"
        ]
      },
      "SerializedDocument": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "case_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "file_name": {
            "type": "string"
          },
          "file_type": {
            "type": "string"
          },
          "file_size": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "applicant": {
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "type": "number"
          },
          "uploaded_by": {
            "type": "string"
          },
          "uploaded_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "reviewed_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "reviewed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "rejection_reason": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "case_id",
          "name",
          "file_name",
          "file_type",
          "file_size",
          "category",
          "status",
          "applicant",
          "version",
          "uploaded_by",
          "uploaded_at",
          "reviewed_by",
          "reviewed_at",
          "rejection_reason"
        ]
      },
      "SerializedEscalation": {
        "type": "object",
        "properties": {
          "collection_case_id": {
            "type": "string"
          },
          "loan_id": {
            "type": "string"
          },
          "escalation_type": {
            "type": "string",
            "description": "`legal` | `charge_off` — the escalation the caller asked for."
          },
          "case_status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "declined",
              "review",
              "invited"
            ],
            "description": "The case's status AFTER the escalation. A charge-off REQUEST does not move it."
          },
          "case_stage": {
            "type": "string",
            "description": "The case's stage AFTER the escalation. `legal` only on a legal escalation."
          },
          "days_past_due": {
            "type": "number"
          },
          "escalated_by": {
            "type": "string",
            "description": "`api_key:<keyId>` — the principal that performed the escalation."
          },
          "escalated_at": {
            "type": "string"
          },
          "charge_off": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/SerializedChargeOffRequest"
              },
              {
                "type": "null"
              }
            ],
            "description": "Null on a legal escalation; the requested charge-off otherwise."
          }
        },
        "required": [
          "collection_case_id",
          "loan_id",
          "escalation_type",
          "case_status",
          "case_stage",
          "days_past_due",
          "escalated_by",
          "escalated_at",
          "charge_off"
        ]
      },
      "SerializedLoan": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "case_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "borrower": {
            "type": "string"
          },
          "borrower_type": {
            "type": "string"
          },
          "product": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "principal_amount": {
            "type": "number"
          },
          "remaining_balance": {
            "type": "number"
          },
          "total_repaid": {
            "type": "number"
          },
          "monthly_payment": {
            "type": "number"
          },
          "interest_rate": {
            "type": "number"
          },
          "term_months": {
            "type": "number"
          },
          "origination_date": {
            "type": [
              "string",
              "null"
            ],
            "description": "Business dates: bare `YYYY-MM-DD`, never an instant."
          },
          "maturity_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "next_payment_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "days_late": {
            "type": "number"
          },
          "payments_made": {
            "type": "number"
          },
          "payments_remaining": {
            "type": "number"
          }
        },
        "required": [
          "id",
          "case_id",
          "borrower",
          "borrower_type",
          "product",
          "status",
          "principal_amount",
          "remaining_balance",
          "total_repaid",
          "monthly_payment",
          "interest_rate",
          "term_months",
          "origination_date",
          "maturity_date",
          "next_payment_date",
          "days_late",
          "payments_made",
          "payments_remaining"
        ]
      },
      "SerializedLoanDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "case_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "borrower": {
            "type": "string"
          },
          "product": {
            "type": "string"
          },
          "original_amount": {
            "type": "number",
            "description": "Whole dollars: the loan's original principal."
          },
          "current_balance": {
            "type": "number",
            "description": "Whole dollars: the loan's remaining balance, as servicing moves it."
          },
          "interest_rate": {
            "type": "number",
            "description": "A PERCENTAGE: 8.9 means 8.9 %. Same unit as `/v1/portfolio`."
          },
          "term_months": {
            "type": "number"
          },
          "monthly_payment": {
            "type": "number"
          },
          "status": {
            "type": "string"
          },
          "next_payment_due": {
            "type": [
              "string",
              "null"
            ],
            "description": "Business date, bare `YYYY-MM-DD`; null for a loan with nothing due."
          },
          "payments_made": {
            "type": "number"
          },
          "payments_remaining": {
            "type": "number"
          },
          "on_time_rate": {
            "type": "number",
            "description": "Percentage of SETTLED installments (paid or partial) that were not late.\n100 when nothing has settled yet — the service's existing semantic\n(`buildPaymentHistory`), and deliberately not \"unknown\"/null here."
          },
          "schedule": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SerializedSchedulePayment"
            },
            "description": "Oldest first, as `findByLoan` orders it."
          }
        },
        "required": [
          "id",
          "case_id",
          "borrower",
          "product",
          "original_amount",
          "current_balance",
          "interest_rate",
          "term_months",
          "monthly_payment",
          "status",
          "next_payment_due",
          "payments_made",
          "payments_remaining",
          "on_time_rate",
          "schedule"
        ]
      },
      "SerializedLoanSchedule": {
        "type": "object",
        "properties": {
          "loan_id": {
            "type": "string"
          },
          "original_amount": {
            "type": "number",
            "description": "Whole dollars: the original principal, i.e. the balance before installment 1."
          },
          "term_months": {
            "type": "number",
            "description": "The loan's CONTRACTUAL term."
          },
          "installment_count": {
            "type": "number",
            "description": "Installments the loan actually has — which is `schedule.length` unless the\nschedule was capped, and which is `term_months` for any schedule this\nplatform generated in full."
          },
          "schedule": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SerializedAmortizationRow"
            },
            "description": "Oldest first, as `findByLoan` orders it (`dueDate: asc`)."
          }
        },
        "required": [
          "loan_id",
          "original_amount",
          "term_months",
          "installment_count",
          "schedule"
        ]
      },
      "SerializedPayment": {
        "type": "object",
        "properties": {
          "payment_id": {
            "type": "string"
          },
          "loan_id": {
            "type": "string"
          },
          "amount": {
            "type": "number",
            "description": "WHOLE DOLLARS. Every money field on this object is."
          },
          "principal_applied": {
            "type": "number"
          },
          "interest_applied": {
            "type": "number"
          },
          "late_fee_applied": {
            "type": "number"
          },
          "overpayment": {
            "type": "number"
          },
          "new_balance": {
            "type": "number",
            "description": "Whole dollars: the loan's remaining balance after this payment was applied."
          },
          "status": {
            "type": "string",
            "description": "One of `applied`, `pending`, `reversed`, `refunded`."
          },
          "method": {
            "type": "string",
            "description": "One of `ach`, `check`, `cash`, `card`, `wire`, `money_order`, `other` — always a value the request could have sent."
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "scheduled_payment_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The installment this payment was applied to, or null when the loan has no\nopen installment left. Chosen by servicing, not by the caller."
          },
          "effective_date": {
            "type": [
              "string",
              "null"
            ],
            "description": "BUSINESS DATE — a calendar day, never an instant."
          },
          "recorded_by": {
            "type": "string",
            "description": "`api_key:<keyId>`. Never body-supplied."
          },
          "recorded_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "An INSTANT: when the row was written, not the business date."
          }
        },
        "required": [
          "payment_id",
          "loan_id",
          "amount",
          "principal_applied",
          "interest_applied",
          "late_fee_applied",
          "overpayment",
          "new_balance",
          "status",
          "method",
          "reference",
          "scheduled_payment_id",
          "effective_date",
          "recorded_by",
          "recorded_at"
        ]
      },
      "SerializedPortfolio": {
        "type": "object",
        "properties": {
          "total_originated": {
            "type": "number",
            "description": "Whole dollars: the original principal of every loan, summed."
          },
          "total_outstanding": {
            "type": "number",
            "description": "Whole dollars."
          },
          "total_collected": {
            "type": "number",
            "description": "Whole dollars."
          },
          "active_loans": {
            "type": "number"
          },
          "avg_interest_rate": {
            "type": "number",
            "description": "A percentage: 11.8 means 11.8 %."
          },
          "avg_term_months": {
            "type": "number"
          },
          "default_rate": {
            "type": "number",
            "description": "A percentage."
          },
          "delinquency_rate": {
            "type": "number",
            "description": "A percentage."
          },
          "portfolio_yield": {
            "type": "null",
            "description": "ALWAYS NULL: no value is ever published here. Never estimated, never zero-filled. The platform does not compute a portfolio yield and never publishes an estimate."
          },
          "30_day_delinquency": {
            "type": "null",
            "description": "ALWAYS NULL: no value is ever published here. Never estimated, never zero-filled. The platform does not compute a 30-day delinquency figure and never publishes an estimate."
          },
          "60_day_delinquency": {
            "type": "null",
            "description": "ALWAYS NULL: no value is ever published here. Never estimated, never zero-filled. The platform does not compute a 60-day delinquency figure and never publishes an estimate."
          },
          "monthly": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SerializedPortfolioMonth"
            },
            "description": "Trailing six calendar months, oldest first. Whole dollars."
          }
        },
        "required": [
          "total_originated",
          "total_outstanding",
          "total_collected",
          "active_loans",
          "avg_interest_rate",
          "avg_term_months",
          "default_rate",
          "delinquency_rate",
          "portfolio_yield",
          "30_day_delinquency",
          "60_day_delinquency",
          "monthly"
        ]
      },
      "SerializedPortfolioMonth": {
        "type": "object",
        "properties": {
          "period": {
            "type": "string",
            "description": "Calendar month, `YYYY-MM`."
          },
          "collected": {
            "type": "number"
          },
          "expected": {
            "type": "number"
          },
          "defaults": {
            "type": "number"
          }
        },
        "required": [
          "period",
          "collected",
          "expected",
          "defaults"
        ]
      },
      "SerializedProduct": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "sub_category": {
            "type": [
              "string",
              "null"
            ]
          },
          "required_fields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Names only, required subset. Kept: it was the published shape."
          },
          "optional_fields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Names only, optional subset. Kept: it was the published shape."
          },
          "application_data_fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SerializedProductField"
            },
            "description": "Every field, typed — what to put in `application_data`."
          }
        },
        "required": [
          "slug",
          "name",
          "description",
          "category",
          "sub_category",
          "required_fields",
          "optional_fields",
          "application_data_fields"
        ]
      },
      "SerializedProductField": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "The key to use inside `application_data`."
          },
          "label": {
            "type": "string",
            "description": "Human label, as the product's own form shows it."
          },
          "type": {
            "type": "string",
            "description": "text | email | tel | number | currency | select | date | textarea | ssn | ein | checkbox"
          },
          "required": {
            "type": "boolean"
          },
          "options": {
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/SerializedProductFieldOption"
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "The permitted values, for `select` only. Null for every other type."
          },
          "help": {
            "type": [
              "string",
              "null"
            ],
            "description": "The form's own hint for this field, when it has one."
          },
          "section": {
            "type": "string",
            "description": "The section the product's form groups this field under."
          }
        },
        "required": [
          "name",
          "label",
          "type",
          "required",
          "options",
          "help",
          "section"
        ],
        "description": "One field of a product's application form."
      },
      "SerializedProductFieldOption": {
        "type": "object",
        "properties": {
          "value": {
            "type": "string"
          },
          "label": {
            "type": "string"
          }
        },
        "required": [
          "value",
          "label"
        ],
        "description": "One permitted value of a `select` field."
      },
      "SerializedSchedulePayment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "description": "Business dates: bare `YYYY-MM-DD`, never an instant."
          },
          "paid_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount": {
            "type": "number"
          },
          "principal": {
            "type": "number"
          },
          "interest": {
            "type": "number"
          },
          "late_fee": {
            "type": "number"
          },
          "paid_amount": {
            "type": "number"
          },
          "status": {
            "type": "string"
          },
          "days_overdue": {
            "type": "number"
          }
        },
        "required": [
          "id",
          "due_date",
          "paid_date",
          "amount",
          "principal",
          "interest",
          "late_fee",
          "paid_amount",
          "status",
          "days_overdue"
        ]
      },
      "SerializedSendLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "link_type": {
            "type": "string",
            "enum": [
              "application",
              "bank_link"
            ]
          },
          "url": {
            "type": "string"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "link_type",
          "url",
          "expires_at",
          "status"
        ],
        "description": "The one shape `POST /v1/applicants/:id/send-link` returns for BOTH link\ntypes. `link_type` echoes the discriminator the partner sent."
      },
      "SerializedUnderwriteSubmission": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "declined",
              "review",
              "invited"
            ]
          },
          "pipeline_stage": {
            "type": "string",
            "description": "Where the case is in the underwriting pipeline, as distinct from `status`,\nwhich is the DECISION. One of:\n\n- `queued` — accepted, waiting for the analysis to run.\n- `awaiting_documents` — you declared `bank_verification: \"statements\"`, so\n  nothing is queued until you upload them. Upload a `financial` document\n  and the analysis starts.\n- `awaiting_evidence` — the analysis ran and found nothing to read: no\n  credit report, no bank data, no background check. Not a failure; the case\n  is waiting for evidence. Uploading a `financial` document starts a fresh\n  run on its own.\n- `analyzed` — an analysis completed. `status` carries the outcome.\n- `failed` — the analysis could not complete for a reason on our side.\n  `POST /v1/cases/{id}/reanalyze` retries it."
          },
          "product": {
            "type": "string"
          },
          "applicant_name": {
            "type": "string"
          },
          "amount": {
            "type": "number",
            "description": "Whole dollars: the amount requested, exactly as submitted."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "estimated_decision": {
            "type": "string"
          },
          "bank_link_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present only when the request sent `bank_link: true`; absent means no link\nwas requested. `null`, with `bank_link_status` `\"failed\"`, when the link\ncould not be created — the application is still accepted, and a link can be\nminted later with POST /v1/bank-link."
          },
          "bank_link_status": {
            "type": "string",
            "enum": [
              "ready",
              "failed"
            ],
            "description": "Present only when the request sent `bank_link: true`. `\"ready\"` when\n`bank_link_url` carries a link; `\"failed\"` when it is `null`."
          },
          "bank_link_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The link's id, for `GET /v1/bank-link/{id}/status` and\n`/transactions`. Present whenever `bank_link_url` is; `null` when the\nlink could not be created.\n\nWithout this a link minted at submit was unpollable: the id existed, and\nthe response dropped it. `POST /v1/bank-link` returned one, this did not,\nso the same resource was addressable or not depending on which door it\ncame through."
          },
          "bank_link_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the link stops working, so a partner forwarding it to an applicant\nknows how long they have. `null` when the link could not be created, and\nalso when it carries no expiry."
          }
        },
        "required": [
          "id",
          "status",
          "pipeline_stage",
          "product",
          "applicant_name",
          "amount",
          "created_at",
          "estimated_decision"
        ]
      },
      "SerializedWebhookSubscription": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The event types this endpoint is subscribed to."
          },
          "products": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Product slugs this endpoint is limited to. EMPTY means every product,\nwhich is the default — an endpoint that named no products hears about all\nof them, exactly as every endpoint did before this filter existed."
          },
          "mode": {
            "type": "string",
            "description": "`live` or `test` — the mode of the API key that registered it."
          },
          "status": {
            "type": "string",
            "description": "`active` or `failing`. `disabled` never reaches a response."
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "products",
          "mode",
          "status",
          "description",
          "created_at",
          "updated_at"
        ]
      }
    }
  }
}
