Standalone starter apps
Six small starters invert an owned still with one generate call over HTTP, MCP, the CLI, Python, Go or a local Node page, and never send the work twice after a crash.
Updated
The starter apps are six small, complete programs that do one job end to end: upload a still image you own, invert its colors with a single generate call, then follow or record the job so a crash never sends the work twice. Each uses a different interface, so you can start from the one that matches your stack. This page explains what each starter does, how to run it, and what to change to make it your own.
- Upload
Send a still you own and keep its sha256.
- Journal
Record "dispatching" before any spending call.
- Generate
Run the three-node invert graph once.
- Follow
Read the saved job; never resubmit.
Choose a starter
| Starter | Interface | Use it when |
|---|---|---|
node-web | Node's built-in http module with a local browser page | You want a single-user web page that calls Wavemaker while the credential stays on the server |
python-worker | Python client | A command-line worker should own invert, inspect, resume and cancel |
go-worker | Go client | The same command model, in Go |
cli | The installed wavemaker executable | You automate from a shell with the CLI |
mcp | Streamable HTTP MCP | Your code or agent host already speaks MCP |
http | Plain HTTPS requests | No client exists for your language |
The job they teach
Every starter sends the same small Comfy graph with one generate call:
{
"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"}}
}The uploaded still is bound to node 1 by its hash: "inputs": {"media": {"1.image": sha256}}. The call carries a request_id, so sending it again returns the same job instead of starting a second one. The Python, Go, CLI, MCP and HTTP starters then read wm://job/{id} to follow the job toward succeeded, failed or cancelled; the Node page returns the job id and its state at submission. Every starter cancels with write and {"uri": "wm://job/{id}", "cancel": true}.
The job is real work and is charged like any graph job. The Python worker reads an optional WAVEMAKER_MAX_MICROCREDITS and passes it as the call's max_microcredits cap.
Run a starter
Every starter needs two things: the service origin and a credential with read, write and run. Create the credential in Connections › Developer setup and save it to a file only you can read. See Client credentials and provider keys.
Python worker
WAVEMAKER_ORIGIN=https://api.wavemaker.io \
WAVEMAKER_TOKEN_FILE=./token \
WAVEMAKER_REQUEST_ID=invert-still-1 \
python worker.py invert still.pngCommands: invert STILL.png, inspect (read the saved job once), resume (wait for the saved job) and cancel. The journal is run.json in the current directory.
Go worker
WAVEMAKER_ORIGIN=https://api.wavemaker.io \
WAVEMAKER_TOKEN="$(cat ./token)" \
WAVEMAKER_JOURNAL=./run.json \
WAVEMAKER_REQUEST_ID=invert-still-1 \
go run . invert still.pnggo run . inspect, go run . resume and go run . cancel work on the same journal. The Go module github.com/AdwaveInc/wavemaker-go is not published yet; the packaged starter carries its own copy of the Go client in ./sdk-go, so GOWORK=off go run . builds without it.
Node web page
WAVEMAKER_ORIGIN=https://api.wavemaker.io \
WAVEMAKER_TOKEN_FILE=./token \
npm startPlace still.png beside server.mjs, then open http://127.0.0.1:3000/ and select Invert still.png. Requires Node 22 or later. WAVEMAKER_TOKEN can hold the credential instead of WAVEMAKER_TOKEN_FILE.
- The server listens on
127.0.0.1only. SetPORTto change the port; 8787 is refused. - The credential stays on the server; the browser never sees it.
- Every change request must come from the page itself: a request with another Host or Origin, or without the page's CSRF token, is refused before anything is sent to Wavemaker.
- The server's
/invertand/cancelroutes refuse a request whose body lacks"confirm": true; the page's button sends it. At most two inverts run at once. - The still is uploaded as one base64 request through the input operations, so it must stay under the 2 MB request limit once encoded (about 1.5 MB of image).
- The
request_idis derived from the still's hash, so the same still replays its job and a new still is new work. - The page returns the accepted job's id and state; it doesn't follow the job.
GET /inspectshows the last job id, andPOST /cancelcancels it.
CLI
WAVEMAKER_SERVICE=https://api.wavemaker.io \
WAVEMAKER_TOKEN="$(cat ./token)" \
WAVEMAKER_REQUEST_ID=invert-still-1 \
./invert.sh still.pnginvert.sh uploads with wavemaker files upload, writes dispatching to the journal, then calls wavemaker api call generate --file request.json --request-id .... It also needs python3 for its journal steps. python invert.py inspect, resume and cancel use the same journal; resume waits up to 25 seconds per call. The job id is at data.job.id in the CLI's JSON output. See Native CLI.
MCP
WAVEMAKER_MCP_URL=https://api.wavemaker.io/mcp \
WAVEMAKER_TOKEN_FILE=./token \
WAVEMAKER_REQUEST_ID=invert-still-1 \
python invert.py still.pngThe still goes up with write and content_base64, so keep it small: a request body over 2 MB is refused, which leaves room for about 1.5 MB of image once encoded. Approving a tool call in an agent host is not a Wavemaker receipt; the job id is.
HTTP
WAVEMAKER_ORIGIN=https://api.wavemaker.io \
WAVEMAKER_TOKEN_FILE=./token \
WAVEMAKER_REQUEST_ID=invert-still-1 \
python invert.py still.pngThis starter calls POST /v1/tools/{tool} directly, checks that each reply's ok is a real boolean, and uploads with the input operations at POST /api/v1/operations/{name} (inputs.begin, inputs.append, inputs.finish) in one base64 chunk, so the same 2 MB request limit applies. Its client also has a streamed upload to POST /api/v1/inputs and a hash-verified download, which the starter doesn't call.
Environment variables
| Variable | Used by | Meaning |
|---|---|---|
WAVEMAKER_ORIGIN | Python, Go, Node, HTTP | The service origin, such as https://api.wavemaker.io |
WAVEMAKER_SERVICE | CLI | The service origin for the CLI |
WAVEMAKER_MCP_URL | MCP | The MCP endpoint, such as https://api.wavemaker.io/mcp |
WAVEMAKER_TOKEN_FILE | Python, Node, HTTP, MCP | Path to a private file holding the credential |
WAVEMAKER_TOKEN | Go, Node, CLI | The credential itself |
WAVEMAKER_REQUEST_ID | All but Node | The request identity for this still |
WAVEMAKER_RUN_ID | Python, Go | The run identity recorded in the journal; defaults to a new UUID (Python) or go-worker (Go) |
WAVEMAKER_JOURNAL | Go, CLI, HTTP, MCP | The journal path; defaults to run.json (the Python worker always uses run.json) |
WAVEMAKER_MAX_MICROCREDITS | Python | An optional spending cap for the call |
PORT | Node | The local port; defaults to 3000 |
How they recover
The starters are built so one run sends one piece of work, even after a crash or timeout.
- Before calling
generate, the Python, Go, CLI, MCP and HTTP starters writedispatchingto a journal and flush it to disk. - Once the service accepts the call, they record
acceptedwith the job id. - If the journal already shows
dispatchingor an accepted job,invertrefuses to send another: "uncertain or already dispatched; observe the saved job, do not resubmit". resumewaits on the saved job id. A wait that times out never cancels the job.- Each run holds a lock file beside the journal (
run.json.lock). If a crash leaves it behind, the next run refuses with "journal locked; recover or reset explicitly"; check that no other run is active, then remove the lock. cancelwrites{"uri": "wm://job/{id}", "cancel": true}for the saved job.
If a run crashed while dispatching, the call may or may not have reached the service, and the starter won't send it again on its own. To reconcile, move the journal aside and run invert again with the same still and the same WAVEMAKER_REQUEST_ID: the service returns the original job if there was one, and runs it once if there wasn't. Never pick a new request_id for the same still.
Make it your own
- Use a new `request_id` for each new still. The Python, Go, CLI, MCP and HTTP starters default to a fixed id per starter, such as
starter:invert:python. Reusing an id for a different still is refusedrequest_conflict; reusing it for the same still returns the first job. - Swap the graph. Replace the three nodes with your own Comfy API graph and bind its inputs by hash in
inputs.media. Check it first withvalidate_only: true, which spends nothing. See Graph portability. - Swap the job. Call a provider route or model instead of a graph; find one with
searchandkinds: ["model", "route"], thenreadits URI for the request schema. - Price before running. Send the same arguments with
quote_only: trueand show the userp50_microcreditsandhold_microcredits. - Download the result. The starters stop at the finished job and don't download its output. Read
wm://artifact/{sha256}for a signed link, or use a client's verified download. - Keep credentials out of source. Read them from a private file or a secret store, never from the repository.