Docs

HTTP API

Create a project without an account, call every tool over plain HTTP, send captures of your app, check the service, and send HTML safely.

The HTTP API gives the same tools as the MCP server, with plain JSON. Your agent uses it for the whole session in which it creates a project, and whenever the MCP server is not added. The app itself also calls POST /api/user/edits, with your session token, to save your edits in the canvas: it takes no project key, and agents do not use it (see Security and data).

Base URL: https://app.agentappbuilder.com/api. Every response is JSON with cache-control: no-store. Request bodies are limited to 1048576 bytes (1 MB), except captures, which take an image of up to 2 MB.

Create a project

POST /api/projects creates a pending project, without any account or key.

Body

Field Type Required Notes
name string, 1 to 80 yes The project name.
app object no Creates the first app at the same time, with the arguments of create_app: name, and optionally kind, device, description, repo_path.
umask 077; mkdir -p /tmp/agentappbuilder && [ -O /tmp/agentappbuilder ] && [ ! -L /tmp/agentappbuilder ] &&
curl -s -X POST https://app.agentappbuilder.com/api/projects \
  -H 'content-type: application/json' \
  -d '{"name":"My app","app":{"name":"Web","kind":"web","device":"desktop"}}' > /tmp/agentappbuilder/my-app.json

The response holds the key: save it to a file outside your repository that only you can read, as above, and see Keep the key out of your repository. The two checks stop the command if /tmp/agentappbuilder belongs to another user of the machine or is a link.

Response (201)

Field Meaning
project id, name and status (pending).
app When you sent app: slug, name, kind, device.
key The project key, aab_ followed by 64 hexadecimal characters. Temporary until the project is claimed. Returned once.
claim_url https://app.agentappbuilder.com/claim/<token>, for the user.
expires_at When the claim URL and the temporary key expire: 7 days after creation.
mcp url of the MCP server and add_command, the claude mcp add command with the key, for you to run in your own terminal if you want the MCP server. Your agent does not run it.
http_tools_url https://app.agentappbuilder.com/api/tools
next_steps The next steps, in plain sentences, for the agent.

Errors

Status When
400 The body is not a JSON object, or a field is missing, too long or not accepted. The error gives an example body.
405 Any method other than POST.
413 The body is over 1048576 bytes.
429 Too many projects were created on the platform in the last hour (50 pending projects). Try again later.
500 Internal error. Try again in a moment.

Call a tool

POST /api/tools/<tool> runs one tool. The body is the tool’s arguments as a JSON object (an empty body counts as {}). The tools and their arguments are listed in the MCP server reference.

curl -s -X POST https://app.agentappbuilder.com/api/tools/get_project_context \
  -H "authorization: Bearer $AAB_KEY"
curl -s -X POST https://app.agentappbuilder.com/api/tools/create_app \
  -H "authorization: Bearer $AAB_KEY" \
  -H 'content-type: application/json' \
  -d '{"name":"Web","kind":"web"}'

Authentication: the header Authorization: Bearer <key>.

Response on success (200)

{
  "ok": true,
  "summary": "Created 'Home · empty' v1 in app web: https://app.agentappbuilder.com/f/<token>",
  "notices": [],
  "result": { "frame_url": "https://app.agentappbuilder.com/f/<token>", "version": 1 }
}

result holds the same fields as the MCP result of the tool (shortened above). One difference: get_frame on a live frame or a capture version returns the signed link capture.image_url (valid one hour), not the image itself. Download it to a temporary file outside your repository to see the image. notices lists the open notices of the project. When it is not empty, summary starts with ATTENTION: N open notice(s), ask the user before continuing.

Response on error

{ "ok": false, "notices": [], "error": "Frame not found in this project: 'abcd1234'. Call get_project_context to list the frame URLs." }

When notices are open, error starts with the same ATTENTION sentence.

Status codes

Status When
200 The tool ran.
400 The body is not a JSON object, an argument is invalid, or the tool refused the call (the error says what to correct).
401 Missing or invalid key. The response has a WWW-Authenticate header.
404 Unknown tool. The error lists the tool names.
405 Any method other than POST.
413 The body is over 1048576 bytes.
500 Internal error. Try again in a moment.

Send a capture

POST /api/captures stores one screenshot of your running app as a live frame: one state of one screen, on the Live app canvas. Your agent’s script calls it once per state, on your machine; the image goes from the script to the platform and never through the model. The guide gives a complete Playwright script in its section “Capture the real app”: https://app.agentappbuilder.com/llms.txt.

Authentication: the header Authorization: Bearer <key>, the same project key as for the tools.

Body: the raw image bytes, not JSON and not a form. The Content-Type header is image/jpeg, image/png or image/webp, and must match the image: the server reads its first bytes to check it. At most 2 MB (2097152 bytes). A JPEG of quality 80 of the window is often around 150 KB.

Query parameters

Parameter Type Required Notes
app string, up to 80 no App slug. Optional when the project has exactly one app. The app must exist: create it with create_app first.
screen string, 1 to 80 yes Screen name, for example Interventions. The screen is created if it does not exist.
screen_slug string, up to 63 no Derived from the name. Must match ^[a-z0-9][a-z0-9-]{1,62}$.
route string, up to 300 no Route in the app, for example /interventions.
source_path string, up to 300 no Source file of the screen. Your agent reads it when it recreates the screen in HTML.
state string, 1 to 80 yes State label, for example list or new intervention modal.
state_key string, up to 63 no Derived from the label, same format as the slug.
url string, up to 2000 no The address that was captured, shown in the right panel. The platform does not visit it.
width, height integers, 1 to 10000 no The window size of the capture. Pass both or neither.

