Skip to content

Submitting Jobs

A job is a uniquely identifiable client-facing unit of work. Every job has a canonical job_id and maps to a single-node workflow under the hood.

import { FabricClient } from "@fabric-platform/sdk";
const fabric = new FabricClient({ apiKey: "fab_xxx" });
const job = await fabric.createJob({
modality: "text",
tier: "basic",
input: { prompt: "Summarize this article" },
params: { temperature: 0.5 },
organizationId: "<org-id>",
});
console.log(job.id, job.status);

The response includes the canonical job_id you can use to track the job:

{
"data": {
"job_id": "job_01HXYZ...",
"status": "pending",
"workflow_run_id": "run_01HXYZ..."
}
}

Jobs progress through these states:

  1. pending — Created, waiting for execution
  2. started — Executor has claimed the job
  3. completed — Finished successfully with output
  4. failed — Execution failed after all retries
// Get a specific job
const job = await fabric.getJob("<job-id>");
// List all jobs
const jobs = await fabric.listJobs();
// Get job usage/cost
const usage = await fabric.getJobUsage("<job-id>");

Fabric supports idempotent job submission to prevent duplicate work from retries or network failures.

Include an idempotency_key in the request:

const job = await fabric.createJob({
modality: "text",
input: { prompt: "Generate a summary" },
organizationId: "<org-id>",
idempotencyKey: "client-request-abc-123",
});

Duplicate submissions with the same idempotency key return the original resource.

Before submitting a job, you can estimate its cost:

const estimate = await fabric.estimateCost({
modality: "text",
model: "gpt-4",
input: { prompt: "Generate a summary" },
});

Local models (Ollama, Whisper) always return $0.00.

Subscribe to real-time events for a specific job:

await fabric.streamEvents("<job-id>", (event) => {
console.log(event.kind, event.payload);
});

Job event types:

EventDescription
job.createdJob was created
job.startedExecutor began processing
job.completedJob finished with output
job.failedJob execution failed

The per-job event stream replays all existing events on connect (catch-up), then streams live events as they occur.

Jobs support these modalities, routed to the appropriate provider:

ModalityDescriptionExample Providers
textText generation and completionOpenAI, Anthropic, Ollama
imageImage generationOpenAI (DALL-E), ComfyUI
audioAudio transcriptionWhisper
embeddingVector embeddingsOpenAI, Ollama (nomic-embed-text)

Specify a tier preference to control provider selection:

  • basic — Local/free providers (Ollama, Whisper, Echo)
  • premium — Remote/paid providers (OpenAI, Anthropic)

If no tier is specified, the router selects by cost (cheapest first), then health status.

POST /v1/workflows/runs accepts request bodies in JSON, YAML, or TOML — selected by the Content-Type header. JSON is the default when the header is missing or unrecognised, so existing clients are unaffected.

HeaderParser
application/json (default)serde_json
application/yaml / text/yaml / application/x-yamlserde_yaml_ng (safe-by-default)
application/toml / text/toml / application/x-tomltoml

The same shape that goes in a JSON body works in YAML and TOML — SubmitRunRequest is just deserialized via serde. This pairs with the operator-side run-spec files (see the Daily Drop guide and examples/run-inputs/): a run-spec checked into the repo can also be POSTed verbatim.

Terminal window
# JSON (current behaviour, unchanged):
curl -X POST https://gofabric.dev/v1/workflows/runs?name=video/talking-head \
-H 'Authorization: Bearer fab_xxx' \
-H 'content-type: application/json' \
-d '{"input": {"topic": "overthinking", "script_formula": "reframe"}}'
# YAML:
curl -X POST https://gofabric.dev/v1/workflows/runs?name=video/talking-head \
-H 'Authorization: Bearer fab_xxx' \
-H 'content-type: application/yaml' \
--data-binary $'input:\n topic: overthinking\n script_formula: reframe\n'
# TOML:
curl -X POST https://gofabric.dev/v1/workflows/runs?name=video/talking-head \
-H 'Authorization: Bearer fab_xxx' \
-H 'content-type: application/toml' \
--data-binary $'[input]\ntopic = "overthinking"\nscript_formula = "reframe"\n'

Response bodies remain JSON. Format negotiation in this release is request-only; clients that want YAML/TOML output convert client-side.

Security notes. serde_yaml_ng parses safely by default — no arbitrary type construction, bounded anchor expansion. TOML parses to a tree, same memory profile as JSON. Body size is bounded by the existing axum body limit. Parse errors return 400 Bad Request with short generic messages — internal serde errors are not propagated verbatim to avoid leaking schema details.

The body shapes above can also live in a checked-in YAML / TOML / JSON file — a run-spec. The same artifact works locally (fab-workflow --from-file), over the wire (curl --data-binary @file.yaml), and as a recurring CI job. See the dedicated Run-specs guide for the full reference: top-level keys, layered input: composition, CLI override precedence, and the --scaffold generator.

Need a starting point for a workflow you haven’t run before? fab-workflow --scaffold renders a populated YAML / TOML / JSON template directly from the workflow’s input schema:

Terminal window
# YAML (default) with field descriptions as inline comments:
fab-workflow --scaffold video/quick-shorts > my-run.yaml
# Other formats:
fab-workflow --scaffold video/quick-shorts -o my-run.toml --format toml
fab-workflow --scaffold video/quick-shorts -o my-run.json --format json
# Show the OUTPUT side — declared aliases + schema, useful when wiring
# this workflow into a workflow set:
fab-workflow --scaffold hooks/generate --show-output
# Required-fields-only template:
fab-workflow --scaffold video/quick-shorts --no-optional

Required fields land uncommented under input:; optional fields appear as comment blocks the operator copies in.

A workflow set bundles multiple runs into one declarative manifest with shared defaults, inter-run wiring, parallel execution, and cross-file composition. See examples/workflow-sets/ for runnable examples and the format reference.

Terminal window
# Run the whole set:
fab-workflow --run-set examples/workflow-sets/chained.yaml -o /tmp/out
# Run a specific stage (deps auto-pulled in topo order):
fab-workflow --run-set examples/workflow-sets/chained.yaml short -o /tmp/out
# Validate + show the resolution plan without executing:
fab-workflow --run-set examples/workflow-sets/chained.yaml --dry-run
# Iterate on a late stage by reusing persisted upstream outputs:
fab-workflow --run-set examples/workflow-sets/chained.yaml short \
-o /tmp/out --use-prior-outputs research --use-prior-outputs hooks
# Run independent stages concurrently:
fab-workflow --run-set examples/workflow-sets/chained.yaml --parallel

Stages reference each other through declared output aliases on the upstream’s WorkflowOutput class — chains never reach into the raw output schema:

# yaml-language-server: $schema=../../schemas/workflow-set.schema.json
set: chained-batch
runs:
- name: research
workflow: global/deep-research
input: { query: "AI productivity" }
- name: hooks
workflow: hooks/generate
inputs_from:
# `hook` is a public alias declared on HooksGenerateOutput.
- { source: research.0.themes, map: themes, optional: true }
input: { niche: "AI productivity" }
- name: short
workflow: video/quick-shorts
inputs_from:
- { source: hooks.0.hook, map: topic }

The # yaml-language-server: $schema= comment activates editor autocomplete in VS Code (YAML extension) and JetBrains. The schema is generated from the WorkflowSet Pydantic model; runtime and editor validation stay in lockstep.