A vendor-ready turnover scope and owner estimate in minutes, with every number traced to a source or a stated assumption.
Turn move-out inspection findings into a priced, vendor-ready make-ready scope.
Try it freeNo sign-in, no account.
Who it is for: property managers, landlords, maintenance coordinators, AI agents working for them
- Issue codes
- 189
- Repair tasks
- 164
- Refer-out rules
- 23
- Cited sources
- 191
When to use it
Quick turnover estimate (start here)
Use it when: Start here when the user describes a move-out or turnover in words ("carpet stained in both bedrooms, oven filthy") and wants a cost estimate or what to tell vendors. Use property_turnover_scope instead for rate cards, owner decisions, measured quantities or per-finding control.
Do not use it for: Diagnosing hazards (they come back as refer-outs to a licensed professional); legal advice on deposits; remodel bids; properties outside the US.
- If the ZIP is missing or not exactly 5 digits, ask the user; never guess or pad it.
- Relay refer_outs first, then the totals; say the numbers are estimates, not quotes.
Scope a property turnover
Use it when: Turnover findings for a US rental unit when you need control: rooms, measured quantities, the manager's own prices (rate_card) or owner approve/decline decisions. For a plain-English estimate, property_turnover_quick_estimate is easier.
Do not use it for: Diagnosing hazards (they come back as refer-outs to a licensed professional); legal advice on deposits; remodel bids; properties outside the US.
- If the ZIP is missing or not exactly 5 digits, ask the user; never guess or pad it.
- Describe each finding in plain words (description). Only send a code returned by property_turnover_issue_codes; unmatched findings come back as manual-quote lines with suggested codes.
- Give measured quantities when you have them (e.g. 120 sq_ft); otherwise typical sizes are assumed and listed in assumptions.
- Send property.floor_coverings when you know whether there is carpet, and unit_type single_family for houses; both change the baseline scope.
- Relay the summary, refer_outs, assumptions and unknowns to the user; costs are estimate ranges, not quotes.
- Use detail=summary unless the user asks for every line item (summary already lists every line tied to a finding).
- Rate card examples - {"trade": "handyman", "unit_price": 65, "uom": "hour"} or {"task_id": "carpet.clean", "unit_price": 0.2, "uom": "sq_ft"}; check rate_card_applied and rate_card_ignored before saying a rate was used.
- For the Excel workbook or owner estimate, give the user links.web_tool_prefilled (opens the free web tool with these findings) or POST the same arguments to links.downloads; do not build a spreadsheet yourself.
Look up turnover issue codes
Use it when: Only while preparing findings for a turnover cost estimate, to find the standard issue code for something observed (e.g. "pet stain on carpet"). Never needed to answer a question; call with no arguments to list the taxonomy areas and the first codes.
Do not use it for: Legal or security-deposit questions; anything outside rental turnover and make-ready work. Not needed before quick_estimate or scope: a plain description works there.
- Call with no arguments to list the taxonomy areas (in `areas`) and the first codes.
- Codes are optional for scoping; a plain description also works.
Try it
Run this in a terminal. The response carries the result, its assumptions, unknowns and a result hash.
Scope a property turnover
From examples/one_bed_charlotte_light_turn.json.
curl -sS 'https://tektolo.com/v1/property-turnover/scope' \
-H 'content-type: application/json' \
-d '{
"property": {
"zip": "28202",
"unit_type": "apartment",
"bedrooms": 1,
"bathrooms": "1",
"sq_ft": 700,
"year_built": 2008
},
"standards": {
"profile": "standard"
},
"findings": [
{
"id": "f1",
"location": {
"area": "bedroom",
"label": "Bedroom"
},
"code": "walls.nail_holes",
"extent": "partial"
},
{
"id": "f2",
"location": {
"area": "living_room",
"label": "Living room"
},
"code": "walls.scuffs",
"extent": "spot"
},
{
"id": "f3",
"location": {
"area": "living_room",
"label": "Living room"
},
"code": "carpet.soiled",
"extent": "partial",
"quantity": {
"value": 220,
"uom": "sq_ft"
}
},
{
"id": "f4",
"location": {
"area": "bedroom",
"label": "Bedroom"
},
"code": "carpet.soiled",
"extent": "partial",
"quantity": {
"value": 140,
"uom": "sq_ft"
}
},
{
"id": "f5",
"location": {
"area": "kitchen",
"label": "Kitchen"
},
"code": "range.dirty"
},
{
"id": "f6",
"location": {
"area": "kitchen",
"label": "Kitchen"
},
"code": "refrigerator.dirty"
},
{
"id": "f7",
"location": {
"area": "living_room",
"label": "Living room"
},
"code": "blinds.bent_missing",
"quantity": {
"value": 1,
"uom": "each"
}
}
],
"client_refs": {
"unit_id": "example-1bed-charlotte"
}
}'Operations
| Operation | REST | MCP tool | Pricing | Artifacts (rendered on request) |
|---|---|---|---|---|
| Quick turnover estimate (start here)The easiest way to estimate a US rental turnover: a ZIP, bedrooms, bathrooms and a list of plain-English issues in, a priced make-ready estimate out (same engine and result as property_turnover_scope, short summary by default). | POST /v1/property-turnover/quick_estimate | property_turnover_quick_estimate | Free tier (rate limited). Free while experimental; rate limited per client. | None |
| Scope a property turnoverTurn move-out / turnover inspection findings for a US rental unit into a priced make-ready scope: standardized tasks by trade, regional low/expected/high costs, must/should/could priorities, vendor scopes, refer-outs to licensed professionals, and every assumption stated. | POST /v1/property-turnover/scope | property_turnover_scope | Free tier (rate limited). Free while experimental; rate limited per client. | POST /v1/property-turnover/scope/render/xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheetPOST /v1/property-turnover/scope/render/html text/htmlPOST /v1/property-turnover/scope/render/csv text/csvPOST /v1/property-turnover/scope/render/vendor_csv application/zip |
| Look up turnover issue codesSearch the standard taxonomy of rental-unit turnover issues (component.issue codes) by keyword or area. | POST /v1/property-turnover/issue_codes | property_turnover_issue_codes | Free | None |
Schemas
Quick turnover estimate (start here): input and output schemas
Input
{
"$defs": {
"PriceTier": {
"description": "Purchasing channel. Library prices are pm_vendor (a PM buying turns from a local vendor).",
"enum": [
"bulk_contract",
"pm_vendor",
"retail_one_off"
],
"title": "PriceTier",
"type": "string"
},
"Profile": {
"enum": [
"minimum",
"standard",
"premium"
],
"title": "Profile",
"type": "string"
},
"UnitType": {
"enum": [
"apartment",
"single_family",
"townhome",
"condo",
"duplex",
"manufactured"
],
"title": "UnitType",
"type": "string"
}
},
"additionalProperties": false,
"description": "Flat, primitive-only input: the easiest way to get an estimate.",
"properties": {
"client_refs": {
"anyOf": [
{
"additionalProperties": {
"type": "string"
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Your own reference ids (e.g. unit_id, job_id). Echoed back, never used.",
"title": "Client Refs"
},
"zip": {
"description": "The user's 5-digit US ZIP code. If it is missing or not exactly 5 digits, ask the user; never guess, pad or correct it.",
"pattern": "^\\d{5}$",
"title": "Zip",
"type": "string"
},
"bedrooms": {
"description": "0 for a studio.",
"maximum": 12,
"minimum": 0,
"title": "Bedrooms",
"type": "integer"
},
"bathrooms": {
"anyOf": [
{
"maximum": 12.0,
"minimum": 0.0,
"multipleOf": 0.5,
"type": "number"
},
{
"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
"type": "string"
}
],
"description": "Half baths count as 0.5.",
"title": "Bathrooms"
},
"issues": {
"description": "What was found, one plain-English item per entry, e.g. ['stained carpet in both bedrooms', 'nail holes everywhere', 'oven is filthy']. A room at the start sets the location ('Bedroom 2 carpet torn').",
"items": {
"type": "string"
},
"maxItems": 100,
"title": "Issues",
"type": "array"
},
"unit_type": {
"$ref": "#/$defs/UnitType",
"default": "apartment"
},
"sq_ft": {
"anyOf": [
{
"maximum": 20000,
"minimum": 150,
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Interior sq ft.",
"title": "Sq Ft"
},
"year_built": {
"anyOf": [
{
"maximum": 2100,
"minimum": 1700,
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Year Built"
},
"carpet": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "Is there wall-to-wall carpet?",
"title": "Carpet"
},
"profile": {
"$ref": "#/$defs/Profile",
"default": "standard"
},
"price_tier": {
"$ref": "#/$defs/PriceTier",
"default": "pm_vendor"
},
"detail": {
"default": "summary",
"description": "summary (default, small) or full (every line).",
"enum": [
"summary",
"full"
],
"title": "Detail",
"type": "string"
}
},
"required": [
"zip",
"bedrooms",
"bathrooms"
],
"title": "QuickEstimateRequest",
"type": "object"
}Output
{
"$defs": {
"CostRange": {
"description": "A low / expected / high estimate. Invariant: low <= expected <= high.",
"properties": {
"low": {
"title": "Low",
"type": "string"
},
"expected": {
"title": "Expected",
"type": "string"
},
"high": {
"title": "High",
"type": "string"
}
},
"required": [
"low",
"expected",
"high"
],
"title": "CostRange",
"type": "object"
},
"Extent": {
"enum": [
"spot",
"partial",
"full"
],
"title": "Extent",
"type": "string"
},
"FindingResolution": {
"properties": {
"finding_id": {
"title": "Finding Id",
"type": "string"
},
"resolution": {
"enum": [
"exact",
"alias",
"unmapped"
],
"title": "Resolution",
"type": "string"
},
"code": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Code"
},
"issue_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Readable name of `code`.",
"title": "Issue Name"
},
"text": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "The finding as given (description, else code), for tracing.",
"title": "Text"
},
"location": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Where, as shown on the lines.",
"title": "Location"
},
"match_note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Set when the match left a fixture word unexplained: check this finding.",
"title": "Match Note"
},
"uncertain": {
"default": false,
"description": "The description hedges ('mold?', 'maybe', 'possible'). Refer-outs from uncertain findings are check-first: they add a warning instead of holding phases.",
"title": "Uncertain",
"type": "boolean"
},
"extent": {
"anyOf": [
{
"$ref": "#/$defs/Extent"
},
{
"type": "null"
}
]
},
"suggested_codes": {
"default": [],
"items": {
"type": "string"
},
"title": "Suggested Codes",
"type": "array"
},
"suggestions": {
"description": "Suggested codes with readable names, best first.",
"items": {
"$ref": "#/$defs/Suggestion"
},
"title": "Suggestions",
"type": "array"
},
"line_ids": {
"default": [],
"items": {
"type": "string"
},
"title": "Line Ids",
"type": "array"
}
},
"required": [
"finding_id",
"resolution",
"code",
"extent"
],
"title": "FindingResolution",
"type": "object"
},
"Note": {
"properties": {
"code": {
"title": "Code",
"type": "string"
},
"message": {
"title": "Message",
"type": "string"
},
"finding_ids": {
"default": [],
"items": {
"type": "string"
},
"title": "Finding Ids",
"type": "array"
},
"line_ids": {
"default": [],
"items": {
"type": "string"
},
"title": "Line Ids",
"type": "array"
}
},
"required": [
"code",
"message"
],
"title": "Note",
"type": "object"
},
"Phase": {
"description": "Make-ready order. Values sort in sequence order via PHASE_ORDER.",
"enum": [
"assess",
"trash_out",
"repairs",
"paint",
"flooring",
"appliances",
"cleaning",
"punch",
"rekey"
],
"title": "Phase",
"type": "string"
},
"PriceBasis": {
"enum": [
"rate_card",
"unit_price",
"labor_derived",
"placeholder"
],
"title": "PriceBasis",
"type": "string"
},
"PriceTier": {
"description": "Purchasing channel. Library prices are pm_vendor (a PM buying turns from a local vendor).",
"enum": [
"bulk_contract",
"pm_vendor",
"retail_one_off"
],
"title": "PriceTier",
"type": "string"
},
"Priority": {
"enum": [
"must",
"should",
"could"
],
"title": "Priority",
"type": "string"
},
"Profile": {
"enum": [
"minimum",
"standard",
"premium"
],
"title": "Profile",
"type": "string"
},
"Provenance": {
"properties": {
"ruleset_id": {
"title": "Ruleset Id",
"type": "string"
},
"ruleset_version": {
"title": "Ruleset Version",
"type": "string"
},
"ruleset_hash": {
"title": "Ruleset Hash",
"type": "string"
},
"sources": {
"items": {
"$ref": "#/$defs/SourceRef"
},
"title": "Sources",
"type": "array"
}
},
"required": [
"ruleset_id",
"ruleset_version",
"ruleset_hash",
"sources"
],
"title": "Provenance",
"type": "object"
},
"RateCardUse": {
"properties": {
"entry": {
"description": "0-based index into the request's rate_card.",
"title": "Entry",
"type": "integer"
},
"target": {
"description": "The entry's task_id or trade.",
"title": "Target",
"type": "string"
},
"lines": {
"default": [],
"items": {
"type": "string"
},
"title": "Lines",
"type": "array"
},
"note": {
"title": "Note",
"type": "string"
}
},
"required": [
"entry",
"target",
"note"
],
"title": "RateCardUse",
"type": "object"
},
"ReferOutNote": {
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"professional": {
"title": "Professional",
"type": "string"
},
"reason": {
"title": "Reason",
"type": "string"
},
"kind": {
"enum": [
"assessment",
"compliance"
],
"title": "Kind",
"type": "string"
},
"finding_ids": {
"items": {
"type": "string"
},
"title": "Finding Ids",
"type": "array"
},
"blocks_phases": {
"items": {
"$ref": "#/$defs/Phase"
},
"title": "Blocks Phases",
"type": "array"
},
"sources": {
"items": {
"type": "string"
},
"title": "Sources",
"type": "array"
},
"unconfirmed": {
"default": false,
"description": "Every triggering finding was hedged ('maybe', '?'): check first; no phase is held until the condition is confirmed (see warnings).",
"title": "Unconfirmed",
"type": "boolean"
}
},
"required": [
"id",
"name",
"professional",
"reason",
"kind",
"finding_ids",
"blocks_phases",
"sources"
],
"title": "ReferOutNote",
"type": "object"
},
"Region": {
"properties": {
"zip3": {
"title": "Zip3",
"type": "string"
},
"basis": {
"enum": [
"metro",
"nonmetro",
"national"
],
"title": "Basis",
"type": "string"
},
"cbsa": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Cbsa"
},
"cbsa_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Cbsa Name"
},
"vintage": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Vintage"
}
},
"required": [
"zip3",
"basis"
],
"title": "Region",
"type": "object"
},
"Responsibility": {
"enum": [
"normal_wear",
"likely_damage",
"elective",
"undetermined",
"mixed"
],
"title": "Responsibility",
"type": "string"
},
"ScopeLine": {
"properties": {
"line_id": {
"title": "Line Id",
"type": "string"
},
"task_id": {
"title": "Task Id",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"trade": {
"$ref": "#/$defs/Trade"
},
"phase": {
"$ref": "#/$defs/Phase"
},
"priority": {
"$ref": "#/$defs/Priority"
},
"location": {
"title": "Location",
"type": "string"
},
"finding_ids": {
"items": {
"type": "string"
},
"title": "Finding Ids",
"type": "array"
},
"quantity": {
"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
"title": "Quantity",
"type": "string"
},
"uom": {
"$ref": "#/$defs/Uom"
},
"quantity_source": {
"enum": [
"given",
"converted",
"derived",
"fixed"
],
"title": "Quantity Source",
"type": "string"
},
"price_basis": {
"$ref": "#/$defs/PriceBasis"
},
"unit_price": {
"$ref": "#/$defs/CostRange"
},
"cost": {
"$ref": "#/$defs/CostRange"
},
"confidence": {
"enum": [
"sourced",
"industry_typical",
"assumed"
],
"title": "Confidence",
"type": "string"
},
"responsibility_hint": {
"$ref": "#/$defs/Responsibility"
},
"responsibility_by_finding": {
"additionalProperties": {
"$ref": "#/$defs/Responsibility"
},
"description": "Set when the hint is 'mixed': the hint of each finding the line carries.",
"title": "Responsibility By Finding",
"type": "object"
},
"allowance": {
"anyOf": [
{
"$ref": "#/$defs/CostRange"
},
{
"type": "null"
}
],
"default": null,
"description": "Quote-required lines only: a non-binding budget range. NOT included in any total; summed separately in totals.allowances."
},
"refer_out": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Refer Out"
},
"decision": {
"anyOf": [
{
"enum": [
"approve",
"decline",
"defer"
],
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Decision"
},
"vendor_note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Vendor Note"
},
"rule_ids": {
"items": {
"type": "string"
},
"title": "Rule Ids",
"type": "array"
}
},
"required": [
"line_id",
"task_id",
"name",
"trade",
"phase",
"priority",
"location",
"finding_ids",
"quantity",
"uom",
"quantity_source",
"price_basis",
"unit_price",
"cost",
"confidence",
"responsibility_hint",
"rule_ids"
],
"title": "ScopeLine",
"type": "object"
},
"SourceRef": {
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"title": {
"title": "Title",
"type": "string"
},
"url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Url"
}
},
"required": [
"id",
"title"
],
"title": "SourceRef",
"type": "object"
},
"Suggestion": {
"properties": {
"code": {
"title": "Code",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
}
},
"required": [
"code",
"name"
],
"title": "Suggestion",
"type": "object"
},
"Totals": {
"properties": {
"note": {
"default": "must, should and could are work lines only. visit_charges (minimum call-out and trip fees, once per trade) are added on top: subtotal = must + should + could + visit_charges; total = subtotal + tax. allowances are outside the total.",
"description": "How the totals add up.",
"title": "Note",
"type": "string"
},
"must": {
"$ref": "#/$defs/CostRange"
},
"should": {
"$ref": "#/$defs/CostRange"
},
"could": {
"$ref": "#/$defs/CostRange"
},
"visit_charges": {
"$ref": "#/$defs/CostRange"
},
"subtotal": {
"$ref": "#/$defs/CostRange"
},
"tax": {
"$ref": "#/$defs/CostRange"
},
"total": {
"$ref": "#/$defs/CostRange"
},
"approved": {
"anyOf": [
{
"$ref": "#/$defs/CostRange"
},
{
"type": "null"
}
],
"default": null
},
"allowances": {
"$ref": "#/$defs/CostRange",
"description": "Budget allowance for quote-required lines (refer-outs, manual quotes). Shown outside the totals: subtotal and total never include it."
}
},
"required": [
"must",
"should",
"could",
"visit_charges",
"subtotal",
"tax",
"total"
],
"title": "Totals",
"type": "object"
},
"Trade": {
"enum": [
"hauling",
"handyman",
"drywall",
"painter",
"flooring",
"carpet_cleaning",
"cleaning",
"appliance",
"plumber",
"electrician",
"hvac",
"locksmith",
"pest_control",
"landscaping",
"window_glass",
"environmental",
"structural"
],
"title": "Trade",
"type": "string"
},
"TradeScope": {
"properties": {
"trade": {
"$ref": "#/$defs/Trade"
},
"vendor": {
"anyOf": [
{
"$ref": "#/$defs/Vendor"
},
{
"type": "null"
}
]
},
"phase": {
"$ref": "#/$defs/Phase"
},
"line_ids": {
"items": {
"type": "string"
},
"title": "Line Ids",
"type": "array"
},
"lines_cost": {
"$ref": "#/$defs/CostRange"
},
"visit_charges": {
"$ref": "#/$defs/CostRange"
},
"total": {
"$ref": "#/$defs/CostRange"
}
},
"required": [
"trade",
"vendor",
"phase",
"line_ids",
"lines_cost",
"visit_charges",
"total"
],
"title": "TradeScope",
"type": "object"
},
"Uom": {
"enum": [
"each",
"sq_ft",
"sq_ft_floor",
"sq_yd",
"linear_ft",
"room",
"unit",
"bedroom",
"bathroom",
"hour",
"cu_yd",
"visit"
],
"title": "Uom",
"type": "string"
},
"Vendor": {
"additionalProperties": false,
"properties": {
"name": {
"maxLength": 120,
"title": "Name",
"type": "string"
},
"contact": {
"anyOf": [
{
"maxLength": 200,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Contact"
}
},
"required": [
"name"
],
"title": "Vendor",
"type": "object"
}
},
"description": "Field order puts the answer first (summary, disclaimer, totals, refer-outs, vendor\nscopes), because agents and truncating clients read from the top.",
"properties": {
"summary": {
"title": "Summary",
"type": "string"
},
"disclaimer": {
"title": "Disclaimer",
"type": "string"
},
"warnings": {
"items": {
"$ref": "#/$defs/Note"
},
"title": "Warnings",
"type": "array"
},
"totals": {
"$ref": "#/$defs/Totals"
},
"refer_outs": {
"items": {
"$ref": "#/$defs/ReferOutNote"
},
"title": "Refer Outs",
"type": "array"
},
"trade_scopes": {
"items": {
"$ref": "#/$defs/TradeScope"
},
"title": "Trade Scopes",
"type": "array"
},
"lines": {
"items": {
"$ref": "#/$defs/ScopeLine"
},
"title": "Lines",
"type": "array"
},
"lines_omitted": {
"description": "Lines left out in summary detail.",
"title": "Lines Omitted",
"type": "integer"
},
"omitted_note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Summary detail: what the omitted lines are (routine turn tasks) and their cost.",
"title": "Omitted Note"
},
"findings": {
"items": {
"$ref": "#/$defs/FindingResolution"
},
"title": "Findings",
"type": "array"
},
"assumptions": {
"items": {
"$ref": "#/$defs/Note"
},
"title": "Assumptions",
"type": "array"
},
"unknowns": {
"items": {
"$ref": "#/$defs/Note"
},
"title": "Unknowns",
"type": "array"
},
"rate_card_applied": {
"description": "Rate card entries that changed a price.",
"items": {
"$ref": "#/$defs/RateCardUse"
},
"title": "Rate Card Applied",
"type": "array"
},
"rate_card_ignored": {
"description": "Rate card entries that changed nothing, with why.",
"items": {
"$ref": "#/$defs/RateCardUse"
},
"title": "Rate Card Ignored",
"type": "array"
},
"detail": {
"enum": [
"summary",
"full"
],
"title": "Detail",
"type": "string"
},
"region": {
"$ref": "#/$defs/Region"
},
"profile": {
"$ref": "#/$defs/Profile"
},
"price_tier": {
"$ref": "#/$defs/PriceTier",
"default": "pm_vendor"
},
"provenance": {
"$ref": "#/$defs/Provenance"
}
},
"required": [
"summary",
"disclaimer",
"warnings",
"totals",
"refer_outs",
"trade_scopes",
"lines",
"lines_omitted",
"findings",
"assumptions",
"unknowns",
"detail",
"region",
"profile",
"provenance"
],
"title": "ScopeResult",
"type": "object"
}Scope a property turnover: input and output schemas
Input
{
"$defs": {
"Area": {
"enum": [
"whole_unit",
"kitchen",
"living_room",
"dining_room",
"bedroom",
"bathroom",
"hallway",
"entry",
"laundry",
"closet",
"garage",
"patio_balcony",
"exterior",
"yard",
"other"
],
"title": "Area",
"type": "string"
},
"Constraints": {
"additionalProperties": false,
"properties": {
"target_ready_date": {
"anyOf": [
{
"format": "date",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Target Ready Date"
},
"occupied": {
"default": false,
"description": "Work happens while occupied.",
"title": "Occupied",
"type": "boolean"
},
"access_notes": {
"anyOf": [
{
"maxLength": 500,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Access Notes"
}
},
"title": "Constraints",
"type": "object"
},
"Decision": {
"additionalProperties": false,
"description": "Owner decision. Re-run the same request with decisions to get the approved scope.",
"properties": {
"line_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Line Id"
},
"finding_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Finding Id"
},
"action": {
"enum": [
"approve",
"decline",
"defer"
],
"title": "Action",
"type": "string"
},
"note": {
"anyOf": [
{
"maxLength": 300,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Note"
}
},
"required": [
"action"
],
"title": "Decision",
"type": "object"
},
"Extent": {
"enum": [
"spot",
"partial",
"full"
],
"title": "Extent",
"type": "string"
},
"Finding": {
"additionalProperties": false,
"description": "One observed condition. Give a taxonomy `code` if you know it, else a `description`.",
"properties": {
"id": {
"anyOf": [
{
"maxLength": 40,
"pattern": "^[A-Za-z0-9_.:-]+$",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Your id for this finding; defaults to f1, f2, ... in order.",
"title": "Id"
},
"location": {
"$ref": "#/$defs/Location",
"description": "Object {\"area\": \"bedroom\", \"label\": \"Bedroom 2\"}, or a plain string such as \"Bedroom 2\" or \"kitchen\" (mapped to an area; other text becomes the label)."
},
"code": {
"anyOf": [
{
"maxLength": 80,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional. Only a code returned by property_turnover_issue_codes (e.g. 'walls.nail_holes'); otherwise leave it out and use description.",
"title": "Code"
},
"description": {
"anyOf": [
{
"maxLength": 300,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Plain description if no code, e.g. 'dog urine stains in bedroom carpet'.",
"title": "Description"
},
"extent": {
"anyOf": [
{
"$ref": "#/$defs/Extent"
},
{
"type": "null"
}
],
"default": null,
"description": "spot | partial | full; defaults per issue."
},
"quantity": {
"anyOf": [
{
"$ref": "#/$defs/Quantity"
},
{
"type": "null"
}
],
"default": null,
"description": "Measured amount, e.g. 120 sq_ft. Derived when omitted."
},
"notes": {
"anyOf": [
{
"maxLength": 1000,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Notes"
}
},
"title": "Finding",
"type": "object"
},
"FloorCoverings": {
"additionalProperties": false,
"description": "What is on the floors. Drives the carpet-cleaning baseline and whole-unit carpet sizes.",
"properties": {
"carpet_present": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "Is there wall-to-wall carpet? Omit if unknown: carpet findings then imply carpet; with no carpet findings, whole-unit carpet cleaning is left out and listed as unknown.",
"title": "Carpet Present"
},
"carpet_sq_ft": {
"anyOf": [
{
"maximum": 20000,
"minimum": 0,
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Carpeted floor area. Omit to assume 60% of the unit floor area.",
"title": "Carpet Sq Ft"
}
},
"title": "FloorCoverings",
"type": "object"
},
"Location": {
"additionalProperties": false,
"description": "Where the finding is. Also accepted as a plain string (\"Bedroom 2\", \"kitchen\").",
"properties": {
"area": {
"$ref": "#/$defs/Area",
"default": "whole_unit"
},
"label": {
"anyOf": [
{
"maxLength": 60,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "e.g. 'Bedroom 2'.",
"title": "Label"
}
},
"title": "Location",
"type": "object"
},
"PriceTier": {
"description": "Purchasing channel. Library prices are pm_vendor (a PM buying turns from a local vendor).",
"enum": [
"bulk_contract",
"pm_vendor",
"retail_one_off"
],
"title": "PriceTier",
"type": "string"
},
"Profile": {
"enum": [
"minimum",
"standard",
"premium"
],
"title": "Profile",
"type": "string"
},
"PropertyProfile": {
"additionalProperties": false,
"properties": {
"zip": {
"description": "5-digit US ZIP code (no street address).",
"pattern": "^\\d{5}$",
"title": "Zip",
"type": "string"
},
"unit_type": {
"$ref": "#/$defs/UnitType",
"default": "apartment",
"description": "Single-family homes and townhomes get house-sized defaults (floor area, locks)."
},
"bedrooms": {
"description": "0 for a studio.",
"maximum": 12,
"minimum": 0,
"title": "Bedrooms",
"type": "integer"
},
"bathrooms": {
"anyOf": [
{
"maximum": 12.0,
"minimum": 0.0,
"multipleOf": 0.5,
"type": "number"
},
{
"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
"type": "string"
}
],
"description": "Half baths count as 0.5.",
"title": "Bathrooms"
},
"sq_ft": {
"anyOf": [
{
"maximum": 20000,
"minimum": 150,
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Interior sq ft.",
"title": "Sq Ft"
},
"year_built": {
"anyOf": [
{
"maximum": 2100,
"minimum": 1700,
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Drives lead-paint (pre-1978) handling.",
"title": "Year Built"
},
"floor_coverings": {
"$ref": "#/$defs/FloorCoverings"
}
},
"required": [
"zip",
"bedrooms",
"bathrooms"
],
"title": "PropertyProfile",
"type": "object"
},
"Quantity": {
"additionalProperties": false,
"properties": {
"value": {
"anyOf": [
{
"exclusiveMinimum": 0.0,
"maximum": 100000.0,
"type": "number"
},
{
"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
"type": "string"
}
],
"title": "Value"
},
"uom": {
"$ref": "#/$defs/Uom"
}
},
"required": [
"value",
"uom"
],
"title": "Quantity",
"type": "object"
},
"RateCardEntry": {
"additionalProperties": false,
"description": "Your own price. Examples: {\"trade\": \"handyman\", \"unit_price\": 65, \"uom\": \"hour\"} (an\nhourly labor rate for that trade); {\"trade\": \"painter\", \"min_charge\": 150} (visit minimum);\n{\"task_id\": \"carpet.clean\", \"unit_price\": 0.2, \"uom\": \"sq_ft\"} (task_id from a result line).\nA task_id entry beats a trade entry; both beat the library. The result lists what was\napplied and what was ignored (rate_card_applied / rate_card_ignored).",
"properties": {
"task_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "A task_id from a result line, e.g. 'carpet.clean'.",
"title": "Task Id"
},
"trade": {
"anyOf": [
{
"$ref": "#/$defs/Trade"
},
{
"type": "null"
}
],
"default": null
},
"unit_price": {
"anyOf": [
{
"minimum": 0.0,
"type": "number"
},
{
"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "USD per uom. On a trade entry, uom 'hour' is an hourly labor rate (replaces the labor part of that trade's prices); another uom prices that trade's tasks in it.",
"title": "Unit Price"
},
"uom": {
"anyOf": [
{
"$ref": "#/$defs/Uom"
},
{
"type": "null"
}
],
"default": null,
"description": "Unit your price is quoted in. Required with a trade unit_price. If it differs from a task's uom, the entry is not applied to that task (see rate_card_ignored)."
},
"min_charge": {
"anyOf": [
{
"minimum": 0.0,
"type": "number"
},
{
"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "USD per trade visit.",
"title": "Min Charge"
},
"trip_charge": {
"anyOf": [
{
"minimum": 0.0,
"type": "number"
},
{
"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "USD per trade visit.",
"title": "Trip Charge"
}
},
"title": "RateCardEntry",
"type": "object"
},
"Standards": {
"additionalProperties": false,
"properties": {
"profile": {
"$ref": "#/$defs/Profile",
"default": "standard",
"description": "minimum (rent-ready basics) | standard | premium (higher finish)."
},
"overrides": {
"$ref": "#/$defs/StandardsOverrides"
},
"price_tier": {
"$ref": "#/$defs/PriceTier",
"default": "pm_vendor",
"description": "bulk_contract (portfolio contract prices) | pm_vendor (default: a property manager buying turns from local vendors) | retail_one_off (an owner hiring one contractor)."
}
},
"title": "Standards",
"type": "object"
},
"StandardsOverrides": {
"additionalProperties": false,
"properties": {
"paint_policy": {
"anyOf": [
{
"enum": [
"touch_up",
"full_if_needed",
"always_full"
],
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Paint Policy"
},
"carpet_policy": {
"anyOf": [
{
"enum": [
"clean",
"replace_if_damaged",
"always_replace"
],
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Carpet Policy"
},
"cleaning_policy": {
"anyOf": [
{
"enum": [
"standard",
"deep"
],
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Cleaning Policy"
},
"always_rekey": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"title": "Always Rekey"
},
"markup_pct": {
"anyOf": [
{
"maximum": 100.0,
"minimum": 0.0,
"type": "number"
},
{
"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Management markup applied to every line, %.",
"title": "Markup Pct"
}
},
"title": "StandardsOverrides",
"type": "object"
},
"Trade": {
"enum": [
"hauling",
"handyman",
"drywall",
"painter",
"flooring",
"carpet_cleaning",
"cleaning",
"appliance",
"plumber",
"electrician",
"hvac",
"locksmith",
"pest_control",
"landscaping",
"window_glass",
"environmental",
"structural"
],
"title": "Trade",
"type": "string"
},
"UnitType": {
"enum": [
"apartment",
"single_family",
"townhome",
"condo",
"duplex",
"manufactured"
],
"title": "UnitType",
"type": "string"
},
"Uom": {
"enum": [
"each",
"sq_ft",
"sq_ft_floor",
"sq_yd",
"linear_ft",
"room",
"unit",
"bedroom",
"bathroom",
"hour",
"cu_yd",
"visit"
],
"title": "Uom",
"type": "string"
},
"Vendor": {
"additionalProperties": false,
"properties": {
"name": {
"maxLength": 120,
"title": "Name",
"type": "string"
},
"contact": {
"anyOf": [
{
"maxLength": 200,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Contact"
}
},
"required": [
"name"
],
"title": "Vendor",
"type": "object"
}
},
"additionalProperties": false,
"properties": {
"client_refs": {
"anyOf": [
{
"additionalProperties": {
"type": "string"
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Your own reference ids (e.g. unit_id, job_id). Echoed back, never used.",
"title": "Client Refs"
},
"property": {
"$ref": "#/$defs/PropertyProfile"
},
"findings": {
"items": {
"$ref": "#/$defs/Finding"
},
"maxItems": 200,
"title": "Findings",
"type": "array"
},
"standards": {
"$ref": "#/$defs/Standards"
},
"rate_card": {
"items": {
"$ref": "#/$defs/RateCardEntry"
},
"maxItems": 300,
"title": "Rate Card",
"type": "array"
},
"trade_vendors": {
"additionalProperties": {
"$ref": "#/$defs/Vendor"
},
"propertyNames": {
"$ref": "#/$defs/Trade"
},
"title": "Trade Vendors",
"type": "object"
},
"constraints": {
"$ref": "#/$defs/Constraints"
},
"tax_rate": {
"anyOf": [
{
"maximum": 0.2,
"minimum": 0.0,
"type": "number"
},
{
"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Applied to all lines, e.g. 0.07.",
"title": "Tax Rate"
},
"decisions": {
"items": {
"$ref": "#/$defs/Decision"
},
"maxItems": 500,
"title": "Decisions",
"type": "array"
},
"detail": {
"default": "full",
"description": "summary: totals, trade scopes, every line tied to a finding (capped at 40) plus the largest must-fix/refer-out lines, a one-line note on the routine lines left out, notes, and only findings needing attention (small). full: every line, finding and source.",
"enum": [
"summary",
"full"
],
"title": "Detail",
"type": "string"
}
},
"required": [
"property"
],
"title": "ScopeRequest",
"type": "object"
}Output
{
"$defs": {
"CostRange": {
"description": "A low / expected / high estimate. Invariant: low <= expected <= high.",
"properties": {
"low": {
"title": "Low",
"type": "string"
},
"expected": {
"title": "Expected",
"type": "string"
},
"high": {
"title": "High",
"type": "string"
}
},
"required": [
"low",
"expected",
"high"
],
"title": "CostRange",
"type": "object"
},
"Extent": {
"enum": [
"spot",
"partial",
"full"
],
"title": "Extent",
"type": "string"
},
"FindingResolution": {
"properties": {
"finding_id": {
"title": "Finding Id",
"type": "string"
},
"resolution": {
"enum": [
"exact",
"alias",
"unmapped"
],
"title": "Resolution",
"type": "string"
},
"code": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Code"
},
"issue_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Readable name of `code`.",
"title": "Issue Name"
},
"text": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "The finding as given (description, else code), for tracing.",
"title": "Text"
},
"location": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Where, as shown on the lines.",
"title": "Location"
},
"match_note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Set when the match left a fixture word unexplained: check this finding.",
"title": "Match Note"
},
"uncertain": {
"default": false,
"description": "The description hedges ('mold?', 'maybe', 'possible'). Refer-outs from uncertain findings are check-first: they add a warning instead of holding phases.",
"title": "Uncertain",
"type": "boolean"
},
"extent": {
"anyOf": [
{
"$ref": "#/$defs/Extent"
},
{
"type": "null"
}
]
},
"suggested_codes": {
"default": [],
"items": {
"type": "string"
},
"title": "Suggested Codes",
"type": "array"
},
"suggestions": {
"description": "Suggested codes with readable names, best first.",
"items": {
"$ref": "#/$defs/Suggestion"
},
"title": "Suggestions",
"type": "array"
},
"line_ids": {
"default": [],
"items": {
"type": "string"
},
"title": "Line Ids",
"type": "array"
}
},
"required": [
"finding_id",
"resolution",
"code",
"extent"
],
"title": "FindingResolution",
"type": "object"
},
"Note": {
"properties": {
"code": {
"title": "Code",
"type": "string"
},
"message": {
"title": "Message",
"type": "string"
},
"finding_ids": {
"default": [],
"items": {
"type": "string"
},
"title": "Finding Ids",
"type": "array"
},
"line_ids": {
"default": [],
"items": {
"type": "string"
},
"title": "Line Ids",
"type": "array"
}
},
"required": [
"code",
"message"
],
"title": "Note",
"type": "object"
},
"Phase": {
"description": "Make-ready order. Values sort in sequence order via PHASE_ORDER.",
"enum": [
"assess",
"trash_out",
"repairs",
"paint",
"flooring",
"appliances",
"cleaning",
"punch",
"rekey"
],
"title": "Phase",
"type": "string"
},
"PriceBasis": {
"enum": [
"rate_card",
"unit_price",
"labor_derived",
"placeholder"
],
"title": "PriceBasis",
"type": "string"
},
"PriceTier": {
"description": "Purchasing channel. Library prices are pm_vendor (a PM buying turns from a local vendor).",
"enum": [
"bulk_contract",
"pm_vendor",
"retail_one_off"
],
"title": "PriceTier",
"type": "string"
},
"Priority": {
"enum": [
"must",
"should",
"could"
],
"title": "Priority",
"type": "string"
},
"Profile": {
"enum": [
"minimum",
"standard",
"premium"
],
"title": "Profile",
"type": "string"
},
"Provenance": {
"properties": {
"ruleset_id": {
"title": "Ruleset Id",
"type": "string"
},
"ruleset_version": {
"title": "Ruleset Version",
"type": "string"
},
"ruleset_hash": {
"title": "Ruleset Hash",
"type": "string"
},
"sources": {
"items": {
"$ref": "#/$defs/SourceRef"
},
"title": "Sources",
"type": "array"
}
},
"required": [
"ruleset_id",
"ruleset_version",
"ruleset_hash",
"sources"
],
"title": "Provenance",
"type": "object"
},
"RateCardUse": {
"properties": {
"entry": {
"description": "0-based index into the request's rate_card.",
"title": "Entry",
"type": "integer"
},
"target": {
"description": "The entry's task_id or trade.",
"title": "Target",
"type": "string"
},
"lines": {
"default": [],
"items": {
"type": "string"
},
"title": "Lines",
"type": "array"
},
"note": {
"title": "Note",
"type": "string"
}
},
"required": [
"entry",
"target",
"note"
],
"title": "RateCardUse",
"type": "object"
},
"ReferOutNote": {
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"professional": {
"title": "Professional",
"type": "string"
},
"reason": {
"title": "Reason",
"type": "string"
},
"kind": {
"enum": [
"assessment",
"compliance"
],
"title": "Kind",
"type": "string"
},
"finding_ids": {
"items": {
"type": "string"
},
"title": "Finding Ids",
"type": "array"
},
"blocks_phases": {
"items": {
"$ref": "#/$defs/Phase"
},
"title": "Blocks Phases",
"type": "array"
},
"sources": {
"items": {
"type": "string"
},
"title": "Sources",
"type": "array"
},
"unconfirmed": {
"default": false,
"description": "Every triggering finding was hedged ('maybe', '?'): check first; no phase is held until the condition is confirmed (see warnings).",
"title": "Unconfirmed",
"type": "boolean"
}
},
"required": [
"id",
"name",
"professional",
"reason",
"kind",
"finding_ids",
"blocks_phases",
"sources"
],
"title": "ReferOutNote",
"type": "object"
},
"Region": {
"properties": {
"zip3": {
"title": "Zip3",
"type": "string"
},
"basis": {
"enum": [
"metro",
"nonmetro",
"national"
],
"title": "Basis",
"type": "string"
},
"cbsa": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Cbsa"
},
"cbsa_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Cbsa Name"
},
"vintage": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Vintage"
}
},
"required": [
"zip3",
"basis"
],
"title": "Region",
"type": "object"
},
"Responsibility": {
"enum": [
"normal_wear",
"likely_damage",
"elective",
"undetermined",
"mixed"
],
"title": "Responsibility",
"type": "string"
},
"ScopeLine": {
"properties": {
"line_id": {
"title": "Line Id",
"type": "string"
},
"task_id": {
"title": "Task Id",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"trade": {
"$ref": "#/$defs/Trade"
},
"phase": {
"$ref": "#/$defs/Phase"
},
"priority": {
"$ref": "#/$defs/Priority"
},
"location": {
"title": "Location",
"type": "string"
},
"finding_ids": {
"items": {
"type": "string"
},
"title": "Finding Ids",
"type": "array"
},
"quantity": {
"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
"title": "Quantity",
"type": "string"
},
"uom": {
"$ref": "#/$defs/Uom"
},
"quantity_source": {
"enum": [
"given",
"converted",
"derived",
"fixed"
],
"title": "Quantity Source",
"type": "string"
},
"price_basis": {
"$ref": "#/$defs/PriceBasis"
},
"unit_price": {
"$ref": "#/$defs/CostRange"
},
"cost": {
"$ref": "#/$defs/CostRange"
},
"confidence": {
"enum": [
"sourced",
"industry_typical",
"assumed"
],
"title": "Confidence",
"type": "string"
},
"responsibility_hint": {
"$ref": "#/$defs/Responsibility"
},
"responsibility_by_finding": {
"additionalProperties": {
"$ref": "#/$defs/Responsibility"
},
"description": "Set when the hint is 'mixed': the hint of each finding the line carries.",
"title": "Responsibility By Finding",
"type": "object"
},
"allowance": {
"anyOf": [
{
"$ref": "#/$defs/CostRange"
},
{
"type": "null"
}
],
"default": null,
"description": "Quote-required lines only: a non-binding budget range. NOT included in any total; summed separately in totals.allowances."
},
"refer_out": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Refer Out"
},
"decision": {
"anyOf": [
{
"enum": [
"approve",
"decline",
"defer"
],
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Decision"
},
"vendor_note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Vendor Note"
},
"rule_ids": {
"items": {
"type": "string"
},
"title": "Rule Ids",
"type": "array"
}
},
"required": [
"line_id",
"task_id",
"name",
"trade",
"phase",
"priority",
"location",
"finding_ids",
"quantity",
"uom",
"quantity_source",
"price_basis",
"unit_price",
"cost",
"confidence",
"responsibility_hint",
"rule_ids"
],
"title": "ScopeLine",
"type": "object"
},
"SourceRef": {
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"title": {
"title": "Title",
"type": "string"
},
"url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Url"
}
},
"required": [
"id",
"title"
],
"title": "SourceRef",
"type": "object"
},
"Suggestion": {
"properties": {
"code": {
"title": "Code",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
}
},
"required": [
"code",
"name"
],
"title": "Suggestion",
"type": "object"
},
"Totals": {
"properties": {
"note": {
"default": "must, should and could are work lines only. visit_charges (minimum call-out and trip fees, once per trade) are added on top: subtotal = must + should + could + visit_charges; total = subtotal + tax. allowances are outside the total.",
"description": "How the totals add up.",
"title": "Note",
"type": "string"
},
"must": {
"$ref": "#/$defs/CostRange"
},
"should": {
"$ref": "#/$defs/CostRange"
},
"could": {
"$ref": "#/$defs/CostRange"
},
"visit_charges": {
"$ref": "#/$defs/CostRange"
},
"subtotal": {
"$ref": "#/$defs/CostRange"
},
"tax": {
"$ref": "#/$defs/CostRange"
},
"total": {
"$ref": "#/$defs/CostRange"
},
"approved": {
"anyOf": [
{
"$ref": "#/$defs/CostRange"
},
{
"type": "null"
}
],
"default": null
},
"allowances": {
"$ref": "#/$defs/CostRange",
"description": "Budget allowance for quote-required lines (refer-outs, manual quotes). Shown outside the totals: subtotal and total never include it."
}
},
"required": [
"must",
"should",
"could",
"visit_charges",
"subtotal",
"tax",
"total"
],
"title": "Totals",
"type": "object"
},
"Trade": {
"enum": [
"hauling",
"handyman",
"drywall",
"painter",
"flooring",
"carpet_cleaning",
"cleaning",
"appliance",
"plumber",
"electrician",
"hvac",
"locksmith",
"pest_control",
"landscaping",
"window_glass",
"environmental",
"structural"
],
"title": "Trade",
"type": "string"
},
"TradeScope": {
"properties": {
"trade": {
"$ref": "#/$defs/Trade"
},
"vendor": {
"anyOf": [
{
"$ref": "#/$defs/Vendor"
},
{
"type": "null"
}
]
},
"phase": {
"$ref": "#/$defs/Phase"
},
"line_ids": {
"items": {
"type": "string"
},
"title": "Line Ids",
"type": "array"
},
"lines_cost": {
"$ref": "#/$defs/CostRange"
},
"visit_charges": {
"$ref": "#/$defs/CostRange"
},
"total": {
"$ref": "#/$defs/CostRange"
}
},
"required": [
"trade",
"vendor",
"phase",
"line_ids",
"lines_cost",
"visit_charges",
"total"
],
"title": "TradeScope",
"type": "object"
},
"Uom": {
"enum": [
"each",
"sq_ft",
"sq_ft_floor",
"sq_yd",
"linear_ft",
"room",
"unit",
"bedroom",
"bathroom",
"hour",
"cu_yd",
"visit"
],
"title": "Uom",
"type": "string"
},
"Vendor": {
"additionalProperties": false,
"properties": {
"name": {
"maxLength": 120,
"title": "Name",
"type": "string"
},
"contact": {
"anyOf": [
{
"maxLength": 200,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Contact"
}
},
"required": [
"name"
],
"title": "Vendor",
"type": "object"
}
},
"description": "Field order puts the answer first (summary, disclaimer, totals, refer-outs, vendor\nscopes), because agents and truncating clients read from the top.",
"properties": {
"summary": {
"title": "Summary",
"type": "string"
},
"disclaimer": {
"title": "Disclaimer",
"type": "string"
},
"warnings": {
"items": {
"$ref": "#/$defs/Note"
},
"title": "Warnings",
"type": "array"
},
"totals": {
"$ref": "#/$defs/Totals"
},
"refer_outs": {
"items": {
"$ref": "#/$defs/ReferOutNote"
},
"title": "Refer Outs",
"type": "array"
},
"trade_scopes": {
"items": {
"$ref": "#/$defs/TradeScope"
},
"title": "Trade Scopes",
"type": "array"
},
"lines": {
"items": {
"$ref": "#/$defs/ScopeLine"
},
"title": "Lines",
"type": "array"
},
"lines_omitted": {
"description": "Lines left out in summary detail.",
"title": "Lines Omitted",
"type": "integer"
},
"omitted_note": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Summary detail: what the omitted lines are (routine turn tasks) and their cost.",
"title": "Omitted Note"
},
"findings": {
"items": {
"$ref": "#/$defs/FindingResolution"
},
"title": "Findings",
"type": "array"
},
"assumptions": {
"items": {
"$ref": "#/$defs/Note"
},
"title": "Assumptions",
"type": "array"
},
"unknowns": {
"items": {
"$ref": "#/$defs/Note"
},
"title": "Unknowns",
"type": "array"
},
"rate_card_applied": {
"description": "Rate card entries that changed a price.",
"items": {
"$ref": "#/$defs/RateCardUse"
},
"title": "Rate Card Applied",
"type": "array"
},
"rate_card_ignored": {
"description": "Rate card entries that changed nothing, with why.",
"items": {
"$ref": "#/$defs/RateCardUse"
},
"title": "Rate Card Ignored",
"type": "array"
},
"detail": {
"enum": [
"summary",
"full"
],
"title": "Detail",
"type": "string"
},
"region": {
"$ref": "#/$defs/Region"
},
"profile": {
"$ref": "#/$defs/Profile"
},
"price_tier": {
"$ref": "#/$defs/PriceTier",
"default": "pm_vendor"
},
"provenance": {
"$ref": "#/$defs/Provenance"
}
},
"required": [
"summary",
"disclaimer",
"warnings",
"totals",
"refer_outs",
"trade_scopes",
"lines",
"lines_omitted",
"findings",
"assumptions",
"unknowns",
"detail",
"region",
"profile",
"provenance"
],
"title": "ScopeResult",
"type": "object"
}Look up turnover issue codes: input and output schemas
Input
{
"additionalProperties": false,
"properties": {
"client_refs": {
"anyOf": [
{
"additionalProperties": {
"type": "string"
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Your own reference ids (e.g. unit_id, job_id). Echoed back, never used.",
"title": "Client Refs"
},
"query": {
"anyOf": [
{
"maxLength": 200,
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Keywords to match against codes, names and aliases, e.g. 'pet stain carpet'.",
"title": "Query"
},
"area": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional taxonomy area filter, e.g. 'flooring' or 'walls' (not a room). Call with no arguments to list every area in `areas`.",
"title": "Area"
},
"limit": {
"default": 8,
"maximum": 200,
"minimum": 1,
"title": "Limit",
"type": "integer"
},
"include_aliases": {
"default": false,
"description": "Also return each code's aliases (larger result).",
"title": "Include Aliases",
"type": "boolean"
}
},
"title": "IssueCodesRequest",
"type": "object"
}Output
{
"$defs": {
"IssueCode": {
"properties": {
"code": {
"description": "Stable code, component.issue, e.g. 'carpet.pet_urine'.",
"title": "Code",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"area": {
"title": "Area",
"type": "string"
},
"aliases": {
"default": [],
"items": {
"type": "string"
},
"title": "Aliases",
"type": "array"
}
},
"required": [
"code",
"name",
"area"
],
"title": "IssueCode",
"type": "object"
}
},
"properties": {
"ruleset_version": {
"title": "Ruleset Version",
"type": "string"
},
"total_matches": {
"title": "Total Matches",
"type": "integer"
},
"codes": {
"items": {
"$ref": "#/$defs/IssueCode"
},
"title": "Codes",
"type": "array"
},
"areas": {
"description": "Every taxonomy area, for the `area` filter (e.g. 'flooring', 'walls').",
"items": {
"type": "string"
},
"title": "Areas",
"type": "array"
}
},
"required": [
"ruleset_version",
"total_matches",
"codes"
],
"title": "IssueCodesResult",
"type": "object"
}Accuracy and rules
- Coverage: US
- Determinism: strict
- Contract version: 0.3.0 (experimental)
- How it works: pipeline, counts, confidence mix and every source.
- Ruleset: pt-2026.10.2, hash
sha256:44f837d84a49ffc28782a6c7b0a3c4156013a4699f13f6322fec3ece055eae83. Every result is stamped with these, so a result can be reproduced exactly.
Sources cited by the ruleset (191)
- NSPIRE Standards index (per-standard PDFs, changelogs) (HUD REAC)
- NSPIRE Terms and Definitions (HUD REAC)
- NSPIRE individual standards v3.0 (2023-06-20 and 2023-08-11) (HUD REAC)
- NSPIRE final rule, 88 FR 30442 (2023-05-11) (HUD / Federal Register)
- NSPIRE scoring notice (FR 2023-13293) and NLIHC summary (HUD / NLIHC)
- Notice PIH 2026-18, NSPIRE administrative procedures for voucher programs (2026-07-15) (HUD PIH)
- NSPIRE compliance date extension for HCV/PBV/Mod Rehab to 2027-02-01 (FR 2025-19070) (HUD / Federal Register)
- 24 CFR 982.404 Maintenance: owner and family responsibility (eCFR via Cornell LII)
- HCV Landlord Responsibility: Expected Wear and Tear flyer (2026) (Rhode Island Housing)
- OEWS Data Tables / download page (BLS)
- Occupational Employment and Wages, May 2025 (released 2026-05-15) (BLS)
- OEWS FAQ and methodology (BLS)
- BLS Public Data API (OEWS series) (BLS)
- OEWS notice: new metropolitan area definitions (OMB Bulletin 23-01) from May 2024 (BLS)
- Employer Costs for Employee Compensation, June 2026 (released 2026-09-09) (BLS)
- EPI note on ECEC compensation-to-pay ratios (Economic Policy Institute)
- HUD-USPS ZIP Code Crosswalk Files (HUD PD&R)
- Census 2020 ZCTA-to-county relationship file (U.S. Census Bureau)
- OMB Bulletin 23-01 CBSA delineation (list1_2023.xlsx) (OMB / U.S. Census Bureau)
- Regional Price Parities by State and Metro Area (BEA)
- UFS 3-701-01 DoD Facilities Pricing Guide (6 June 2026) (DoD / WBDG)
- Contractor profit and overhead references ('10 and 10') (Levelset)
- Study of Life Expectancy of Home Components (2007) (NAHB Economics Group)
- Appendix F Estimated Useful Life Tables, Form 4099.F (Aug 2019) (Fannie Mae Multifamily)
- IRS Publication 946 (2025) How to Depreciate Property (IRS)
- Security Deposit Refunds, Residential Leases, California: Basic Law (Stimmel Law)
- What is Normal Wear and Tear? A California Landlord's Guide (Bay Legal PC)
- California Civil Code 1950.5 (California Legislature)
- Texas Property Code 92.103 (Texas Legislature)
- Florida Statutes 83.49 (Florida Legislature)
- New York General Obligations Law 7-108(e) (NY Senate)
- North Carolina G.S. 42-52 (NC General Assembly)
- Georgia O.C.G.A. 44-7-34 (Georgia General Assembly)
- Arizona A.R.S. 33-1321 (Arizona Legislature)
- Washington RCW 59.18.280 (Washington Legislature)
- Apartment Turnover guidance (2025-03-06) (National Apartment Association)
- Crunching the Numbers on Turnover Costs (2016-10-24) (National Apartment Association)
- Move-Out Make Ready: Repairs, Paint, Flooring & Rekeying Work Orders (Treaty Oak Property Management)
- RFP Vacant Unit Turns (2025-11) (Raleigh Housing Authority)
- RFP 24-0318 Make Ready/Vacant Unit Turnover (Housing Authority of Kansas City, MO)
- Re-Bid RFP Vacant Unit Turn Services (2026-03-16) (Wilmington Housing Authority)
- 2026 Multifamily Turn Economics Report (Rent Ready)
- How Much Does a Make-Ready Cost? 2026 Benchmarks (Lula)
- Apartment Make Ready Checklist: Unit Turn Guide 2026 (Dropcurb)
- What is apartment make ready for rent, and what does it cost? (Los Angeles price list) (TurnOver LA)
- What will be the cost to turn a unit on average? (practitioner thread) (Multifamily Insiders)
- How much should you charge for apartment turnovers? (2016) (Cleaning-4-Profit)
- Apartment Turnover Painting Cost Guide for Property Managers (Advanced Painting and Drywall)
- Rental Property Turnover Painting: A Landlord's Guide (2026) (Paint Techs)
- How to Create Profitable Estimates, Part 2: Calculating your Rates (EstimateRocket)
- Introduction to Production Rate Estimation (PaintScout)
- zInspector / AppFolio integration (zInspector)
- HappyCo Inspections support (HappyCo)
- Streamline unit turns (AppFolio Unit Turn Board) (AppFolio)
- Create a service request for Buildium (Propexo) (Propexo)
- Vendor Portal Guide (AppFolio vendor work orders) (Green Residential)
- A Brief Guide to Mold, Moisture and Your Home (U.S. EPA)
- 40 CFR 745.82, 745.83, 745.85, 745.103 (EPA Renovation, Repair and Painting rule) (eCFR via Cornell LII)
- Renovation, Repair and Painting Program (U.S. EPA)
- Protect Your Family From Exposures to Asbestos (U.S. EPA)
- Learn About Asbestos (U.S. EPA)
- Lead-based paint disclosure rule for pre-1978 housing (40 CFR Part 745 Subpart F) (EPA / HUD)
- NFPA 72 smoke-alarm replacement and placement (secondary report) (Security Sales & Integration)
- B10 Emergency Escape and Rescue Openings (IRC R310) (Boulder County Building Dept.)
- What Should You Do If You Smell Natural Gas? (CenterPoint Energy)
- Condition Values: The N, S, D, & ! of Property Inspection (zInspector)
- Rental Property Move-Out Inspection Form with Security Deposit Calculator (Paperform)
- Propexo service-request models for Buildium, AppFolio and generic PMS (Propexo)
- Interior painting cost (2026) (HomeGuide)
- Paint a ceiling (HomeGuide)
- Paint a door (HomeGuide)
- Paint trim and baseboards (HomeGuide)
- Paint kitchen cabinets (HomeGuide)
- Drywall repair (HomeGuide)
- Wallpaper removal (HomeGuide)
- Popcorn ceiling removal (HomeGuide)
- Ceiling repair (HomeGuide)
- Carpet cleaning (2026 prices) (HomeGuide)
- Carpet installation (2026) (HomeGuide)
- Carpet removal (2026) (HomeGuide)
- Carpet padding (2026) (HomeGuide)
- Carpet repair (HomeGuide)
- Carpet stretching (HomeGuide)
- Carpet prices per square foot (HomeGuide)
- Rent a carpet cleaner (HomeGuide)
- Vinyl flooring installation (HomeGuide)
- Luxury vinyl plank/tile flooring (HomeGuide)
- Laminate flooring installation (HomeGuide)
- Refinish hardwood floors (HomeGuide)
- Hardwood floor repair (HomeGuide)
- Tile and grout cleaning (HomeGuide)
- Regrout shower or bathroom (HomeGuide)
- Move-out cleaning (HomeGuide)
- House cleaning prices (HomeGuide)
- Apartment cleaning prices (HomeGuide)
- Deep cleaning a house (HomeGuide)
- Window cleaning prices (HomeGuide)
- Pressure washing prices (HomeGuide)
- Dryer vent cleaning (HomeGuide)
- Junk removal prices (HomeGuide)
- Dumpster rental prices (HomeGuide)
- Construction debris removal (HomeGuide)
- Mattress removal (HomeGuide)
- Cost to change locks (rekey/replace) (HomeGuide)
- Locksmith cost (HomeGuide)
- Door knob replacement (HomeGuide)
- Door repair (HomeGuide)
- Interior door installation (HomeGuide)
- Closet door installation (HomeGuide)
- Window screen replacement (HomeGuide)
- Window repair (HomeGuide)
- Blinds installation cost (HomeGuide)
- Window treatments (HomeGuide)
- Bathtub repair (incl. caulking) (HomeGuide)
- Bathtub refinishing (HomeGuide)
- Toilet installation (HomeGuide)
- Toilet repair (HomeGuide)
- Bathroom faucet installation (HomeGuide)
- Garbage disposal installation (HomeGuide)
- Garbage disposal repair (HomeGuide)
- Cabinet repair (HomeGuide)
- Replace cabinet doors (HomeGuide)
- Install cabinet hardware (HomeGuide)
- Laminate countertops (HomeGuide)
- Replace bathroom countertop (HomeGuide)
- Appliance installation costs (HomeGuide)
- Dishwasher installation (HomeGuide)
- Oven or stove cost (HomeGuide)
- Refrigerator cost (HomeGuide)
- Appliance repair costs (HomeGuide)
- Water heater installation (HomeGuide)
- Install electrical outlet (HomeGuide)
- GFCI outlet (HomeGuide)
- Install or replace a light switch (HomeGuide)
- Replace or repair a light fixture (HomeGuide)
- Ceiling fan installation (HomeGuide)
- Smoke detector installation (HomeGuide)
- HVAC repair (HomeGuide)
- Pest control prices (HomeGuide)
- Cockroach exterminator (HomeGuide)
- Bed bug exterminator (HomeGuide)
- Mice exterminator (HomeGuide)
- 2026 Handyman Hourly Rates (HomeGuide) (HomeGuide)
- Electrician Cost Per Hour (HomeGuide, 2026) (HomeGuide)
- 2026 Plumber Cost Per Hour (HomeGuide) (HomeGuide)
- Lawn mowing cost (HomeGuide)
- Lawn care prices (HomeGuide)
- Yard cleanup (HomeGuide)
- Landscaping costs (HomeGuide)
- Mold removal (HomeGuide)
- Mold inspection (HomeGuide)
- Lead paint removal (HomeGuide)
- Asbestos removal (HomeGuide)
- Smoke remediation (HomeGuide)
- Hoarding cleanup (HomeGuide)
- Biohazard cleanup (HomeGuide)
- Baseboard installation (HomeGuide)
- Air duct cleaning (HomeGuide)
- NSPIRE Standard: Wall - Interior (v3.0, 2023-08-11) (HUD REAC)
- NSPIRE Standard: Smoke Alarm (v3.0, 2023-08-11) (HUD REAC)
- NSPIRE Standard: Mold-like Substance (v3.0, 2023-08-11) (HUD REAC)
- QuietOps modelling assumption (QuietOps)
- Apartment painting cost LA: real prices and timelines (TurnOver LA, 2026-07-29) (TurnOver LA)
- Apartment Painting in Coeur d'Alene: Multifamily Costs (2026-09-21) (Coeur d'Alene Painters)
- Multifamily interior painting in Atlanta (Total Apartment Solutions) (Total Apartment Solutions)
- Asbury Park HA, RFP Vacancy Prep Unit Turnover (2026) (Asbury Park Housing Authority)
- Microwave Installation Cost (HomeGuide, 2026) (HomeGuide)
- Home Appliance Prices (HomeGuide, 2026) (HomeGuide)
- Asbestos Testing Cost (HomeGuide) (HomeGuide)
- Junk Removal Cost (HomeAdvisor, 2025) (HomeAdvisor)
- How much will it cost contractors to comply with the RRP rule? (EPA, archived) (US EPA)
- Window repair vs removal under RRP (EPA FAQ) (US EPA)
- OSHA asbestos standard for construction (significant changes; PACM) (OSHA)
- NSPIRE Standards v2.2, HVAC (HUD REAC)
- Carbon Monoxide Detector Requirements (NCSL) (NCSL)
- Free-Standing Kitchen Ranges (CPSC) (US CPSC)
- Texas Property Code § 92.156, Rekeying (FindLaw (Texas statute))
- Cal. Civ. Code § 1984 (abandoned property notice) (California Public Law)
- U.S. average apartment size (RentCafe) (RentCafe)
- OEWS metropolitan and nonmetropolitan area definitions (May 2024 county list, via the Internet Archive) (US BLS)
- Regional Price Parities 2024 (BEA, Feb 2026) (US BEA)
- Grand Rapids Housing Commission, Res. 24-43 painting per unit (board minutes 2024-11-19) (Grand Rapids Housing Commission)
- Grand Rapids Housing Commission, Res. 25-79 unit-turn hourly labor rates (2026-03-17) (Grand Rapids Housing Commission)
- San Antonio HA, prior bid tab Unit Make Ready Paint (2020) (San Antonio Housing Authority)
- Corpus Christi HA, RFP P2006 Unit Make Ready Services (2020) (Corpus Christi Housing Authority)
- Calvert County ITB 2025-023 interior painting bid comparison (Calvert County Government)
- Fort Bend County B25-040 carpet supply and install, signed contract (Fort Bend County Purchasing)
- SC statewide painting GS-FPB-230213-02, intent to award (SC Materials Management Office)
- Iowa DAS (Iowa PBS) commercial janitorial contract price sheet (Iowa DAS)
- Nebraska DAS awarded bid tab, North Platte custodial services (State of Nebraska DAS)
- Carpet-cleaning per-sq-ft bid tabs, five agencies (research 09) (QuietOps research 09)
Limitations
- Estimate ranges, not quotes. Prices vary by vendor, market and condition.
- Does not diagnose structural, electrical, gas, mold, asbestos or lead hazards; those are referred to licensed professionals.
- Responsibility labels (wear vs damage) are hints, not legal determinations; deposit rules vary by state.
Results are estimate ranges for planning, not quotes or professional advice. Read the full disclaimer.
Privacy
- Estimates: We keep an anonymized record of estimates (ZIP3, unit size, findings and totals; never your notes, address, IP or contact details) to improve accuracy.
- Retention: 730 days.
- Used to train models: no
- Personal data: Inputs need a ZIP code, not an address. Request bodies and free-text notes are never logged or stored. Each estimate leaves one anonymized record: first 3 digits of the ZIP, size and age in coarse buckets, issue codes, totals and a daily-rotating anonymous client fingerprint; no notes, labels, names, vendors, rate-card prices, IP address or user agent. Descriptions of issues the taxonomy could not match are kept with digits, emails and links removed. These records are deleted 730 days after they are written and are not used to train models. Optional feedback (a rating, a note, an email only if typed) is stored separately, with a few estimate identifiers (hashes, versions, region, total) but never the request or notes.
Use it from your AI assistant
Every tool is also an MCP tool. Add this URL as a remote MCP server; the free tier needs no sign-in.
https://tektolo.com/mcp
- Claude: Settings, Connectors, Add custom connector, then paste the URL above.
- ChatGPT: enable developer mode, then add a custom MCP server with the URL above (menu names change; see OpenAI's connector docs).
- Claude Code:
claude mcp add --transport http quietops https://tektolo.com/mcp - Other MCP clients: streamable HTTP transport, URL as above, no headers. Connection metadata: server card.
Request an improvement
Something missing, wrong or confusing in Property Turnover Scope? Tell us.