# Placements

> Create, update, and view lender placements in the Lev API.

Source: https://www.lev.com/docs/build/placements

Last updated: June 2026

---
## Overview

Placements connect deals to lenders. Each placement has a status that tracks the lender's progression (e.g. `sent`, `lender_reviewing`, `terms_received`, `closed`).

| Endpoint | Description |
|----------|-------------|
| `GET /placements` | List placements with filtering and pagination |
| `GET /placements/{id}` | Get a single placement |
| `POST /placements` | Create a placement against a deal |
| `PATCH /placements/{id}` | Update a placement (use `status="archived"` to remove) |
| `GET /placements/{id}/notes` | List notes logged on a placement |
| `POST /placements/{id}/notes` | Add a note to a placement |
| `PATCH /placements/{id}/notes/{note_id}` | Edit a placement note |
| `DELETE /placements/{id}/notes/{note_id}` | Delete a placement note |

Placement records themselves do not have a `DELETE` verb. To remove a placement, send `PATCH` with `status="archived"`. `deal_id`, `private_company_id`, and `contact_id` are set at create time and cannot be reassigned — archive the placement and create a new one to change linkage.

Placement responses include `lender_name` and `contact_name` for display. When you can see the real lender identity, `lender_name` uses the deal owner's lender name and falls back to the canonical lender name. On masked non-owner reads, `lender_name` uses the same generic borrower-portal label style as Lender A, while `private_company_id`, `contact_id`, and `contact_name` are `null`.

## List Placements

### `GET /api/external/v2/placements`

List placements with pagination

#### Query Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `limit` | `integer` | No | Results per page (1–200, default 50) |
| `cursor` | `string` | No | Cursor for next page |

Response: `200`

#### Error Responses

- **401** (unauthorized): Authentication required
- **400** (bad_request): cursor and sort cannot be combined; use offset pagination when sorting

## Get Placement

### `GET /api/external/v2/placements/{placement_id}`

Get a single placement by ID

#### Path Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `placement_id` | `integer` | Yes | The placement ID |

Response: `200`

#### Error Responses

- **401** (unauthorized): Authentication required
- **404** (not_found): Placement not found

## Create Placement

### `POST /api/external/v2/placements`

Create a placement against a deal

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `deal_id` | `integer` | Yes | ID of the deal this placement belongs to |
| `private_company_id` | `integer` | Yes | ID of the lender private company (use `GET /companies` to find candidates) |
| `contact_id` | `integer` | No | ID of the lender contact for this outreach. Must belong to the lender at `private_company_id`. Omit it and Lev runs an async AI match to recommend a contact at that lender; the recommended contact's name stays masked until you unlock it |
| `status` | `string` | No | Placement status enum name. Defaults to `new`. Cannot be `archived` on create |
| `visibility` | `string` | No | Placement visibility (`hidden`, `masked`, `shared`) |
| `description` | `string` | No | Free-text note (e.g. lender-specific feedback) |
| `score` | `number` | No | Manual relevance score (0–100) |
| `outreach_date` | `string` | No | ISO 8601 timestamp the lender was first contacted |
| `lender_status` | `string` | No | Lender-side status enum name |

Response: `201`

#### Error Responses

- **400** (bad_request): Cannot create a placement with status='archived'; create an active placement and PATCH to archive; A placement already exists for this deal, contact, and lender; use PATCH to update it
- **401** (unauthorized): Authentication required
- **404** (not_found): Deal not found; Private company not found; Contact not found
- **422** (validation_error): status: input is not a valid enum member

- Supports idempotent writes with the `Idempotency-Key` header.

## Update Placement

### `PATCH /api/external/v2/placements/{placement_id}`

Update a placement (partial update)

