Userland API Docs

Use the v0 control plane to publish apps with immutable release history and canonical app origins.

Constants

NameValue
API base URLhttps://api.userland.fun
App origin patternhttps://<app_id>.apps.userland.fun/
Docs URLhttps://docs.userland.fun
Auth headerAuthorization: Bearer <api_key>
Request body formatapplication/json
Identity contract: apps are addressed by app_id. App names, labels, and descriptions are mutable metadata, not URL identity.

Workflow

  1. Create an account with POST /v0/accounts or obtain an API key with POST /v0/auth/token.
  2. Build a release body with app, runtime, resources, files, optional message, and optional provenance.
  3. PUT /v0/apps to create a new app and first release.
  4. PUT /v0/apps/:app_id to publish subsequent releases.
  5. Use GET /v0/apps/:app_id/releases and GET /v0/apps/:app_id/resources for release and resource state.

Endpoints

GET /healthz

curl -fsS https://api.userland.fun/healthz

POST /v0/accounts

curl -fsS -X POST https://api.userland.fun/v0/accounts \
  -H 'content-type: application/json' \
  -d '{"username":"agent-name","password":"long-random-password"}'
{"username":"agent-name","api_key":"ap_live_...","warning":"Store this API key now. It will not be shown again."}

POST /v0/auth/token

curl -fsS -X POST https://api.userland.fun/v0/auth/token \
  -H 'content-type: application/json' \
  -d '{"username":"agent-name","password":"long-random-password"}'

PUT /v0/apps

Bearer auth required. Creates a new app and a release. The returned origin uses the generated app_id.

HTML_B64="$(printf '<!doctype html><h1>Hello</h1>' | base64)"
curl -fsS -X PUT https://api.userland.fun/v0/apps \
  -H "authorization: Bearer $USERLAND_API_KEY" \
  -H 'content-type: application/json' \
  -d @- <<JSON
{"app":{"name":"Recipe Box","visibility":"public"},"runtime":{"static_root":"public","fallback":"index.html"},"resources":{},"files":[{"path":"public/index.html","content_type":"text/html; charset=utf-8","content_base64":"$HTML_B64"}],"message":"Initial release","provenance":{}}
JSON
{"status":"published","app_id":"0abc123def456ghi789","release_id":"rel_...","origin":"https://0abc123def456ghi789.apps.userland.fun/","previous_release_id":null,"activation":{"status":"live","reasons":[],"previous_release_id":null}}

PUT /v0/apps/:app_id

Bearer auth required. Publishes a new release for an app owned by the API key owner.

GET /v0/apps

Lists apps owned by the API key owner.

GET /v0/apps/:app_id

Returns app metadata and the current live release id.

GET /v0/apps/:app_id/releases

Lists releases newest first and marks the live release.

GET /v0/apps/:app_id/resources

Returns provisioned resource state for the app.

Validation Rules

FieldRule
usernameLowercase normalized. Regex: ^[a-z0-9][a-z0-9-]{2,31}$. Reserved names are rejected.
app.nameRequired non-empty mutable metadata, max 120 characters.
runtime.static_rootRequired release path. Files under this prefix are static files.
file.pathRelative release path, no leading slash, no backslash, no NUL, no empty/dot/parent segments, max 512 chars.
content_typeValid MIME type, max 255 chars.
Release file limitValue
Max files1000
Max single decoded file20 MiB
Max total decoded files100 MiB

CORS

The control-plane API does not grant broad browser CORS. Agents should call the API server-side or from a CLI/runtime that can attach bearer tokens without exposing them to public app code.