# Properties

22 tools. Generated from the MCP server: names, inputs and prices match the code.

Source: https://leverage.immo/docs/tools/properties/

## properties_completeness_get

**Check Property Completeness** · Read · Free

Advisory data-room completeness for a property: which required/recommended documents are fulfilled, missing, outdated, or too few (status insufficient_count), plus a score and per-tenancy rental-contract checks. Each requirement carries the evidence for its status (matched_dates, newest_date, counted_in_window) and the top-level document_labels maps document ids to file names, so say why a row is not fulfilled. Advisory; never blocks.

REST: `GET /api/v1/properties/{id}/completeness`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `property_id` | `string` | yes |  |

## properties_create

**Create Property** · Write · 1 API credit

Create a property (calculation): a deal the user can evaluate. With deal_id alone it promotes that imported listing: calculation_strategy/property_type are derived when omitted, repeat calls safely reuse the existing property (reused=true) unless force_create_new is set, and allow_incomplete (+ placeholder_address) promotes deals still missing price, area, or address; gaps come back in fields_needing_review with screening_ready=false. Without deal_id, send calculation_strategy, property_type, address, purchase_price, living_area. Financing and assumptions come from search_profile_id when set (its overrides on the defaults), else from the calculation defaults of its strategy (calculation_defaults_get). interest_rate, repayment_rate and loan_amount_percentage are decimal fractions (0.04 = 4 % per year, 0.8 = 80 % of the purchase price), never percentage points. Costs 1 API credit when it creates a property, 0 when reused=true. The Free plan caps new properties per calendar month (403 calculation_limit_reached); a reused property never counts.

Price:

- 1 API credit: when it creates a property
- 0 API credits: returns an existing property (`reused: true`)

REST: `POST /api/v1/properties`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `deal_id` | `string` | no |  |
| `calculation_strategy` | `string` | no | Values: `buy_and_hold`, `fix_and_flip`, `own_use`, `special_rental` |
| `property_type` | `string` | no | Values: `multi_family_house`, `house`, `apartment` |
| `purchase_price` | `number` | no |  |
| `living_area` | `number` | no |  |
| `address` | `object` | no |  |
| `interest_rate` | `number` | no |  |
| `repayment_rate` | `number` | no |  |
| `loan_amount_percentage` | `number` | no |  |
| `search_profile_id` | `integer` | no | One of your search profiles: its calculation assumptions (else your calculation defaults) seed the numbers. Its strategy, when it has one, must match calculation_strategy. |
| `force_create_new` | `boolean` | no |  |
| `allow_incomplete` | `boolean` | no |  |
| `placeholder_address` | `boolean` | no |  |
| `created_via` | `string` | no | Values: `human`, `nora_agent`, `routine`, `leveragedd`, `sales_push` |
| `external_ref` | `string \| null` | no |  |
| `unit_number` | `string \| null` | no |  |
| `floor_number` | `integer \| null` | no |  |
| `building_year` | `integer \| null` | no |  |
| `number_of_indoor_parking_spaces` | `integer \| null` | no |  |
| `number_of_outdoor_parking_spaces` | `integer \| null` | no |  |
| `number_of_rooms` | `number \| null` | no |  |
| `number_of_units` | `integer \| null` | no |  |
| `number_of_bathrooms` | `integer \| null` | no |  |
| `energy_label` | `string \| null` | no | Values: `a_plus_plus_plus`, `a_plus_plus`, `a_plus`, `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h` |
| `is_leasehold` | `boolean \| null` | no |  |
| `land_registry_court` | `string \| null` | no |  |
| `land_registry_district` | `string \| null` | no |  |
| `land_registry_sheet` | `string \| null` | no |  |
| `cadastral_sector` | `string \| null` | no |  |
| `cadastral_parcel` | `string \| null` | no |  |
| `co_ownership_share` | `number \| null` | no |  |
| `co_ownership_share_total` | `number \| null` | no |  |

## properties_delete

**Delete Property** · Write · Destructive · Free

Delete a property (calculation) by id.

REST: `DELETE /api/v1/properties/{id}`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes |  |

## properties_document_matches_apply

**Apply Document Matches** · Write · Destructive · Free

