Skip to main content

JSON events schema

The CLI switches to JSON-lines mode automatically when it runs inside an AI agent (Cursor, Claude Code) or when stdout is not a TTY. You can also turn it on explicitly with the --json flag or LAYERO_JSON=1.

In this mode the CLI:

  • asks no questions — every interactive confirmation is skipped (--prod still needs --yes);
  • prints one {"event":"…", …} line per action on stdout;
  • reports errors with a stable code and a next_action;
  • adds a ts field (ISO-8601 timestamp) to every event.

Events

Each line is a standalone JSON object. Route on the event field.

auth_required

The CLI has started the device-flow login. Show the URL to the user as a clickable link.

fieldtypenote
urlstringe.g. https://app.layero.ru/cli?code=ABCD-1234
user_codestringe.g. ABCD-1234 — also shown on the confirmation page

The CLI keeps polling every 2 seconds. Once the user confirms, authorized follows. On expiry you get error{code: "auth_expired" | "auth_timeout"}.

authorized

Login succeeded.

fieldtype
userstring — username, email or user id

detected

Framework auto-detection finished.

fieldtype
frameworkstring — next/vite/astro/sveltekit/nuxt/gatsby/cra/docusaurus/static
build_cmdstring
output_dirstring
confidentboolean — false for the static fallback

project_created

First deploy in this directory. A new project was created.

fieldtype
project_idstring
slugstring
organizationstring — organization slug

project_linked

Deploy into an existing project (cwd is linked via .layero/project.json).

fieldtype
project_idstring
slugstring

packing

The CLI packed the directory into a tar.gz.

fieldtype
filesnumber
bytesnumber
sha256string

uploading

Archive upload to S3 started. No extra fields.

uploaded

Upload succeeded.

fieldtype
archive_keystring

prebuilt

The deploy ships a ready-made build (--prebuilt <dir>) — installing dependencies and building on the platform side are skipped.

fieldtype
dirstring — the directory holding the artifact

runtime_type_applied

The project was recognised as a runtime application and its type was set automatically.

fieldtype
project_typessr_next · node_web · python_web · streamlit · gradio · flask

runtime_type_apply_failed

The type was detected but could not be applied. The deploy continues with the project's previous type.

fieldtype
errorstring

setup_applied

Project settings (framework_hint / build_cmd / output_dir) were applied on the first deploy. No fields.

repeated_failure_guard

Consecutive builds failed with the same error, so the platform stopped before starting another one. The event carries the error text itself: an agent that reached the repeat usually never read it — it arrives at the end of a long build log while the agent looks at the exit code.

Next the CLI either asks for confirmation (interactive terminal) or exits with the repeated_failure code. It cannot continue automatically — that is exactly the loop this rule breaks.

fieldtype
streaknumber — how many failures with this error were counted
thresholdnumber — the stop threshold
scope"project" | "owner" — where it was counted: in this project, or summed across all your projects
failure_stagestring, optional — build stage
errorstring, optional — error text

scope: "owner" answers the first question this raises: "I only built here three times, where does ten come from?". The same error is also counted across all of the owner's projects — moving the app into a fresh project does not get around the rule, because the cause is not the project.

deploy_started

The backend accepted the job.

fieldtype
deploy_idstring

stage

The build moved to a new stage.

fieldtype
nameclone/install/build/upload/activate

build_log

One line of build output. Only worth forwarding to the user when it contains an error — successful builds produce a lot of noise.

fieldtype
linestring
streamstdout/stderr

ready

The final event. The deploy is live. Show url to the user and stop.

