ComfyUI-API-Optimizer
API cost tracking, deterministic caching, and lazy execution bypass for ComfyUI cloud API workflows. Circuit-breaker spending control with persistent ledger.
Nodes (5)
Your API bill called. This node is the circuit breaker.
Never pay for the same prompt twice
Stop typing cache labels. Wire them instead.
Bank your API result so you never call it again
The node that stops your API node from running at all
ComfyUI API Optimizer

A pack of custom nodes for ComfyUI, built for workflows that call external remote APIs (Kling 3.0, Magnific, Banana.dev, RunPod, etc.).
When compute moves to the cloud, the bottleneck moves with it: from VRAM to API cost, latency, and serialization. This pack handles all three inside ComfyUI.
Included nodes
1. API Cost & Quota Tracker
It's a circuit breaker for your wallet. Wire your prompt or image through this node before it reaches your API node.
Set a $ Budget Limit and $ Cost Per Run, and a persistent ledger tracks every charge; if the next run would exceed the budget, execution halts before the API gets billed. The arithmetic runs on decimal.Decimal internally, so there's no floating-point drift on large batch runs. Every charge is appended to api_transactions.jsonl with a timestamp, for a full audit trail. Resetting the budget archives the previous ledger instead of discarding it. File locking prevents corruption when multiple ComfyUI instances share the same output directory.
2. The Deterministic Hash Vault Suite (3 nodes)
ComfyUI's native caching often breaks with external API nodes (dynamic timestamps, non-deterministic seeds). The Hash Vault is a disk-caching layer that hashes your prompt, parameters, and input tensors.
- Hash Vault (Check Cache): hashes any combination of a prompt STRING and up to four
any_inputslots. Wire an image, a converted-widget dropdown, a converted-widget float, whatever defines uniqueness for your API call. All inputs are optional; any subset you connect factors into the cache key. No prompt? Hash on image and widget values alone. - Lazy API Switch: uses ComfyUI's
{"lazy": True}evaluation engine. On a cache hit, this switch prevents the upstream API node from executing, so you save money and time. - Hash Vault (Save Result): writes new API outputs to the vault for future cache hits.
Key features
Hashing covers the full byte representation of tensors, including dtype and shape metadata, so there are no lossy approximations. Nested structures (dicts, lists, tuples) hash recursively, which covers the latents and conditioning ComfyUI actually passes around. Cache entries support an optional TTL: expired entries get removed automatically and count as a miss, and setting TTL to 0 means an entry never expires. Tensors save to CPU and load with map_location="cpu", so cache files work regardless of GPU configuration. Cache files write to a temp file first and get atomically replaced, so an interrupted write can't corrupt anything. File locking covers every cache read and write.
Sidecar metadata (v1.3.0): every saved entry gets a {hash_key}.json sidecar with a human-readable label, created_at, last_accessed_at, and a payload summary. Image outputs also get a {hash_key}.thumb.png 256px preview. Sidecar data is decoupled from the hash, so editing a label never invalidates the cache. This is what the Hash Vault Browser indexes.
Hash Vault Browser (v1.4.0+): a modal gallery over the entire vault. Three ways to open it:
- Keyboard:
Ctrl+Shift+Hfrom anywhere in ComfyUI (added v1.4.1) - Sidebar: click the
Hash Vaulttab in the left sidebar, thenOpen Browser(added v1.4.1) - Menu:
Extensions → AKURATE: Hash Vault Browserin the top menu bar
It shows every cached entry as a card with thumbnail, label, relative age, and .pt size, with a live substring filter on label and hash. Click a card to copy its hash to clipboard. Cards sort by last-accessed, most recent first. There's no load-into-workflow action yet; that's v1.5+.
Sidecar label input
The Save Result node has an optional label input (v1.3.0+). Wire a human-readable string like "Alec Soth / Songbook / full" and the Hash Vault Browser surfaces it when you need to find a specific past run. The label lives in the sidecar JSON only, so changing it never breaks existing cache hits.
Auto-labeling with the Label Builder node (v1.4.2)
Typing labels by hand gets old fast. The 🏷️ Hash Vault Label Builder node concatenates up to four any-type inputs into one STRING, ready to wire into Save Result's label input. Wire it once per workflow and never type a label again.
For the Gemini Style Transfer pattern:
in_1← Settings node'sstyleoutput (e.g."Alec Soth")in_2← Settings node'svariantoutput (e.g."Niagara")in_3← Settings node'sintensityoutput (e.g."full")separator=" / "- → outputs
"Alec Soth / Niagara / full"
For a Prompt Studio → Gemini Image Generate pattern:
in_1← Prompt Studio'sstyleoutputin_2← the assembled prompt (first line, or a short identifier)- → outputs
"Sebastião Salgado / Serra Pelada gold mine workers"
Empty or None inputs are skipped, so partial wiring stays clean. Non-string inputs are stringified at join time.
Migrating pre-v1.3.0 entries
Existing .pt files have no sidecar. To backfill:
# From the pack root, using the ComfyUI embedded Python:
python tools/migrate_hash_vault.py --dry-run # preview
python tools/migrate_hash_vault.py # execute
# Override the vault location:
python tools/migrate_hash_vault.py --vault-dir "F:/path/to/output/hash_vault"
Orphan entries are labelled (legacy) by default and assigned created_at from file mtime.
How to use the Hash Vault
To bypass an API node, sandwich it with the vault nodes:
- Connect your Prompt/Image to Check Cache.
- Connect the
is_cachedoutput to the Lazy API Switch. - Connect your Prompt/Image to your actual API Node.
- Connect the output of your API Node to Save Result (using the
hash_keyfrom step 1). - Connect both the
cached_data(from step 1) and theapi_data(from step 4) to the Lazy API Switch.
What to feed Check Cache
Check Cache has one STRING socket (payload_string) and four any-type sockets (any_input, any_input_2, any_input_3, any_input_4). All are optional; connect whatever defines uniqueness for your API call.
For a prompt-driven API (Gemini Image Generate, say), wire the prompt STRING to payload_string and you're done. For an image + prompt API (an image edit call), wire the prompt to payload_string and the image to any_input. For an image-only API with widgets and no prompt, like Gemini Style Transfer with its style dropdown and strength float: right-click the style widget, Convert Widget to Input, do the same for strength, then wire image → any_input, style → any_input_2, strength → any_input_3. All three factor into the hash; change any of them and you get a new cache key.
Any subset of slots works; unused slots contribute nothing to the hash. Adding or ignoring any_input_2/3/4 in a new workflow doesn't invalidate existing cache keys from older workflows that only used payload_string + any_input.
┌─────────────┐
┌───────────►│ API Node ├──► 💾 Save Result ──┐
│ └─────────────┘ │
Prompt/Image─┤ │
│ ┌─────────────┐ ▼
└───────────►│ 🔍 Check ├──────────────► 🔀 Lazy Switch ──► Output
│ Cache │ is_cached ▲
└──┬──────────┘ │
│ cached_data ────────────────────┘
Example workflows
Prompt-driven API: workflows/hash_vault_basic.json
The classic pattern for an API node whose uniqueness lives in a prompt STRING (Gemini Image Generate, for example). One StringConstantMultiline feeds both Hash Vault's payload_string and the API node's prompt. Drag it in, set your Gemini API key, press Queue twice: the first run generates and caches, the second returns the cached image with zero API calls.
Image-only API with widget inputs: workflows/hash_vault_image_only.json
Shows the v1.2.0 pattern for an API with no prompt STRING but meaningful widgets, using Gemini Style Transfer (image + style dropdown + intensity dropdown). The Style Transfer Settings node owns the real dropdowns and emits style and intensity as STRING outputs. Each output fans out to both the matching Style Transfer input (widget converted to input) and a Hash Vault any_input_N slot. The image fans out to Style Transfer and to Hash Vault's any_input. All three factor into the hash; change any one of them and you get a new cache key, so the API runs once.
The Settings node exists specifically to keep the dropdown UX. A raw STRING primitive driving a converted-widget input has no validation and no typeahead, so a typo silently breaks the cache (the hash is still valid, but Style Transfer errors on the bad value at runtime). The Settings node provides the same ComfyUI-native combo box the Style Transfer widget has, so the wired value is always a known-valid option.
Requires
Both workflows use ComfyUI-Gemini-Direct as the API node. The prompt-driven workflow uses ComfyUI-KJNodes's StringConstantMultiline as the prompt source; any STRING primitive works. The image-only workflow uses Gemini-Direct's own Gemini Style Transfer Settings node for the style + intensity dropdowns.
Output files
All data is stored under your ComfyUI output directory:
| Path | Description |
|------|-------------|
| output/api_metrics/api_costs.json | Current cost ledger (per-provider totals) |
| output/api_metrics/api_transactions.jsonl | Append-only audit log with timestamps |
| output/api_metrics/api_costs_archive_*.json | Archived ledgers from budget resets |
| output/hash_vault/*.pt | Cached API outputs (PyTorch format) |
| output/hash_vault/*.json | Sidecar metadata: label, timestamps, payload summary (v1.3.0+) |
| output/hash_vault/*.thumb.png | 256px preview for image outputs (v1.3.0+) |
Installation
Clone this repository into your ComfyUI/custom_nodes/ directory:
cd ComfyUI/custom_nodes/
git clone https://github.com/jeremieLouvaert/ComfyUI-API-Optimizer.git
pip install -r ComfyUI-API-Optimizer/requirements.txt
Restart ComfyUI.
Dependencies
- PyTorch: already present in any ComfyUI installation
- filelock: usually already installed as a transitive dependency of PyTorch/HuggingFace. If not,
pip install filelock.