Adopt values a document asserts onto the property. Pass document_id plus the keys[] the user accepted (from properties_document_matches_get), and/or contact_keys[] from contact_suggestions to create the named Verwalter/Notar/Handwerker as a contact and link it to the property in that role, and/or create_tenancy to create the tenancy a rental contract describes, with a tenant contact for every tenant it names (a couple or a WG is one tenancy). Nothing is adopted automatically: show the user the value-vs-value rows and get their explicit confirmation before calling this. Safe to retry: keys already satisfied come back in unchanged_keys and tenancy creation is idempotent per tenant name. A key that is no longer offered (the document was re-extracted, or the property changed) is rejected rather than overwriting newer data. If properties_document_matches_get flagged the document with a unit_mismatch warning (a lease for another flat), nothing is written until you pass acknowledge_unit_mismatch: true, which you may only do after the user confirmed the document belongs to this property; usually the right fix is properties_documents_unlink.

REST: `POST /api/v1/properties/{id}/document-matches/apply`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `property_id` | `string` | yes |  |
| `document_id` | `string` | yes |  |
| `keys` | `array<string>` | no | Values: `address`, `purchase_price`, `living_area`, `building_year`, `number_of_rooms`, `number_of_units`, `unit_number`, `energy_class`, `is_leasehold`, `co_ownership_share`, `co_ownership_share_total`, `land_registry_court`, `land_registry_district`, `land_registry_sheet`, `cadastral_sector`, `cadastral_parcel`, `rent_net` |
| `contact_keys` | `array<string>` | no |  |
| `create_tenancy` | `boolean` | no |  |
| `unit_id` | `string` | no |  |
| `acknowledge_unit_mismatch` | `boolean` | no |  |

## properties_document_matches_get

**Compare Documents with Property** · Read · Free

Compare what a property’s documents assert against the property’s own fields; run it after properties_completeness_get. Returns per-document field matches (status match | mismatch | property_missing | document_missing, both values, and the value an adopt action would write) plus tenancy reconciliation for rent rolls and leases. Also returns contact_suggestions: the people and companies the documents name (the Verwalter from a Verwaltervertrag or ETV protocol, the notary from a purchase contract, the contractor from an invoice), each with the role it would hold and whether it is already on file or already linked. Advisory only; adopt accepted rows with properties_document_matches_apply.

REST: `GET /api/v1/properties/{id}/document-matches`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `property_id` | `string` | yes |  |

## properties_documents_link

**Link Documents to Property** · Write · Destructive · Free

Link existing document UUIDs to a property. Optional paths map document_id → data-room path (includes filename). Optional unit_ids says which units of this property the documents are about (a floor plan of WE 8 and WE 13).

REST: `POST /api/v1/properties/{id}/documents`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `property_id` | `string` | yes |  |
| `document_ids` | `array<string>` | yes |  |
| `is_pinned` | `boolean` | no |  |
| `paths` | `object` | no |  |
| `unit_ids` | `array<string>` | no | Unit ids of this property (properties_units_list). Replaces the unit set of every listed document; [] means the whole building; omitted leaves it as it is. A unit of another property fails the call with 404 before anything is written. |

## properties_documents_list

**List Property Documents** · Read · Free

List documents linked to a property. Each item carries unit_ids: the units of this property the document is about (from properties_units_list); empty means the whole building.

REST: `GET /api/v1/properties/{id}/documents`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `property_id` | `string` | yes |  |
| `document_type` | `string` | no | Only documents of this type, e.g. floor_plan. Values: `tenant_operating_costs_statement`, `expose`, `owners_association_meeting_minutes`, `owners_association_operating_costs_statement`, `condominium_declaration`, `energy_certificate`, `land_register`, `rental_contract`, `purchase_contract`, `economic_plan`, `rent_roll`, `bank_statement`, `cadastral_map`, `floor_plan`, `building_permit`, `loan_agreement`, `insurance_policy`, `property_tax_notice`, `property_management_contract`, `inspection_report`, `renovation_invoice`, `valuation_report`, `living_area_calculation`, `other` |
| `limit` | `integer` | no |  |
| `offset` | `integer` | no |  |

## properties_documents_unlink

**Unlink Documents from Property** · Write · Destructive · Free

