Typed clients for the code around your agent: your backend, a CI job, a webhook receiver, an admin script. The hand-written JavaScript/TypeScript and Python clients share the same methods and shapes; a typed Go client is also generated from the OpenAPI spec.
Not for your agent's tool-calling loop. If your agent decides on its own when to provision a server or run a command, point it at the MCP server directly, it already speaks tool-calling and needs none of these SDKs. These clients are for the surrounding system: the parts of your stack that call the API on a schedule, in response to a webhook, or from your own product logic.
Every example below switches together, and stays put on your next visit.
Examples in
Install
The JS client is ESM, Node 18+, and ships its own types. The Python client is sync and async, supports 3.9+, and depends only on httpx. The Go client is generated from the OpenAPI spec and needs Go 1.21+.
shell
npm install @server4agent/sdk
shell
pip install server4agent
shell
go get github.com/Server4Agent/server4agent-go
Configure the client
The Python and JavaScript clients read your key from the SERVER4AGENT_API_KEYenvironment variable if you don't pass one explicitly, and let you set a per-request timeout and the retry count. The Go client authenticates differently: put the key on a context value and pass that context to every call. This key is a sk_live_ secret: keep it server-side, never ship it in a browser bundle.
client.ts
import { Server4Agent } from "@server4agent/sdk";// With no arguments, reads SERVER4AGENT_API_KEY from the environment.const client = new Server4Agent();// Or pass the key and tune the transport explicitly.const tuned = new Server4Agent({ apiKey: process.env.SERVER4AGENT_API_KEY, timeoutMs: 30_000, // per-request timeout (default 60_000) maxRetries: 4, // retries on 429/5xx/network (default 2)});
client.py
from server4agent import Server4Agent# With no arguments, reads SERVER4AGENT_API_KEY from the environment.client = Server4Agent()# Or pass the key and tune the transport explicitly.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: ...
client.go
package mainimport ( "context" "os" server4agent "github.com/Server4Agent/server4agent-go")func main() { // NewConfiguration() targets api.server4agent.com by default. client := server4agent.NewAPIClient(server4agent.NewConfiguration()) // The generated client authenticates with a context value, not a // constructor arg: put your sk_live_ key on the context as a bearer token // and pass that ctx to every call. Keep it server-side. ctx := context.WithValue(context.Background(), server4agent.ContextAccessToken, os.Getenv("SERVER4AGENT_API_KEY")) _, _ = client, ctx}
Quickstart
Provision a server, hand the on-server agent a goal, and wait for a live URL. Every method maps 1:1 to a REST endpoint; the SDK just gives it a type.
quickstart.ts
import { Server4Agent } from "@server4agent/sdk";const client = new Server4Agent();// create() resolves to a handle you can act on directly.const server = await client.servers.create({ tier: "small" });// Hand the on-server agent a goal and block until it's live.const build = await server.builds.start("a FastAPI todo API with a small web UI");await build.wait({ onProgress: (b) => console.log("build:", b.status) });console.log("live at", build.url);
quickstart.py
from server4agent import Server4Agentclient = Server4Agent()# create() returns a handle you can act on directly.server = client.servers.create(tier="small")# Hand the on-server agent a goal and block until it's live.build = server.builds.start("a FastAPI todo API with a small web UI")build.wait(on_poll=lambda b: print("build:", b.status))print("live at", build.url)
quickstart.go
client := server4agent.NewAPIClient(server4agent.NewConfiguration())ctx := context.WithValue(context.Background(), server4agent.ContextAccessToken, os.Getenv("SERVER4AGENT_API_KEY"))// Provision a server.tier := server4agent.SERVERTIER_SMALLserver, _, err := client.ServersAPI.CreateServer(ctx). ServerCreate(server4agent.ServerCreate{Tier: &tier}). Execute()if err != nil { panic(err)}// Hand the on-server agent a goal.build, _, err := client.BuildsAPI.StartBuild(ctx, server.GetId()). BuildCreate(server4agent.BuildCreate{Goal: "a FastAPI todo API with a small web UI"}). Execute()if err != nil { panic(err)}// The generated client has no .wait(): poll BuildsAPI.GetBuild until the// status is completed, then read the live url off the finished build.fmt.Println("build", build.GetBuildId(), build.GetStatus())
Handles
create() and get() return rich handles: data records you can also act on. A Server handle exposes sub-resources (tasks, builds, files, projects) already scoped to it, so you never pass its id twice. Every method also has a flat form on the client if you'd rather pass ids around.
handles.ts
// create() and get() both resolve to a Server handle.const server = await client.servers.get("srv_abc");// Its sub-resources are already scoped to it: no id to pass again.await server.tasks.create("Summarize today's sales into report.md");await server.files.write("notes.txt", "hello");await server.exec("ls -la");await server.deploy();await server.refresh(); // re-fetch the record in place// Prefer passing ids around? The flat API takes a server id up front.await client.tasks.create(server.id, "Same task, id passed explicitly.");await client.files.write(server.id, "notes.txt", "hello");
handles.py
# create() and get() both return a Server handle.server = client.servers.get("srv_abc")# Its sub-resources are already scoped to it: no id to pass again.server.tasks.create("Summarize today's sales into report.md")server.files.write("notes.txt", "hello")server.exec("ls -la")server.deploy()server.refresh() # re-fetch the record in place# Prefer passing ids around? The flat API takes a server id up front.client.tasks.create(server.id, "Same task, id passed explicitly.")client.files.write(server.id, "notes.txt", "hello")
handles.go
// The generated Go client has no handle objects. Everything is a per-resource// service call that takes the ids explicitly, which is the same "flat" form the// TypeScript and Python clients also expose.server, _, err := client.ServersAPI.GetServer(ctx, "srv_abc").Execute()if err != nil { panic(err)}client.TasksAPI.CreateTask(ctx, server.GetId()). TaskCreate(server4agent.TaskCreate{Goal: "Summarize today's sales into report.md"}). Execute()client.FilesAPI.WriteFile(ctx, server.GetId()). FileWrite(server4agent.FileWrite{Path: "notes.txt", Content: "hello"}). Execute()client.DeploymentsAPI.DeployServer(ctx, server.GetId()).Execute()
Servers
A server is a sized compute box. tier picks the size; the task field is just a human-readable label. Create, list, get, control the lifecycle, and delete.
servers.ts
// tier sizes the box: "small" | "medium" | "large". task is a label.const server = await client.servers.create({ tier: "medium", task: "data pipeline" });const all = await client.servers.list();const one = await client.servers.get(server.id);// Lifecycle controls.await server.stop();await server.start();await server.restart();// Rename, re-fetch, tear down.await server.update({ task: "renamed pipeline" });await server.refresh();await server.delete();
servers.py
# tier sizes the box: "small" | "medium" | "large". task is a label.server = client.servers.create(tier="medium", task="data pipeline")all_servers = client.servers.list()one = client.servers.get(server.id)# Lifecycle controls.server.stop()server.start()server.restart()# Rename, re-fetch, tear down.server.update(task="renamed pipeline")server.refresh()server.delete()
servers.go
// tier sizes the box; task is a human-readable label. Optional fields are// pointers, so take their address (or use server4agent.PtrString / Ptr*).tier := server4agent.SERVERTIER_MEDIUMtask := "data pipeline"created, _, err := client.ServersAPI.CreateServer(ctx). ServerCreate(server4agent.ServerCreate{Tier: &tier, Task: &task}). Execute()if err != nil { panic(err)}id := created.GetId()// List and get return the wrapped payloads.list, _, _ := client.ServersAPI.ListServers(ctx).Execute()one, _, _ := client.ServersAPI.GetServer(ctx, id).Execute()_, _ = list.GetServers(), one.GetServer()// Lifecycle: start / stop / restart.client.ServersAPI.ControlServer(ctx, id). ServerActionRequest(server4agent.ServerActionRequest{ Action: server4agent.SERVERACTIONREQUESTACTION_STOP, }). Execute()// Rename, then tear down.newTask := "renamed pipeline"client.ServersAPI.UpdateServer(ctx, id). ServerUpdate(server4agent.ServerUpdate{Task: &newTask}). Execute()client.ServersAPI.DeleteServer(ctx, id).Execute()
Run commands
exec() runs a shell command on the server and resolves to its stdout, handy for installing packages, running scripts, or inspecting the workspace.
exec.ts
// exec() runs a shell command on the server and resolves to stdout.await server.exec("pip install httpx");const out = await server.exec("python -c 'import sys; print(sys.version)'");console.log(out);
exec.py
# exec() runs a shell command on the server and returns stdout.server.exec("pip install httpx")out = server.exec("python -c 'import sys; print(sys.version)'")print(out)
exec.go
// ExecCommand runs a shell command on the server and returns its stdout.res, _, err := client.ServersAPI.ExecCommand(ctx, id). ExecRequest(server4agent.ExecRequest{ Command: "python -c 'import sys; print(sys.version)'", }). Execute()if err != nil { panic(err)}fmt.Println(res.GetStdout())
Files
Read, write, list, and delete files in the server's workspace. The workspace is durable: files and installed dependencies persist between tasks and builds.
files.ts
// The workspace is durable: files and installed deps survive between tasks.await server.files.write("app.py", "print('hello from the server')");const listing = await server.files.list(); // string[] of pathsconst content = await server.files.read("app.py");await server.files.delete("app.py");
files.py
# The workspace is durable: files and installed deps survive between tasks.server.files.write("app.py", "print('hello from the server')")listing = server.files.list() # list[str] of pathscontent = server.files.read("app.py")server.files.delete("app.py")
files.go
// Write a file into the durable workspace.client.FilesAPI.WriteFile(ctx, id). FileWrite(server4agent.FileWrite{ Path: "app.py", Content: "print('hello from the server')", }). Execute()// readOrListFiles: with a path it reads that file; without one it lists the// workspace. deleteFile takes the path as a query param too.client.FilesAPI.ReadOrListFiles(ctx, id).Path("app.py").Execute()client.FilesAPI.ReadOrListFiles(ctx, id).Execute() // list everythingclient.FilesAPI.DeleteFile(ctx, id).Path("app.py").Execute()
Tasks
A task hands the on-server agent a single goal. wait() polls until it reaches a terminal state; pass a progress callback (onProgress in JS, on_poll in Python) to stream status to a log or UI. A task can finish as completed, failed, cancelled, or awaiting_input when the agent needs a decision from you.
tasks.ts
// A task hands the on-server agent a goal. wait() polls until it settles.const task = await server.tasks.create("Scrape today's HN front page to hn.json.");await task.wait({ onProgress: (t) => console.log("task:", t.status) });// Terminal states: completed, failed, cancelled, awaiting_input.if (task.status === "completed") { console.log(task.result);} else if (task.status === "awaiting_input") { // The agent needs a decision from you before it can continue. console.log("agent asks:", task.question);}
tasks.py
# A task hands the on-server agent a goal. wait() polls until it settles.task = server.tasks.create("Scrape today's HN front page to hn.json.")task.wait(on_poll=lambda t: print("task:", t.status))# Terminal states: completed, failed, cancelled, awaiting_input.if task.status == "completed": print(task.result)elif task.status == "awaiting_input": # The agent needs a decision from you before it can continue. print("agent asks:", task.question)
tasks.go
// Hand the on-server agent a goal.created, _, err := client.TasksAPI.CreateTask(ctx, id). TaskCreate(server4agent.TaskCreate{ Goal: "Scrape today's HN front page to hn.json.", }). Execute()if err != nil { panic(err)}// The generated client has no .wait(): poll TasksAPI.GetTask(ctx, id,// created.GetTaskId()) on an interval until the status is terminal:// completed, failed, cancelled, or awaiting_input (the agent needs a decision).fmt.Println("task", created.GetTaskId(), created.GetStatus())
Builds
For a whole project, builds.start() runs an ordered pipeline (plan, scaffold, implement, install, build, deploy) on the server. The build handle carries per-step status, the produced artifacts, and the final live url.
builds.ts
// A build runs an ordered pipeline: plan, scaffold, implement, install,// build, deploy. Stream step-by-step progress through onProgress.const build = await server.builds.start("Build a SaaS dashboard with auth and deploy it.");await build.wait({ timeoutMs: 900_000, onProgress: (b) => { const step = b.steps.find((s) => s.status === "running"); console.log("building:", step?.label ?? b.status); },});console.log("live at", build.url);console.log("artifacts:", build.artifacts); // e.g. ["app/page.tsx", "package.json"]
builds.py
# A build runs an ordered pipeline: plan, scaffold, implement, install,# build, deploy. Stream step-by-step progress through on_poll.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"]
builds.go
// A build runs an ordered pipeline: plan, scaffold, implement, install,// build, deploy.created, _, err := client.BuildsAPI.StartBuild(ctx, id). BuildCreate(server4agent.BuildCreate{ Goal: "Build a SaaS dashboard with auth and deploy it.", }). Execute()if err != nil { panic(err)}// Poll BuildsAPI.GetBuild(ctx, id, created.GetBuildId()) until the status is// completed. The finished Build carries per-step status, GetArtifacts(), and// the live GetUrl().fmt.Println("build", created.GetBuildId(), created.GetStatus())
Projects & deploys
A project is a workspace and app inside a server, with its own slug and public URL. New projects are private: their url stays null until you flip visibility to public, and the slug and URL survive visibility toggles. Start from a template (list them with templates.list()) and mark short-lived ones ephemeral so they clean up.
projects.ts
// A project is a workspace + app inside a server. New projects are PRIVATE:// url is null until you publish. Start from a template if you like.const templates = await client.templates.list();const project = await server.projects.create({ name: "status-page", template: "dashboard", // visibility defaults to "private"; lifecycle defaults to "persistent".});console.log(project.url); // null while private// Publish it. The slug and URL stay stable across visibility toggles.await project.update({ visibility: "public" });await project.refresh();console.log(project.url); // https://<slug>.apps.server4agent.com// Deploy the current workspace to a live URL, list deployments.const url = await server.deploy();const history = await server.deployments();// Ephemeral projects clean up with cleanup(); persistent ones with delete().await project.cleanup();
projects.py
# A project is a workspace + app inside a server. New projects are PRIVATE:# url is None until you publish. Start from a template if you like.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# Publish it. The slug and URL stay stable across visibility toggles.project.update(visibility="public")project.refresh()print(project.url) # https://<slug>.apps.server4agent.com# Deploy the current workspace to a live URL, list deployments.url = server.deploy()history = server.deployments()# Ephemeral projects clean up with cleanup(); persistent ones with delete().project.cleanup()
projects.go
// A project is a workspace + app inside a server. New projects are PRIVATE:// GetUrl() is empty until you publish. Optional fields use the Ptr* helpers.project, _, err := client.ProjectsAPI.CreateProject(ctx, id). ProjectCreate(server4agent.ProjectCreate{ Name: server4agent.PtrString("status-page"), Template: server4agent.PtrString("dashboard"), }). Execute()if err != nil { panic(err)}fmt.Println(project.GetUrl()) // empty while private// Publish it. The slug and URL stay stable across visibility toggles.pub := server4agent.PROJECTVISIBILITY_PUBLICupdated, _, _ := client.ProjectsAPI.UpdateProject(ctx, project.GetId()). ProjectUpdate(server4agent.ProjectUpdate{Visibility: &pub}). Execute()fmt.Println(updated.GetUrl()) // https://<slug>.apps.server4agent.com// Deploy the workspace; clean up ephemeral projects.client.DeploymentsAPI.DeployServer(ctx, id).Execute()client.ProjectsAPI.CleanupProject(ctx, project.GetId()).Execute()
Errors & retries
Every failure is a Server4AgentError (JS) or subclass of it (Python), so you can catch broadly or by type: NotFoundError, RateLimitError, APIStatusError. Each carries the HTTP status, a machine-readable code, the message, and a request id to quote to support. Retryable failures (429, 5xx, network blips) are retried automatically with exponential backoff; tune the count with maxRetries / max_retries.
errors.ts
import { Server4AgentError, NotFoundError, RateLimitError, APIStatusError,} from "@server4agent/sdk";try { await client.servers.get("srv_does_not_exist");} catch (err) { if (err instanceof NotFoundError) { // 404 } else if (err instanceof RateLimitError) { // 429, already retried; quote err.requestId to support } else if (err instanceof APIStatusError) { console.error(err.status, err.code, err.message, err.requestId); } else if (err instanceof Server4AgentError) { // anything else from the SDK (timeouts, network) }}
errors.py
from server4agent import ( Server4AgentError, NotFoundError, RateLimitError, APIStatusError,)try: client.servers.get("srv_does_not_exist")except NotFoundError: ... # 404except RateLimitError as e: ... # 429, already retried; quote e.request_id to supportexcept APIStatusError as e: print(e.status, e.code, e.message, e.request_id)except Server4AgentError: ... # anything else from the SDK (timeouts, network)
errors.go
// Every call returns the model, the raw *http.Response, and an error._, httpResp, err := client.ServersAPI.GetServer(ctx, "srv_does_not_exist").Execute()if err != nil { // Decode the typed API error for the raw body (and the JSON Error model). var apiErr server4agent.GenericOpenAPIError if errors.As(err, &apiErr) { fmt.Println("body:", string(apiErr.Body())) } // The response is still available for the status code: 404, 429, 5xx, … if httpResp != nil { fmt.Println("status:", httpResp.StatusCode) }}
Async (Python)
Python ships a second client, AsyncServer4Agent, that mirrors the sync one method-for-method: same names, same shapes, every call awaited. The JS client is already promise-based, so there's nothing extra to import.
async_client.py
import asynciofrom server4agent import AsyncServer4Agent# The async client mirrors the sync one method-for-method; every call awaits.async def main(): async with AsyncServer4Agent() as client: server = await client.servers.create(tier="small") task = await server.tasks.create("Summarize today's top HN posts to summary.md") await task.wait(on_poll=lambda t: print("task:", t.status)) print(task.result)asyncio.run(main())
note.ts
// The JS client is already promise-based: every method returns a Promise,// so there's no separate async client. Just await.const server = await client.servers.create({ tier: "small" });const task = await server.tasks.create("Summarize today's top HN posts to summary.md");await task.wait();console.log(task.result);
concurrency.go
// Go has no async client either: concurrency is goroutines. Every call already// takes a context, so fan out in parallel and cancel with the context.var wg sync.WaitGroupfor _, goal := range goals { wg.Add(1) go func(goal string) { defer wg.Done() client.TasksAPI.CreateTask(ctx, id). TaskCreate(server4agent.TaskCreate{Goal: goal}). Execute() }(goal)}wg.Wait()
Verifying webhooks
The Python and JavaScript clients export a verify_webhook_signature /verifyWebhookSignature helper that checks the Server4Agent-Signatureheader against your webhook's secret (from Go, verify it yourself). Always verify against the raw request body, before you parse it as JSON. See Webhooks for the event catalog and how to subscribe.
verify.ts
import { verifyWebhookSignature } from "@server4agent/sdk";// req.text() must be the RAW body. Verify before you JSON.parse it:// re-serializing changes the bytes and breaks the signature.const raw = await req.text();const ok = await verifyWebhookSignature( process.env.SERVER4AGENT_WEBHOOK_SECRET!, raw, req.headers.get("server4agent-signature"),);if (!ok) return new Response("invalid signature", { status: 401 });const event = JSON.parse(raw);
verify.py
import osfrom 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
verify.go
// The signature helper ships in the Python and JavaScript clients. From Go,// verify the Server4Agent-Signature header against the RAW request body// yourself, using the scheme in the Webhooks guide, before you decode the JSON.//// raw, _ := io.ReadAll(r.Body)// if !verifySignature(secret, raw, r.Header.Get("Server4Agent-Signature")) {// http.Error(w, "invalid signature", http.StatusUnauthorized)// return// }// var event map[string]any// json.Unmarshal(raw, &event)
Recipes
Ephemeral preview environments in CI
Provision a real, disposable server per pull request, deploy the branch to it, and clean it up when the PR closes.
preview-env.ts
// Run from CI on every PR: give it a real, disposable URL, then tear it// down when the PR closes. lifecycle "ephemeral" marks it for cleanup.import { Server4Agent } from "@server4agent/sdk";const client = new Server4Agent();const pr = process.env.PR_NUMBER!;const server = await client.servers.create({ task: `pr-${pr}` });const project = await server.projects.create({ name: `pr-${pr}`, visibility: "public", lifecycle: "ephemeral",});const build = await server.builds.start("Install deps, build the app, and deploy it.");await build.wait();console.log(`Preview: ${project.url}`);// On PR close: await project.cleanup();
preview_env.py
# Run from CI on every PR: give it a real, disposable URL, then tear it# down when the PR closes. lifecycle "ephemeral" marks it for cleanup.import osfrom server4agent import Server4Agentclient = Server4Agent()pr = os.environ["PR_NUMBER"]server = client.servers.create(task=f"pr-{pr}")project = server.projects.create( name=f"pr-{pr}", visibility="public", lifecycle="ephemeral",)build = server.builds.start("Install deps, build the app, and deploy it.")build.wait()print(f"Preview: {project.url}")# On PR close: project.cleanup()
A webhook receiver that reacts to deployments
Subscribe once (see Webhooks), then verify and act on each delivery.
webhook-receiver.ts
import express from "express";import { verifyWebhookSignature } from "@server4agent/sdk";const app = express();app.post("/hooks/server4agent", express.text({ type: "*/*" }), async (req, res) => { // req.body must be the raw string; verify before you JSON.parse it. const ok = await verifyWebhookSignature( process.env.SERVER4AGENT_WEBHOOK_SECRET!, req.body, req.header("Server4Agent-Signature"), ); if (!ok) return res.status(401).end(); const event = JSON.parse(req.body); if (event.event === "deployment.live") { await notifySlack(`Deployed: ${event.url}`); } res.status(200).end();});
webhook_receiver.py
import jsonimport osfrom flask import Flask, requestfrom server4agent import verify_webhook_signatureapp = Flask(__name__)@app.post("/hooks/server4agent")def hook(): # request.get_data() must be the raw body; verify before you json.loads it. raw = request.get_data(as_text=True) ok = verify_webhook_signature( os.environ["SERVER4AGENT_WEBHOOK_SECRET"], raw, request.headers.get("Server4Agent-Signature"), ) if not ok: return "invalid signature", 401 event = json.loads(raw) if event["event"] == "deployment.live": notify_slack(f"Deployed: {event['url']}") return "", 200
Per-customer workspaces from your own product
If a customer action should provision a real environment, that's your backend calling the SDK directly, no agent tool-calling loop involved.
provision.ts
// Your product's backend, not an agent: a customer submits a request, your// own business logic decides what to build, and you provision a real// workspace for them on demand.import { Server4Agent } from "@server4agent/sdk";const client = new Server4Agent();export async function provisionForCustomer(customerId: string, goal: string) { const server = await client.servers.create({ task: `customer-${customerId}` }); const project = await server.projects.create({ name: `${customerId}-workspace`, visibility: "public", template: "dashboard", }); await server.tasks.create(goal); return project.url;}
provision.py
# Your product's backend, not an agent: a customer submits a request, your# own business logic decides what to build, and you provision a real# workspace for them on demand.from server4agent import Server4Agentclient = Server4Agent()def provision_for_customer(customer_id: str, goal: str) -> str: server = client.servers.create(task=f"customer-{customer_id}") project = server.projects.create( name=f"{customer_id}-workspace", visibility="public", template="dashboard", ) server.tasks.create(goal) return project.url
Scoping a key before handing it to another tool
Mint a key restricted to one server (or project) instead of sharing your account-wide key.
scoped-key.ts
// Hand a support tool a key that can only touch one customer's server,// not your whole account.const supportKey = await client.keys.create("support-tool");await client.keys.updateScope(supportKey.id, { allowedServerIds: [server.id],});// supportKey.key is the plaintext, shown once, here. Hand it to the tool// and store the id if you'll need to revoke it later.// await client.keys.revoke(supportKey.id);
scoped_key.py
# Hand a support tool a key that can only touch one customer's server,# not your whole account.support_key = client.keys.create("support-tool")client.keys.update_scope(support_key.id, allowed_server_ids=[server.id])# support_key.key is the plaintext, shown once, here. Hand it to the tool# and store the id if you'll need to revoke it later.# client.keys.revoke(support_key.id)
About the Go client
Flip the language switch to Go and every example above rewrites itself in Go. A few conventions are worth calling out, because the Go client is generated from the OpenAPI spec rather than hand-written like the Python and JS clients:
Auth is a context value, not a constructor argument, so set your sk_live_ key as a bearer token and pass the context to every call.
There are no handles. Every call is a per-resource service (client.ServersAPI, client.TasksAPI, …) that takes the ids explicitly and returns the model, the *http.Response, and an error.
No wait(): poll GetTask / GetBuild until the status is terminal.
Optional fields are pointers; the server4agent.PtrString helpers and typed enum constants (SERVERTIER_SMALL) fill them in.
The SDKs are open source, one public repo per language: server4agent-python, server4agent-js, and server4agent-go. The Python and JS repos ship runnable examples and a full test suite. Every method wraps one of the endpoints on the REST API page.