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:
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:
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.
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:
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:
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:
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:
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:
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:
{"source":"container","image":"docker.io/library/nginx:1.27","manifest":{"version":1,"build":{"steps":[]},"run":{"image":"docker.io/library/nginx:1.27","port":80}}}
{"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:
{"source":"source_upload","upload_id":"UPLOAD_UUID"}
Install a curated app after enumerating /app-templates and reading its params_schema:
{"source":"app_template","template":"postgresql","version":"16","params":{"database":"app","username":"app"}}
For MySQL/MariaDB templates, omit params.username to use the declared
u_{{ site.token }} default. The renderer resolves site.token from the
persisted site ID; the default is stable across redeploys and unique per site.
The default in params_schema is a template expression, not a literal login
username. Read the resolved manifest and owner-revealed credentials for the
concrete value. Template metadata reflects persisted rows; seed-source changes
require the normal seed rollout before appearing in API reads.
{"source":"app_template","template":"mysql","params":{"database":"app"}}
Redis/Valkey seed manifests declare protocol: redis, tls: {}, and
connect.scheme: rediss; they disable plaintext, disable ACL user default,
and authenticate user app with the generated password and -@dangerous
permissions. Do not use a plaintext redis:// URL.
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.
Manifest contract and edge readiness#
Keep version: 1. Services accept optional protocol values http, postgres,
mysql, redis, amqp, tcp, and mongodb, and the empty-map tls: {} marker.
connect.scheme is a display hint only. The marker is invalid on public services
and on protocol: mysql. Service names are free-form except for the Kubernetes
DNS-label restriction; the source edge policy selects protocol rather than name.
For new private services, omitted protocol is intended to mean internal-only.
Current validation defaults it to tcp, and the source edge policy can grant
that service if tls: {} is present; omission alone does not enforce isolation.
Declare protocols explicitly and withhold grants for internal services.
Omitted-protocol private database@5432 is normalized to postgres for
compatibility. Explicit tcp
overrides that inference.
The service certificate convention is
/var/run/olibato/tls/<service>/tls.crt,
/var/run/olibato/tls/<service>/tls.key, and
/var/run/olibato/tls/ca.crt. Managed certificate issuance and delivery are
implemented and tested for gVisor mounts, Firecracker disk material, and K3s
Secrets. Local integration tests boot Redis with real certificates. Public
edge activation and each endpoint's connectivity still require deployment
verification; manifest acceptance alone does not establish either. See
TCP edge for stage status.
Deploy inspection and actions#
GET /sites/{slug}/deployslists deploy attempts.GET /sites/{slug}/deploys/{id}returns status.GET /sites/{slug}/deploys/{id}/eventsstreamstext/event-streambuild milestones.GET /sites/{slug}/deploys/{id}/manifestreturns the resolved manifest with environment and connection values hidden.GET /sites/{slug}/deploys/{id}/variableslists environment variable names and sources by service.POST /sites/{slug}/deploys/{id}/variables/revealexplicitly returns values for the active instance's next wake or restart. It requires site read access and is never cached.POST /sites/{slug}/deploys/{id}/containers/{service}/logsfetches bounded current runtime output for one container. Standard sites return an operation ID for polling atGET /sites/{slug}/deploys/{id}/containers/{service}/logs/{operation_id}; Kubernetes sites return output directly.POST /sites/{slug}/deploys/{id}/actions/cancelcancels an active attempt.POST /sites/{slug}/deploys/{id}/actions/retryretries a terminal GitHub attempt.POST /sites/{slug}/deploys/{id}/actions/activateselects 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:
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:
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:
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:
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:
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 source response includes service, hostname, port, connection,
cidr, expires_at, operation_id, grant_id, edge_state, and
protocol_state, plus protocol and nullable routing_username. MySQL uses
mysql://<routing_username>@<hostname>:3306; other protocols use
tcp://<hostname>:<port>.
configured_unverified / unverified is not a successful connectivity probe.
Connect from the reported CIDR before expiry with application credentials and
hostname-verified TLS. Unauthorized sources must be rejected before wake.
Source policy: PostgreSQL selects 5432;
MySQL/MariaDB selects the gateway on 3306; Redis/Valkey selects 443 with TLS,
SNI, and ACL user app; AMQP selects 5671 with amqps. Client-first TLS uses
443 and SNI. Use the returned endpoint rather than inventing a binding.
Configure non-MySQL clients for their protocol despite the generic tcp:// URI.
tcp, redis, mongodb, and amqp require tls: {} for grants.
Protocol policy, gateway credential snapshots, internal endpoint socket_path /
internal_port persistence, and private edge-state/authz/credential-sync APIs
exist in source. Internal fields/routes are not customer PAT interfaces and
are outside the public OpenAPI. See TCP edge for
the implemented private contracts and public deployment verification gates. The checked-in
OpenAPI describes implemented public schemas only.
The MySQL client must use the concrete site-unique routing username; Redis/Valkey
must use ACL user app. Passwords remain separate secrets, never PATs or URI
contents. Use the client commands
only after that stage and endpoint are verified.
Client connections do not survive instance recycle (checkpoint/restore, snapshots, restart, or idle scale-to-zero); clients must reconnect. Configured durable state persists. Resolve uncertain transaction outcomes before retrying writes. Keepalives keep an instance awake and that awake time is billable.
Network metering status#
The Supervisor is the authoritative billing meter; edge counters are for
reconciliation only. Its endpoint relay source emits network_bytes with
received/sent bytes. Django ingestion records their sum, directional counters,
and duration, validates node/endpoint ownership, and deduplicates by node,
Supervisor stream, and sequence. The metered set includes wake_minutes,
requests, storage_gb_hours, and network_bytes; the typed public site usage
response exposes network_bytes as an exact integer total. This source support does not establish live
collection, a byte-pricing rule, or complete accounting coverage.
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.