# Edit the landing page

> Read the catalog and the page, change the draft with operations, and publish in two calls with the extra permission.

Last updated: 2026-10-30. Source: https://docs.smartchat.marketing/en/guides/build-a-landing-page/

You need `landing:read` and `landing:write`. Publishing needs `landing:publish`, which has its own checkbox in the app. The answers on this page belong to one story: the text of a bakery page is changed and published.

## 1. Read the catalog and the page

The catalog lists the building blocks and the fields of each block. You choose blocks and fill fields; you do not write HTML.

**curl**

```bash
curl "https://api.smartchat.marketing/v1/landing-catalog" \
  -H "Authorization: Bearer $SMARTCHAT_API_KEY"
```

**Python**

```python
import os
import requests

response = requests.get(
    "https://api.smartchat.marketing/v1/landing-catalog",
    headers={"Authorization": "Bearer " + os.environ["SMARTCHAT_API_KEY"]},
)
response.raise_for_status()
print(response.json())
```

**TypeScript**

```typescript
const response = await fetch("https://api.smartchat.marketing/v1/landing-catalog", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.SMARTCHAT_API_KEY}`,
  },
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

Response (200):


```json
{
  "object": "landing_catalog",
  "version": "2d26379cee484580",
  "operations": [
    {
      "type": "replace_text"
    }
  ]
}
```

**curl**

```bash
curl "https://api.smartchat.marketing/v1/landing-page?include=text" \
  -H "Authorization: Bearer $SMARTCHAT_API_KEY"
```

**Python**

```python
import os
import requests

response = requests.get(
    "https://api.smartchat.marketing/v1/landing-page",
    headers={"Authorization": "Bearer " + os.environ["SMARTCHAT_API_KEY"]},
    params={
        "include": "text",
    },
)
response.raise_for_status()
print(response.json())
```

**TypeScript**

```typescript
const response = await fetch("https://api.smartchat.marketing/v1/landing-page?include=text", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.SMARTCHAT_API_KEY}`,
  },
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

Response (200):


```json
{
  "object": "landing_page",
  "status": "live",
  "url": "https://example-shop.smartchat.marketing",
  "structure": "free",
  "language": "en",
  "title": "Example Shop",
  "description": "Fresh bread every morning.",
  "has_draft": false,
  "draft_changed_at": null,
  "previous_version_available": true,
  "previous_version_saved_at": "2026-10-29T18:00:00Z",
  "never_offline": true,
  "protected_parts": [
    "signup",
    "footer",
    "cookie_banner"
  ],
  "updated_at": "2026-10-29T18:00:00Z",
  "text_blocks": [
    {
      "index": 0,
      "text": "Fresh bread every morning"
    }
  ]
}
```

## 2. Change the draft