Remove the link between a property and documents (e.g. a Mietvertrag of another unit linked here by mistake). The documents stay, and so do their links to other properties: use this, never documents_delete, to fix a wrong link. Completeness and document matches stop counting them right away; values already adopted from them stay. Ids that were not linked come back in not_linked, so a retry is safe.

REST: `DELETE /api/v1/properties/{id}/documents/{documentId}` REST unlinks one document per call; the tool takes a list.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `property_id` | `string` | yes |  |
| `document_ids` | `array<string>` | yes |  |

## properties_get

**Get Property** · Read · Free

Get one property by UUID. The id is the calculation id in the app. Address fields are flat (no nested address object); rates are decimal fractions. Each tenancy lists its tenants in contacts (lease order) and contact_ids. Hosts with MCP Apps render a Property card; the JSON is always returned as text too.

REST: `GET /api/v1/properties/{id}`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes |  |

## properties_images_upload

**Upload Property Image** · Write · Free

Upload a property image from base64 content and append it to the property’s images. JPEG, PNG or WebP, at most 10 MB decoded; the type is read from the bytes. Returns the stored reference (blob_path) and the updated images list.

REST: `POST /api/v1/properties/{id}/images` REST takes the image as multipart/form-data, not content_base64.

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `property_id` | `string` | yes |  |
| `content_base64` | `string` | yes |  |

## properties_keyfigures_get

**Get Property Key Figures** · Read · Free

Key figures for a property. range: full | year1 | year10 (required). date_unit: month | year (default year). Buy-and-hold vs fix-and-flip shapes differ; property must have an address. When present, deal_score holds the 0-100 score, its factors, caps and open items: explain a score from these, never from memory.

REST: `GET /api/v1/properties/{id}/keyfigures`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes |  |
| `range` | `string` | yes | Values: `full`, `year1`, `year10` |
| `date_unit` | `string` | no | Values: `month`, `year` |

## properties_list

**List Properties** · Read · Free

List the user’s properties (paginated: limit 1–200 default 50, offset). Optional filters, each accepting comma-separated values: property_lifecycle (new,in_progress,purchased,canceled), property_stage_id (UUIDs), created_via (human,nora_agent,routine), external_ref (exact match). Items carry flattened address fields and key metrics.

REST: `GET /api/v1/properties`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `property_lifecycle` | `string` | no |  |
| `property_stage_id` | `string` | no |  |
| `created_via` | `string` | no |  |
| `external_ref` | `string` | no |  |
| `limit` | `integer` | no |  |
| `offset` | `integer` | no |  |

## properties_loans_create

**Add Loan to Property** · Write · Free

Add a loan (Darlehen) to a property. Required: property_id, label, start_date (YYYY-MM-DD), loan_amount (EUR), interest_rate and repayment_rate as fractions (0.035 = 3.5 %, 0.02 = 2 % Tilgung). A contract that names only the monthly rate in EUR (Annuität): repayment_rate = that rate * 12 / loan_amount - interest_rate. fixed_repayment_rate is a fixed Tilgung on top of the interest, never that rate; it wins over repayment_rate, so null it when correcting a loan. The Zinsbindung ends after loan_commitment_years (default 10) unless interest_commitment_end_date, the date in the contract, is set: send it when the user knows it, the end then counts as confirmed instead of estimated. An Anschlussdarlehen is a new loan with previous_loan_id = the loan it refinances (same property), start_date the month after the old Zinsbindung ends and loan_amount the Restschuld then (remaining_debt_at_commitment_end in portfolio_loans_list). end_date is a planned payoff, not a Zinsbindung end. Confirm the figures with the user before writing. Returns projection, to check against the contract: monthly_rate is the rate a contract names (interest + Tilgung after any free months, without savings into a Tilgungsträger). remaining_debt_at_commitment_end is the Restschuld at the Zinsbindung end, unless end_date is set: then both end fields describe the planned payoff, so do not compare them with the contract. On a real mismatch fix the loan with loans_patch.