#### Path Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `placement_id` | `integer` | Yes | The placement ID |

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `status` | `string` | No | Placement status enum name. Use `archived` to remove the placement |
| `visibility` | `string` | No | Placement visibility (`hidden`, `masked`, `shared`) |
| `description` | `string` | No | Free-text note. Changing it also appends a note to the placement's notes (List Placement Notes) |
| `score` | `number` | No | Manual relevance score (0–100) |
| `outreach_date` | `string` | No | ISO 8601 timestamp the lender was first contacted |
| `lender_status` | `string` | No | Lender-side status enum name |

Response: `200`

#### Error Responses

- **400** (bad_request): Request body must contain at least one field to update
- **401** (unauthorized): Authentication required
- **404** (not_found): Placement not found
- **422** (validation_error): status: input is not a valid enum member

- Supports idempotent writes with the `Idempotency-Key` header.

## List Placement Notes

### `GET /api/external/v2/placements/{placement_id}/notes`

List notes logged on a placement

#### Path Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `placement_id` | `integer` | Yes | The placement ID |

#### Query Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `limit` | `integer` | No | Results per page (1–200, default 50) |
| `cursor` | `string` | No | Cursor for next page |

Response: `200`

#### Error Responses

- **401** (unauthorized): Authentication required
- **404** (not_found): Placement not found

## Create Placement Note

### `POST /api/external/v2/placements/{placement_id}/notes`

Add a note to a placement

#### Path Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `placement_id` | `integer` | Yes | The placement ID |

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `text` | `string` | Yes | Note text. Must be non-empty. |

Response: `201`

#### Error Responses

- **401** (unauthorized): Authentication required
- **404** (not_found): Placement not found
- **422** (validation_error): text must not be blank

- Supports idempotent writes with the `Idempotency-Key` header.

## Update Placement Note

### `PATCH /api/external/v2/placements/{placement_id}/notes/{note_id}`

Edit a placement note

#### Path Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `placement_id` | `integer` | Yes | The placement ID |
| `note_id` | `integer` | Yes | The note ID |

#### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `text` | `string` | Yes | Replacement note text. Must be non-empty. |

Response: `200`

#### Error Responses

- **401** (unauthorized): Authentication required
- **404** (not_found): Placement or note not found
- **400** (bad_request): text cannot be empty
- **422** (validation_error): text must not be blank

- Supports idempotent writes with the `Idempotency-Key` header.

## Delete Placement Note

### `DELETE /api/external/v2/placements/{placement_id}/notes/{note_id}`

Permanently delete a placement note

#### Path Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `placement_id` | `integer` | Yes | The placement ID |
| `note_id` | `integer` | Yes | The note ID |

Response: `200`

#### Error Responses

- **401** (unauthorized): Authentication required
- **404** (not_found): Placement or note not found

## Placement Object

| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Placement identifier |
| `deal_id` | integer | Associated deal ID |
| `private_company_id` | integer\|null | Lender private company ID. Null on masked non-owner reads |
| `lender_name` | string\|null | Display lender name. Masked non-owner reads use borrower-portal labels such as Lender A |
| `contact_id` | integer\|null | Lender contact ID. Null on masked non-owner reads |
| `contact_name` | string\|null | Lender contact name. Null on masked non-owner reads, and null for an AI-recommended contact until you reveal it via [Unlock Contact](/build/contacts#unlock-contact) — `contact_id` is still present so you can unlock it |
| `status` | string\|null | Current placement status |
| `lender_status` | string\|null | Lender-side status |
| `description` | string\|null | Placement description |
| `lev_score` | number\|null | AI-generated match score |
| `score` | number\|null | Manual score |
| `outreach_date` | string\|null | Date of initial outreach |
| `outreach_source` | string\|null | Source of outreach |
| `last_communication_date` | string\|null | Date of last communication |
| `lender_first_response_date` | string\|null | Date of lender's first response |
| `visibility` | string\|null | Placement visibility |
| `created_at` | string\|null | Creation timestamp |
| `updated_at` | string\|null | Last update timestamp |