{
  "openapi": "3.0.3",
  "info": {
    "title": "RentRollAPI",
    "version": "1.0.0",
    "description": "Convert commercial real estate rent rolls — PDF (native or scanned), Excel (.xlsx), and CSV — into structured, verified data. Every extraction includes a deterministic verification report: totals recomputed from extracted units are reconciled against the totals stated on the document, unit counts are cross-checked, and uncertain fields are flagged with confidence scores instead of silently guessed.\n\nWorks with rent rolls exported from Yardi, RealPage, AppFolio, Buildium, Entrata, MRI, and custom spreadsheets.\n\nGet an API key at https://rentrollapi.com — free 3-docs/day browser demo, plans from $49/month.",
    "termsOfService": "https://rentrollapi.com/terms.html",
    "contact": {
      "name": "RentRollAPI Support",
      "url": "https://rentrollapi.com",
      "email": "hello@rentrollapi.com"
    },
    "x-logo": {
      "url": "https://rentrollapi.com/icon-192.png"
    }
  },
  "externalDocs": {
    "description": "Full API documentation",
    "url": "https://rentrollapi.com/docs.html"
  },
  "servers": [
    { "url": "https://rentrollapi.com" }
  ],
  "security": [
    { "ApiKeyAuth": [] }
  ],
  "tags": [
    { "name": "Extraction", "description": "Rent roll extraction and conversion" },
    { "name": "Account", "description": "Key, usage, and billing management" }
  ],
  "paths": {
    "/v1/extract": {
      "post": {
        "tags": ["Extraction"],
        "operationId": "extractRentRoll",
        "summary": "Extract structured data from a rent roll document",
        "description": "Upload a rent roll as PDF (native or scanned), .xlsx, or .csv. Returns structured data with per-field status/confidence and a deterministic verification report, or a converted Excel/CSV file depending on `format`.",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Response format. `json` returns the structured schema; `xlsx` and `csv` return a downloadable file.",
            "schema": { "type": "string", "enum": ["json", "xlsx", "csv"], "default": "json" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The rent roll document (PDF, XLSX, or CSV). Max 25 MB."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extraction result",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ExtractionResult" }
              },
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": { "type": "string", "format": "binary" }
              },
              "text/csv": {
                "schema": { "type": "string" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/v1/extract/operating-statement": {
      "post": {
        "tags": ["Extraction"],
        "operationId": "extractOperatingStatement",
        "summary": "Extract structured data from a T12 / operating statement",
        "description": "Upload a trailing-twelve-month operating statement (income & expense report) as PDF (native or scanned), .xlsx, or .csv. Returns structured line items with per-field status/confidence and a deterministic verification report (row totals, EGI, OpEx, NOI reconciliation), or a converted Excel/CSV file depending on `format`.",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Response format. `json` returns the structured schema; `xlsx` and `csv` return a downloadable file.",
            "schema": { "type": "string", "enum": ["json", "xlsx", "csv"], "default": "json" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The operating statement document (PDF, XLSX, or CSV). Max 25 MB."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extraction result",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/OperatingStatementResult" }
              },
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": { "type": "string", "format": "binary" }
              },
              "text/csv": {
                "schema": { "type": "string" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/api/account": {
      "get": {
        "tags": ["Account"],
        "operationId": "getAccount",
        "summary": "Get plan, usage, and remaining quota",
        "responses": {
          "200": {
            "description": "Account status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plan": { "type": "string" },
                    "usedThisMonth": { "type": "integer" },
                    "monthlyQuota": { "type": "integer" },
                    "credits": { "type": "integer" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/account/rotate": {
      "post": {
        "tags": ["Account"],
        "operationId": "rotateKey",
        "summary": "Issue a new API key and revoke the current one",
        "description": "Credits and subscription carry over to the new key.",
        "responses": {
          "200": {
            "description": "New key issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "apiKey": { "type": "string" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/portal": {
      "post": {
        "tags": ["Account"],
        "operationId": "createPortalSession",
        "summary": "Get a Stripe Customer Portal URL",
        "description": "Manage subscription: upgrade, downgrade, cancel, view invoices.",
        "responses": {
          "200": {
            "description": "Portal URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "url": { "type": "string", "format": "uri" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/credits/checkout": {
      "post": {
        "tags": ["Account"],
        "operationId": "createCreditsCheckout",
        "summary": "Get a Stripe Checkout URL for a prepaid credit top-up",
        "description": "Credits never expire and survive key rotation and subscription cancellation.",
        "responses": {
          "200": {
            "description": "Checkout URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "url": { "type": "string", "format": "uri" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "API key issued at rentrollapi.com (format: rr_live_...)."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Missing/empty file field, file over 25 MB, or unknown format",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "Invalid or revoked API key",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unprocessable": {
        "description": "Unsupported or unreadable document",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "QuotaExceeded": {
        "description": "Monthly quota exceeded and no prepaid credits remain",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "description": "Human-readable error message" }
        }
      },
      "StringField": {
        "type": "object",
        "description": "An extracted string field with status, confidence, and flag reason.",
        "properties": {
          "value": { "type": "string", "nullable": true },
          "status": { "type": "string", "enum": ["Extracted", "Derived", "Flagged", "Missing"] },
          "confidence": { "type": "number", "format": "double", "nullable": true, "minimum": 0, "maximum": 1 },
          "flag": { "type": "string", "nullable": true, "description": "Reason the field was flagged instead of trusted" }
        }
      },
      "NumberField": {
        "type": "object",
        "description": "An extracted numeric field with status, confidence, and flag reason.",
        "properties": {
          "value": { "type": "number", "nullable": true },
          "status": { "type": "string", "enum": ["Extracted", "Derived", "Flagged", "Missing"] },
          "confidence": { "type": "number", "format": "double", "nullable": true, "minimum": 0, "maximum": 1 },
          "flag": { "type": "string", "nullable": true }
        }
      },
      "Unit": {
        "type": "object",
        "description": "One row of the rent roll: a unit/lease.",
        "properties": {
          "unitNumber": { "$ref": "#/components/schemas/StringField" },
          "tenantName": { "$ref": "#/components/schemas/StringField" },
          "unitType": { "$ref": "#/components/schemas/StringField" },
          "squareFeet": { "$ref": "#/components/schemas/NumberField" },
          "leaseStart": { "$ref": "#/components/schemas/StringField" },
          "leaseEnd": { "$ref": "#/components/schemas/StringField" },
          "monthlyRent": { "$ref": "#/components/schemas/NumberField" },
          "marketRent": { "$ref": "#/components/schemas/NumberField" },
          "securityDeposit": { "$ref": "#/components/schemas/NumberField" },
          "occupancy": { "$ref": "#/components/schemas/StringField" }
        }
      },
      "StatedTotals": {
        "type": "object",
        "description": "Totals as stated on the document itself, used for reconciliation.",
        "properties": {
          "unitCount": { "$ref": "#/components/schemas/NumberField" },
          "totalMonthlyRent": { "$ref": "#/components/schemas/NumberField" },
          "totalSquareFeet": { "$ref": "#/components/schemas/NumberField" },
          "occupancyRate": { "$ref": "#/components/schemas/NumberField" }
        }
      },
      "VerificationCheck": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "example": "TotalMonthlyRentReconciliation" },
          "result": { "type": "string", "enum": ["Pass", "Fail", "Skipped"] },
          "detail": { "type": "string" }
        }
      },
      "Verification": {
        "type": "object",
        "description": "Deterministic verification report: recomputed values reconciled against stated totals.",
        "properties": {
          "documentConfidence": { "type": "number", "format": "double", "minimum": 0, "maximum": 1 },
          "flaggedFieldCount": { "type": "integer" },
          "allChecksPassed": { "type": "boolean" },
          "checks": { "type": "array", "items": { "$ref": "#/components/schemas/VerificationCheck" } }
        }
      },
      "StatementLineItem": {
        "type": "object",
        "description": "One line item of the operating statement.",
        "properties": {
          "label": { "$ref": "#/components/schemas/StringField" },
          "kind": { "type": "string", "enum": ["Income", "Expense", "Subtotal", "Other"] },
          "category": { "type": "string", "description": "Normalized category bucket, e.g. GrossPotentialRent, VacancyLoss, RealEstateTaxes, Insurance, Utilities, RepairsMaintenance, Payroll, ManagementFee, EffectiveGrossIncome, TotalOperatingExpenses, NetOperatingIncome." },
          "monthlyValues": { "type": "array", "items": { "$ref": "#/components/schemas/NumberField" }, "description": "Aligned index-for-index with the document-level months array. Empty for annual-only statements." },
          "total": { "$ref": "#/components/schemas/NumberField" }
        }
      },
      "OperatingStatementResult": {
        "type": "object",
        "properties": {
          "propertyName": { "$ref": "#/components/schemas/StringField" },
          "basis": { "$ref": "#/components/schemas/StringField" },
          "months": { "type": "array", "items": { "type": "string" }, "description": "Reporting periods as yyyy-MM, e.g. [\"2025-08\", ..., \"2026-07\"]." },
          "lineItems": { "type": "array", "items": { "$ref": "#/components/schemas/StatementLineItem" } },
          "verification": { "$ref": "#/components/schemas/Verification" }
        }
      },
      "ExtractionResult": {
        "type": "object",
        "properties": {
          "propertyName": { "$ref": "#/components/schemas/StringField" },
          "asOfDate": { "$ref": "#/components/schemas/StringField" },
          "units": { "type": "array", "items": { "$ref": "#/components/schemas/Unit" } },
          "statedTotals": { "$ref": "#/components/schemas/StatedTotals" },
          "verification": { "$ref": "#/components/schemas/Verification" }
        }
      }
    }
  }
}