REST: `POST /api/v1/properties/{id}/loans`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `label` | `string` | yes | Name of the loan, 1 to 50 characters. |
| `start_date` | `string` | yes | First month of the loan, YYYY-MM-DD. |
| `loan_amount` | `number` | yes | Loan amount in EUR. |
| `interest_rate` | `number` | yes | Nominal interest rate p.a. as a fraction, 0 to 0.2 (0.035 = 3.5 %). |
| `repayment_rate` | `number` | yes | Initial Tilgung p.a. as a 0-1 fraction (0.02 = 2 %). When a contract names only the monthly rate in EUR: that rate * 12 / loan_amount - interest_rate. |
| `lender` | `string \| null` | no | Bank, at most 100 characters. |
| `loan_commitment_years` | `integer` | no | Zinsbindung in years; the end month is start + years unless interest_commitment_end_date is set. |
| `interest_commitment_end_date` | `string \| null` | no | Zinsbindung end as written in the loan contract, after start_date; wins over start + years. |
| `repayment_type` | `string` | no | How the loan is repaid. On create, annuity_loan when omitted. Values: `annuity_loan`, `bullet_loan`, `initial_repayment_free`, `repayment_substitute` |
| `repayment_free_months` | `integer \| null` | no | Months with interest-only payments at the start (initial_repayment_free). |
| `repayment_substitute_monthly` | `number \| null` | no | Monthly savings into the Tilgungsträger (repayment_substitute), EUR, more than 0. |
| `repayment_substitute_expected_return` | `number \| null` | no | Expected annual return on the Tilgungsträger as a 0-1 fraction (0.04 = 4 %). |
| `fixed_repayment_rate` | `number \| null` | no | Fixed monthly Tilgung in EUR, more than 0, on top of the interest (annuity_loan, and initial_repayment_free after its free months; ignored otherwise). An amount, not a rate. Wins over repayment_rate. Never the full monthly rate from a contract: for an annuity send repayment_rate with this field null. |
| `end_date` | `string \| null` | no | Planned payoff date: the loan is repaid in full then, not refinanced. |
| `previous_loan_id` | `integer \| null` | no | The loan this one refinances (Anschlussdarlehen); a loan of the same property. |
| `processing_fee` | `number` | no | One-time fee as a 0-1 fraction of the loan amount (0.01 = 1 %); 0 on create when omitted. Fix & flip only. |
| `loan_duration_months` | `integer` | no | Months the loan runs in a fix & flip calculation; 6 on create when omitted. |
| `property_id` | `string` | yes | Property id from properties_list. |

## properties_patch

**Update Property** · Write · Destructive · Free

Update a property by id (partial; unknown fields are rejected). Move between pipeline stages with property_stage_id (from property_stages_list). Mark a property as sold by setting sale_date + sale_price (null both to undo). Fix & flip assumptions: fixflip_project_duration_in_months, fixflip_target_sqm_price, renovation_detail + estimated_renovation_costs_per_square_meter. The Hausgeld share the owner carries per Wirtschaftsplan is operating_costs_non_transferable (at most the Hausgeld; null derives it as Hausgeld minus prepayment), and operating_costs_reserve the WEG reserve inside it (a cost, not tax-deductible until the WEG spends it). AfA from the Kaufpreisaufteilung or the tax assessment: buyhold_building_share is the building AfA basis in EUR (an amount, not a share; land is the rest), buyhold_amortization_rate a fraction (0.02 = 2 %), buyhold_amortization_method linear or degressive, always sent with its rate. Record where that split stands with depreciation_basis_status (declared or assessed for tax-office values) and its source with depreciation_basis_document_id. Other writable fields include living_area, purchase_price, purchase_date, strategy, unit/floor numbers, building_year, number_of_indoor_parking_spaces / number_of_outdoor_parking_spaces (parking_spaces on the response is their sum and is not writable), rooms/units/bathrooms, external_ref, and images (an https URL on an allowed image host, or the stored references properties_images_upload returns; upload raw bytes with it first).

