---
title: "Read a document into findings (no estimate)"
method: POST
path: "/v1/property-turnover/read_document"
tags: ["property-turnover"]
---

# Read a document into findings (no estimate)

`POST /v1/property-turnover/read_document`

## Request

- Base URL: `https://tektolo.com` (the document declares no server; this is the origin it was published from)
- URL: `https://tektolo.com/v1/property-turnover/read_document`
- Auth: none declared

Reads a move-out inspection report (PDF, Word, Excel, PowerPoint or CSV) or up to 5 photos of paper pages, 5 pages at most, and returns what it read as findings: a `reading`, never an estimate. Check each finding against its evidence (page and quote), then send `reading.findings` unchanged to `POST /v1/property-turnover/scope`, which prices them by our own rules. Rows not turned into findings are listed in `set_aside` with the reason.

**Key required.** Send a connector key (`Authorization: Bearer <key>`; free from `POST /v1/keys`). The web tool's page token is not accepted here. One document counts as one call of the key's daily free allowance, and a key can read 3 documents a day (UTC). Not on the x402 paid lane.

**Body.** `multipart/form-data` with the field `files` (one file, or up to 5 photos), or `application/json` `{"files": [{"name": "report.pdf", "content_base64": "..."}]}`. 15 MB a file, 25 MB an upload. Files with macros, scripts, embedded files or a password are refused.

**Privacy.** Upload a report (PDF, Word, Excel, CSV) or up to 5 photos, 5 pages at most. Pages are read by Microsoft Azure OpenAI. Please redact tenant names and signatures. Our server opens the file in an isolated converter, which turns each page into a plain image and drops everything else in the file. The page images are sent to Microsoft Azure OpenAI (run by Microsoft for us inside its US data zone) to copy out the findings and the unit facts; it is told not to copy names, addresses, phone numbers or signatures, and we remove any that slip through. Findings our own matcher cannot match to a standard repair item are then sent as text, with contact details removed, to the same model. Nothing from the file is stored: the file, the page images and the text read from them are held in memory only while the request is processed. For up to a day we keep the answer for a page in memory, found by a one-way hash of the page image, so the same page gets the same answer. Prices are always worked out by our own rules, never by the AI model. How we handle uploads: /privacy#document-upload.

## Request body

- object
  - `files` object[], required
    - `name` string, required: The file name, with its extension.
    - `content_base64` string, byte, required: The file's bytes, standard base64.

## Response `200`

Successful Response

- ReadDocumentResponse: The API's answer to a document: a reading, never an estimate.
  - `reading` DocumentReading, required: A document read for the API (``kind: "document"``): the shared reading (``tektolo_core.Reading``: id, model, prompt version, counts, scope-ready findings, notes) plus what only a document has. Never a price: the caller checks the findings and sends them to scope (with ``confirmed: true`` on the ones checked).
    - `id` string, required: rd_ and 24 hex characters; the same reading has the same id.
    - `kind` 'notes' | 'document', required
    - `model` string, nullable: The model that read it.
    - `prompt_version` string, nullable
    - `counts` DocumentCounts, required
      - `total` integer, required: Findings in the reading.
      - `ai` integer, required: Findings the AI model read (they carry extraction).
      - `parser` integer, required: Findings the deterministic parser and matcher placed, or sent coded.
      - `unmatched` integer, required: Findings nothing could match (manual-quote lines).
      - `set_aside` integer, required: Rows read but not turned into findings.
    - `findings` object[], required: Every finding the rules answered, in the shape the rules operation accepts.
    - `notes` ReadingNote[]: What the reading noticed while reading the text.
      - `kind` 'warning' | 'assumption', required
      - `code` string, required
      - `message` string, required
      - `finding_ids` string[]
    - `schema_version` string, required: The page schema the model filled.
    - `pages` integer, required: Pages read.
    - `evidence` Evidence[]
      - `finding` string, required: The finding's id (its page and quote are in its extraction).
      - `row` object: The row as read: room, item, condition, comment, count.
      - `match` object, nullable: The standard item the matcher placed it on, if any.
      - `needs_review` boolean
      - `flags` string[]: Why to check it, as codes.
      - `hints` string[]
      - `refer_outs` ReferOutHint[]
        - `id` string, required
        - `name` string, required
        - `professional` string, required
    - `set_aside` SetAside[]: Every row read but not turned into a finding, and why.
      - `reason` string, required: Why the row was not turned into a finding, as a code.
      - `row` string, required: The row as read, contact details masked.
      - `page` integer, nullable
    - `unit` object, nullable: Unit facts printed on the pages (zip, bedrooms, bathrooms, sq_ft, year_built), each {value, confidence, page} or null.
  - `status` string, required: ok, partial (some pages unread) or nothing_found.
  - `message` string, required
  - `documents_left` integer, nullable, required: Documents this key may still read today; null where the cap is off.
  - `next` object, required: Where the checked findings go: scope.

## Other responses

- `401`: No connector key (`key_required`), or an invalid one.
- `408`: The upload was too slow (`slow_upload`).
- `413`: Over 25 MB an upload or 15 MB a file (`too_large`).
- `415`: Not multipart/form-data or JSON (`unsupported_media_type`).
- `422`: A file we do not read (`rejected`, with `code`).
- `429`: This key's documents or free calls for today (`cap_address`, `free_tier_exhausted`), or the day's page budget (`cap_global`).
- `503`: Reading is not available right now (`unavailable`, `busy`).

---

[API](https://skmtc.dev/tektolo/apis/tektolo-capabilities-api.md) · [All operations](https://skmtc.dev/tektolo/apis/tektolo-capabilities-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tektolo/apis/tektolo-capabilities-api/revisions/6e667aa7b6f7?raw)
