---
title: REST API guide for agents
description: Exact authentication, site creation, deployment, status, logs, and deletion requests for an API client with no prior serverl3ss context.
audience: Software agents and API clients
status: canonical
last_updated: 2026-09-01
order: 1
related:
  - /v/docs/api/index.md
---

# serverl3ss REST API guide for agents

Use the control-plane REST API at `https://olibato.com/v/api/v1`. Hosted app URLs remain under `serverl3ss.com`; do not send control-plane tokens to app hosts. Send a personal access token on every non-registration request:

```http
Authorization: Bearer oli_pat_...
Content-Type: application/json
```

Create tokens in Account → API tokens. A `read` token can inspect resources, a `write` token can create sites and deploy, and an `owner` token is required to delete a site. Mutations documented with `Idempotency-Key` require a stable caller-generated value. Repeating the same request with the same key replays the stored response; changing the request returns `409 idempotency-conflict`.

Destination-aware replay binds site requests to the authorized site/namespace
identity and canonical query parameters. Omitting `namespace`, using `personal`,
or using your username resolves to the same personal site. Query-key order does
not matter; changing a destination, query value or body does. Tenant access is
checked before site replay.

Receipts from before destination-aware replay cannot prove their original query.
They are retained but return `409 idempotency-legacy-reconciliation-required`,
including when the caller believes the retry is identical. Inspect the original
resource/deployment and its outcome before making any new submission; do not
rotate the key or wait out its expiry merely to bypass this response. The upgrade
does not rewrite receipts or reconstruct missing destination evidence.

For CLI deployment, supply a unique `--idempotency-key` per deployment intent and
retain it until the outcome is known. If omitted, the CLI prints the generated
key before submitting the build. After a timeout or unreadable response, inspect
the site's deploy list/status first; retry only the identical request with the
same key within the API's 24-hour retention window. Do not use a new key to
recover an uncertain submission. Keys must not contain secrets and must be
unique across sites and namespaces. After retention expires, reconcile before
submitting anything again.

Local source uploads use a stable filename and a key scoped to destination,
commit, branch and archive contents. The CLI prints the returned upload UUID;
retain it and use `--upload-id UUID` when recovering a deployment instead of
archiving a potentially changed worktree. This cannot recover the identity of
an upload made with an older CLI's random filename: inspect existing uploads
before retrying those requests with the repaired CLI.

`status HOST --deploy ID` and `logs HOST` are single reads; the CLI does not poll
or activate automatically. Bound any caller polling deadline and space requests
at least two seconds apart. A successful build still requires the activation
API below, followed by runtime status and app-content verification. Updates use
a new deployment intent and the same build-then-activate sequence.

## Five-minute container flow

Set the API URL and token without putting the token in shell history:

```bash
API=https://olibato.com/v/api/v1
read -rsp 'PAT: ' TOKEN; export TOKEN
```

Create a site in the personal namespace. Omit `host` to derive it from `name`.

```bash
curl -sS "$API/sites" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: quickstart-site-1' \
  -d '{"name":"Agent web","host":"agent-web","access":"public"}'
```

Deploy a versioned container and ask the build agent to author `deploy.yaml`:

```bash
curl -sS "$API/sites/agent-web/deploys" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: quickstart-deploy-1' \
  -d '{"source":"container","image":"docker.io/library/nginx:1.27","agent":true}'
```

Poll the returned deploy `id` until `status` is `succeeded` or terminal:

```bash
curl -sS "$API/sites/agent-web/deploys/DEPLOY_ID" -H "Authorization: Bearer $TOKEN"
```

Activate the successful deploy. Activation starts or replaces the site runtime and preserves exactly one active version:

```bash
curl -sS -X POST "$API/sites/agent-web/deploys/DEPLOY_ID/actions/activate" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: quickstart-activate-1' \
  -d '{}'
```

Check site state, inspect logs, and open the returned `host_url` when state becomes `running`:

```bash
curl -sS "$API/sites/agent-web/status" -H "Authorization: Bearer $TOKEN"
curl -sS "$API/sites/agent-web/logs" -H "Authorization: Bearer $TOKEN"
curl -i https://agent-web.USERNAME.serverl3ss.com
```

Delete the site with an `owner` token. This soft-deletes control-plane metadata and submits teardown for any live runtime:

```bash
curl -sS -X DELETE "$API/sites/agent-web" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: quickstart-delete-1' \
  -d '{}'
```

## Namespaces and host conflicts

Sites use global `/sites` URLs. `namespace` is a JSON field on create and a query parameter on reads, updates, deploys, and deletion. It defaults to the caller's personal namespace. Supply an organization slug for an organization site, for example `POST /sites` with `"namespace":"acme"`, then use `?namespace=acme` on the site-scoped URLs.

Your personal source workspace is provisioned automatically during signup and repaired automatically if it is ever missing. Personal-site deploys are always attributed and billed to that deterministic workspace, regardless of which team workspace is currently selected; organization-site deploys remain attributed to the site's organization. No workspace setup action is required.

Check a host before creation with `GET /host-availability?host=web1&namespace=personal`. A taken or reserved host returns HTTP 409 with a stable `code` and a `reason`. Creation performs the same check atomically. Quota failures return HTTP 429 and a stable quota code.

## Deployment sources

`POST /sites/{slug}/deploys` accepts exactly one source shape:

