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.
Creating a Job
Section titled “Creating a Job”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);from fabric_platform import FabricClient
fabric = FabricClient(api_key="fab_xxx")
job = fabric.create_job( modality="text", tier="basic", input={"prompt": "Summarize this article"}, params={"temperature": 0.5}, organization_id="<org-id>",)print(job["id"], job["status"])use fabric_sdk::FabricClient;
let client = FabricClient::new("https://gofabric.dev", api_key)?;
let job = client.create_job(serde_json::json!({ "modality": "text", "tier": "basic", "input": {"prompt": "Summarize this article"}, "params": {"temperature": 0.5}, "organization_id": "<org-id>"})).await?;println!("{} {}", job.id, job.status);curl -X POST https://gofabric.dev/v1/jobs \ -H 'Authorization: Bearer fab_xxx' \ -H 'content-type: application/json' \ -d '{ "modality": "text", "tier": "basic", "input": {"prompt": "Summarize this article"}, "params": {"temperature": 0.5}, "organization_id": "<org-id>" }'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..." }}Job Lifecycle
Section titled “Job Lifecycle”Jobs progress through these states:
- pending — Created, waiting for execution
- started — Executor has claimed the job
- completed — Finished successfully with output
- failed — Execution failed after all retries
Querying Jobs
Section titled “Querying Jobs”// Get a specific jobconst job = await fabric.getJob("<job-id>");
// List all jobsconst jobs = await fabric.listJobs();
// Get job usage/costconst usage = await fabric.getJobUsage("<job-id>");# Get a specific jobjob = fabric.get_job("<job-id>")
# List all jobsjobs = fabric.list_jobs()
# Get job usage/costusage = fabric.get_job_usage("<job-id>")// Get a specific joblet job = client.get_job("<job-id>").await?;
// List all jobslet jobs = client.list_jobs().await?;
// Get job usage/costlet usage = client.get_job_usage("<job-id>").await?;# Get a specific jobcurl https://gofabric.dev/v1/jobs/<job-id>
# List all jobscurl https://gofabric.dev/v1/jobs
# Get job usage/costcurl https://gofabric.dev/v1/jobs/<job-id>/usageIdempotency
Section titled “Idempotency”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",});job = fabric.create_job( modality="text", input={"prompt": "Generate a summary"}, organization_id="<org-id>", idempotency_key="client-request-abc-123",)let job = client.create_job(serde_json::json!({ "modality": "text", "input": {"prompt": "Generate a summary"}, "organization_id": "<org-id>", "idempotency_key": "client-request-abc-123"})).await?;curl -X POST https://gofabric.dev/v1/jobs \ -H 'Authorization: Bearer fab_xxx' \ -H 'content-type: application/json' \ -d '{ "modality": "text", "input": {"prompt": "Generate a summary"}, "organization_id": "<org-id>", "idempotency_key": "client-request-abc-123" }'Duplicate submissions with the same idempotency key return the original resource.
Cost Estimation
Section titled “Cost Estimation”Before submitting a job, you can estimate its cost:
const estimate = await fabric.estimateCost({ modality: "text", model: "gpt-4", input: { prompt: "Generate a summary" },});estimate = fabric.estimate_cost( modality="text", model="gpt-4", input={"prompt": "Generate a summary"},)let estimate = client.estimate_cost(serde_json::json!({ "modality": "text", "model": "gpt-4", "input": {"prompt": "Generate a summary"}})).await?;curl -X POST https://gofabric.dev/v1/providers/estimate \ -H 'content-type: application/json' \ -d '{ "modality": "text", "model": "gpt-4", "input": {"prompt": "Generate a summary"} }'Local models (Ollama, Whisper) always return $0.00.
Job Events
Section titled “Job Events”Subscribe to real-time events for a specific job:
await fabric.streamEvents("<job-id>", (event) => { console.log(event.kind, event.payload);});for event in fabric.stream_job_events("<job-id>"): print(event["kind"], event["payload"])let mut stream = client.stream_job_events("<job-id>").await?;while let Some(event) = stream.next().await { println!("{} {:?}", event.kind, event.payload);}curl -N https://gofabric.dev/v1/jobs/<job-id>/eventsJob event types:
| Event | Description |
|---|---|
job.created | Job was created |
job.started | Executor began processing |
job.completed | Job finished with output |
job.failed | Job execution failed |
The per-job event stream replays all existing events on connect (catch-up), then streams live events as they occur.
Modalities
Section titled “Modalities”Jobs support these modalities, routed to the appropriate provider:
| Modality | Description | Example Providers |
|---|---|---|
text | Text generation and completion | OpenAI, Anthropic, Ollama |
image | Image generation | OpenAI (DALL-E), ComfyUI |
audio | Audio transcription | Whisper |
embedding | Vector embeddings | OpenAI, Ollama (nomic-embed-text) |
Tier Selection
Section titled “Tier Selection”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.
Workflow Run Submission Body Formats
Section titled “Workflow Run Submission Body Formats”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.
| Header | Parser |
|---|---|
application/json (default) | serde_json |
application/yaml / text/yaml / application/x-yaml | serde_yaml_ng (safe-by-default) |
application/toml / text/toml / application/x-toml | toml |
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.
# 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.
Run-specs (file-based submissions)
Section titled “Run-specs (file-based submissions)”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.
Scaffolding a run-spec
Section titled “Scaffolding a run-spec”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:
# 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 tomlfab-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-optionalRequired fields land uncommented under input:; optional fields
appear as comment blocks the operator copies in.
Running a workflow set
Section titled “Running a workflow set”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.
# 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 --parallelStages 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.jsonset: chained-batchruns: - 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.