Encode each value in the query string (URLSearchParams in Node does it). Unknown parameters are refused. When a parameter is repeated, the first value counts.

A capture of a state that already has one replaces it: same app, same screen and same state key. The label is updated, and the previous image is removed from storage unless an imported capture version still shows it.

curl -s -X POST "https://app.agentappbuilder.com/api/captures?app=web&screen=Interventions&state=list&route=%2Finterventions&url=http%3A%2F%2Flocalhost%3A3000%2Finterventions&width=1440&height=900" \
  -H "authorization: Bearer $AAB_KEY" \
  -H 'content-type: image/jpeg' \
  --data-binary @interventions-list.jpg

--data-binary sends the file byte for byte.

The same call from Node 18 or later, after a Playwright screenshot:

import { chromium } from "playwright";

const KEY = process.env.AAB_KEY; // never write the key in a file
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto("http://localhost:3000/interventions", { waitUntil: "networkidle" });
const image = await page.screenshot({ type: "jpeg", quality: 80 });

const query = new URLSearchParams({
  app: "web", screen: "Interventions", state: "list", route: "/interventions",
  url: page.url(), width: "1440", height: "900",
});
const res = await fetch(`https://app.agentappbuilder.com/api/captures?${query}`, {
  method: "POST",
  headers: { authorization: `Bearer ${KEY}`, "content-type": "image/jpeg" },
  body: image,
});
console.log(res.status, await res.json());
await browser.close();

Response (201)

{
  "ok": true,
  "frame_url": "https://app.agentappbuilder.com/f/<token>",
  "frame_id": "<uuid>",
  "screen": "Interventions",
  "state": "list",
  "captured_at": "2026-09-28T09:12:44.512Z",
  "canvas_url": "https://app.agentappbuilder.com/p/<project>?canvas=live&app=web",
  "notices": []
}
Field Meaning
frame_url The URL of the live frame. It opens for members of the project once it is claimed.
frame_id The id of the live frame.
screen, state The screen name and state label.
captured_at When the platform stored the capture.
canvas_url The Live app canvas of that app, or null while the project is pending: give the claim link instead.
notices The open notices of the project, as for the tools.

Every capture adds a live_captured entry to the project’s journal, which shows the message “Claude Code captured Interventions · list” on an open canvas.

Errors: {"ok": false, "error": "..."}, with a message that says what to correct.

Status When
400 A query parameter is missing, too long, unknown or in the wrong format (the error gives an example), width without height or the reverse, an empty body, an unknown app, no app yet, several apps and no app, or a limit of a pending project (below).
401 Missing or invalid key. The response has a WWW-Authenticate header.
405 Any method other than POST.
413 The image is over 2097152 bytes (2 MB). Send a JPEG of quality 80 of the window, not of the full page.
415 The Content-Type is not image/jpeg, image/png or image/webp, or the bytes are not an image of that type.
502 The image could not be stored. Nothing was saved: send it again.
500 Internal error. Try again in a moment.

Limits of a pending project: a project waiting to be claimed holds at most 100 frames (prototype and live together) and 10 MB of captures. All pending projects of the platform also share a total budget of 200 MB of captures; when it is used up, captures are paused for pending projects. At the limit, even a new capture of a state that already has one is refused: the image it would replace still counts until it is removed. In every case the error asks to claim the project first, then capture again. Claimed projects have no capture quota of their own today.

Wait for a choice

GET /api/variants/<round>/choice tells the outcome of a round of variants. propose_variants returns this address as choice_url, with wait_command, the shell command that calls it until you choose. It takes no key: the round id, random and given only to the agent that proposed the round, stands for it, and the answer only tells the outcome of the round.

  • While the round waits, the request waits up to 20 seconds for your choice, then returns 204 with no body.
  • Once the round is settled, it returns 200 with one sentence in plain text for the agent: the variant you chose (label, version, frame URL, whether you edited it), or the active version when the round was set aside without a choice.
curl -s https://app.agentappbuilder.com/api/variants/<round>/choice

Check the service

GET /api/health returns {"ok": true}. Any other method returns 405.

curl -s https://app.agentappbuilder.com/api/health

Send HTML safely

Prototype HTML is full of quotes and line breaks. Do not paste it into a shell string: write the arguments to a JSON file with Node, which escapes the HTML correctly, then send the file. Keep both files outside your repository, next to the saved key.

node -e 'const fs=require("fs");fs.writeFileSync("/tmp/agentappbuilder/frame.json",JSON.stringify({screen:{name:"Home",route:"/"},state:{label:"empty"},html:fs.readFileSync("/tmp/agentappbuilder/home-empty.html","utf8")}))'

curl -s -X POST https://app.agentappbuilder.com/api/tools/create_proto_frame \
  -H "authorization: Bearer $AAB_KEY" \
  -H 'content-type: application/json' \
  --data-binary @/tmp/agentappbuilder/frame.json

rm /tmp/agentappbuilder/frame.json /tmp/agentappbuilder/home-empty.html

--data-binary sends the file byte for byte. Delete the files once they are sent.

Keep the key out of your repository

Keep the response of POST /api/projects in a file outside the repository that only you can read (umask 077, as in Create a project). Each command may start a fresh shell: read the key from the file in each command, and never print it.

AAB_KEY=$(node -p 'require("/tmp/agentappbuilder/my-app.json").key') && curl -s -X POST https://app.agentappbuilder.com/api/tools/get_project_context -H "authorization: Bearer $AAB_KEY"
  • Never write the key in a committed file: not in the code, not in a config file, not in a script.
  • Claude Code’s configuration also keeps it out of the repository, where claude mcp add --scope local puts it.
  • If a key leaks, an owner or admin of the organization revokes it in Settings, Keys, and creates a new one. See Security and data.