You send a list of operations. If any operation is rejected, **nothing** is changed and the answer lists the rejected ones ([`landing_ops_rejected`](https://docs.smartchat.marketing/en/errors/#landing_ops_rejected)). The live page does not change yet; the answer says `has_draft: true`.

**curl**

```bash
curl -X POST "https://api.smartchat.marketing/v1/landing-page/operations" \
  -H "Authorization: Bearer $SMARTCHAT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "operations": [
    {
      "type": "replace_text",
      "find": "Fresh bread every morning",
      "replace": "Fresh bread from 6 o'\''clock"
    }
  ]
}'
```

**Python**

```python
import os
import uuid
import requests

response = requests.post(
    "https://api.smartchat.marketing/v1/landing-page/operations",
    headers={
        "Authorization": "Bearer " + os.environ["SMARTCHAT_API_KEY"],
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "operations": [
            {
                "type": "replace_text",
                "find": "Fresh bread every morning",
                "replace": "Fresh bread from 6 o'clock",
            },
        ],
    },
)
response.raise_for_status()
print(response.json())
```

**TypeScript**

```typescript
const response = await fetch("https://api.smartchat.marketing/v1/landing-page/operations", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SMARTCHAT_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "operations": [
      {
        "type": "replace_text",
        "find": "Fresh bread every morning",
        "replace": "Fresh bread from 6 o'clock"
      }
    ]
  }),
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

Response (200):


```json
{
  "object": "landing_page_operations",
  "applied": 1,
  "dry_run": false,
  "diff": [
    {
      "operation": 0,
      "field": "text",
      "before": "Fresh bread every morning",
      "after": "Fresh bread from 6 o'clock"
    }
  ],
  "has_draft": true
}
```

## 3. Publish

Publishing takes **two calls**. Both are `PUT` to the same address and both need an `Idempotency-Key`, a **different** one each time.

**3a. Preview.** Call without `plan_id`. Nothing goes live. The answer shows how many fields change and lists problems (`issues`). If the checker finds a problem, the answer is [`landing_not_publishable`](https://docs.smartchat.marketing/en/errors/#landing_not_publishable). The `plan_id` is valid for 10 minutes and can be used once.

**curl**

```bash
curl -X PUT "https://api.smartchat.marketing/v1/landing-page/publication" \
  -H "Authorization: Bearer $SMARTCHAT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**Python**

```python
import os
import uuid
import requests

response = requests.put(
    "https://api.smartchat.marketing/v1/landing-page/publication",
    headers={
        "Authorization": "Bearer " + os.environ["SMARTCHAT_API_KEY"],
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={},
)
response.raise_for_status()
print(response.json())
```

**TypeScript**

```typescript
const response = await fetch("https://api.smartchat.marketing/v1/landing-page/publication", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.SMARTCHAT_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({}),
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

Response (200):


```json
{
  "object": "landing_publish_plan",
  "plan_id": "pln_9f8e7d6c5b4a43210fedcba987654321",
  "expires_at": "2026-10-30T09:51:07Z",
  "changed_fields": 1,
  "issues": []
}
```

**3b. Publish.** Call again with the `plan_id`. Now the draft becomes the live page.

**curl**

```bash
curl -X PUT "https://api.smartchat.marketing/v1/landing-page/publication" \
  -H "Authorization: Bearer $SMARTCHAT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "plan_id": "pln_9f8e7d6c5b4a43210fedcba987654321"
}'
```

**Python**

```python
import os
import uuid
import requests

response = requests.put(
    "https://api.smartchat.marketing/v1/landing-page/publication",
    headers={
        "Authorization": "Bearer " + os.environ["SMARTCHAT_API_KEY"],
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "plan_id": "pln_9f8e7d6c5b4a43210fedcba987654321",
    },
)
response.raise_for_status()
print(response.json())
```

**TypeScript**

```typescript
const response = await fetch("https://api.smartchat.marketing/v1/landing-page/publication", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.SMARTCHAT_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "plan_id": "pln_9f8e7d6c5b4a43210fedcba987654321"
  }),
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

Response (200):


```json
{
  "object": "landing_page",
  "status": "live",
  "url": "https://example-shop.smartchat.marketing",
  "structure": "free",
  "language": "en",
  "title": "Example Shop",
  "description": "Fresh bread every morning.",
  "has_draft": false,
  "draft_changed_at": null,
  "previous_version_available": true,
  "previous_version_saved_at": "2026-10-30T09:42:00Z",
  "never_offline": true,
  "protected_parts": [
    "signup",
    "footer",
    "cookie_banner"
  ],
  "updated_at": "2026-10-30T09:42:00Z"
}
```

The live page is never taken offline and the previous version is kept; you can bring it back with [`restore_landing_page`](https://docs.smartchat.marketing/en/api/reference/landing/restore_landing_page/). Restoring replaces an unpublished draft for good, so it is refused unless you send `discard_draft: true`.

| Code | Meaning |
|---|---|
| [`plan_expired`](https://docs.smartchat.marketing/en/errors/#plan_expired) | The `plan_id` is older than 10 minutes. Do step 3a again. |
| [`plan_already_used`](https://docs.smartchat.marketing/en/errors/#plan_already_used) | The `plan_id` was already used. Do step 3a again. |
| [`plan_changed`](https://docs.smartchat.marketing/en/errors/#plan_changed) | The draft changed after the preview. Do step 3a again. |
| [`landing_not_publishable`](https://docs.smartchat.marketing/en/errors/#landing_not_publishable) | The checker found problems. Read `issues`, fix the draft and try again. |

