Documentation

API

Create and publish websites from a script or an agent.

The API lets a script, a CI pipeline or an AI agent create a website and publish files to it, with one token for your whole account. It is available on paid plans.

Ask your agent

If you use Claude Code or another agent that can run commands, create a token in your account settings, set it as LOVELYCODE_TOKEN in the agent’s environment, and ask for what you want:

What to say
Publish the website in ./site to Lovelycode as "My Portfolio".
The API is described at https://lovelycode.app/llms.txt and the token is in $LOVELYCODE_TOKEN.
Tell me the address when it is live.

Everything on these pages is also served as plain text at /llms.txt, so an agent can read it directly. Keep the token in the environment rather than in the conversation, so it stays out of the transcript.

Create a token

Open Account, then API, and press Create token. Give it a name that says where it lives, choose how long it lasts, and copy it: it is shown once. Revoke it from the same place if it leaks or you stop using it.

A token can create websites and publish files to websites that take their files from the API. It cannot read files or settings, delete a website, or touch a website whose files are managed in the dashboard or synced from GitHub. A website created through the API is read only in the dashboard while its deploy key exists, and publishes automatically unless you turn that off in its settings.

Create a website

create.sh
curl -fsS -X POST https://lovelycode.app/api/v1/sites \
  -H "Authorization: Bearer $LOVELYCODE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"My Portfolio"}'

# → { "site": { "id": "…", "url": "https://my-portfolio.lovelyco.de", … }, "deploy_key": "lc_deploy_…" }

Add "subdomain" to the body to choose the address yourself; without it, one is made from the name. The response holds the website’s id and address, plus a deploy key. The key can publish to that one website and nothing else, which is what a CI pipeline should hold. Either the token or the key works for the steps below.

Publish files

A publish is three calls: ask for an upload address, upload a zip of your files, then commit. Zip the contents of your folder so index.html is at the top level. Needs jq.

deploy.sh
# Needs jq. Zip the CONTENTS of your build folder, so index.html is at the top.
(cd site && zip -qr ../site.zip .)
SIZE=$(wc -c < site.zip)

# 1. Ask for a one-time upload URL. The size is signed into it, so it must be exact.
DEPLOY=$(curl -fsS -X POST https://lovelycode.app/api/v1/sites/$SITE_ID/deploys \
  -H "Authorization: Bearer $LOVELYCODE_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"size\":$SIZE}")

# 2. Upload the archive straight to storage. Raw body, not multipart.
curl -fsS -X PUT "$(echo "$DEPLOY" | jq -r .deploy.upload_url)" \
  -H "Content-Type: application/zip" \
  --data-binary @site.zip

# 3. Publish. Unchanged files are skipped, files you removed are deleted.
curl -fsS -X POST https://lovelycode.app/api/v1/sites/$SITE_ID/deploys/$(echo "$DEPLOY" | jq -r .deploy.id)/commit \
  -H "Authorization: Bearer $LOVELYCODE_TOKEN"

# A 429 or 409 means another deploy is too close or still running. Both are safe to
# retry after a few seconds; anything else will not improve with waiting.

Files that have not changed are skipped, files you removed are deleted, and an identical build changes nothing. Each publish makes a version, so a mistake is one Restore away in the dashboard. Publish the same way to update the website later.

Endpoints

CallWhat it does
POST /api/v1/sitesCreates a website. Body: name, optional subdomain and description. Returns the site and a deploy key.
POST /api/v1/sites/{id}/deploysStarts a publish. Body: size, the exact byte length of the zip. Returns an upload address, valid for 15 minutes.
POST /api/v1/sites/{id}/deploys/{deployId}/commitApplies the uploaded zip and publishes a version.

Send the token as Authorization: Bearer on every call. A site’s deploy key works in place of the token for its own deploys.

Errors

Every error is JSON with a sentence in error and a stable code. When retryable is true, wait a few seconds and try again; anything else will not improve with waiting.

CodeMeaning
unauthorizedThe token is missing, wrong, revoked or expired. The message says which.
plan_requiredThe account is not on a paid plan.
invalid_requestSomething in the body is missing or the wrong shape. The message says what.
forbiddenA deploy key was used where an account token is needed, or the token lacks the scope.
not_foundNo website with that id belongs to this account, or the archive was not uploaded before committing.
subdomain_takenThe address you chose is in use. Pick another, or leave it out to have one made from the name.
files_source_conflictThe website’s files come from the dashboard or GitHub. Create a deploy key in its settings to publish through the API.
deploy_running, rate_limited, deploy_conflictAnother publish is in progress, too recent, or landed while yours was publishing. All retryable.
archive_invalid, archive_too_largeThe zip has a bad path, no index.html at the top level, or is over 100 MB. Zip the folder’s contents, not the folder.
website_limit_reached, storage_limit_reachedA plan limit. Delete something or upgrade.
internalOur side. Try again in a minute; if it persists, tell us.

What the API does not do

  • Read anything. There is no call that returns your files, settings or passwords, so a leaked token cannot take anything out.
  • Delete a website or change its settings. Those stay in the dashboard, behind your login.
  • Edit single files. A publish is always a whole set of files, which is what makes a version.
  • Run more than one publish per website at a time, or two within ten seconds.

Last updated 11 September 2026