Python, TypeScript and Go clients
The Python, TypeScript and Go clients are not on a public registry yet. Until they are, call the HTTP API or MCP.
No public package yet
The Python, TypeScript and Go clients are not on PyPI, npm or a public Go module path. There is nothing to download from this site.
Until a public package exists, call the HTTP API or connect over MCP.
curl --fail-with-body https://api.wavemaker.io/openapi.jsonUpdated
The Python, TypeScript and Go clients call the same HTTP API documented on HTTP API: the twelve tools, the session API, quotes and identity, plus the older operations the service still serves. They add safe retries, job waiting and hash-verified file transfers, so your application doesn't have to rebuild them. This page shows how to create a client, call tools, run a hosted session, wait on jobs, move files and handle errors.
The notice on this page says whether the Python, TypeScript and Go clients are on a public registry. None of them is published yet: they aren't on PyPI, npm or the Go module proxy, and the Go module path github.com/AdwaveInc/wavemaker-go has no release. Until a client you need is published, call the HTTP API or connect over MCP; the examples below show how each client is used once you have it.
What the clients cover
- The twelve tools through one call that returns the whole envelope, so
job,costandhintsstay available. - The session API: create a session, post turns, read events, read a turn by its
request_id, decide approvals, interrupt and close. POST /v1/quotesandGET /v1/identity.- Waiting on a job until it is
succeeded,failedorcancelled. - Upload with a source hash check, and download with a full hash check that never overwrites a file.
- Typed access to the older operations at
/api/v1/ops/{name}, such asworkspace.inspectandcredits.balance.
| Language | Runtime | Import |
|---|---|---|
| Python | Python 3.11 or later | from wavemaker_sdk import Client |
| TypeScript | Node 22 or later (Node only) | Client from the TypeScript package |
| Go | Go 1.25.4 or later | package wavemaker, wavemaker.New(...) |
Create a client
Every client takes the service origin and a credential with the scopes you need, usually read, write and run. Create one in Connections › Developer setup; see Client credentials and provider keys.
from wavemaker_sdk import Client
with Client("https://api.wavemaker.io", token_file="/private/path/wavemaker-token") as client:
print(client.identity()["workspace"])// Client is exported by the TypeScript package named in the obtainability notice on this page.
const client = new Client({url: 'https://api.wavemaker.io', token: process.env.WAVEMAKER_TOKEN!});
const me = await client.identity();client, err := wavemaker.New("https://api.wavemaker.io", token)
if err != nil {
log.Fatal(err)
}
me, err := client.Identity(ctx)| Option | Python | TypeScript | Go |
|---|---|---|---|
| Credential | token= or token_file= (exactly one) | token | second argument to New |
| Request timeout | timeout=30.0 seconds | timeoutMs: 30000 | WithTimeout(d), default 30 s |
| Retries | retry=RetryPolicy(...) or retry=False | retry: {...} or retry: false | WithRetry(policy) or WithoutRetry() |
| Custom transport | transport= | fetch: | WithHTTPClient(c) |
Every client refuses an origin that isn't HTTPS, except plain HTTP on your own machine (localhost, 127.0.0.1 or ::1). An origin with a path, query or embedded credentials is refused. Clients never follow redirects, so the credential is never sent to another host. A long poll extends the request timeout to cover the server's wait.
The client reads the credential once, when you create it. It doesn't refresh a CLI sign-in or reread a changed token file. When a credential expires, create a new client. The one exception is a resumable download in Python or TypeScript, which can take a refresh_token (refreshToken) callback that is called at most once on a 401.
Call a tool
tool sends POST /v1/tools/{tool} and returns the whole envelope {ok, data, error, job, cost, hints}. Pass project to work in a project, and a request_id for anything that can spend.
results = client.tool("search", {"query": "vertical video ad", "kinds": ["card", "format"], "limit": 5})
for item in results["data"]["results"]:
print(item["kind"], item["uri"])
balance = client.tool("read", {"uri": "wm://credits"})["data"]const results = await client.tool('search', {query: 'vertical video ad', kinds: ['card', 'format'], limit: 5});
const reply = await client.tool('generate', args, {project: 'spring', requestId: 'hero-still-v1'});
console.log(reply.job.id, reply.cost);env, err := client.Tool(ctx, "generate", args, "spring", "hero-still-v1")
var handle struct{ Id string `json:"id"` }
json.Unmarshal(env.Job, &handle)- Price first: the same arguments with
quote_only: truereturn a quote and hold nothing.quote_onlycalls need norequest_id. - Sending the same
request_idagain returns the original result. Reusing it for different work is refusedrequest_conflict. - A name outside the twelve tools is refused by the client before anything is sent.
- Argument schemas for every tool are at
GET /v1/tools.
A complete example: invert an uploaded still
This is the job the starter apps teach. It uploads a still, runs a three-node Comfy graph once, waits for the job and downloads the output.
from pathlib import Path
from wavemaker_sdk import Client
GRAPH = {
"1": {"class_type": "LoadImage", "inputs": {"image": "INPUT"}},
"2": {"class_type": "ImageInvert", "inputs": {"image": ["1", 0]}},
"3": {"class_type": "SaveImage", "inputs": {"images": ["2", 0], "filename_prefix": "invert"}},
}
with Client("https://api.wavemaker.io", token_file="/private/path/wavemaker-token") as client:
uploaded = client.upload(Path("still.png"))
args = {"graph": GRAPH, "inputs": {"media": {"1.image": uploaded["sha256"]}}}
print(client.tool("generate", {**args, "quote_only": True})["data"])
reply = client.tool("generate", args, request_id="invert-still-v1")
job_id = reply["job"]["id"] # save this before you wait
job = client.wait(job_id)
if job["state"] == "succeeded":
sha = job["result"]["artifacts"][0]["sha256"]
client.download("artifact", sha, "inverted.png")Wait for a job
Long work returns a job. wait reads wm://job/{id} until the job is succeeded, failed or cancelled, and returns the job.
| Python | TypeScript | Go | |
|---|---|---|---|
| Call | wait(job_id, timeout=300.0, interval=0.5, stop=None) | wait(jobId, {timeoutMs, intervalMs, signal}) | Wait(ctx, jobID, timeout) |
| Default deadline | 300 seconds | 300,000 ms | the timeout you pass |
| Stop early | a threading.Event as stop | an AbortSignal | cancel ctx |
- Each read long-polls on the server and answers as soon as the job changes, so waiting doesn't flood the service.
- A
failedjob is returned, not raised. Checkstate, thenerroron the job. - When the deadline passes or you stop early, the client raises
ObservationStoppedwith the job id. The job keeps running and nothing is cancelled. - Save the job id before you wait. To resume after a restart, call
waitagain with the same id; never resubmit to check progress. - To cancel, write to the job:
client.tool("write", {"uri": "wm://job/" + job_id, "cancel": True}).
A finished job lists its outputs under result.artifacts[], each with uri, sha256, size and mime, and often role and filename.
Upload and download files
upload(path) sends a file in 512 KiB chunks through the inputs.begin, inputs.append and inputs.finish operations and returns the stored input with its sha256. It checks that the stored file matches the source's hash (Python and TypeScript also check its size).
- Pass the
sha256to a tool: a graph input asinputs.media({"node.input": sha256}, for example{"1.image": sha256}), or a session turn attachment. - If an upload stops partway, the client raises
TransferInterruptedwith the transfer id and the acknowledged offset. Resume withupload(path, transfer_id=..., offset=...)(Python),upload(path, {transferId, offset})(TypeScript) orUpload(ctx, path, transferID, offset)(Go), using the same source file.
download saves a file you own and verifies it before it appears:
client.download("artifact", sha, "final.mp4") # Pythonawait client.download({kind: 'artifact', id: sha}, 'final.mp4');err := client.Download(ctx, "artifact", sha, "final.mp4", sha)- Python and TypeScript check each range and the final SHA-256 and size; Go checks that each chunk starts where the last ended, the total size, steady progress and the final SHA-256. The file is then linked into place.
- An existing destination is never overwritten; the call fails instead, with
FileExistsError(Python),SDKErrorfile_exists(TypeScript) oros.ErrExist(Go). - Python and TypeScript can resume an interrupted download on macOS and Linux. Pass
resume=True(resume: true) with the artifact'sworkspace_scope, whichartifacts.inspectreturns; in TypeScript,download({kind, id, workspace_scope}, path, {resume: true}). The checkpoint lives in a hidden directory beside the destination, which can remain after the download finishes. Resumable downloads default to a 10-minute deadline.
When a link is enough, read of wm://artifact/{sha256} returns a signed download link that needs no credential and expires within an hour.
Run a hosted session
The session methods return the data of each reply. This Python example posts a brief, follows events and reads the result:
session = client.create_session({"request_id": "batch-1"})
sid, controller = session["id"], session["controller"]["token"]
client.post_turn(sid, {
"request_id": "brief-1",
"controller": controller,
"text": "Three square product stills for a spring sale.",
"deliverables": [{"id": "hero", "kind": "image", "aspect": "1:1", "count": 3}],
})
after, done = 0, False
while not done:
page = client.session_events(sid, after=after, wait_s=20)
done = any(e["type"] in ("turn.completed", "turn.failed", "turn.interrupted")
for e in page["items"])
after = page["next_after"] # save it after you have processed the page
turn = client.turn_for_request(sid, "brief-1") # outcome.deliveries and cost
client.close_session(sid, {"request_id": "close-1", "controller": controller})session, err := client.CreateSession(ctx, map[string]any{"request_id": "batch-1"})
controller := session["controller"].(map[string]any)["token"]
_, err = client.PostTurn(ctx, session["id"].(string), map[string]any{
"request_id": "brief-1", "controller": controller, "text": brief,
"deliverables": []map[string]any{{"id": "hero", "kind": "image", "aspect": "1:1"}},
})
page, err := client.SessionEvents(ctx, session["id"].(string), 0, 20)To price a turn first, post it with quote_only: true, then post it again with the returned quote_id. To resume after a restart, read session(sid), then continue events from your saved cursor. The full contract is in Make an ad.
Method reference
| Call | Python | TypeScript | Go |
|---|---|---|---|
| Call a tool | tool(name, args, project=, request_id=) | tool(name, args, {project, requestId}) | Tool(ctx, name, args, project, requestID) |
| Wait for a job | wait | wait | Wait |
| Identity | identity() | identity() | Identity(ctx) |
| Quote a turn | quote(request) | quote(request) | Quote(ctx, request) |
| Create a session | create_session | createSession | CreateSession |
| List sessions | sessions(**params) | sessions(params) | Sessions(ctx, query) |
| Read a session | session | session | Session |
| Claim the controller | claim | claim | Claim |
| Post a turn | post_turn | postTurn | PostTurn |
| Read events | session_events | sessionEvents | SessionEvents, SessionEventsQuery |
| Read a turn | turn, turn_for_request | turn, turnForRequest | Turn, TurnForRequest |
| Decide an approval | approve | approve | Approve |
| Interrupt, close | interrupt, close_session | interrupt, closeSession | Interrupt, CloseSession |
| Upload, download | upload, download | upload, download | Upload, Download |
| Older operation | call("workspace.inspect", {}) or workspace_inspect() | call('workspace.inspect', {}) | WorkspaceInspect(ctx, nil) (nil is sent as an empty request) |
| Workspace job events | events(after) | events(after) | Events(ctx, after), EventCursor() |
Older operations reply in their own wmlab.result/1 format; the clients return its data. An operation the service no longer serves answers HTTP 501 operation_not_ported. A name outside the catalog answers 404 without an envelope; the clients report it as protocol_error with status 404. See Account and workspace API access.
Errors
A refusal raises SDKError (Go returns *SDKError). In Python and TypeScript, ObservationStopped and TransferInterrupted are subclasses of SDKError; Go returns them as separate *ObservationStopped (with JobID) and *TransferInterrupted (with TransferID and Offset) errors.
| Field | Meaning |
|---|---|
code | The service's error code, such as insufficient_credits or scope_denied |
message | A readable message; don't branch on it |
status | The HTTP status |
details | Extra facts, such as required_microcredits or details.path |
retryable | Whether the service says a retry can succeed; unset for older operations in Python and TypeScript, false in Go |
The clients add their own codes (in Go, the last two are the separate error types above):
| Code | Meaning | What to do |
|---|---|---|
transport_error | The connection dropped after a write was sent, so its outcome is unknown | Retry with the same request_id, or read the original |
protocol_error | The reply wasn't a valid envelope, or a job read returned a different id | Check the origin; report it if it persists |
integrity_error | Uploaded or downloaded bytes didn't match the expected hash or size | Retry the transfer; a failed download leaves no file at the destination |
file_exists | TypeScript only: the download destination already exists | Choose another path |
observation_stopped | A wait reached its deadline or was stopped | Wait again on the same job id |
transfer_interrupted | An upload stopped partway | Resume with the transfer id and offset |
A 401 while waiting doesn't mean the job failed. Get a new credential, create a new client and wait on the same job id. Don't resubmit to get an output.
Retries
The clients retry only when a retry can't create new work:
- Overload responses (429 and 503), connections that failed before the request was sent, and, for reads only, any dropped connection.
- Only reads (
GETrequests,read,search, quotes and read-scope operations) and requests that carry arequest_id. - Up to 5 attempts, with jittered delays growing from 0.5 seconds to 10 seconds. A
Retry-Afterheader orerror.retry_after_sis honored as a floor, up to 30 seconds. - A 429 or 503 whose error says
retryable: false, such asroute_unavailableorquota_exceeded, is returned at once: waiting won't change it.
Anything else is sent once. If the response to a write without a request_id is lost, the client raises transport_error instead of guessing. Turn retries off with retry=False (Python), retry: false (TypeScript) or WithoutRetry() (Go).
Numbers in TypeScript
TypeScript keeps a plain number API. A reply containing an integer outside JavaScript's safe range is refused with protocol_error rather than silently rounded. Fields that can carry large integers include max_microcredits, event cursors, transfer sizes and offsets, and provider seeds. Sending an unsafe integer throws a TypeError before anything is sent.