REST: `PATCH /api/v1/properties/{id}`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | `string` | yes |  |
| `name` | `string \| null` | no |  |
| `property_stage_id` | `string` | no |  |
| `notes` | `string \| null` | no |  |
| `listing_link` | `string \| null` | no |  |
| `description` | `string \| null` | no |  |
| `purchase_price` | `number` | no |  |
| `living_area` | `number` | no |  |
| `purchase_date` | `string` | no |  |
| `sale_date` | `string \| null` | no |  |
| `sale_price` | `number \| null` | no |  |
| `strategy` | `string` | no | Values: `buy_and_hold`, `fix_and_flip`, `own_use`, `special_rental` |
| `property_type` | `string` | no | Values: `multi_family_house`, `house`, `apartment` |
| `fixflip_project_duration_in_months` | `integer` | no |  |
| `fixflip_target_sqm_price` | `number` | no |  |
| `renovation_detail` | `string \| null` | no | Values: `simple`, `detailed`, `complete` |
| `estimated_renovation_costs_per_square_meter` | `number` | no |  |
| `board_order_index` | `integer` | no |  |
| `image` | `string \| null` | no |  |
| `images` | `array<string>` | no |  |
| `links` | `any` | no |  |
| `external_ref` | `string \| null` | no |  |
| `unit_number` | `string \| null` | no |  |
| `floor_number` | `integer \| null` | no |  |
| `building_year` | `integer \| null` | no |  |
| `number_of_indoor_parking_spaces` | `integer \| null` | no |  |
| `number_of_outdoor_parking_spaces` | `integer \| null` | no |  |
| `number_of_rooms` | `number \| null` | no |  |
| `number_of_units` | `integer \| null` | no |  |
| `number_of_bathrooms` | `integer \| null` | no |  |
| `operating_costs_non_transferable` | `number \| null` | no |  |
| `operating_costs_reserve` | `number \| null` | no |  |
| `rent_net` | `number` | no |  |
| `purchase_price_discount` | `number \| null` | no |  |
| `buyhold_building_share` | `number \| null` | no | Building AfA basis in EUR (Gebäudeanteil as an amount, not a share): the building part of purchase price plus ancillary costs, e.g. 200000. Land is the rest. 0 or at least 100. |
| `buyhold_amortization_rate` | `number` | no | AfA rate per year as a fraction: 0.02 = 2 %, 0.03 = 3 %, 0.05 = 5 % degressive. |
| `buyhold_amortization_method` | `string` | no | linear (§ 7 Abs. 4 EStG) or degressive (§ 7 Abs. 5a EStG). Requires buyhold_amortization_rate in the same call (refused otherwise). Values: `linear`, `degressive` |
| `depreciation_basis_status` | `string \| null` | no | Where the AfA split stands: proposed (an estimate), declared (in the tax return), assessed (confirmed by the tax office), disputed (objection filed). declared, assessed and disputed keep buyhold_building_share as an amount when the price changes. null: not stated. Values: `proposed`, `declared`, `assessed`, `disputed` |
| `depreciation_basis_document_id` | `string \| null` | no | Id of the document the AfA split comes from (Kaufvertrag, Kaufpreisaufteilung, Steuerbescheid), from documents_upload or properties_documents_list. Any of your own documents; null clears it. |
| `energy_label` | `string \| null` | no | Values: `a_plus_plus_plus`, `a_plus_plus`, `a_plus`, `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h` |
| `is_leasehold` | `boolean \| null` | no |  |
| `leasehold_end_date` | `string \| null` | no |  |
| `land_registry_court` | `string \| null` | no |  |
| `land_registry_district` | `string \| null` | no |  |
| `land_registry_sheet` | `string \| null` | no |  |
| `cadastral_sector` | `string \| null` | no |  |
| `cadastral_parcel` | `string \| null` | no |  |
| `co_ownership_share` | `number \| null` | no |  |
| `co_ownership_share_total` | `number \| null` | no |  |

## properties_pdf_export

**Export Property PDF** · Write · 2 API credits

Export a property’s calculation as a PDF. Returns an embedded application/pdf resource (base64). The PDF carries the account’s referral link in the footer and as a QR code on the last page; a paid plan can switch it off in the app. Costs 2 API credits.

REST: `POST /api/v1/properties/{id}/export`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `property_id` | `string` | yes |  |

## properties_rent_roll_apply

**Apply Rent Roll** · Write · Free

