---
title: "Update Deal"
method: PATCH
path: "/api/external/v2/deals/{deal_id}"
tags: ["external-v2-deals"]
---

# Update Deal

`PATCH /api/external/v2/deals/{deal_id}`

Partially update an existing deal (JSON Merge Patch semantics).

Only the fields present in the request body are updated.  An empty body
(all fields absent) returns 400 because no meaningful update was requested.

Property edits are explicit sub-operations on ``add_properties`` /
``update_properties`` / ``remove_property_ids`` — no implicit
replace-all. All boundary checks (cross-list overlap, access scoping,
target-exists, min-property post-op count) happen here before any
controller call so a single rejected request leaves the deal untouched.

## Path parameters

- `deal_id` integer, required

## Request body

- UpdateDealRequest — PATCH /api/external/v2/deals/<id> request body. All fields are optional — only the fields included in the request body are updated (partial update / JSON Merge Patch semantics). Note: ``pipeline_ids`` is intentionally absent. The ``update_deal`` controller uses ``deal_pipelines`` dicts for pipeline manipulation, which is a different shape. Use POST /deals/<id>/pipeline to move a deal into a pipeline. Property edits are explicit sub-operations (``add_properties``, ``update_properties``, ``remove_property_ids``) rather than a single ``properties`` list with replace-all semantics. The GraphQL surface uses replace-all today, but for an LLM-facing wire surface a single typo on a multi-property portfolio deal would silently nuke whichever entries got dropped from the payload — same shape of footgun MCP-114 closed for the create path's IDOR concern, expressed as data loss instead of unauthorized read.
  - `title` string, nullable — Deal title
  - `loan_amount` number, nullable — Requested loan amount
  - `priority` 'No priority' | 'Urgent' | 'High' | 'Medium' | 'Low'
  - `loan_type` 'Construction' | 'Heavy Bridge' | 'Light Bridge' | 'Permanent' | 'Land' | 'Predevelopment' | 'TBD'
  - `transaction_type` 'TBD' | 'Acquisition' | 'Refinance' | 'New Construction'
  - `business_plan` 'stabilized' | 'value_add' | 'construction' | 'land'
  - `description` string, nullable — Free-text deal description
  - `estimated_close_date` string, date, nullable — Estimated close date (ISO 8601 date string, e.g. 2025-12-31)
  - `sponsor_private_company_id` integer, nullable — ID of the sponsor company (a PrivateCompany) to link to this deal. Must belong to the caller's account.
  - `deal_financials` CreateDealFinancialsInput — Canonical deal-level financial denominators on POST/PATCH /deals.
    - `purchase_price` number, nullable — Purchase price for LTV (acquisition).
    - `estimated_value` number, nullable — Estimated value for LTV (refinance).
    - `total_cost` number, nullable — Total project cost for construction LTC.
    - `total_cost_basis` number, nullable — Total cost basis for LTC (bridge, etc.).
    - `land_cost` number, nullable — Land cost for construction LTC.
  - `add_properties` CreateDealPropertyInput[], nullable — Properties to attach to this deal. Same entry shape as POST /deals — either a link (property_id) or an inline-create (address + optional structural fields). Up to 25 entries per request (request-shape bound, matching POST /deals — not a product cap on properties per deal).
    - `property_id` integer, nullable — ID of an existing property to link. Must be reachable from the caller's account (already linked to a deal the account owns). Mutually exclusive with the inline-create fields below.
    - `address` string, nullable — Street address.
    - `city` string, nullable
    - `state` string, nullable — State/province (e.g. 'NY', 'CA').
    - `zip` string, nullable
    - `asset_type_id` integer, nullable — Asset subtype ID — discover via get_asset_types.
    - `units` number, nullable — Unit count (e.g. apartment units).
    - `square_footage` number, nullable — Rentable/leasable area in square feet.
    - `year_built` integer, nullable
  - `update_properties` UpdateDealPropertyInput[], nullable — Partial updates to properties already on this deal. Each entry's property_id must currently be on the deal (else 404). Omitted fields are left untouched.
    - `property_id` integer, required — ID of an existing property on this deal — required to identify what to update.
    - `address` string, nullable — Street address.
    - `city` string, nullable
    - `state` string, nullable — State/province (e.g. 'NY', 'CA').
    - `zip` string, nullable
    - `asset_type_id` integer, nullable — Asset subtype ID — discover via get_asset_types.
    - `units` number, nullable — Unit count (e.g. apartment units).
    - `square_footage` number, nullable — Rentable/leasable area in square feet.
    - `year_built` integer, nullable
  - `remove_property_ids` integer[], nullable — Property IDs to detach from this deal. Each must currently be on the deal (else 404). Removal is a hard delete of the DealProperty join row; the underlying Property record is preserved. The deal must keep at least one property after the operation (else 400).

## Response `200`

Successful Response

- DealObjectResponse — OpenAPI schema for GET/POST/PATCH /api/external/v2/deals (object envelope).
  - `request_id` string, required
  - `timestamp` string, required
  - `data` DealResponse, required
    - `id` integer, required
    - `title` string, nullable, required
    - `loan_amount` number, nullable, required
    - `priority` string, required
    - `loan_type` string, nullable, required
    - `transaction_type` string, nullable, required
    - `business_plan` string, nullable, required
    - `description` string, nullable, required
    - `estimated_close_date` string, nullable, required
    - `close_date` string, nullable, required
    - `owner_account_id` integer, nullable, required
    - `owner_user` DealOwnerUserResponse — The person who owns the deal — its primary ``deal_principal`` team member. Deliberately narrower than ``DealTeamMemberResponse``: ``user_id`` (not ``id``) so it joins against ``team[].user_id``, and no ``email`` — the v2 team serializer does not honor ``DealUser.user_configs.visible_fields.email``.
      - `user_id` integer, required
      - `first_name` string, nullable, required
      - `last_name` string, nullable, required
    - `sponsor_private_company_id` integer, nullable, required
    - `urls` DealUrlsResponse, required — Borrower-portal deeplinks emitted on every deal payload — keys mirror ``app.utils.url_generator.DEAL_SUBPAGE_PATHS``.
      - `overview` string, required
      - `data` string, required
      - `dataroom` string, required
      - `lenders` string, required
      - `quote_matrix` string, required
      - `vaults` string, required
    - `created_at` string, nullable, required
    - `updated_at` string, nullable, required
    - `archived` boolean, required
    - `archived_at` string, nullable, required
    - `financials` DealFinancialsResponse
      - `id` integer, required
      - `in_place_noi` number, nullable, required
      - `stabilized_noi` number, nullable, required
      - `stabilized_value` number, nullable, required
      - `stabilized_occupancy` number, nullable, required
      - `purchase_price` number, nullable, required
      - `asking_price` number, nullable, required
      - `estimated_value` number, nullable, required
      - `total_cost` number, nullable, required
      - `total_cost_basis` number, nullable, required
      - `capex` number, nullable, required
      - `existing_debt` number, nullable, required
      - `going_in_cap_rate` number, nullable, required
      - `land_cost` number, nullable, required
    - `properties` DealPropertyResponse[], nullable
      - `id` integer, required
      - `name` string, nullable, required
      - `address` string, nullable, required
      - `city` string, nullable, required
      - `state` string, nullable, required
      - `zip` string, nullable, required
      - `asset_type_id` integer, nullable, required
      - `units` integer, nullable, required
      - `square_footage` number, nullable, required
      - `year_built` integer, nullable, required

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.net/lev/apis/lev-api.md) · [All operations](https://skmtc.net/lev/apis/lev-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/lev/lev-api/versions/e1915e1423cf/schema)
