# Properties API

CRUD endpoints for managing your property listings programmatically.

For image uploads, see [Property Images](/docs/api-reference/property-images).

## List Properties

```
GET /api/v1/properties
```

Returns a paginated list of your non-deleted properties.

**Query parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `page` | integer | Page number (default `1`). |
| `limit` | integer | Items per page (default `20`, max `100`). |
| `minRent` | number | Minimum monthly rent (USD). |
| `maxRent` | number | Maximum monthly rent (USD). |
| `minBedrooms` | integer | Minimum bedrooms. |
| `minBathrooms` | number | Minimum bathrooms (allows `1.5` etc). |
| `availableBefore` | string | ISO date `YYYY-MM-DD` — only properties available on or before this date. |
| `petFriendly` | `true`\|`false` | Filter pet-friendly properties. |
| `hasParking` | `true`\|`false` | Filter properties with parking. |
| `city` | string | Substring match on city. |

**Response (`200 OK`):**

```json
{
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "address": "123 Main St, Apt 4B, New York, NY 10001",
      "monthlyRent": 2500,
      "bedrooms": 2,
      "bathrooms": 1,
      "available": true,
      "createdAt": "2026-01-15T10:30:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 42,
    "totalPages": 3
  }
}
```

## Create Property

```
POST /api/v1/properties
```

**Request body:**

```json
{
  "address": "123 Main St, Apt 4B, New York, NY 10001",
  "monthlyRent": 2500,
  "bedrooms": 2,
  "bathrooms": 1,
  "description": "Sunny 2BR with updated kitchen and hardwood floors.",
  "available": true,
  "imageUrls": ["https://example.com/photo1.jpg"]
}
```

`imageUrls` is optional — if provided, the server queues an async image import and the response includes an `imageImport` block with a `jobId` you can poll via `GET /properties/:id/images/import/:jobId`.

**Response (`201 Created`):** Includes a `Location: /api/v1/properties/:id` header.

```json
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "address": "123 Main St, Apt 4B, New York, NY 10001",
    "monthlyRent": 2500,
    "bedrooms": 2,
    "bathrooms": 1,
    "available": true,
    "createdAt": "2026-01-15T10:30:00.000Z"
  }
}
```

## Get Property

```
GET /api/v1/properties/:id
```

**Response (`200 OK`):** `{ "data": <property> }`.

## Update Property

```
PATCH /api/v1/properties/:id
```

Send only the fields you want to update.

**Response (`200 OK`):** `{ "data": <updated property> }`.

## Delete Property

```
DELETE /api/v1/properties/:id
```

**Soft-delete.** Sets `deletedAt` on the property — the row is hidden from list/get responses but kept in the database for audit and recovery. Returns `204 No Content`.

## Bulk Create Properties

```
POST /api/v1/properties/bulk
```

Submit up to 500 properties in a single request. Processing happens asynchronously — you get a job ID back immediately.

**Field names are flexible.** You can use camelCase (`monthlyRent`, `bedrooms`) or common alternatives from property management tools (`rent`, `beds`, `street_address`, `monthly_rent`, etc.). Values like `"$1,500"`, `"3 BR"`, and `"yes"` are coerced automatically. Unknown fields are surfaced in the job result as `unmappedFields`.

**Requires:** Pro or Scale plan.

**Request body:**

```json
{
  "properties": [
    { "street": "123 Main St", "rent": 1500, "beds": 2, "baths": 1, "city": "Austin" },
    { "address": "456 Oak Ave", "monthlyRent": 2000, "bedrooms": 3, "bathrooms": 2 }
  ]
}
```

Supports `Idempotency-Key` header.

**Response (`202 Accepted`):**

```json
{
  "data": {
    "jobId": "550e8400-e29b-41d4-a716-446655440000",
    "status": "pending",
    "total": 2
  }
}
```

## Get Bulk Import Job Status

```
GET /api/v1/properties/bulk/:jobId
```

Poll this endpoint to check progress. You can also subscribe to the `bulk_import.completed` webhook event.

**Response (`200 OK`):**

```json
{
  "data": {
    "jobId": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "total": 100,
    "created": 95,
    "failed": 5,
    "createdPropertyIds": ["id1", "id2", "..."],
    "unmappedFields": ["custom_field_x", "internal_id"],
    "errors": [
      { "row": 3, "field": "monthlyRent", "message": "Required", "code": "validation" },
      { "row": 98, "message": "Property limit exceeded (100 max for pro plan)", "code": "capacity" }
    ],
    "createdAt": "2026-02-19T10:00:00.000Z",
    "completedAt": "2026-02-19T10:00:05.000Z"
  }
}
```

`status` is one of `pending`, `processing`, `completed`, `failed`.
