Skip to content

Onboarding API ​

The onboarding endpoints guide a fresh Lamaste installation through domain setup, DNS verification, and stack provisioning.

In Plain English ​

When you first install Lamaste and open the admin panel, the system does not know your domain name yet. The onboarding API is the set of endpoints that walk you through the initial setup: telling the system your domain, checking that DNS is correctly configured, and then provisioning all the backend services (Chisel, Authelia, nginx vhosts, TLS certificates).

Once onboarding completes, these endpoints permanently return "410 Gone" — they are a one-time setup flow, not something you revisit.

Authentication ​

All onboarding endpoints require a valid mTLS client certificate, the same as every other API endpoint. See the API Overview for details.

Availability ​

EndpointAvailable When
GET /api/onboarding/statusAlways (no guard)
All other onboarding endpointsstatus != COMPLETED

After onboarding completes, all endpoints except /status return:

json
HTTP/1.1 410 Gone

{
  "error": "Onboarding already completed"
}

Onboarding State Machine ​

The onboarding progresses through a linear sequence of states. Each API call validates the current state before proceeding.

FRESH ──POST /domain──▶ DOMAIN_SET ──POST /verify-dns──▶ DNS_READY
                                                              │
                                                   POST /provision
                                                              │
                                                              ▼
                                                        PROVISIONING
                                                              │
                                                        (background)
                                                              │
                                                              ▼
                                                         COMPLETED

Endpoints ​

GET /api/onboarding/status ​

Returns the current onboarding state. This is the first endpoint the panel client calls on load to determine whether to show the onboarding wizard or the management UI.

This endpoint is always accessible regardless of onboarding state — it has no guard.

Request:

No request body.

bash
# Create a curl config file (do this once):
# echo 'cert = "client.p12:YOUR_P12_PASSWORD"' > ~/.curl-lamaste
# chmod 600 ~/.curl-lamaste

curl -s -K ~/.curl-lamaste \
  https://203.0.113.42:9292/api/onboarding/status

Response (200):

json
{
  "status": "FRESH",
  "domain": null,
  "ip": "203.0.113.42"
}
FieldTypeDescription
statusstringOne of: FRESH, DOMAIN_SET, DNS_READY, PROVISIONING, COMPLETED
domainstring | nullThe configured domain, or null if not yet set
ipstringThe droplet's public IP address

After domain is set:

json
{
  "status": "DOMAIN_SET",
  "domain": "example.com",
  "ip": "203.0.113.42"
}

POST /api/onboarding/domain ​

Sets the domain name and Let's Encrypt contact email. This is the first step in the onboarding flow.

Request:

json
{
  "domain": "example.com",
  "email": "admin@example.com"
}
FieldTypeValidationDescription
domainstringFQDN regex, min 1 charFully qualified domain name
emailstringLETSENCRYPT_EMAIL_REGEXContact email for Let's Encrypt registration. The local part must start with a letter or digit; this is the same rule the root-owned lamaste-certbot wrapper enforces, so an address accepted here is never refused at certificate issuance

The domain is validated against this pattern:

^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$
bash
curl -s -K ~/.curl-lamaste \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com","email":"admin@example.com"}' \
  https://203.0.113.42:9292/api/onboarding/domain

Response (200):

json
{
  "ok": true,
  "domain": "example.com",
  "email": "admin@example.com"
}

Errors:

StatusBodyWhen
400{"error":"Validation failed","details":{"issues":[...]}}Invalid domain format, missing or invalid email
409{"error":"Cannot change domain in current state","onboardingStatus":"DNS_READY"}Onboarding has progressed past DOMAIN_SET
410{"error":"Onboarding already completed"}Onboarding is finished

State transition: FRESH or DOMAIN_SET → DOMAIN_SET

The endpoint is idempotent during the FRESH and DOMAIN_SET states — you can call it multiple times to correct the domain before verifying DNS. Once DNS verification succeeds, the domain is locked.


POST /api/onboarding/verify-dns ​

Checks whether the three subdomains Lamaste itself serves — panel., auth. and tunnel. — resolve to the droplet's IP address. These are the names provisioning issues certificates for. It also reports the wildcard and the base domain itself, neither of which is required.

Request:

No request body. The domain is read from the server's configuration.

bash
curl -s -K ~/.curl-lamaste \
  -X POST \
  https://203.0.113.42:9292/api/onboarding/verify-dns

Response (200) — DNS correct:

