> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibepeak.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Edit Property Showcase

> Edit an existing property showcase image with a natural-language prompt and style overrides

Edits an existing property showcase image through the image-edit flow. You provide the URL of the image to edit plus at least one editing directive (a prompt and/or style overrides). Editing runs asynchronously — the request returns a job ID immediately and the edited image is delivered to your `webhook_url`.

At least one of `prompt`, `dominant_color`, `font`, or `language` must be supplied — a request with no changes is rejected with a `VALIDATION_ERROR`.

<Warning>
  Set a `webhook_url`. Showcase jobs are **not** pollable: [Get Task](/api-reference/videos/get-task) covers video tasks only and returns `404 TASK_NOT_FOUND` for a showcase `job_id`. The webhook is the only way to receive `output_image_url`.
</Warning>

## Request Body

<ParamField body="image_url" type="string" required>
  URL of the property showcase image to edit. This must be the result URL (`output_image_url`) of a property showcase **you previously generated** — you cannot edit arbitrary images. A URL that doesn't belong to one of your showcases returns `FORBIDDEN`.

  <Note>
    Omitting `image_url` returns the message `Either source_result_id or image_url is required`. `source_result_id` is an internal identifier used by the VibePeak dashboard and is not available to API clients — always send `image_url`.
  </Note>

  <Warning>
    Image URLs cannot point to private/internal networks (SSRF protection). A blocked URL returns `SSRF_BLOCKED`.
  </Warning>
</ParamField>

<ParamField body="prompt" type="string">
  Natural-language editing instruction, e.g. `"replace the For Sale label with Sold"`. **Maximum 250 characters.**
</ParamField>

<ParamField body="dominant_color" type="string">
  Dominant color to apply to the showcase, e.g. `"#1A4D2E"` or `"navy"`.
</ParamField>

<ParamField body="font" type="string">
  Font to apply to the showcase text, e.g. `"Montserrat"`.
</ParamField>

<ParamField body="language" type="string">
  Language used to render the showcase copy. One of `en`, `es`, `de`, `fr`, `it`, `pt`. Forwarded to the generation service so the rendered text matches the requested locale.
</ParamField>

<Note>
  You must send at least one of `prompt`, `dominant_color`, `font`, or `language`. An edit request with none of these is rejected with a `VALIDATION_ERROR`.
</Note>

<ParamField body="webhook_url" type="string">
  URL to receive a webhook notification when the job completes. Must be a valid HTTPS URL, at most 2048 characters. May be `null`. See [Webhooks](/concepts/webhooks) for details.

  <Warning>
    **The webhook is the only way to receive the edited image.** Edit jobs are not exposed through [Get Task](/api-reference/videos/get-task). Without a `webhook_url` you cannot retrieve `output_image_url`.
  </Warning>

  <Warning>
    Webhook URLs must use HTTPS and cannot point to private/internal networks (SSRF protection). A non-HTTPS or private URL is rejected with `VALIDATION_ERROR` and the message `Webhook URL must be HTTPS and cannot point to private networks`.
  </Warning>

  <Note>
    `webhook_url` on its own does not count as an editing directive — you still need at least one of `prompt`, `dominant_color`, `font` or `language`.
  </Note>
</ParamField>

## Response

<ResponseField name="job_id" type="string" required>
  Unique identifier for the edit job. Use this to check status or correlate the webhook.
</ResponseField>

<ResponseField name="status" type="string" required>
  Initial job status. Always `pending` for new jobs.
</ResponseField>

<ResponseField name="message" type="string" required>
  Human-readable confirmation that the request was accepted for async processing.
</ResponseField>

