From API key to published version: add a link, move it to the top, change the design, validate the draft and publish – with curl and jq.
In this example you update an existing page entirely via the API: you add a link, move it to the top, change the button style, validate the draft and publish it. The commands run in any shell with curl and jq.
1. Create an API key
- Open “Callback & API” in the dashboard
- Click “+ Create new key”
- Choose “Write” for both “Pages” and “Links” (this includes read access)
- Click “Create key” and copy the key secret
- Copy the key ID from the list
Store both values as environment variables, not in the script:
export LH_KEY_ID="6F668EA6598B0692"
export LH_KEY_SECRET="lt_…"
export API="https://dock.at-harbor.com" 2. Get an access token
LH_TOKEN=$(curl -sS -X POST "$API/v1/token" \
-H "Content-Type: application/json" \
-d "{\"key_id\":\"$LH_KEY_ID\",\"key_secret\":\"$LH_KEY_SECRET\"}" \
| jq -r .token) The token is valid for 15 minutes. If your script runs longer, get a new one in between.
3. Find the page
curl -sS "$API/v1/pages" -H "Authorization: Bearer $LH_TOKEN" \
| jq '.items[] | {id, handle, title, status, current_version}' Note the page's id. In this example it is 42:
PAGE_ID=42 4. Add a link
LINK_ID=$(curl -sS -X POST "$API/v1/pages/$PAGE_ID/links" \
-H "Authorization: Bearer $LH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Autumn sale","target_url_template":"https://shop.example.com/autumn","enabled":true}' \
| jq -r .id)
echo "New link: $LINK_ID" The API responds with HTTP 201 and the new ID. New links go to the end of the list. If your plan's link limit is reached, you get 409 link_limit_reached.
5. Move the link to the top
curl -sS -X POST "$API/v1/pages/$PAGE_ID/links/reorder" \
-H "Authorization: Bearer $LH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"order\":[$LINK_ID]}" It is enough to send the IDs that should move to the front. All other links keep their order behind them.
6. Change the design in the draft
theme_config is replaced completely on PATCH. So you read the draft first, change only the value you want and send the whole object back:
THEME=$(curl -sS "$API/v1/pages/$PAGE_ID/draft" -H "Authorization: Bearer $LH_TOKEN" \
| jq -c '(.theme_config // {}) | .button_style = "pill"')
curl -sS -X PATCH "$API/v1/pages/$PAGE_ID/draft" \
-H "Authorization: Bearer $LH_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"theme_config\":$THEME}" button_style accepts rounded, pill and square. Other theme_config fields include bg_color, text_color, button_color, button_text_color and background.
7. Validate the draft
curl -sS "$API/v1/pages/$PAGE_ID/draft/validate" -H "Authorization: Bearer $LH_TOKEN"
# → {"ok":true,"valid":true,"issues":[]} If valid is false, issues lists the problems. Fix them before you publish. Whether the draft differs from the live version at all is shown by GET /v1/pages/$PAGE_ID in the has_unpublished_changes field – if it is false, you can skip publishing.
8. Publish
curl -sS -X POST "$API/v1/pages/$PAGE_ID/publish" -H "Authorization: Bearer $LH_TOKEN"
# → {"ok":true,"version":8,"checksum":"…"} Visitors now see the new version – including the new link, the order and the design. If you have a webhook for page.published, it now receives a delivery. If the draft cannot be published, the API responds with 409 draft_not_publishable and the list in details.issues.
9. Undo
If you do not like the result, bring the previous version back into the draft and publish again:
curl -sS -X POST "$API/v1/pages/$PAGE_ID/versions/7/restore" -H "Authorization: Bearer $LH_TOKEN"
curl -sS -X POST "$API/v1/pages/$PAGE_ID/publish" -H "Authorization: Bearer $LH_TOKEN" Instead of a version number you can pass current: this discards all draft changes since the last publish. The restore brings back the design, template, custom CSS, SEO, blocks and links; title, handle and visibility are not versioned.
Common errors
- 400 bad_request
- The Content-Type: application/json header is missing. Without it, curl -d sends a different type.
- 401 invalid_token
- The token expired after 15 minutes. Get a new one.
- 403 insufficient_scope
- The key is missing a scope, for example “Write” for links.
- Design suddenly reset
- theme_config was sent with only one field and replaced the whole theme. Send the complete theme again, or use restore current to discard all draft changes since the last publish.
All codes are explained in Error format and error codes, and all endpoints are listed in Endpoints by area.
You might also find this useful
Nothing found?
Tell us what you need, and our support team will help you personally.