json
{
  "ok": true,
  "domain": "example.com",
  "expectedIp": "203.0.113.42",
  "records": [
    { "name": "panel.example.com", "resolvedIps": ["203.0.113.42"], "ok": true },
    { "name": "auth.example.com", "resolvedIps": ["203.0.113.42"], "ok": true },
    { "name": "tunnel.example.com", "resolvedIps": ["203.0.113.42"], "ok": true }
  ],
  "apexOk": true,
  "apexResolvedIps": ["203.0.113.42"],
  "wildcardOk": true,
  "wildcardResolvedIps": ["203.0.113.42"],
  "message": "DNS is correctly configured. panel, auth and tunnel resolve to your server."
}

Response (200) — DNS not yet propagated:

json
{
  "ok": false,
  "domain": "example.com",
  "expectedIp": "203.0.113.42",
  "records": [
    { "name": "panel.example.com", "resolvedIps": [], "ok": false },
    { "name": "auth.example.com", "resolvedIps": [], "ok": false },
    { "name": "tunnel.example.com", "resolvedIps": [], "ok": false }
  ],
  "apexOk": false,
  "apexResolvedIps": [],
  "wildcardOk": false,
  "wildcardResolvedIps": [],
  "message": "panel.example.com does not resolve; auth.example.com does not resolve; tunnel.example.com does not resolve. Point panel.example.com, auth.example.com, tunnel.example.com (or a wildcard *.example.com) at 203.0.113.42. DNS propagation can take up to 48 hours, but usually completes within minutes."
}

Response (200) — base domain served by another host:

json
{
  "ok": true,
  "domain": "example.com",
  "expectedIp": "203.0.113.42",
  "records": [
    { "name": "panel.example.com", "resolvedIps": ["203.0.113.42"], "ok": true },
    { "name": "auth.example.com", "resolvedIps": ["203.0.113.42"], "ok": true },
    { "name": "tunnel.example.com", "resolvedIps": ["203.0.113.42"], "ok": true }
  ],
  "apexOk": false,
  "apexResolvedIps": ["198.51.100.7"],
  "wildcardOk": true,
  "wildcardResolvedIps": ["203.0.113.42"],
  "message": "DNS is correctly configured. panel, auth and tunnel resolve to your server. example.com itself does not point to this server — that is only needed to serve a site on it."
}
FieldTypeDescription
okbooleantrue if panel., auth. and tunnel. all resolve to the server
domainstringThe domain being verified
expectedIpstringThe droplet's public IP
recordsobject[]One entry per required name: name, resolvedIps, ok
apexOkbooleantrue if the base domain itself resolves to the server (optional)
apexResolvedIpsstring[]IP addresses the base domain resolves to
wildcardOkbooleantrue if wildcard DNS is configured (optional)
wildcardResolvedIpsstring[]IP addresses the wildcard resolves to
messagestringHuman-readable diagnostic message

Errors:

StatusBodyWhen
409{"error":"Domain must be set before DNS verification","onboardingStatus":"FRESH"}Domain has not been set yet
410{"error":"Onboarding already completed"}Onboarding is finished

State transition: DOMAIN_SET or DNS_READY → DNS_READY (only when ok is true)

The endpoint can be called repeatedly — it is safe to poll while waiting for DNS propagation. The state only advances when verification succeeds.

The wildcard check probes test-lamaste-check.<domain>. Wildcard DNS is optional; tunnels will still work with individual A records. The base domain is optional too: it can keep pointing at another host, and is only needed once a site is served on it.


POST /api/onboarding/provision ​

Starts the full stack provisioning process in the background. This configures and starts Chisel and Authelia, issues certbot certificates, and writes nginx vhosts. The Chisel and Authelia binaries and units are installed by create-lamaste, not here: if one is missing, the task fails with the error "Chisel is not installed. Run npx @lamalibre/create-lamaste on the server to repair the installation." (likewise for Authelia). Task IDs are unchanged from earlier versions; only the titles changed.

Provisioning runs asynchronously. This endpoint returns immediately with a 202 status. Use the WebSocket stream endpoint to follow progress in real time.

Request:

No request body.

bash
curl -s -K ~/.curl-lamaste \
  -X POST \
  https://203.0.113.42:9292/api/onboarding/provision

Response (202):

json
{
  "ok": true,
  "message": "Provisioning started"
}

Errors:

StatusBodyWhen
409{"error":"DNS must be verified before provisioning"}State is FRESH or DOMAIN_SET
409{"error":"Provisioning already in progress"}Provisioning is currently running
410{"error":"Onboarding already completed"}Onboarding is finished

State transition: DNS_READY → PROVISIONING → COMPLETED (on success)

Provisioning Tasks ​

The provisioning sequence runs these tasks in order:

