# Quickstart: REST (https://docs.propertypixel.app/quickstart/rest)

Enhance a listing from the command line with curl.



This walks through one listing: create a project, add photos by URL, enhance them with a quote, wait for the results, and download them.

## 1. Create an API key [#1-create-an-api-key]

In PropertyPixel, open [Settings → Developers](https://propertypixel.app/settings/developers) and select **Create key**. Pick the workspace and choose **View and edit** access. Copy the key (it starts with `pp_live_`); it is shown only once.

```bash
export PP_API_KEY="pp_live_..."
export PP_API="https://api.propertypixel.app/v1"
```

Check that it works:

```bash
curl -s "$PP_API/account" -H "Authorization: Bearer $PP_API_KEY"
```

The response shows the workspace, your access level and `credits.available`.

## 2. Create a project [#2-create-a-project]

```bash
curl -s -X POST "$PP_API/projects" \
  -H "Authorization: Bearer $PP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "14 Harbour Street", "address": "14 Harbour Street, Sydney"}'
```

Note the `id` in the response:

```bash
export PROJECT_ID="..."
```

## 3. Add photos by URL [#3-add-photos-by-url]

```bash
curl -s -X POST "$PP_API/projects/$PROJECT_ID/photos" \
  -H "Authorization: Bearer $PP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"image_urls": [
        "https://example.com/photos/living-room.jpg",
        "https://example.com/photos/front.jpg"
      ]}'
```

The response lists the new `photos` with their IDs, and a `failed` list for any URL that couldn't be used. To upload files from disk instead, see [Uploading photos](/docs/uploading-photos#signed-upload).

## 4. Get a quote [#4-get-a-quote]

Spending operations price the request before running it. Call `POST /enhancements` without a `quote_id`:

```bash
curl -s -X POST "$PP_API/enhancements" \
  -H "Authorization: Bearer $PP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"project_id": "'"$PROJECT_ID"'", "all_photos": true,
       "enhancement_types": ["improve_lighting", "blue_skies"]}'
```

Nothing is spent. The response is `409` with code `quote_required`, and a `quote`:

```json
{
  "type": "https://docs.propertypixel.app/errors#quote_required",
  "title": "Quote required",
  "status": 409,
  "code": "quote_required",
  "detail": "This request spends credits. Confirm the quote by sending quote_id, or send auto_confirm: true.",
  "quote": {
    "object": "quote",
    "id": "5d1c…",
    "operation": "enhance_photos",
    "credits": 200,
    "balance": { "available": 500, "after": 300 },
    "sufficient": true,
    "watermarked": true,
    "expires_at": "2026-10-04T10:10:00Z"
  }
}
```

## 5. Confirm and run [#5-confirm-and-run]

Send the same body again with the quote's `id`:

```bash
curl -s -X POST "$PP_API/enhancements" \
  -H "Authorization: Bearer $PP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"project_id": "'"$PROJECT_ID"'", "all_photos": true,
       "enhancement_types": ["improve_lighting", "blue_skies"],
       "quote_id": "5d1c…"}'
```

The response (`201`) is a job batch: one `job` per photo, `credits_spent`, and `poll_after_ms`.

<Callout title="Scripts that don't need a quote">
  Send `"auto_confirm": true` instead of `quote_id` to spend in one call. Only do this in code you
  control; see [Credits and quotes](/docs/credits-and-quotes).
</Callout>

## 6. Wait for the results [#6-wait-for-the-results]

```bash
curl -s "$PP_API/jobs?ids=JOB_ID_1,JOB_ID_2" -H "Authorization: Bearer $PP_API_KEY"
```

Repeat after `poll_after_ms` until `all_done` is `true`. Each completed job has `result.url`, signed for one hour. Or skip polling and subscribe to the `job.completed` [webhook](/docs/webhooks).

## 7. Download [#7-download]

One result is a single signed link. Several start a zip:

```bash
curl -s -X POST "$PP_API/downloads" \
  -H "Authorization: Bearer $PP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"job_ids": ["JOB_ID_1", "JOB_ID_2"]}'
```

If `status` is `processing`, poll `GET /downloads/{id}` until it returns a `url`.

## Next [#next]

* [Jobs and polling](/docs/jobs)
* [Webhooks](/docs/webhooks)
* [API reference](/docs/api)
