Rent Roll API

Rent roll extraction API with built-in verification

Documentation

API documentation

One endpoint does the work: upload a rent roll, get structured data back with a deterministic verification report. No async jobs, no polling — a single HTTP call.

Machine-readable spec: OpenAPI 3.0 (openapi.json) — import into Postman, Insomnia, or your codegen tool.

Authentication

Pass your API key in the X-Api-Key header (or as a bearer token). Get a key from the pricing page; manage it at your account.

X-Api-Key: rr_live_your_key_here

or

Authorization: Bearer rr_live_your_key_here

No key? The web app allows 3 free extractions/day per IP for evaluation.

POST/v1/extract

Extracts structured data from one document. Send multipart/form-data with a file field.

Supported inputs

  • PDF — digital or scanned (OCR handled automatically), up to 25 MB
  • Excel — .xlsx, .xlsm
  • CSV, HTML

Query parameters

ParameterValuesDefaultDescription
formatjson · csv · xlsxjsonResponse format. csv/xlsx return a downloadable file.

Example

curl -X POST "https://rentrollapi.com/v1/extract?format=json"
-H "X-Api-Key: rr_live_your_key_here"
-F "file=@rent-roll.pdf"

.NET SDK

Official client on NuGet: RentRollAPI.Client (netstandard2.0 / net8.0).

dotnet add package RentRollAPI.Client

using RentRollApi.Client;

using var client = new RentRollClient("rr_live_your_key_here"); var result = await client.ExtractAsync("rent-roll.pdf");

Console.WriteLine($"{result.Units.Count} units, confidence {result.Verification.DocumentConfidence:P0}"); foreach (var check in result.Verification.Checks) Console.WriteLine($"[{check.Result}] {check.Name}: {check.Detail}");

// Or get Excel / CSV directly: byte[] xlsx = await client.ExtractToXlsxAsync(stream, "rent-roll.pdf");

Response (JSON)

Every field carries a value, status (Extracted / Missing / Flagged), and confidence. The verification block is computed deterministically — totals are re-added and reconciled against what the document states, so you know when to trust the output.

{ "propertyName": { "value": "Maplewood Commerce Center", "status": "Extracted", "confidence": 0.9 }, "asOfDate": { "value": "2026-06-30", "status": "Extracted", "confidence": 0.9 }, "units": [ { "unitNumber": { "value": "101", "status": "Extracted", "confidence": 0.9 }, "tenantName": { "value": "Cedar Point Dental", "status": "Extracted", "confidence": 0.9 }, "squareFeet": { "value": 1850, "status": "Extracted", "confidence": 0.9 }, "monthlyRent": { "value": 4625.0, "status": "Extracted", "confidence": 0.9 }, "leaseStart": { "status": "Missing" }, "leaseEnd": { "value": "2028-03-31", "status": "Extracted", "confidence": 0.9 }, "occupancy": { "value": "Occupied", "status": "Extracted", "confidence": 0.9 } } ], "totals": { "unitCount": 8, "monthlyRent": 28155.0, "squareFeet": 13750 }, "verification": { "documentConfidence": 0.9, "flaggedFieldCount": 0, "allChecksPassed": true, "checks": [ { "name": "UnitCountReconciliation", "result": "Pass", "detail": "Extracted 8 units; document states 8." }, { "name": "TotalMonthlyRentReconciliation", "result": "Pass", "detail": "Computed 28,155.00 vs stated 28,155.00 (within tolerance)." }, { "name": "TotalSquareFeetReconciliation", "result": "Pass", "detail": "Computed 13,750.00 vs stated 13,750.00 (within tolerance)." }, { "name": "DuplicateUnits", "result": "Pass", "detail": "No duplicate unit numbers." }, { "name": "LeaseDateOrder", "result": "Pass", "detail": "All lease end dates follow start dates." }, { "name": "NonNegativeRents", "result": "Pass", "detail": "No negative rents." } ] } }

Errors

StatusMeaning
400Missing/empty file field, file over 25 MB, or unknown format
401Invalid or revoked API key
422Unsupported or unreadable document
429Monthly quota exceeded and no prepaid credits remain (or demo limit reached)

All errors return {"error": "human-readable message"}.

POST/v1/extract/operating-statement

Extract a T12 / operating statement (trailing-twelve-month income & expense report) into structured line items. Same inputs, auth, metering, and format options as /v1/extract — one extraction counts as one document against your quota.

Example

curl -X POST "https://rentrollapi.com/v1/extract/operating-statement"
-H "X-Api-Key: rr_live_your_key_here"
-F "file=@t12.pdf"

{ "propertyName": { "value": "Maplewood Apartments", "status": "extracted", "confidence": 0.95 }, "basis": { "value": "Accrual", "status": "extracted", "confidence": 0.9 }, "months": ["2025-08", "2025-09", "...", "2026-07"], "lineItems": [ { "label": { "value": "Gross Potential Rent", "status": "extracted", "confidence": 0.95 }, "kind": "Income", "category": "GrossPotentialRent", "monthlyValues": [ { "value": 52000, "status": "extracted", "confidence": 0.9 }, "..." ], "total": { "value": 631200, "status": "extracted", "confidence": 0.95 } } ], "verification": { "documentConfidence": 0.9, "allChecksPassed": true, "checks": [ { "name": "RowTotalReconciliation", "result": "pass", "detail": "All 15 row totals reconcile with their monthly values." }, { "name": "NoiReconciliation", "result": "pass", "detail": "EGI − OpEx matches stated NOI (within tolerance)." } ] } }

Deterministic checks: row totals vs monthly values, EGI = income sum, Total OpEx = expense sum, NOI = EGI − OpEx, non-negative expenses, and consecutive-month continuity. Line-item kind is Income, Expense, Subtotal, or Other; category normalizes labels into ~23 standard buckets (RealEstateTaxes, Insurance, Utilities, RepairsMaintenance, Payroll, ManagementFee, ...). Contra-revenue rows like vacancy and concessions are Income-kind with negative values.

MCP server (AI assistants)

RentRollAPI is also a Model Context Protocol server. Point any MCP-capable client (Claude, ChatGPT, Copilot, Cursor...) at the streamable-HTTP endpoint and it gains extract_rent_roll and extract_operating_statement tools:

{ "mcpServers": { "rentrollapi": { "type": "http", "url": "https://rentrollapi.com/mcp", "headers": { "X-Api-Key": "rr_live_your_key_here" } } } }

Omit the header to use the free 3-docs/day demo allowance. Metering is identical to the REST API.

Account endpoints

All authenticated with the same X-Api-Key header.

EndpointDescription
GET/api/accountPlan, monthly usage, remaining quota, prepaid credits
POST/api/account/rotateIssues a new key, immediately revokes the current one (credits carry over)
POST/api/portalReturns a Stripe Customer Portal URL — upgrade, downgrade, cancel, invoices
POST/api/credits/checkoutReturns a Stripe Checkout URL for a 25-credit top-up applied to this key

Quotas & billing

  • Subscriptions (Starter 50/mo, Pro 300/mo): quota resets on the 1st (UTC). Overage draws from prepaid credits when you have them.
  • Pay-as-you-go: each extraction consumes one prepaid credit.
  • Credits never expire and survive key rotation and subscription cancellation.

Data handling

Documents are processed in memory and never stored after your extraction completes. See the privacy policy.