Task IDTitleWhat It Does
install-chiselStarting ChiselChecks the binary is installed, creates key and authfile, starts service
install-autheliaConfiguring AutheliaChecks the binary is installed, writes config, creates admin user, starts service
issue-certsIssuing TLS certificatesIssues Let's Encrypt cert for panel.<domain>, sets up auto-renewal
configure-nginxConfiguring nginxWrites panel/auth/tunnel vhosts, enables sites, tests and reloads
verify-servicesVerifying servicesChecks all services are running (Chisel, Authelia, nginx, panel)
finalizeFinalizing setupUpdates config to COMPLETED state

WS /api/onboarding/provision/stream ​

WebSocket endpoint for real-time provisioning progress. Connects via the standard WebSocket upgrade handshake.

javascript
const ws = new WebSocket('wss://203.0.113.42:9292/api/onboarding/provision/stream');

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log(data);
};

Initial message (sent immediately on connect):

When a client connects, the server sends the full current state so late-joining clients can catch up:

json
{
  "type": "state",
  "isRunning": true,
  "tasks": [
    {
      "id": "install-chisel",
      "title": "Starting Chisel",
      "status": "done",
      "message": "Chisel 1.12.0 running",
      "log": null
    },
    {
      "id": "install-authelia",
      "title": "Configuring Authelia",
      "status": "running",
      "message": "Creating admin user...",
      "log": null
    },
    {
      "id": "issue-certs",
      "title": "Issuing TLS certificates",
      "status": "pending",
      "message": null,
      "log": null
    },
    {
      "id": "configure-nginx",
      "title": "Configuring nginx",
      "status": "pending",
      "message": null,
      "log": null
    },
    {
      "id": "verify-services",
      "title": "Verifying services",
      "status": "pending",
      "message": null,
      "log": null
    },
    {
      "id": "finalize",
      "title": "Finalizing setup",
      "status": "pending",
      "message": null,
      "log": null
    }
  ],
  "error": null,
  "result": null
}

Progress messages (sent as tasks advance):

json
{
  "task": "install-authelia",
  "title": "Configuring Authelia",
  "status": "running",
  "message": "Writing configuration...",
  "log": null,
  "progress": { "current": 2, "total": 6 }
}
FieldTypeDescription
taskstringTask identifier
titlestringHuman-readable task title
statusstringOne of: pending, running, done, error
messagestring | nullCurrent step description within the task
logstring | nullAdditional log output (version numbers, skipped notices)
progressobject{ current, total } — overall progress counter

Completion message:

json
{
  "task": "complete",
  "status": "done",
  "message": "Provisioning complete",
  "result": {
    "adminUsername": "admin",
    "adminPassword": "aB3dEf7hIjKlMn0p",
    "panelUrl": "https://panel.example.com",
    "authUrl": "https://auth.example.com"
  },
  "progress": { "current": 6, "total": 6 }
}

The result object contains the initial Authelia admin credentials. The password is randomly generated and shown only once.

Error message:

json
{
  "task": "configure-nginx",
  "status": "error",
  "message": "Failed: Configuring nginx",
  "error": "nginx configuration test failed: ...",
  "progress": { "current": 3, "total": 6 }
}

If a task fails, all subsequent tasks remain in pending status and provisioning stops.

Quick Reference ​

MethodPathState RequiredReturns
GET/api/onboarding/statusAnyCurrent state, domain, IP
POST/api/onboarding/domainFRESH or DOMAIN_SETConfirmation
POST/api/onboarding/verify-dnsDOMAIN_SET or DNS_READYDNS resolution results
POST/api/onboarding/provisionDNS_READY202 Accepted
WS/api/onboarding/provision/streamAny (read-only)Real-time progress

State Machine ​

FRESH ──▶ DOMAIN_SET ──▶ DNS_READY ──▶ PROVISIONING ──▶ COMPLETED
  │           │              │
  └───────────┘              │
  (domain can be changed)    │
                             └── (provision runs in background)

curl Cheat Sheet ​

bash
# Check current state
curl -s -K ~/.curl-lamaste \
  https://203.0.113.42:9292/api/onboarding/status | jq

# Set domain
curl -s -K ~/.curl-lamaste \
  -X POST -H "Content-Type: application/json" \
  -d '{"domain":"example.com","email":"admin@example.com"}' \
  https://203.0.113.42:9292/api/onboarding/domain | jq

# Verify DNS
curl -s -K ~/.curl-lamaste \
  -X POST \
  https://203.0.113.42:9292/api/onboarding/verify-dns | jq

# Start provisioning
curl -s -K ~/.curl-lamaste \
  -X POST \
  https://203.0.113.42:9292/api/onboarding/provision | jq

Released under the PolyForm Noncommercial License 1.0.0