Build with / HTTP
Your own agent
Call the AgentServe /v0 API directly from your own agent code: deploy a tarball, keep the manage token, hand the claim URL to a human.
If you're writing your own agent, you don't need the CLI or MCP. The /v0 API is a handful of endpoints:
upload a tarball, get URLs and a claim link back, and use a bearer token for everything after that.
What you get#
| Endpoint | What it does |
|---|---|
POST /v0/stacks | Create a stack from a multipart upload. Add ?wait=true to return
after the build (up to 90 s; poll the stack if it is still building). |
GET /v0/stacks/{id} | Status, URLs, expiry, errors. |
POST /v0/stacks/{id}/deploy | Redeploy a new upload to the same URLs. |
PATCH /v0/stacks/{id} | Extend an unclaimed stack: {"ttl": "30m"}. |
DELETE /v0/stacks/{id} | Delete the stack and its data. |
GET /v0/stacks/{id}/logs | Plain-text logs. kind=run|build,
service, tail. |
GET /v0/stacks/{id}/events | Lifecycle events. Pass after=<last id> to
poll. |
POST /v0/claim | Claim a stack into an account. Needs that account's API key. |
GET /v0/me | The API key's account and its stacks. |
Every field is in the API reference.
Auth in one paragraph#
Creating a stack needs no auth at all. The response contains a manage_token (ast_anon_…),
shown once; send it as Authorization: Bearer <manage_token> on every call about that stack. If you
send an account API key (as_live_…) when creating, the stack goes straight into that account with no
clock and no claim code, and the same key works on that stack afterwards. An invalid key on create is a 401, not a
silent anonymous deploy.
Setup#
Add your API key (optional)#
# deploy into your account instead of anonymously export AGENTSERVE_API_KEY=as_live_…Deploy from Python#
This packs a directory, deploys it, stores the token and prints what a human needs. It uses
httpx; any HTTP client that does multipart works.import io import json import os import tarfile from pathlib import Path import httpx SERVER = "https://agentserve.sh" API_KEY = os.environ.get("AGENTSERVE_API_KEY") SKIP = {"node_modules", ".git", ".venv", "__pycache__", ".agentserve"} def pack(root: Path) -> bytes: buf = io.BytesIO() with tarfile.open(fileobj=buf, mode="w:gz") as tf: for p in sorted(root.rglob("*")): rel = p.relative_to(root) if p.is_file() and not SKIP.intersection(rel.parts): tf.add(p, arcname=str(rel)) return buf.getvalue() def deploy(root: Path) -> dict: state_file = root / ".agentserve" / "stack.json" files = {"source": ("source.tar.gz", pack(root), "application/gzip")} with httpx.Client(base_url=SERVER, timeout=900) as c: if state_file.exists(): # redeploy: same stack, same URLs state = json.loads(state_file.read_text()) r = c.post(f"/v0/stacks/{state['id']}/deploy", params={"wait": "true"}, files=files, headers={"Authorization": f"Bearer {state['manage_token']}"}) r.raise_for_status() return {**r.json(), "claim_url": state.get("claim_url")} headers = {"Authorization": f"Bearer {API_KEY}"} if API_KEY else {} r = c.post("/v0/stacks", params={"wait": "true"}, files=files, data={"name": root.name}, headers=headers) r.raise_for_status() stack = r.json() state_file.parent.mkdir(exist_ok=True) (state_file.parent / ".gitignore").write_text("*\n") # the token is a secret state_file.write_text(json.dumps({k: stack.get(k) for k in ("id", "manage_token", "claim_url")})) return stack stack = deploy(Path("./my-project")) print(stack["status"], stack["urls"]) if stack.get("deploy_error"): print("redeploy failed, previous version still serving:", stack["deploy_error"]) if stack.get("claim_url"): print("Keep it:", stack["claim_url"])If the project has an
agentserve.yamlat its root, it's read from the upload. You can also send the manifest separately as amanifestform field. Other form fields:name,ttlandclaim_email.Read logs when it fails#
headers = {"Authorization": f"Bearer {manage_token}"} print(httpx.get(f"{SERVER}/v0/stacks/{stack_id}/logs", params={"kind": "build", "service": "api", "tail": 200}, headers=headers).text)A failed first build sets
statustofailedwitherror. A failed redeploy leaves the stackliveon the previous version, withdeploy_errorset.
A first task to try#
Point the script at a project with an agentserve.yaml for a FastAPI API and a static Vite frontend, like
the one in Multi-service stacks, so you'll get two URLs, with the frontend already wired to the API. Run it twice: the second run
redeploys to the same URLs, and the todos survive because they're stored in $DATA_DIR.
How the claim link reaches you#
An anonymous create returns claim_url, claim_code and a note asking you to pass
the link to a human. That's your agent's job: put it in the chat, the PR, the Slack message, wherever the person is.
The stack runs for 1 hour; claiming before or after expiry (while the snapshot is kept) keeps it at the same URLs, with
its data. See Claiming.
If your agent isn't around when time runs low, add notify: {webhook: URL} to the manifest. Every event,
including stack.expiring, is POSTed there, signed with the webhook_secret from the create
response.
Don't call POST /v0/claim unless a person gave your agent their API key for
that purpose. Deciding what to keep is the human's call.
Troubleshooting#
- 400 "missing 'source' file". The upload must be a multipart field named
sourceholding a tar or tar.gz. - 401 on create. You sent an
Authorizationheader that isn't a valid API key. Drop it for an anonymous deploy. - 403 on a stack. Wrong token for that stack, or the owner revoked it; retry with the account's API key if you have it.
- 404 on a stack. It was deleted. Create a new one, and tell your human the URLs changed.
- 409. A build is already running, or the stack expired; an expired stack has to be claimed before it can be redeployed.
- 413 / 422. The upload is too big, or the manifest is invalid. The
detailfield says which. - 429. Too many unclaimed stacks running from your network, or too many creates. Honor
Retry-Afterwhen it's present.
Errors are JSON with a detail field, which is written to be read by an agent. Pass it to the model as is.
More: API, Manifest, Quickstart.