Server4Agent
Back to blog

The Server4Agent Python SDK: provision and orchestrate agent servers in a few lines

A developer's guide to the official Server4Agent Python SDK: install it, provision a server, run tasks and builds, deploy to a live URL, and verify webhooks, with both a sync and an async client.

Created Jul 14, 2026 9 min read

There are two ways an agent can touch Server4Agent. If the agent does its own tool-calling, you point it at the MCP server and it drives everything itself. But plenty of the code you write lives around the agent instead of inside it: a backend that provisions a workspace per customer, a scheduled job that rebuilds a report every morning, a webhook receiver that reacts to a deploy. That outer code wants a typed client, not a tool-calling loop.

The official Python SDK is that client. It wraps the whole Server4Agent REST API behind typed methods, ships a synchronous and an asynchronous variant, retries transient failures for you, and depends only on `httpx`. This post walks through it end to end.

Quick answer

Install the client with pip install server4agent, set your SERVER4AGENT_API_KEY, and you can provision a server, run commands, start agent tasks and structured builds, publish a project to a live URL, and verify webhook signatures, all from typed Python. The SDK works on Python 3.9 and newer and ships both a Server4Agent sync client and an AsyncServer4Agent async client that mirror each other method for method.

Key takeaways

  • One pip install gives you a typed client for the entire REST API.
  • create() and get() return rich handles you can act on directly, so you rarely pass IDs around by hand.
  • The async client mirrors the sync one, which makes fan-out across many servers straightforward.
  • Retries on 429 and 5xx are automatic, with tunable timeouts and retry counts.
  • A built-in helper verifies webhook signatures so you can trust a payload before acting on it.

Install

bash
pip install server4agent

It requires Python 3.9 or newer and pulls in only httpx. Once it is installed, the client reads your key from the SERVER4AGENT_API_KEY environment variable, so nothing else needs configuring to make the first call. You can grab a key from your dashboard and export it:

bash
export SERVER4AGENT_API_KEY="sk_live_..."

Your first server

The smallest useful program provisions a server, hands the on-server build agent a goal, and waits for a live URL:

python
from server4agent import Server4Agent

client = Server4Agent()  # reads SERVER4AGENT_API_KEY from the environment

# create() returns a handle you can act on directly
server = client.servers.create(tier="small")

# kick off a build and block until it's live
build = server.builds.start("a FastAPI todo API with a web UI").wait()
print(build.url)

# run a command right on the server
print(server.exec("ls -la"))

Three things are worth noticing. First, client.servers.create() returns a *handle*, not a bare dictionary, so server.builds and server.exec are available immediately. Second, build.wait() polls until the build reaches a terminal state, so you do not write the polling loop yourself. Third, the return value of a finished build includes a URL, because a live URL is the artifact that turns generated code into usable software.

If you want to pass the key explicitly or tune the transport, the constructor takes both:

python
tuned = Server4Agent(
    api_key="sk_live_...",
    timeout=30.0,   # per-request timeout in seconds (default 60)
    max_retries=4,  # retries on 429/5xx/network (default 2)
)

# Use it as a context manager to close the connection pool when you're done.
with Server4Agent() as client:
    ...

Handles, not ID juggling

The design choice that makes the SDK pleasant is that create() and get() return objects that are both data records and action surfaces. You read fields off them, and you call methods on them, without threading server and project IDs through every call:

python
server = client.servers.get("srv_abc")
server.tasks.create("Scrape today's launch list into launches.json")
server.files.write("app.py", "print('hello')")
server.deploy()
server.refresh()                    # re-fetch in place

project = client.projects.get("prj_xyz")
project.update(visibility="public")

Sub-resources like server.tasks and server.files are already scoped to the server you fetched, which is what keeps the call sites short. This is the same servers-and-projects model the whole product is built on, covered in depth in give your AI agent its own server with one MCP call.

Tasks and builds

Two verbs cover most real work. A task hands the on-server agent a goal and lets it work; a build runs a structured pipeline you can watch step by step. Both return something you can wait() on.

python
# A task: one goal, run to completion, read the result.
task = server.tasks.create("Summarize today's top HN posts to summary.md")
task.wait()
print(task.status, task.result)

# A build: watch each step, then read the live URL and artifacts.
build = server.builds.start("Build a SaaS dashboard with auth and deploy it.")

def show(b):
    running = next((s for s in b.steps if s.status == "running"), None)
    print("building:", running.label if running else b.status)

build.wait(timeout=900.0, on_poll=show)
print("live at", build.url)
print("artifacts:", build.artifacts)  # e.g. ["app/page.tsx", "package.json"]

The on_poll callback fires on every poll, which is enough to stream progress into a log line, a Slack message, or a status field in your own database without wiring up a separate event stream.

Projects, visibility, and deploy

