Edit the 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 "https://api.smartchat.marketing/v1/landing-catalog" \
-H "Authorization: Bearer $SMARTCHAT_API_KEY"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())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):
{
"object": "landing_catalog",
"version": "2d26379cee484580",
"operations": [
{
"type": "replace_text"
}
]
}curl "https://api.smartchat.marketing/v1/landing-page?include=text" \
-H "Authorization: Bearer $SMARTCHAT_API_KEY"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())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):
{
"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). The live page does not change yet; the answer says has_draft: true.
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"
}
]
}'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())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):
{
"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. The plan_id is valid for 10 minutes and can be used once.
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 '{}'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())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):
{
"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 -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"
}'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())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):
{
"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. Restoring replaces an unpublished draft for good, so it is refused unless you send discard_draft: true.
| Code | Meaning |
|---|---|
plan_expired | The plan_id is older than 10 minutes. Do step 3a again. |
plan_already_used | The plan_id was already used. Do step 3a again. |
plan_changed | The draft changed after the preview. Do step 3a again. |
landing_not_publishable | The checker found problems. Read issues, fix the draft and try again. |