---
title: "Update a website request form"
url: https://docs.light-ai.app/api-reference/website-forms/update-form.md
canonical_url: https://docs.light-ai.app/api-reference/website-forms/update-form
last_updated: 2026-10-13
summary: "PATCH /v1/forms/{form_id}: Pause or resume, rename, edit the wording, replace the allowed websites, or change the instant preview (on/off; one image per request at 5 credits, `images_per_lead` 1 or 2 is accepted for compatibility and stored as 1; monthly credit cap 5"
api_status: beta
related:
  - https://docs.light-ai.app/api-reference/website-forms
---

# Update a website request form

> PATCH /v1/forms/{form_id}: Pause or resume, rename, edit the wording, replace the allowed websites, or change the instant preview (on/off; one image per request at 5 credits, `images_per_lead` 1 or 2 is accepted for compatibility and stored as 1; monthly credit cap 5

`PATCH /v1/forms/{form_id}`

Pause or resume, rename, edit the wording, replace the allowed websites, or change the instant preview (on/off; one image per request at 5 credits, `images_per_lead` 1 or 2 is accepted for compatibility and stored as 1; monthly credit cap 5–5000; turning previews on without a cap sets 250). `expected_updated_at` is required (409 `stale_state` when the form changed). Changing the websites or turning previews on sends the account owners a notice and appears in Developers → Activity. Pro only; test keys are refused. Needs `forms:write`, which is never part of a preset.

|   |   |
| --- | --- |
| Scope | `forms:write` |
| Credential | API key: `Authorization: Bearer lai_live_…` (or `lai_test_…`). Partner keys add `Light-AI-Organization`. |
| Test mode | Live keys only (answers 400 `test_mode_unsupported` to a test key). |

## Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `form_id` | string | yes | The form id (`form_…`). Id prefix `form_`. |

## Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `Light-AI-Organization` | string |  | Partner keys: the connected organization to act for (`org_…`). Organization keys may omit it. Id prefix `org_`. |
| `Light-AI-Version` | string |  | Pin a dated API version (defaults to the key's version). One of: `2026-11-01`. |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `expected_updated_at` | timestamp | yes | The form's updated_at you last read (409 stale_state otherwise). |
| `name` | string |  | 1–120 characters. |
| `status` | string |  | One of: `active`, `paused`. |
| `domains` | string[] |  | Replaces the allowed websites (origins). At most 25 items. |
| `copy` | object |  |  |
| `copy.heading` | string |  | 1–120 characters. |
| `copy.subheading` | string |  | 1–300 characters. |
| `copy.button_label` | string |  | 1–80 characters. |
| `copy.success_message` | string |  | 1–300 characters. |
| `instant_preview` | object |  |  |
| `instant_preview.enabled` | boolean |  |  |
| `instant_preview.images_per_lead` | number |  |  |
| `instant_preview.monthly_credit_cap` | integer |  | 5–5000. |

## Response 200

The updated form.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Id prefix `form_`. |
| `object` | string | yes | Always `form`. |
| `name` | string | yes |  |
| `status` | string | yes | One of: `active`, `paused`, `archived`. |
| `service_type` | string | yes | One of: `christmas_lights`, `custom`. |
| `public_id` | string | yes | The id in the install snippet (`ec_…`). |
| `install` | object | yes |  |
| `install.script` | string | yes | The snippet to paste on the website. |
| `install.iframe_url` | string | yes |  |
| `install.hosted_url` | string \| null | yes | Top-level page link (null unless the hosted page is on). |
| `domains` | string[] | yes | Websites allowed to show the form (origins; `https://*.example.com` wildcards). |
| `hosted_page_enabled` | boolean | yes |  |
| `fields` | object | yes |  |
| `photo` | object | yes |  |
| `consent` | object | yes |  |
| `questions` | object[] | yes | In the order the form asks them (the owner can drag questions in the editor; follow-ups always come right after their parent question). The fixed address & photo card follows `surfaces` and `primary_color`, then contact details; the request is sent from the last question. |
| `questions[].key` | string | yes |  |
| `questions[].prompt` | string | yes |  |
| `questions[].type` | string | yes | One of: `color`, `choice`, `multi_choice`, `toggle`, `text`. |
| `questions[].enabled` | boolean | yes |  |
| `questions[].required` | boolean | yes |  |
| `questions[].choices` | object[] | yes |  |
| `questions[].choices[].value` | string | yes |  |
| `questions[].choices[].label` | string | yes |  |
| `questions[].choices[].fullWidth` | boolean |  | Shown across the whole row when it starts one (e.g. ASAP). |
| `questions[].choices[].availableUntil` | object |  | The last day of the year it can be picked (recurring, the contractor's timezone); afterwards the form greys it out and a submitted pick is refused. Example: October until { month: 10, day: 31 }. |
| `mockup_reveal` | object |  | How the request's AI mockup appears on the form (it starts on the homeowner's photo): `slider` (default, a draggable before/after divider), `flicker`, `day_night`, `bloom`, `sweep` or `curtain`; `slider_after` hands the five other styles over to the slider when they end. The form's own choice, else the company default. Set in the app. |
| `mockup_reveal.style` | string | yes | One of: `slider`, `flicker`, `day_night`, `bloom`, `sweep`, `curtain`. |
| `mockup_reveal.slider_after` | boolean | yes |  |
| `copy` | object | yes |  |
| `copy.heading` | string \| null | yes |  |
| `copy.subheading` | string \| null | yes |  |
| `copy.button_label` | string \| null | yes |  |
| `copy.success_message` | string \| null | yes |  |
| `instant_preview` | object | yes |  |
| `instant_preview.enabled` | boolean | yes |  |
| `instant_preview.images_per_lead` | integer | yes | AI mockup previews per request: always 1 (one AI mockup per request since October 2026). Kept for compatibility: PATCH still accepts 1 or 2 and stores 1. |
| `instant_preview.credits_per_image` | integer | yes | Always 5. |
| `instant_preview.monthly_credit_cap` | integer \| null | yes |  |
| `instant_preview.used_this_month` | integer | yes | Credits used or held by previews this UTC month. |
| `created_at` | timestamp | yes | RFC 3339 timestamp (UTC, milliseconds). |
| `updated_at` | timestamp | yes | RFC 3339 timestamp (UTC, milliseconds). |

## Errors

| Status | Meaning |
| --- | --- |
| `400` | Bad request. [`bad_request`](https://docs.light-ai.app/errors#bad_request), [`test_mode_mismatch`](https://docs.light-ai.app/errors#test_mode_mismatch), [`test_mode_unsupported`](https://docs.light-ai.app/errors#test_mode_unsupported), [`unsupported_api_version`](https://docs.light-ai.app/errors#unsupported_api_version) |
| `401` | Missing or invalid credential. [`authentication_required`](https://docs.light-ai.app/errors#authentication_required), [`invalid_api_key`](https://docs.light-ai.app/errors#invalid_api_key), [`api_key_revoked`](https://docs.light-ai.app/errors#api_key_revoked), [`api_key_expired`](https://docs.light-ai.app/errors#api_key_expired), [`api_key_suspended`](https://docs.light-ai.app/errors#api_key_suspended) |
| `403` | Not allowed. [`insufficient_scope`](https://docs.light-ai.app/errors#insufficient_scope), [`plan_upgrade_required`](https://docs.light-ai.app/errors#plan_upgrade_required), [`organization_paused`](https://docs.light-ai.app/errors#organization_paused), [`organization_off_season`](https://docs.light-ai.app/errors#organization_off_season), [`api_disabled`](https://docs.light-ai.app/errors#api_disabled), [`api_access_not_enabled`](https://docs.light-ai.app/errors#api_access_not_enabled), [`ip_not_allowed`](https://docs.light-ai.app/errors#ip_not_allowed), [`forbidden`](https://docs.light-ai.app/errors#forbidden) |
| `404` | Not found (also for objects of other organizations and for organizations without an active grant). |
| `409` | Conflict. [`stale_state`](https://docs.light-ai.app/errors#stale_state), [`conflict`](https://docs.light-ai.app/errors#conflict), [`idempotency_in_progress`](https://docs.light-ai.app/errors#idempotency_in_progress), [`idempotency_scope_mismatch`](https://docs.light-ai.app/errors#idempotency_scope_mismatch), [`quote_already_decided`](https://docs.light-ai.app/errors#quote_already_decided), [`quote_not_current`](https://docs.light-ai.app/errors#quote_not_current), [`billing_review_required`](https://docs.light-ai.app/errors#billing_review_required) |
| `413` | Request bodies are limited to 256 KB. |
| `415` | The body is not JSON: send Content-Type: application/json. [`unsupported_media_type`](https://docs.light-ai.app/errors#unsupported_media_type) |
| `422` | Validation failed. [`idempotency_key_reused`](https://docs.light-ai.app/errors#idempotency_key_reused) |
| `429` | Rate limited or daily cap reached. See Retry-After and the RateLimit headers. [`rate_limited`](https://docs.light-ai.app/errors#rate_limited), [`daily_cap_reached`](https://docs.light-ai.app/errors#daily_cap_reached) |
| `500` | Unexpected server error. Safe to retry with exponential backoff; quote the Request-Id to support. [`internal_error`](https://docs.light-ai.app/errors#internal_error) |
| `503` | Temporarily unavailable. Retry with exponential backoff. [`service_unavailable`](https://docs.light-ai.app/errors#service_unavailable) |

## Examples

**Request**

curl:

```bash
curl -X PATCH "https://api.light-ai.app/v1/forms/{form_id}" \
  -H "Authorization: Bearer $LIGHT_AI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "expected_updated_at": "2026-10-14T15:02:11.120Z",
  "status": "paused"
}'
```

JavaScript:

```javascript
const response = await fetch('https://api.light-ai.app/v1/forms/{form_id}', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.LIGHT_AI_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "expected_updated_at": "2026-10-14T15:02:11.120Z",
    "status": "paused"
  }),
});
const data = await response.json();
```

Python:

```python
import os, uuid, requests

response = requests.patch(
    "https://api.light-ai.app/v1/forms/{form_id}",
    headers={
        "Authorization": f"Bearer {os.environ['LIGHT_AI_API_KEY']}",
    },
    json={
      "expected_updated_at": "2026-10-14T15:02:11.120Z",
      "status": "paused"
    },
    timeout=30,
)
data = response.json()
```

**Response 200**

```json
{
  "id": "form_3f9c2a7be41d…",
  "object": "form",
  "name": "Dana Reyes",
  "status": "active",
  "service_type": "christmas_lights",
  "public_id": "string",
  "install": {
    "script": "string",
    "iframe_url": "https://example.com/…",
    "hosted_url": "https://example.com/…"
  },
  "domains": [
    "string"
  ],
  "hosted_page_enabled": true,
  "fields": {},
  "photo": {},
  "consent": {},
  "questions": [
    {
      "key": "string",
      "prompt": "string",
      "type": "color",
      "enabled": true,
      "required": true,
      "choices": [
        {
          "value": "string",
          "label": "string",
          "fullWidth": true,
          "availableUntil": {
            "month": 0,
            "day": 0
          }
        }
      ]
    }
  ],
  "mockup_reveal": {
    "style": "slider",
    "slider_after": true
  },
  "copy": {
    "heading": "string",
    "subheading": "string",
    "button_label": "string",
    "success_message": "string"
  },
  "instant_preview": {
    "enabled": true,
    "images_per_lead": 1,
    "credits_per_image": 0,
    "monthly_credit_cap": 0,
    "used_this_month": 0
  },
  "created_at": "2026-10-12T14:03:11.000Z",
  "updated_at": "2026-10-12T14:03:11.000Z"
}
```
