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.

What every starter does
  1. Upload

    Send a still you own and keep its sha256.

  2. Journal

    Record "dispatching" before any spending call.

  3. Generate

    Run the three-node invert graph once.

  4. Follow

    Read the saved job; never resubmit.

Choose a starter

StarterInterfaceUse it when
node-webNode's built-in http module with a local browser pageYou want a single-user web page that calls Wavemaker while the credential stays on the server
python-workerPython clientA command-line worker should own invert, inspect, resume and cancel
go-workerGo clientThe same command model, in Go
cliThe installed wavemaker executableYou automate from a shell with the CLI
mcpStreamable HTTP MCPYour code or agent host already speaks MCP
httpPlain HTTPS requestsNo 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.png

Commands: 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.png

go 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 start

Place 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.1 only. Set PORT to 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 /invert and /cancel routes 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_id is 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 /inspect shows the last job id, and POST /cancel cancels it.

CLI

WAVEMAKER_SERVICE=https://api.wavemaker.io \
WAVEMAKER_TOKEN="$(cat ./token)" \
WAVEMAKER_REQUEST_ID=invert-still-1 \
./invert.sh still.png

invert.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.png

The 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.png

This 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

VariableUsed byMeaning
WAVEMAKER_ORIGINPython, Go, Node, HTTPThe service origin, such as https://api.wavemaker.io
WAVEMAKER_SERVICECLIThe service origin for the CLI
WAVEMAKER_MCP_URLMCPThe MCP endpoint, such as https://api.wavemaker.io/mcp
WAVEMAKER_TOKEN_FILEPython, Node, HTTP, MCPPath to a private file holding the credential
WAVEMAKER_TOKENGo, Node, CLIThe credential itself
WAVEMAKER_REQUEST_IDAll but NodeThe request identity for this still
WAVEMAKER_RUN_IDPython, GoThe run identity recorded in the journal; defaults to a new UUID (Python) or go-worker (Go)
WAVEMAKER_JOURNALGo, CLI, HTTP, MCPThe journal path; defaults to run.json (the Python worker always uses run.json)
WAVEMAKER_MAX_MICROCREDITSPythonAn optional spending cap for the call
PORTNodeThe 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 write dispatching to a journal and flush it to disk.
  • Once the service accepts the call, they record accepted with the job id.
  • If the journal already shows dispatching or an accepted job, invert refuses to send another: "uncertain or already dispatched; observe the saved job, do not resubmit".
  • resume waits 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.
  • cancel writes {"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 refused request_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 with validate_only: true, which spends nothing. See Graph portability.
  • Swap the job. Call a provider route or model instead of a graph; find one with search and kinds: ["model", "route"], then read its URI for the request schema.
  • Price before running. Send the same arguments with quote_only: true and show the user p50_microcredits and hold_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.

Related guidance