Documentation for the app.nz AI agent cloud.
One control-plane API and one OpenAI-compatible model gateway, both authenticated with the same key. Launch coding agents on your repos, route chat across providers, and publish sites, all over HTTP. Every endpoint here maps to a live route in the Go server — click any one for its own reference page.
Use these docs with your AI agent
Every page here is machine-readable. Point a coding agent at /llms.txt for the index, or /llms-full.txt for a self-contained build-an-agent guide. Paste this prompt into any agent:
Read https://app.nz/llms-full.txt and follow it to build an agent on app.nz: call the OpenAI-compatible gateway, launch a cloud coding agent, and ship a deploy.from openai import OpenAI
# Same OpenAI code — only the base URL and key change.
client = OpenAI(base_url="https://app.nz/v1", api_key="pk_live_...")
r = client.chat.completions.create(
model="app/auto", # router picks the model, or pin "provider/model"
messages=[{"role": "user", "content": "Hello from app.nz"}],
)Authentication
Every API call authenticates with a bearer key created from your account. Pass it as an Authorization header. Browser sessions (cookie auth) work too, but keys are the right choice for servers, agents, and the CLI. Secrets are shown once at create/rotate time, so store them immediately.
/api/keysList keys (metadata only, no secrets)API keyPOST/api/keysCreate a key; response includes the one-time secretAPI keyPOST/api/keys/rotateRotate by id, by name, or all at onceAPI keyDELETE/api/keys?id=key_123Revoke a keyAPI keyGET/api/meCurrent account for the supplied key or sessionAPI key# Create a key (secret returned once)
curl -sX POST https://app.nz/api/keys \
-H "Authorization: Bearer $APP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"deploy bot"}'
# Use it on every other request
curl -s https://app.nz/api/me \
-H "Authorization: Bearer pk_live_..."Models gateway
An OpenAI-compatible API in front of 15+ providers. Point any OpenAI or Anthropic client at the base URL and keep your code. Use app/auto to let the router pick a model from the prompt, or bias it with a variant, or pin a specific provider model. The same key meters usage across every call.
/v1/chat/completionsChat completions (streaming + tools)API keyPOST/v1/messagesAnthropic-compatible messages (supports thinking)API keyGET/v1/modelsList routable models and aliasesAPI keyPOST/v1/embeddingsText embeddingsAPI keyPOST/v1/images/generationsImage generationAPI keyPOST/v1/videos/generationsVideo generationAPI keyPOST/v1/videos/editsVideo editingAPI keyPOST/v1/videos/extensionsVideo extensionAPI keyGET/v1/videos/{request_id}Poll xAI video jobsAPI keyPOST/v1/audio/speechText-to-speechAPI keyPOST/v1/audio/transcriptionsAudio transcriptionAPI keyPOST/v1/searchWeb and papers searchAPI keyapp/autorouteDefault. Reads the prompt and picks the backend.app/auto-coderouteBias toward strong coding models.app/auto-fastrouteLowest latency for interactive use.app/auto-cheaprouteBias toward lowest cost per token.app/auto-reasoningrouteRoute reasoning depth automatically.app/auto-visionrouteBias toward image-input models.app/auto-imagerouteBias toward image-generation models.routing_strategyparamprice by default; use config to preserve catalogue order or app/auto-fast for latency bias.provider/modelpinPin a specific upstream model, e.g. anthropic/claude or openai/gpt.curl -s https://app.nz/v1/chat/completions \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"model": "app/auto",
"messages": [{"role": "user", "content": "Explain CRDTs in one paragraph."}],
"reasoning_effort": "auto",
"stream": false
}'Agents API
Run cloud coding agents against a repo and a prompt, then poll status, stream step events, inspect changed files, and cancel or retry. GET /api/agents/config returns the option lists (engines, models, reasoning efforts, machine types, providers, skills) and needs no auth so a UI can render the form before sign-in.
/api/agents/configOption lists for the agent formpublicPOST/api/agents/tasksLaunch an agent taskAPI keyGET/api/agents/tasksList your agent tasksAPI keyGET/api/agents/tasks/{id}Get a task with its current statusAPI keyGET/api/agents/tasks/{id}/eventsStep-by-step events and statusAPI keyPOST/api/agents/tasks/{id}/cancelCancel a running taskAPI keyPOST/api/agents/tasks/{id}/retryRetry a finished or failed taskAPI keyGET/api/agents/tasks/{id}/filesList files the agent changedAPI keyPUT/api/agents/tasks/{id}/files/{fileId}Edit a changed file before reviewAPI keyprompt*stringWhat the agent should do (max 20k chars).sourcestringProvider that runs it: openpaths (default, our cloud) | devin | cursor | codex-cloud. See the Agents SDK section.repostringTarget repo as owner/name.branchstringWorking branch to create or use.baseBranchstringBranch to base the work on.modelstringModel route, e.g. app/auto-code.enginestringAgent engine (see /config).reasoningEffortstringnone | low | medium | high | auto.machineTypestringWorker capability tier (see /config), e.g. auto, cpx32, gpu-a100, or win-cpx32 for a Windows worker.providerstringExecution tier, e.g. shared (auto fleet), cloud CPU, or cloud GPU (see /config).skillsstring[]Enabled skills, e.g. ["github","visualbench"].autoMergePrbooleanAuto-merge the PR when checks pass.autoNextStepsbooleanLet the agent queue follow-up steps.spendCapUsdnumberHard spend ceiling for the task.timeoutSecondsnumberWall-clock timeout.projectIdstringProject to bill and scope to; defaults to your default project.titlestringDisplay title; derived from the prompt if omitted.curl -sX POST https://app.nz/api/agents/tasks \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"prompt": "add usage analytics to the dashboard",
"repo": "acme/ai-dashboard",
"model": "app/auto-code",
"reasoningEffort": "high",
"machineType": "auto",
"provider": "shared",
"skills": ["github", "visualbench"],
"spendCapUsd": 2.00,
"autoMergePr": false
}'Agents SDK — one API, every provider
The same /api/agents surface is a meta-agent router: set `source` to choose who runs the task. openpaths (the default) runs it on app.nz workers using our model credentials — which take precedence over any provider key when running in our cloud — and opens PRs through our GitHub app. devin, cursor, and codex-cloud launch the run on that vendor and the result (status, logs, diff, pull request) normalizes back into the same task — so every provider reads through one trace. GET /api/agents/config returns `sources` plus a `sourcesConfigured` map so a UI can disable providers that lack server credentials.
/api/agents/configSources + which are configured, plus models/enginespublicPOST/api/agents/tasksLaunch on any provider via the source fieldAPI keyGET/api/agents/tasksList your runs across all providersAPI keyGET/api/agents/tasks/{id}Normalized trace: steps, messages, diff, PRAPI keyPOST/api/agents/tasks/{id}/cancelCancel a running taskAPI keyopenpathssourceDefault. Runs on app.nz workers with our credentials; engine selects Claude Code / Codex / Gemini. Real PRs via our GitHub app.devinsourceLaunches a Devin cloud session. Needs server DEVIN_API_KEY + DEVIN_ORG_ID.cursorsourceLaunches a Cursor cloud agent with auto-PR. Needs server CURSOR_API_KEY.codex-cloudsourceLaunches an OpenAI Codex Cloud task against a GitHub-connected repo. Needs CODEX_CLOUD_ENV_ID + the codex CLI.curl -sX POST https://app.nz/api/agents/tasks \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"source": "devin",
"prompt": "fix failing CI and open a pull request",
"repo": "lee101/edukids",
"baseBranch": "main",
"autoMergePr": false
}'
# CLI equivalent:
# app agents-sdk run "fix failing CI and open a PR" --source devin --repo lee101/edukids
# app agents-sdk providers # see which providers are configured
# app agents-sdk logs <id> # read the normalized traceCharacter chat API
Start chats with any character by url_name or id, pin a model for that chat, and stream replies over the same server-sent-events shape as the OpenAI-compatible gateway. User-created characters can include long-form text or Markdown documents; app.nz strips image links, chunks the text, stores it in the character vector-search boundary, and retrieves relevant excerpts for each turn.
/api/search-ais?q=luna&limit=20Find characters by name, title, tags, or descriptionpublicGET/api/get-ai-by-name?name=lunaLoad one character by url_name or display namepublicPOST/api/charactersCreate a user-owned character and ingest text/Markdown docsAPI keyPATCH/api/characters/{url_name}Update your character and optionally replace indexed docsAPI keyPOST/api/assistant/chatsCreate a chat for a character with a chosen modelAPI keyPOST/api/assistant/chats/{id}/messagesSend a turn and stream the character replyAPI keyGET/api/assistant/chats/{id}Reload a chat and its active branchAPI keyname*stringPOST /api/characters display name.system_promptstringOptional persona prompt; generated from name/description/greeting if omitted.documentsarrayOptional long-form docs: [{ "name": "lore.md", "content": "# Markdown..." }]. Alias: knowledge_docs.character_url_name*stringPOST /api/assistant/chats target character slug.modelstringModel or route for this chat, e.g. app/auto or app/auto-fast.reasoning_effortstringauto | off | low | medium | high.content*stringMessage text for POST /messages.# Create a character with long-form Markdown knowledge.
curl -sX POST https://app.nz/api/characters \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "GPU Brain Operator",
"description": "Helps operate the gpu-brain search stack.",
"voice": "Kore",
"documents": [{
"name": "runbook.md",
"content": "# Runbook\nUse gobed for low-latency KNN over text chunks. Never ingest image links."
}]
}'
# Start a chat with a given model.
CHAT=$(curl -sX POST https://app.nz/api/assistant/chats \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"character_url_name": "gpu-brain-operator",
"model": "app/auto-fast",
"reasoning_effort": "auto"
}' | jq -r .chat.id)
# Send a message. The response is text/event-stream; relevant doc excerpts are
# retrieved server-side before the model call.
curl -N -sX POST https://app.nz/api/assistant/chats/$CHAT/messages \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{"content":"What should I check if KNN search looks stale?"}'Sites API
Host a static site straight from the control plane. Create a slug, push files (index.html + assets, text or binary), and it is published instantly at both app.nz/sites/<slug>/ and its own subdomain <slug>.app.nz. No build step, no DNS wait. The subdomain is the right home for SPAs — bundlers emit absolute /assets/… paths that only resolve from a site root — and it is served straight from Cloudflare R2 at the edge, so static traffic never touches an origin. A fresh site is seeded with a working starter (index.html, styles.css, app.js). Slugs are 2–40 chars: lowercase letters, numbers, and hyphens, and a few reserved names are blocked. Files are up to 8 MB each; binary assets (images, fonts, audio) are stored too.
/api/sitesList your sitesAPI keyPOST/api/sitesCreate a site (seeds a starter)API keyGET/api/sites/{id}Get a site with its filesAPI keyDELETE/api/sites/{id}Delete a siteAPI keyPUT/api/sites/{id}/filesCreate or update a file (publishes)API keyDELETE/api/sites/{id}/files?path=app.jsDelete a fileAPI key/sites/{slug}/{path}Public serving — no auth, defaults to index.htmlpublichttps://{slug}.app.nz/{path}Public serving on the site’s own subdomain (SPA-friendly)publicslug*stringPOST /api/sites — public name; normalized to a valid slug.titlestringPOST /api/sites — display title (max 120 chars).path*stringPUT files — file path, e.g. index.html or css/app.css.contentstringPUT files — UTF-8 file contents (max 8 MB). Use for text files.contentBase64stringPUT files — base64 bytes for binary files (images, fonts, audio).contentTypestringPUT files — overrides the type inferred from the extension.# Easiest: push a whole build directory with the app CLI. It creates the
# site on first run, uploads every text file, and prunes anything removed.
app login --api-key pk_live_...
npm run build # e.g. Vite -> dist/
app sites deploy my-app dist --title "My app"
# Live at https://app.nz/sites/my-app/
# and https://my-app.app.nz/
# Or drive the REST API directly:
SITE=$(curl -sX POST https://app.nz/api/sites \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{"slug":"my-landing","title":"My landing"}')
ID=$(echo "$SITE" | jq -r .site.id)
# Publish index.html — live at /sites/my-landing/ and my-landing.app.nz
curl -sX PUT https://app.nz/api/sites/$ID/files \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{"path":"index.html","content":"<!doctype html><h1>Hi</h1>"}'Billing & plans
Everything is paid from one prepaid credit balance (credits are ~$0.001 each). On top of pay-as-you-go, a Pro ($20), Ultra ($60), or Max ($200) plan grants monthly credits, a guaranteed number of concurrent machines, and per-product free quotas. Plan credits are used before purchased credits and roll over for one month.
/api/pricingUsage-based pricing: model token rates, compute, search, credit unitpublicGET/api/plansList plan catalog (price, credits, machines, quotas)publicGET/api/plans/meYour plan, credit balance, and product quota usageAPI keyPOST/api/plans/subscribeStart a Pro/Ultra/Max checkoutsessionPOST/api/plans/cancelCancel at period endsessionPOST/api/plans/portalOpen the Stripe billing portalsessionGET/api/credits/balancePrepaid credit balance + auto-topupAPI keyPOST/api/credits/checkoutBuy credit packssessionPOST/api/credits/autotopupConfigure automatic top-upssessionfreeplan$0 — pay-as-you-go credits, shared worker pool.proplan$20/mo — 25,000 credits, 1 guaranteed machine.ultraplan$60/mo — 80,000 credits, 3 machines + priority.maxplan$200/mo — 280,000 credits, 6 machines + top priority.# Inspect the catalog
curl -s https://app.nz/api/plans | jq
# From the CLI (app login first)
app plan show
app plan subscribe ultra
app billing usageFirst-party products
RA1 art generation, paper search, and agentic search are first-party APIs metered against the same balance. Plan subscribers get a monthly free quota per product, consumed before any credits are charged. All three accept a Bearer key or a signed-in session.
/api/ra1/generateGenerate images with RA1 — prompt, count, aspect, style, negative (50 credits each)API keyGET/api/ra1/statusPoll a queued render by jobId for status + image URLsAPI keyGET/api/papers/searchSearch 200M+ papers (1 credit each)API keyPOST/api/searchAgentic web search (10 credits per depth)API keyapp chat "research R2-backed model hosting" --web --deep --papers
app ra1 generate --prompt "a cat astronaut" --count 2
app papers search "diffusion transformers" --limit 5
app search "best vector database 2026" --depth 2Conventions
- • Auth:
Authorization: Bearer pk_live_...on every non-public route. - • Bodies and responses are JSON; successful writes return
{ "success": true, ... }. - • Errors return a non-2xx status with
{ "error": "message" }. - • Get a key with the CLI:
app keys create --name "deploy bot".