<ResponseField name="request_id" type="string" required>
  Unique identifier for this request, useful for support and debugging.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.vibepeak.ai/v1/real-estate/property-showcase/edit \
    -H "Authorization: Bearer vpk_live_xxxxx" \
    -H "Content-Type: application/json" \
    -d '{
      "image_url": "https://vibepeak-property-showcase-production.s3.eu-west-1.amazonaws.com/showcases/4d7fbd2c-8675-4fa9-aa62-570d4088b8a5/showcase_luxury_vertical_1788184569063.jpg",
      "prompt": "replace the For Sale label with Sold",
      "dominant_color": "#1A4D2E",
      "font": "Montserrat",
      "language": "es",
      "webhook_url": "https://yourserver.com/webhooks/vibepeak"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.vibepeak.ai/v1/real-estate/property-showcase/edit', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer vpk_live_xxxxx',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      image_url: 'https://vibepeak-property-showcase-production.s3.eu-west-1.amazonaws.com/showcases/4d7fbd2c-8675-4fa9-aa62-570d4088b8a5/showcase_luxury_vertical_1788184569063.jpg',
      prompt: 'replace the For Sale label with Sold',
      dominant_color: '#1A4D2E',
      font: 'Montserrat',
      language: 'es',
      webhook_url: 'https://yourserver.com/webhooks/vibepeak'
    })
  });

  const job = await response.json();
  console.log(`Job created: ${job.job_id}`);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.vibepeak.ai/v1/real-estate/property-showcase/edit',
      headers={
          'Authorization': 'Bearer vpk_live_xxxxx',
          'Content-Type': 'application/json'
      },
      json={
          'image_url': 'https://vibepeak-property-showcase-production.s3.eu-west-1.amazonaws.com/showcases/4d7fbd2c-8675-4fa9-aa62-570d4088b8a5/showcase_luxury_vertical_1788184569063.jpg',
          'prompt': 'replace the For Sale label with Sold',
          'dominant_color': '#1A4D2E',
          'font': 'Montserrat',
          'language': 'es',
          'webhook_url': 'https://yourserver.com/webhooks/vibepeak'
      }
  )

  job = response.json()
  print(f"Job created: {job['job_id']}")
  ```
</RequestExample>

<ResponseExample>
  ```json 202 Accepted theme={null}
  {
    "job_id": "3301b1dc-9d3f-4754-8a22-08956432b812",
    "status": "pending",
    "message": "Property showcase edit request accepted for async processing",
    "request_id": "req_ZfI8Az4uwbSqXlzKUAtDg"
  }
  ```

  ```json 400 Bad Request (No editing directive) theme={null}
  {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "At least one of prompt, dominant_color, font or language is required",
      "details": {
        "field": "prompt",
        "issues": [
          { "path": "prompt", "message": "At least one of prompt, dominant_color, font or language is required" }
        ]
      },
      "request_id": "req_ZfI8Az4uwbSqXlzKUAtDg"
    }
  }
  ```

  ```json 400 Bad Request (No source image) theme={null}
  {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "Either source_result_id or image_url is required",
      "details": {
        "field": "source_result_id",
        "issues": [
          { "path": "source_result_id", "message": "Either source_result_id or image_url is required" }
        ]
      },
      "request_id": "req_ZfI8Az4uwbSqXlzKUAtDg"
    }
  }
  ```

  ```json 400 Bad Request (Prompt too long) theme={null}
  {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "prompt must be at most 250 characters",
      "details": {
        "field": "prompt",
        "issues": [
          { "path": "prompt", "message": "prompt must be at most 250 characters" }
        ]
      },
      "request_id": "req_ZfI8Az4uwbSqXlzKUAtDg"
    }
  }
  ```

  ```json 400 Bad Request (Invalid URL) theme={null}
  {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "image_url must be a valid URL",
      "details": {
        "field": "image_url",
        "issues": [
          { "path": "image_url", "message": "image_url must be a valid URL" }
        ]
      },
      "request_id": "req_ZfI8Az4uwbSqXlzKUAtDg"
    }
  }
  ```

  ```json 400 Bad Request (Blocked image URL) theme={null}
  {
    "error": {
      "code": "SSRF_BLOCKED",
      "message": "Image URL points to a private network which is not allowed.",
      "request_id": "req_ZfI8Az4uwbSqXlzKUAtDg"
    }
  }
  ```

  ```json 402 Payment Required theme={null}
  {
    "error": {
      "code": "INSUFFICIENT_CREDITS",
      "message": "Not enough credits to process the request.",
      "request_id": "req_ZfI8Az4uwbSqXlzKUAtDg"
    }
  }
  ```

  ```json 403 Forbidden (Not your showcase) theme={null}
  {
    "error": {
      "code": "FORBIDDEN",
      "message": "image_url must be the URL of a property showcase you generated",
      "request_id": "req_ZfI8Az4uwbSqXlzKUAtDg"
    }
  }
  ```

  ```json 415 Unsupported Media Type theme={null}
  {
    "error": {
      "code": "INVALID_CONTENT_TYPE",
      "message": "Request Content-Type must be application/json",
      "request_id": "req_ZfI8Az4uwbSqXlzKUAtDg"
    }
  }
  ```
</ResponseExample>

## Webhook Payload

If you provide a `webhook_url`, VibePeak sends a POST request when the job finishes, carrying `X-VibePeak-Event`, `X-VibePeak-Signature` and `X-VibePeak-Timestamp` headers. Delivery is attempted up to 3 times with exponential backoff; a `4xx` response from your endpoint stops the retries. See [Webhooks](/concepts/webhooks) for signature verification.

Edit jobs use the same payload shape as generation jobs, with `kind` set to `edit`. An edit has no template, so `template_id` is an empty string.

### Completed

```json theme={null}
{
  "event": "showcase.completed",
  "job_id": "3301b1dc-9d3f-4754-8a22-08956432b812",
  "status": "succeeded",
  "kind": "edit",
  "template_id": "",
  "output_image_url": "https://vibepeak-property-showcase-production.s3.eu-west-1.amazonaws.com/showcases/4d7fbd2c-8675-4fa9-aa62-570d4088b8a5/edit_vertex_1788184618383.jpg",
  "error": null,
  "created_at": "2026-08-31T13:56:53.109+00:00",
  "completed_at": "2026-08-31T13:57:11.529+00:00"
}
```

### Failed

```json theme={null}
{
  "event": "showcase.failed",
  "job_id": "3301b1dc-9d3f-4754-8a22-08956432b812",
  "status": "failed",
  "kind": "edit",
  "template_id": "",
  "output_image_url": null,
  "error": "An unexpected error occurred while generating the property showcase. Please try again later.",
  "created_at": "2026-08-31T13:56:53.109+00:00",
  "completed_at": "2026-08-31T13:57:11.529+00:00"
}
```

<Note>
  On failure `error` is always the fixed string above — the underlying provider message is never exposed. Use `request_id` from the edit call when contacting support.
</Note>

## Error Codes

| Code                         | Status | Description                                                                                                                           |
| ---------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `VALIDATION_ERROR`           | 400    | Invalid request parameters, missing source image, missing editing directive, prompt over 250 characters, or invalid image/webhook URL |
| `SSRF_BLOCKED`               | 400    | `image_url` points to a private/internal network or is otherwise not allowed                                                          |
| `INVALID_JSON`               | 400    | Request body is not valid JSON                                                                                                        |
| `INVALID_API_KEY`            | 401    | Invalid or missing API key                                                                                                            |
| `INSUFFICIENT_CREDITS`       | 402    | Not enough credits to process the request                                                                                             |
| `FORBIDDEN`                  | 403    | `image_url` is not the result of a property showcase you generated                                                                    |
| `PLAN_REQUIRED`              | 403    | Plan doesn't include API access                                                                                                       |
| `NOT_FOUND`                  | 404    | The referenced source showcase result does not exist                                                                                  |
| `INVALID_CONTENT_TYPE`       | 415    | `Content-Type` header is missing or is not `application/json`                                                                         |
| `CONCURRENCY_LIMIT_EXCEEDED` | 429    | Concurrent job limit reached                                                                                                          |
| `EXTERNAL_SERVICE_ERROR`     | 502    | The image edit service could not be reached; the job is marked failed                                                                 |

<Note>
  **Error shape.** `VALIDATION_ERROR` responses carry a `details` object of the form `{ "field": "<dotted.path>", "issues": [{ "path": "<dotted.path>", "message": "..." }] }` — `field` is the first failing path and `issues` lists every failure. All other error codes return no `details`.
</Note>

See [Error Handling](/concepts/error-handling) for more details.

## Credits

This endpoint charges **2 credits** per request for accounts **without an annual plan**. Accounts on an **annual plan are not charged** (0 credits). Credits are charged upon successful job creation and are not refunded if the edit fails.

<Warning>
  **Test mode is not supported on this endpoint.** Unlike the video endpoints, a `vpk_test_` key is not sandboxed here — the request runs the real generation pipeline and charges credits. Use it only when you intend to generate a real image. See [Test Mode](/concepts/test-mode).
</Warning>

## Next Steps

After creating a job:

1. **Wait for the webhook**: your `webhook_url` receives a `showcase.completed` or `showcase.failed` notification. Verify the `X-VibePeak-Signature` header as described in [Webhooks](/concepts/webhooks).
2. **Download the image**: read `output_image_url` from the `showcase.completed` payload.

An edited image is itself a showcase you own, so its `output_image_url` can be passed straight back into this endpoint to chain another edit.

<Warning>
  There is no polling endpoint for showcase jobs. [Get Task](/api-reference/videos/get-task) serves video tasks only and returns `404 TASK_NOT_FOUND` for a showcase `job_id`. If you do not set a `webhook_url`, the edited image cannot be retrieved.
</Warning>
