Graph portability and explicit dependencies
Submit plain Comfy API graphs, name media and models by content-addressed identity, keep credentials out, and know what validation rejects.
Updated
Wavemaker runs graphs as ordinary ComfyUI API JSON. A graph you write, export or adapt stays a plain Comfy graph: no Wavemaker wrapper, no hidden steps, and no credentials inside it. Media and models enter it by content-addressed identity, so the same graph with the same inputs names the same work wherever it runs.
Submit a graph
- Discover what the runtime offers:
searchwithkinds: ["node"],["graph"]or["model"], thenreadwm://node/{class},wm://graph/{id}orwm://model/{id}. - Upload each input file and keep its sha256. See HTTP API.
- Check the graph without running it:
generatewithgraphandvalidate_only: true. - Price it: the same call with
quote_only: true. - Run it: the same call with a
request_id, then follow the job. See Quote and run work.
curl --fail-with-body \
--header "Authorization: Bearer $WAVEMAKER_API_KEY" \
--header "Content-Type: application/json" \
--data '{"args": {"graph": {...},
"inputs": {"media": {"12.image": "9f86d0..."},
"bindings": {"6.text": "A cold brew can on a train seat", "3.seed": 42}},
"validate_only": true}}' \
https://api.wavemaker.io/v1/tools/generate
# → 200 {"ok": true, "data": {"valid": true, "problems": [], "available": true, "graph": "inline", ...}}Replace {...} with your Comfy API graph. In this example node 12 is an image loader whose image input takes the uploaded file, node 6 holds the prompt text and node 3 the seed; use your own graph's node ids and input names. Each key must name exactly one existing node input, or the call is refused with invalid_graph.
Check that it worked
validistrueandproblemsis empty.availableistrue: a runtime can take the graph now.- A graph the service refuses answers
valid: falsewithproblems[], each with apath(such asgraph/12), amessageand acode. Other failures, such as a media sha256 your workspace doesn't own or abusypause, come back as ordinary errors. Validation needs norequest_id, and nothing is quoted, held or charged.
What a graph is
- Plain Comfy API JSON. An object of node ids, each
{class_type, inputs}. A link is[source_node_id, output_index]. Catalog files may wrap the graph as{meta, graph}; send the inner graph. - Exactly one target.
generatetakes one ofgraph,modelorroute.graphis an inline graph or a catalog id (wm://graph/{id}).modelruns a model profile through its reviewed graph. - Two kinds of input.
inputs.mediastages owned files into upload fields, keyed by node id and input name, such as{"12.image": "9f86d0..."}.inputs.bindingssets literal values the same way, such as{"3.seed": 42}. Any other key underinputsis refused withinvalid_graph, so a misspelled input never runs silently on a placeholder. - Takes.
count(up to 8) runs takes that differ: each take adds its index to every literalseedandnoise_seedinput, and take 0 runs as sent. - Outputs. The service collects every output the graph produces. Each becomes an artifact named by its sha256 under the job's
result.artifacts[].
Content-addressed identities
| What | How a graph names it |
|---|---|
| Media you upload or made earlier | Its sha256, through inputs.media. Only files your workspace owns can be staged. |
| Outputs | wm://artifact/{sha256}. The same bytes always have the same name. |
| A model your workspace registered | wm://model/{name}/{version}, or its file name {name}-v{version}.safetensors, in a loader input. The service checks that your workspace registered that version and still holds its exact bytes. |
| A catalog graph | wm://graph/{id}. A reviewed catalog revision runs only when its file matches the sha256 the catalog recorded. |
A registered model whose weights are missing is refused with conflict. An unknown wm://model/ reference is not_found. A file name nobody registered is left to the runtime's own models.
Make dependencies explicit
A graph only names node classes, inputs and links. The runtime that executes it owns the node schemas and the models. Before you share or move a graph, record what it needs:
- the custom node packs and their versions;
- each model's weights, by name and sha256;
- the input files, by sha256, and which node input each one feeds;
- any declared license for the nodes and weights;
- for native recipes, the Python packages, FFmpeg or other tools they call.
An empty dependency list is not a portability guarantee. Structural validity doesn't prove the models load, the output is useful, or that you may distribute the dependencies.
Keep credentials out of graphs
A graph carries no secrets. Provider keys belong in Connections, entered on the provider's private enrollment page. Don't put keys in graph input defaults, exported source, prompts or public examples. See Credentials.
Partner nodes inside a graph run on Wavemaker's credits, never on keys in the graph, and a saved provider key in Connections doesn't fund them. One graph take's partner calls may hold up to 100 credits unless you set params.partner_max_microcredits, and never more than what that take's share of max_microcredits leaves after its GPU hold. Where partner nodes can't reach their supplier, the graph is refused before anything is held, with route_unavailable; use the partner's route with generate and route instead.
What validation rejects
The service checks a graph before it holds any credits. A refusal is invalid_graph (422), or a validate_only report with valid: false.
Shape and schema
A graph that isn't a non-empty object of nodes, each with class_type and inputs, is refused at once with invalid_graph. A graph no runtime admits is refused with invalid_graph and details.rejected[]: each distinct reason once, such as node_not_admitted (a runtime lacks a node class), resource_bounds (an input above a runtime's maximum), graph_node_limit or graph_schema_invalid. A graph_schema_invalid reason lists up to 10 errors, each with its node, input and one of these codes:
| Problem | Code |
|---|---|
| A node class the runtime has no schema for | unknown_node |
| A required input missing | missing_input |
| A link to a node that isn't in the graph, or to an output slot it doesn't have | missing_source, output_index |
| A link between ports of different types | port_type |
| A value of the wrong type, outside the schema's bounds, or not one of the listed choices | value_type, value_range, enum_value |
| A dynamic input not sent through its declared ports | dynamic_input |
| A cycle | cycle |
| An empty-latent width or height that isn't a multiple of 8, or an H3 image-to-video or reference-to-video width or height that isn't a multiple of 32 | model_alignment |
An input the schema doesn't declare is a warning (unknown_input), not a refusal.
Safety rules
- Node classes you may send. A graph you write may use only the node classes Wavemaker's own catalog graphs and model profiles use and ComfyUI's built-in nodes. Classes from other community packs are added only after review. Another class is refused with
invalid_graphanddetails.reason: "node_not_permitted". - Denied classes. Nodes that delete or probe files, dump GPU memory, evaluate code, or save model weights never run.
- Path, URL and expression inputs. An input named like a path, folder, file, URL or expression takes only a literal value, never a link from another node. A free-text value there is refused; a value picked from the input's list of choices is allowed.
ComfyMathExpressionis allowed, with an expression of at most 256 characters. - Output names.
filename_prefixmust stay inside the output folder. - Media inputs. Only declared upload fields can take staged files.
Placement
The service places each graph on a runtime that has its nodes and weights. When none admits it, the refusal message names what to fix first, for example an input the runtimes that have its model refused, or an input above their maximum. A graph over a runtime's node limit is refused too. While graph runs are paused for maintenance, a graph is refused with busy (503) and retry_after_s; retry with the same request_id.
Validate locally with the CLI
The wavemaker CLI checks a local graph file without running it. With a direct Comfy runtime connected (wavemaker runtime connect), graph validate checks against that runtime's node schemas; without one, it asks the selected Wavemaker service to validate it:
wavemaker graph inspect graph.api.json --json
wavemaker graph validate graph.api.json --json
wavemaker graph diff graph.api.json revised.api.jsongraph bind writes a copy with new input values, graph slice extracts a node and its upstream nodes, and graph import converts a ComfyUI workflow into API JSON using a directly connected Comfy runtime's schemas; the hosted service has no workflow import. Validation is structural; the runtime that executes the graph stays authoritative. See CLI.
Run it only when you mean to
Run a graph with generate and a request_id only when you intend to pay for it. The same request_id with the same graph and inputs returns the original jobs instead of running again. See Quote and run work and Graphs in the web app.