A project is a workspace and app that lives inside a server. New projects are private: their URL is None until you publish. The slug and URL stay stable across visibility toggles, so the link you review privately is the exact link that goes live.

python
templates = client.templates.list()
project = server.projects.create(
    name="status-page",
    template="dashboard",
    # visibility defaults to "private"; lifecycle defaults to "persistent".
)

print(project.url)  # None while private

project.update(visibility="public")
project.refresh()
print(project.url)  # https://<slug>.apps.server4agent.com

url = server.deploy()
history = server.deployments()

# Ephemeral projects clean up with cleanup(); persistent ones with delete().
project.cleanup()

Private by default is not a small detail. It is the checkpoint that lets you let an agent build freely and still review the running result before anything is public, which we make the full case for in private by default: reviewing agent-built software before you ship it.

The async client, for fan-out

The synchronous client is fine for scripts and request handlers. The moment you want to drive many servers at once, reach for AsyncServer4Agent, which mirrors the sync client method for method with awaitable calls:

python
import asyncio
from server4agent import AsyncServer4Agent

async def main():
    async with AsyncServer4Agent() as client:
        server = await client.servers.create(tier="small")
        task = await server.tasks.create("Scrape today's HN front page to JSON.")
        await task.wait()
        print(task.status, task.result)

asyncio.run(main())

Because every call is awaitable, fanning a task out across a batch of servers is an asyncio.gather away, and the shared connection pool keeps that efficient. This is the natural fit for a backend that provisions one workspace per customer and needs to kick off dozens of builds without blocking a worker on each one.

Handle errors precisely

Failures are typed, so you can catch broadly or narrowly:

python
from server4agent import (
    Server4AgentError,
    NotFoundError,
    RateLimitError,
    APIStatusError,
)

try:
    client.servers.get("srv_does_not_exist")
except NotFoundError:
    ...  # 404
except RateLimitError as e:
    ...  # 429, already retried; quote e.request_id to support
except APIStatusError as e:
    print(e.status, e.code, e.message, e.request_id)
except Server4AgentError:
    ...  # anything else from the SDK (timeouts, network)

Retryable failures (429, 5xx, and network blips) are retried automatically with exponential backoff before they ever reach your except block, so the errors you do catch are the ones that genuinely need a decision. Every APIStatusError carries a request_id worth quoting to support.

Verify webhooks before you trust them

If your backend subscribes to build and deploy events, verify the signature on every delivery before you parse the body. The SDK ships a helper for exactly that:

python
import os
from server4agent import verify_webhook_signature

# request.data must be the RAW body. Verify before you json.loads it:
# re-serializing changes the bytes and breaks the signature.
ok = verify_webhook_signature(
    secret=os.environ["SERVER4AGENT_WEBHOOK_SECRET"],
    body=request.data.decode("utf-8"),
    header=request.headers.get("Server4Agent-Signature"),
)
if not ok:
    return "invalid signature", 401

The one rule that trips people up: verify the *raw* bytes. Parsing the JSON and re-serializing it changes the bytes and breaks the signature, so always check first and parse second. The full event catalog and receiver patterns live in the webhooks docs, and there is a worked example in I asked Server4Agent to build a Stripe webhook debugger.

Do you even need the SDK?

Not always, and that is the honest answer. If your agent decides on its own when to provision a server or run a command, connect it to the MCP server instead: it already speaks tool-calling and needs no SDK. Reach for the Python client for the code around the agent, the parts of your stack that call the API on a schedule, in response to a webhook, or straight from your product logic. That distinction is the whole reason MCP tools should ship software rather than just fetch data, and the SDK is how the rest of your backend joins that loop.

Prefer another language? The same surface ships as a TypeScript client for CI and Node backends and a typed Go client generated from our OpenAPI spec.

FAQ

How do I install the Server4Agent Python SDK?

Run pip install server4agent. It works on Python 3.9 and newer and depends only on httpx. The client reads your SERVER4AGENT_API_KEY from the environment, or you can pass api_key to the constructor.

Does the Python SDK support async?

Yes. Alongside the synchronous Server4Agent client, it ships AsyncServer4Agent, which mirrors the sync client method for method with awaitable calls. Use it when you want to drive many servers concurrently.

What can I do with the SDK?

Provision and control servers, run shell commands with exec, read and write files, start agent tasks and structured builds, deploy projects to a live URL, manage API keys, and verify webhook signatures. Every method maps to a REST endpoint.

How does authentication work?

With a Server4Agent API key. The client reads SERVER4AGENT_API_KEY by default, or you pass api_key="sk_live_..." explicitly. Keys are server-side secrets, so keep them out of any browser bundle.

Do I still need the SDK if my agent uses MCP?

No. If the agent does its own tool-calling, point it at the MCP server. The SDK is for the surrounding code: backends, CI jobs, cron, and webhook receivers.

Related reading

External references