Apply a rent roll (Mieterliste) to a property: creates units, tenancies and tenant contacts from a completed rent_roll extraction (pass document_id) or an inline rent_roll (units[].unit_label is required, plus living_area_sqm for a unit that does not exist yet; lease_end_date records an ended lease). Per unit, operating_costs_monthly is the Nebenkostenvorauszahlung the tenant pays (tenancy operating_costs_transferable) and owner_costs_monthly the Hausgeld the owner pays (tenancy operating_costs, at least the prepayment); without owner_costs_monthly the prepayment stands in as Hausgeld and the unit is listed in owner_costs_defaulted: ask the user for the Hausgeld and set it with tenancies_patch (operating_costs). non_transferable_monthly is the share of the Hausgeld the owner carries per Wirtschaftsplan (tenancy operating_costs_non_transferable, at most the Hausgeld); missing, it is derived. reserve_monthly is the WEG reserve inside that share (tenancy operating_costs_reserve). A row the database would refuse fails the whole apply with a 422 naming the unit, before anything is written. Units are matched by name; an existing unit gets the tenancy unless it already holds that tenant or another tenant still occupies it (occupied_units: end that tenancy with tenancies_patch, then re-apply). A tenant the property already has is reused. Safe to retry. Units listed in start_date_defaulted got today as lease start: confirm the real dates with the user and correct them with tenancies_patch. Show the user the parsed units (documents_get with include ["extraction"]) and get their confirmation before calling this. To correct a tenancy afterwards, use tenancies_patch.

REST: `POST /api/v1/properties/{id}/rent-roll/apply`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `property_id` | `string` | yes |  |
| `document_id` | `string` | no |  |
| `rent_roll` | `object` | no |  |

## properties_tenancies_import

**Import Tenancies** · Write · Free

Bulk-create tenancies for a property, optionally assigned to existing units via unit_id (from properties_units_list). start_date (YYYY-MM-DD) is required: ask the user when the list has none. A tenant the property already has is reused by name instead of duplicated. Safe to retry: a row whose tenant already has a tenancy on that unit with that start date is skipped. To correct a tenancy afterwards, use tenancies_patch.

REST: `POST /api/v1/properties/{id}/tenancies/import`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `tenants` | `array<object>` | yes |  |
| `property_id` | `string` | yes |  |

## properties_units_list

**List Property Units** · Read · Free

List units of a property with their tenancies (tenant contacts in lease order, rent_net at the lease start, rent_net_target, and events: the dated changes such as rent increases, each applying from its month on), paginated. Use to inspect the unit/tenant structure, e.g. before or after applying a rent roll, and to find tenancy_id and event_id for tenancies_events_create / _delete.

REST: `GET /api/v1/properties/{id}/units`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `property_id` | `string` | yes |  |
| `limit` | `integer` | no |  |
| `offset` | `integer` | no |  |

## property_stages_create

**Create Property Stage** · Write · Free

Create a custom property stage under a property lifecycle. Requires lifecycle, name, color (#RRGGBB).

REST: `POST /api/v1/property-stages`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `lifecycle` | `string` | yes | Values: `new`, `in_progress`, `purchased`, `canceled` |
| `name` | `string` | yes |  |
| `description` | `string \| null` | no |  |
| `color` | `string` | yes |  |

## property_stages_delete

**Delete Property Stage** · Write · Destructive · Free

Delete a property stage and reassign its properties to replacement_stage_id (same property lifecycle). Cannot delete archived, last stage in a lifecycle, or creation-default new stage.

REST: `DELETE /api/v1/property-stages/{id}`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `replacement_stage_id` | `string` | yes |  |
| `id` | `string` | yes |  |

## property_stages_list

**List Property Stages** · Read · Free

List property stages (pipeline steps under property lifecycles new|in_progress|purchased|canceled). Optional lifecycle filter (comma-separated). Each item includes property_count. Prefer this before filtering properties_list by property_stage_id.

REST: `GET /api/v1/property-stages`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `lifecycle` | `string` | no |  |
| `limit` | `integer` | no |  |
| `offset` | `integer` | no |  |

## property_stages_patch

**Update Property Stage** · Write · Destructive · Free

Patch a property stage by id (name, description, color, sort_order, is_default). is_default only for lifecycle=new. Cannot rename archived.

REST: `PATCH /api/v1/property-stages/{id}`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | no |  |
| `description` | `string \| null` | no |  |
| `color` | `string` | no |  |
| `sort_order` | `integer` | no |  |
| `is_default` | `boolean` | no |  |
| `id` | `string` | yes |  |