```json
{"source":"container","image":"docker.io/library/nginx:1.27","manifest":{"version":1,"build":{"steps":[]},"run":{"image":"docker.io/library/nginx:1.27","port":80}}}
```

```json
{"source":"repository","repo":"owner/name","ref":"main","manifest_path":".olibato/deploy.yaml"}
```

The GitHub repository must first be connected and reviewed in the namespace's source workspace. Private repositories use the existing GitHub App installation.

For local code, upload a committed ZIP or tar archive as multipart form data to `/sites/{slug}/source-uploads` with `source`, `commit_sha`, and optional `branch`. Then deploy it:

```json
{"source":"source_upload","upload_id":"UPLOAD_UUID"}
```

Install a curated app after enumerating `/app-templates` and reading its `params_schema`:

```json
{"source":"app_template","template":"postgresql","version":"16","params":{"database":"app","username":"app","resource_size":"small"}}
```

For container, repository, and source-upload deployments, provide an embedded `manifest`, rely on the source tree's `manifest_path`, or set `"agent":true`. Supplied and agent-authored manifests enter the same validation, isolated build, security scan, and version pipeline.

## Deploy inspection and actions

- `GET /sites/{slug}/deploys` lists deploy attempts.
- `GET /sites/{slug}/deploys/{id}` returns status.
- `GET /sites/{slug}/deploys/{id}/events` streams `text/event-stream` build milestones.
- `GET /sites/{slug}/deploys/{id}/manifest` returns the supplied or resolved manifest.
- `POST /sites/{slug}/deploys/{id}/actions/cancel` cancels an active attempt.
- `POST /sites/{slug}/deploys/{id}/actions/retry` retries a terminal GitHub attempt.
- `POST /sites/{slug}/deploys/{id}/actions/activate` selects a successful version and starts the site.

## Custom domains

Attach a single hostname after the site has an active deploy. Wildcards are not accepted. The response contains the exact TXT ownership record and CNAME traffic record in `dns.records`:

```bash
curl -sS -X POST "$API/sites/agent-web/domains" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: agent-web-domain-1' \
  -d '{"domain":"app.example.com"}'
```

Publish both returned records at the domain's DNS provider. If Cloudflare hosts the zone, keep the CNAME DNS-only (grey cloud). Do not proxy it through Cloudflare. An A record whose value is the returned edge IP may replace the CNAME where the DNS provider cannot use a CNAME.

After public DNS sees both records, request verification:

```bash
curl -sS -X POST "$API/sites/agent-web/domains/app.example.com/verify" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: agent-web-domain-verify-1' \
  -d '{}'
```

`pending` means DNS has not propagated or the edge target is missing. `failed` includes an actionable verification or certificate error. `verified` means ownership and traffic records passed; Certbot HTTP-01 issuance is queued. Poll `GET /sites/agent-web/domains/app.example.com` until both the domain and certificate are `active`/`issued`, then open `https://app.example.com`.

Remove the router alias and withdraw its dedicated certificate with:

```bash
curl -sS -X DELETE "$API/sites/agent-web/domains/app.example.com" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: agent-web-domain-delete-1' \
  -d '{}'
```

For an organization site, add `?namespace=ORGANIZATION_SLUG` to every site-domain URL.

## Site and TCP access

Switch an HTTP site between public and signed-in-only access with a `write`
token. Anonymous traffic to an authenticated site is redirected before the
runtime wakes:

```bash
curl -sS -X PATCH "$API/sites/agent-web/access" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: agent-web-private-1' \
  -d '{"access":"authenticated"}'
```

Private TCP services use temporary source-IP grants. Name a non-public service
from the active deploy manifest and choose a duration from 1 to 1440 minutes:

```bash
curl -sS -X POST "$API/sites/agent-web/tcp-access" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: agent-web-db-access-1' \
  -d '{"service":"database","duration_minutes":60}'
```

The response includes `connection`, `cidr`, and `expires_at`. Connect from the
reported CIDR before expiry; the edge rejects other sources and self-expires
the grant without waking the site.

## Endpoint index

- Account and namespaces: `GET /account`, `GET /organizations`.
- Sites: `GET|POST /sites`, `GET|PATCH|DELETE /sites/{slug}`, `PATCH /sites/{slug}/access`, `POST /sites/{slug}/tcp-access`, `GET /host-availability`.
- Local code: `GET|POST /sites/{slug}/source-uploads`.
- Deploys: `GET|POST /sites/{slug}/deploys`, deploy detail, events, manifest, and actions listed above.
- Operations: `GET /sites/{slug}/status`, `GET /sites/{slug}/logs`.
- Custom domains: `GET|POST /sites/{slug}/domains`, `GET|DELETE /sites/{slug}/domains/{domain}`, `POST /sites/{slug}/domains/{domain}/verify`.
- App registry: `GET /app-templates`, `GET /app-templates/{slug}`.
- Legacy organization build and dashboard endpoints remain available for dashboard compatibility.

The complete machine-readable contract is at `/v/docs/api/openapi.json`.

## Errors and rate behavior

Errors use `application/problem+json` with `status`, `detail`, stable `code`, `request_id`, and optional field-level `errors`. Treat 409 as a state or identity conflict, 412 as stale optimistic concurrency, 429 as quota or rate admission, and 503 as a temporary dependency failure. Honor `Retry-After` when present. Build and write endpoints are rate-limited independently; poll deploy status no faster than every two seconds and use the events stream when practical.
