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#

EndpointWhat it does
POST /v0/stacksCreate 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}/deployRedeploy 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}/logsPlain-text logs. kind=run|build, service, tail.
GET /v0/stacks/{id}/eventsLifecycle events. Pass after=<last id> to poll.
POST /v0/claimClaim a stack into an account. Needs that account's API key.
GET /v0/meThe 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#

  1. Add your API key (optional)#

    # deploy into your account instead of anonymously
    export AGENTSERVE_API_KEY=as_live_…
  2. 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.yaml at its root, it's read from the upload. You can also send the manifest separately as a manifest form field. Other form fields: name, ttl and claim_email.

  3. 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 status to failed with error. A failed redeploy leaves the stack live on the previous version, with deploy_error set.

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.

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 source holding a tar or tar.gz.
  • 401 on create. You sent an Authorization header 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 detail field says which.
  • 429. Too many unclaimed stacks running from your network, or too many creates. Honor Retry-After when 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.

Stuck? Your agent can read /skill.md, or connect it over MCP.