| Navigation: Developer Index | Providers Overview → AtlasCloud |
Runs: cloud. Needs:
ATLASCLOUD_API_KEY(Settings → API Keys). Chat models use AtlasCloud’s OpenAI-compatible API and need no manifest entry.
Add a model by appending to the manifest and running the sync and build commands:
node scripts/sync-atlascloud-manifest.mjs # fill the entry's fields from AtlasCloud's schema
node scripts/sync-atlascloud-manifest.mjs --check # exit 1 on drift
npm run build:packages # copy the manifest into dist/
npm run typecheck && npm run test --workspace=packages/atlascloud-nodes
Most models need no TypeScript. The steps are under Add a new model/node.
Where things live
| What | Path |
|---|---|
| Manifest (JSON, single source of truth. Entries are added by hand and fields are reconciled by the sync script) | packages/atlascloud-nodes/src/atlascloud-manifest.json |
Manifest copy in dist/ (generated by build) |
packages/atlascloud-nodes/dist/atlascloud-manifest.json |
| Sync script (reconciles manifest fields with AtlasCloud’s schemas) | scripts/sync-atlascloud-manifest.mjs |
| Node class factory (reads manifest at runtime) | packages/atlascloud-nodes/src/atlascloud-factory.ts |
| Submit, wait, download (used by the provider, which the nodes call) | packages/runtime/src/providers/atlascloud-transport.ts |
| Pending-prediction registry for callbacks | packages/runtime/src/providers/atlascloud-webhook-registry.ts |
| Signed callback route | packages/websocket/src/routes/atlascloud-webhook.ts |
Package entry point (exports ATLASCLOUD_NODES) |
packages/atlascloud-nodes/src/index.ts |
| Runtime provider (chat via the OpenAI-compatible API, image/video model lists, textToImage etc.) | packages/runtime/src/providers/atlascloud-provider.ts |
| Provider registration | packages/runtime/src/providers/index.ts (registerBuiltinProvider(PROVIDER_IDS.ATLASCLOUD, ...)) |
| Manifest-to-model-list helper (shared with KIE, FAL) | packages/runtime/src/providers/manifest-models.ts |
How AtlasCloud models/nodes are defined
atlascloud-manifest.json is the single file you edit to add a model. You write each entry’s identity (className, moduleName, modelId, title, description) by hand. scripts/sync-atlascloud-manifest.mjs keeps the fields of shipped models in line with the Input schema that AtlasCloud publishes for each model, and it never adds models.
Manifest → node class
packages/atlascloud-nodes/src/index.ts loads the manifest with loadPackageAssetJson at module load time and passes it to loadAtlasNodesFromManifest. That function calls createAtlasNodeClass once per manifest entry. createAtlasNodeClass (in atlascloud-factory.ts) produces a BaseNode subclass whose nodeType is atlascloud.<moduleName>.<className>, whose input props match the fields array, and whose process() method calls context.callProviderEndpoint with the manifest modality as the route, then context.downloadProviderMedia. AtlasCloudProvider holds ATLASCLOUD_API_KEY and runs atlasSubmit → atlasAwaitResult → atlasDownload from packages/runtime/src/providers/atlascloud-transport.ts, so each run writes a generation record.
The build script in package.json compiles TypeScript then copies src/atlascloud-manifest.json to dist/:
"build": "node ../../scripts/build-typescript-workspace.mjs && node -e \"require('node:fs').cpSync('src/atlascloud-manifest.json', 'dist/atlascloud-manifest.json')\""
The manifest export declared in package.json is:
"./atlascloud-manifest.json": "./dist/atlascloud-manifest.json"
Manifest → model list (provider)
AtlasCloudProvider.getAvailableImageModels() and getAvailableVideoModels() call loadImageModels / loadVideoModels from manifest-models.ts, which load the manifest through loadPackageAssetJson from @nodetool-ai/atlascloud-nodes/atlascloud-manifest.json. The model-list functions read outputType, modelId, and title from each entry. buildModelMap() in the provider reads the same manifest to know which fields each model accepts for the generic textToImage / imageToVideo etc. methods.
The manifest drives both the node UI (fields, defaults, enum values) and the model picker, so one edit covers both.
Wire protocol
- Submit:
POST https://api.atlascloud.ai/api/v1/model/generateImageor.../generateVideo - Body: flat JSON —
{ model: "<modelId>", ...fields }(not nested underinput) - Poll:
GET https://api.atlascloud.ai/api/v1/model/prediction/<id> - Terminal success statuses:
completed,complete,succeeded,success,done. Terminal failure statuses:failed,fail,error,cancelled,canceled(http-transport.ts) - Chat models use AtlasCloud’s OpenAI-compatible API at
https://api.atlascloud.ai/v1throughAtlasCloudProviderand need no manifest entry. - Submit is never retried — a 5xx may have already created and billed the job upstream.
Add a new model/node
Step 1: Identify the model
From the AtlasCloud docs or API, collect:
- The model id string, e.g.
"bytedance/seedance-3.0/text-to-video" - The modality:
"image"or"video" - The field names and types the endpoint accepts
Step 2: Choose a className and moduleName
className: PascalCase identifier used as the TypeScript class name and the last segment of the node type. Example:"Seedance3TextToVideo".moduleName:"image"or"video"(matches the output type). Used in the node type:atlascloud.video.Seedance3TextToVideo.
Step 3: Append an entry to the manifest
Open packages/atlascloud-nodes/src/atlascloud-manifest.json. Append an object following this shape (copy the closest existing entry and adjust):
{
"className": "Seedance3TextToVideo",
"moduleName": "video",
"modality": "video",
"modelId": "bytedance/seedance-3.0/text-to-video",
"outputType": "video",
"title": "Seedance 3.0 — Text to Video",
"description": "AtlasCloud / ByteDance Seedance 3.0 — text-to-video with native audio.",
"pollInterval": 5000,
"maxAttempts": 600,
"fields": [
{
"name": "prompt",
"type": "str",
"default": "",
"title": "Prompt",
"description": "Text prompt describing the desired video.",
"required": true
},
{
"name": "duration",
"type": "int",
"default": 5,
"title": "Duration (seconds)",
"values": [-1, 4, 5, 6, 7, 8, 9, 10]
},
{
"name": "resolution",
"type": "enum",
"default": "720p",
"title": "Resolution",
"values": ["480p", "720p", "1080p"]
},
{
"name": "ratio",
"type": "enum",
"default": "16:9",
"title": "Aspect Ratio",
"values": ["16:9", "9:16", "1:1", "adaptive"]
}
]
}
Field type reference:
type |
Description |
|---|---|
"str" |
Text input |
"int" |
Integer (coerced from string dropdowns) |
"float" |
Float |
"bool" |
Checkbox |
"enum" |
Dropdown; requires "values" array |
"image" |
Single image ref |
"video" |
Single video ref |
"audio" |
Single audio ref |
"list[image]" |
Multiple image refs |
"list[video]" |
Multiple video refs |
"list[audio]" |
Multiple audio refs |
Use "list[image]" (not image with "array": true) for multi-image inputs. Use "null" as the default for optional enum fields without a sensible preselected value (e.g. aspect_ratio on Nano Banana models).
pollInterval: milliseconds between poll requests. Existing entries use 3000 for all image models and most video models, and 5000 for some video models. The factory defaults to 3000.
maxAttempts: maximum polls before timing out. Existing entries use 300 for image and 600 for video. The factory defaults to 600.
wrapInto: set on a typed asset field (reference_images, reference_videos, reference_audios) when the API takes one mixed array such as refers: [{url, type}]. The factory appends each field’s resolved URLs into that array in field order. The provider’s generic imageToVideo reads the same flag.
Step 4: Reconcile fields with the published schema
node scripts/sync-atlascloud-manifest.mjs --check # exit 1 if the manifest is out of date
node scripts/sync-atlascloud-manifest.mjs # rewrite the manifest
The script fetches the catalog at GET https://api.atlascloud.ai/api/v1/models (no auth) and each model’s Input schema. It updates enum options, defaults, numeric bounds, scalar types, and required flags. It skips asset fields (image, video, audio, list[...]) and the transport switches enable_base64_output, enable_sync_mode, return_last_frame, and output_dir, which break the poll-then-download flow or cannot surface. A schema property typed anyOf needs a hand-written field. After you add an entry, --check must report the manifest up to date. A drift line against the new model means the entry disagrees with its schema.
Step 5: Build
npm run build:packages
This compiles TypeScript and copies the manifest into dist/. The factory reads it at module load.
Step 6: Smoke-test the node
ATLASCLOUD_API_KEY=<your_key> \
npm run dev:nodetool -- node run \
atlascloud.video.Seedance3TextToVideo \
--props '{"prompt":"a red balloon floating upward","duration":5,"resolution":"720p","ratio":"16:9"}'
For a hermetic check (no secrets, no API call — verifies the node can be instantiated):
npm run dev:nodetool -- node run atlascloud.video.Seedance3TextToVideo \
--props '{"prompt":"test"}' --no-secrets 2>&1 | grep -E "error|ATLASCLOUD"
Expected: ATLASCLOUD_API_KEY is required, which means the node loaded and reached the provider constructor.
Step 7: Validate (no API call)
npm run dev:nodetool -- validate workflow.json
If you have a workflow JSON referencing the new node, this verifies the node type is registered and all edges type-check.
Prediction callbacks (webhooks)
AtlasCloud can POST a prediction’s terminal state back instead of making the run poll for it (AtlasCloud webhooks). Nothing in the manifest turns this on. It follows from one setting.
Set NODETOOL_PUBLIC_URL to an https address on a public host that AtlasCloud
can reach, and atlasSubmit adds
webhook_url: <NODETOOL_PUBLIC_URL>/api/providers/atlascloud/webhook to every
submission. An http, loopback, or private address is ignored rather than sent,
because AtlasCloud refuses those.
What happens then:
- AtlasCloud POSTs the callback with an Ed25519 signature over
<timestamp>.<raw body>, the signing key id, and the delivery timestamp. - The route verifies the signature against the provider’s JWKS
(
https://api.atlascloud.ai/api/v1/webhooks/jwks.json), which it caches for a day and refreshes once when a key id is unknown. Deliveries older than five minutes are rejected. The route skips session auth because the signature is the credential. - A verified callback resolves the pending prediction in the registry, and the
run that submitted it continues with the callback’s
payload.outputs.
Delivery is at-least-once and best effort, so the callback is an optimization,
never the only way a run finishes. atlasAwaitResult races it against a poll
of the prediction endpoint every 15s, and the run takes whichever reports a
terminal state first. A callback that never arrives costs latency, not the run.
A callback this process cannot match, such as a duplicate or one for a
prediction another replica submitted, is acknowledged with matched: false so
AtlasCloud stops retrying it.
Run npm run test --workspace=packages/runtime -- atlascloud-webhook and
npm run test --workspace=packages/websocket -- atlascloud-webhook after
changing any of this. nodetool harness gate selects both from the
atlascloud-callbacks surface.
Verify
Run these in order after any manifest change:
# 1. Rebuild (copies manifest to dist)
npm run build:packages
# 2. Type check the whole repo
npm run typecheck
# 3. Test atlascloud-nodes
npm run test --workspace=packages/atlascloud-nodes
# 4. Test runtime providers (checks manifest-models and the provider)
npm run test --workspace=packages/runtime
# 5. Affected tests, lint, and the harness gate
npm run test:affected
npm run lint
npm run dev:nodetool -- harness gate --base origin/main
None of these make live API calls. Tests use vitest with mocked HTTP.
Include the AtlasCloud model id, the node type string, and any omitted API fields in the PR description.