Navigation: docs/developer/index.md → Topaz provider guide
Runs: cloud. Needs:
TOPAZ_API_KEY. Calls throwTOPAZ_API_KEY is not configuredwithout it.
TL;DR
- Edit
packages/topaz-nodes/src/topaz-manifest.json— one JSON object per endpoint (node). npm run build:packages— copies the manifest todist/and compiles the factory.npm run typecheck && npm run lint— must pass before committing.- No new TypeScript files needed for standard endpoints; the factory generates the node class at runtime.
Where things live
| What | Path |
|---|---|
| Manifest (all nodes) | packages/topaz-nodes/src/topaz-manifest.json |
| Request building, result mapping, video probing | packages/topaz-nodes/src/topaz-base.ts |
| Runtime node factory | packages/topaz-nodes/src/topaz-factory.ts |
| Package entry point | packages/topaz-nodes/src/index.ts |
Runtime provider (the Topaz transport, upscaleImage and the endpoint calls every node makes) |
packages/runtime/src/providers/topaz-provider.ts |
| Manifest loader (for ImageModel list) | packages/runtime/src/providers/manifest-models.ts |
| Provider registration | packages/runtime/src/providers/index.ts |
| Unit tests | packages/topaz-nodes/tests/topaz-base.test.ts, topaz-factory.test.ts, packages/runtime/tests/providers/topaz-provider.test.ts |
How Topaz models and nodes are defined
The manifest is the single source of truth. Every Topaz node is described by one JSON object in packages/topaz-nodes/src/topaz-manifest.json. There is no code-generation script. The manifest is hand-maintained. At package load time src/index.ts loads the JSON with loadPackageAssetJson and calls loadTopazNodesFromManifest, which feeds each entry to createTopazNodeClass in topaz-factory.ts. The factory builds a full BaseNode subclass at runtime — no decorators, no separate .ts file per node.
The build script (package.json build) compiles the TypeScript and then copies src/topaz-manifest.json to dist/topaz-manifest.json so the package.json exports entry "./topaz-manifest.json" resolves correctly. The runtime provider (topaz-provider.ts) loads the same file through loadManifest in manifest-models.ts to build its list of ImageModel objects, but only exposes the enhance and enhance-gen endpoints as upscale-task models. All other endpoints (sharpen, sharpen-gen, denoise, denoise-gen, lighting, matting, restore-gen) live solely as workflow nodes. The manifest holds nine image endpoints and two video endpoints (EnhanceVideo, InterpolateVideo).
Every node calls context.callProviderEndpoint with the topaz provider id, so TopazProvider holds TOPAZ_API_KEY and each run writes a generation record.
Image nodes send the image bytes and form fields on the image route. TopazProvider runs the multipart POST → poll status → download lookup flow, and topazExecuteImageTask (topaz-base.ts) downloads the result through context.downloadProviderMedia.
Video nodes send the create body and the source bytes on the video route. TopazProvider runs create job → accept (get presigned upload URLs) → PUT bytes → complete-upload → poll status, and the node downloads the result. The node probes the source video’s resolution, frame rate, and duration in process before the job is created.
Add a new model or node
Case A — new model variant in an existing endpoint
Open packages/topaz-nodes/src/topaz-manifest.json and add the variant name to the values array of the relevant entry’s model field:
{
"name": "model",
"type": "enum",
"default": "Standard V2",
"title": "Model",
"required": false,
"values": [
"Standard V2",
"High Fidelity V2",
"Upscale High Fidelity V3",
"Low Resolution V2",
"CGI",
"Transparency Upscale",
"Detail",
"Faces",
"Text Refine",
"Standard V3"
]
}
That is the entire change. The factory picks up the new variant automatically.
If the new variant is in the enhance or enhance-gen endpoint, the runtime provider will also surface it as a new ImageModel entry named topaz/image/enhance/Standard V3 (the modelId/variant pattern used in buildVariantMap).
Case B — new image endpoint (new node)
Add one object to the top-level array in topaz-manifest.json. Copy the shape of an existing image entry:
{
"className": "RemoveBackground",
"moduleName": "image",
"modelId": "topaz/image/remove-bg",
"title": "Topaz Remove Background",
"description": "Topaz Image API — background removal.\n\n topaz, image, background, removal\n\n Multipart POST /image/v1/remove-bg/async.",
"outputType": "image",
"submitEndpoint": "https://api.topazlabs.com/image/v1/remove-bg/async",
"statusEndpoint": "https://api.topazlabs.com/image/v1/status/{process_id}",
"downloadEndpoint": "https://api.topazlabs.com/image/v1/download/{process_id}",
"pollInterval": 3000,
"maxAttempts": 400,
"fields": [
{
"name": "image",
"type": "image",
"title": "Image",
"description": "Source image.",
"required": true,
"uploadField": true
},
{
"name": "output_format",
"type": "enum",
"default": "png",
"title": "Output Format",
"required": false,
"values": ["jpeg", "png", "tiff"]
}
],
"validation": []
}
Key rules:
className— PascalCase, unique across the manifest. Becomes the node class name and part of the node type:topaz.<moduleName>.<className>.modelId— slash-separated string, unique across the manifest. Used as the primary key in the provider’s variant map.- Exactly one field must set
"uploadField": true. This is the asset (image or video) the factory reads and sends to Topaz. submitEndpointmust be an absolute URL.statusEndpointanddownloadEndpointuse{process_id}as a placeholder.pollIntervalis in milliseconds.maxAttempts×pollIntervalsets the total timeout budget. Image entries use 3000 ms and 400 attempts. Video entries use 15000 ms and 1000 attempts.
Case C — new video endpoint
Copy either EnhanceVideo or InterpolateVideo from the manifest. Video entries add:
"outputType": "video""videoKind": "upscale"or"videoKind": "interpolate""acceptEndpoint"and"completeEndpoint"for the two extra pipeline steps- A
"video"typed field with"uploadField": true
The factory detects outputType === "video" and routes through topazExecuteVideoTask, which probes the source in process before creating the job.
Verify
After editing the manifest, run these commands in order:
# 1. Build the package (compiles TS, which type-checks it, and copies manifest to dist/)
npm run build:packages
# 2. Type-check web, electron and mobile
npm run typecheck
# 3. Lint
npm run lint
# 4. Run the topaz-nodes and provider unit tests
npm run test --workspace=packages/topaz-nodes
npm run test --workspace=packages/runtime -- topaz-provider
# 5. Run a node in isolation to confirm it loads from the manifest
# (replace 'topaz.image.SharpenImage' with your new className)
npm run dev:nodetool -- node run topaz.image.SharpenImage \
--props '{"image":{"type":"image","uri":"https://example.com/photo.jpg"}}' \
--no-secrets
# 6. Static validation: confirm the manifest-derived node type is known
npm run dev:nodetool -- validate workflow.json
Step 5 will hit a missing-API-key error in --no-secrets mode, which is expected — the goal is to confirm the node type resolves and all fields load without a panic. A TOPAZ_API_KEY not configured error means the node registered correctly.
Document API-level details (endpoint URL, poll interval, field constraints) in the manifest description field.