fieldtypenote
urlstringThe live public address of the site — not the dashboard. For a plain layero deploy of a CLI project this is the project's production address (CLI uploads auto-promote to the apex). For a deploy into a named branch (--branch) it is that branch's preview address. It is reachable straight away; this is the link to open and to show the user.
dashboard_urlstring?The project management page (https://app.layero.ru/projects/<id>). This is not the site — never hand it over as the link to the finished site.
preview_urlstring?Legacy, no longer emitted. A separate per-deploy preview host in the *.preview.layero.ru zone. It existed to give out a link while the apex warmed up on the CDN. layero.app has no separate preview zone and no user sites remain on layero.ru, so the field is never populated.
edge_readybool?Whether the address answers at the moment the deploy finishes. The field used to mean "the apex warmed up on the CDN" and stayed false forever for new hosts; it now comes from a real probe. You still should not gate on it — the address is live immediately.
edge_eta_secondsnumber?Legacy, no longer emitted. An estimate of the remaining CDN warm-up. There is nothing to propagate — user sites do not sit behind a CDN.
deploy_idstring

The apex now points at the given deploy. Emitted by layero promote and by layero deploy --promote.

fieldtype
urlstring — the public address
deploy_idstring

error

fieldtype
codestring — see the table below
next_actionstring — the concrete command or URL that resolves it
messagestring — human-readable description

Error codes

Checked against the CLI sources: these are all the codes it actually emits. Do not write handling for codes that are not on this list.

codeWhen it happensWhat to do (next_action)
auth_requiredNo token in ~/.layero/config.json and none in LAYERO_TOKENRun layero login, or set LAYERO_TOKEN
auth_expiredThe login no longer works: either the user_code expired (15 min TTL) without confirmation, or the saved token expired (7-day TTL) / its session was revoked — the API answered 401Run layero login again
auth_timeoutThe CLI polled for 15 minutes and the user never confirmedRun layero login again
plan_limitA plan limit: the API answered 402 (e.g. more projects than the free plan allows)Change the plan at app.layero.ru/billing, or delete what you no longer need
username_requiredThe account has no username (it doubles as the personal organisation's address) — the API answers 412. An interactive terminal is asked during login/deploy; in agent mode there is nobody to asklayero username <name>
username_rejectedThe name is taken, reserved, or malformedPick another: lowercase latin letters, digits and hyphens, 2–32 characters
oauth_unavailableThe sign-in provider is unreachableOurs to fix — retry later
project_unknownRun outside a project directory and without --projectRun from the project directory or pass --project <id|slug>
project_not_found--project points at a project that does not existlayero projects list
cli_deploys_disabledAn admin turned CLI deploys off for the projectEnable it in Project Settings, or deploy into another project
invalid_type--type with an unknown valueDrop the flag (auto-detection) or pass a valid preset — listed in the message
invalid_choiceAn interactive prompt got an invalid choice in non-TTY modePass the value as an explicit flag
prebuilt_no_dirThe --prebuilt directory does not existPass it explicitly: --prebuilt ./dist
prebuilt_no_indexThe --prebuilt directory has no index.htmlPoint it at the folder containing the built index.html
deploy_not_startedThe build never startedRe-run layero deploy; if it repeats, check the project in the dashboard
deploy_failedThe build never reached readyOpen the logs at the URL in next_action
repeated_failureConsecutive builds keep failing with the same error, so the platform refused to ship another one blindly. The error text is in message and in the repeated_failure_guard eventRead the error and fix its cause. Re-running unchanged fails the same way. If you already fixed it — layero deploy --confirm-repeated-failure
repeated_failure_declinedSame, but the interactive prompt «ship anyway?» was answered noFix the error and run layero deploy again
no_deploy / no_deploysThe project has no deploys yetRun layero deploy first
rollback_unsupportedThe deploy has no servable artifact: either a runtime project, or static whose artifact was purged by retentionRebuild the commit with layero deploy
env_not_foundNo such variablelayero env list
nothing_to_setlayero env set called without a KEY=value pairlayero env set KEY=value
bad_formatAn argument could not be parsedThe expected format is in the message
domain_not_foundThe project has no such domainlayero domains list
domain_rejectedThe platform refused the domainThe reason is in the message
forbiddenThe token lacks the scope this operation needsIssue a token with the required scope
branch_without_envThe branch has no environment yetDeploy that branch first
analytics_not_connectedAnalytics is not connectedlayero analytics connect
no_runsNo speed-check runs recordedlayero perf check
internalAn unexpected CLI error (network, unhandled exception)Re-run with --debug
The deploy code is built from the status

The code for an unsuccessful deploy is assembled as deploy_<status> from the build status, and a deploy has four statuses: ready, building, failed, cancelled. So in practice you will only ever see deploy_failed and deploy_cancelleddeploy_error and deploy_timed_out do not exist, do not branch on them.

Cold-start template for an agent

A minimal behavioural block to drop into a system prompt:

If user asks to deploy via Layero:
1. Run: npx layero@latest deploy --json
2. Parse each stdout line as JSON, route on .event:
- "auth_required" → render .url as clickable link, keep waiting
- "ready" → show .url (the live site) to user. It is reachable right
away — do NOT gate on .edge_ready. Then stop.
- "error" → follow .next_action verbatim
3. Never run `git init`. Never run `npm install -g layero`.

A fuller example is in Deploying from AI agents.