Skip to content

Quickstart

1. Get an API key

Your invitation code starts inv_. Redeem it for a key - no install needed:

curl https://api.factorialcompute.com/v1/signup \
  -H "Content-Type: application/json" \
  -d '{"invite": "inv_..."}'

The answer holds api_key. It is shown once and stored only as a hash, so keep it somewhere safe, then:

export FACTORIAL_API_KEY=fc_...

Each invitation works once. More keys - for CI or a server - come from POST /v1/keys once you have the first; a teammate gets their own invitation from POST /v1/invites.

Prefer a coding agent?

Setting up an agent does the same thing and installs Factorial's tools into Claude Code, Codex or Cursor - with every file it writes listed first.

2. Run a model

A run is POST /v1/executions with a model and its input. Files are uploaded once with POST /v1/media and passed by id.

API=https://api.factorialcompute.com
AUTH="Authorization: Bearer $FACTORIAL_API_KEY"

# Upload a photo; the answer's "id" is its media id
curl $API/v1/media?kind=image -H "$AUTH" -F file=@shelf.jpg

# Find the boxes in it
curl $API/v1/executions -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"model": "sam3-segment",
       "input": {"image": "med_...", "objects": [{"id": "box", "text": "cardboard box"}]}}'
pip install factorial-compute
from factorial_compute import Client

f = Client()  # reads FACTORIAL_API_KEY

# A path, a URL or bytes: the client uploads it for you
result = f.run(model="sam3-segment", input={
    "image": "shelf.jpg",
    "objects": [{"id": "box", "text": "cardboard box"}],
})
for mask in result.masks:
    print(mask.id, mask.box)       # box = [x0, y0, x1, y1] in pixels
    mask.save(f"{mask.id}.png")

No package to install: Node 18 or newer has everything this needs.

import { readFile } from "node:fs/promises";

const API = "https://api.factorialcompute.com";
const auth = { Authorization: `Bearer ${process.env.FACTORIAL_API_KEY}` };

async function upload(path, kind = "image") {
  const form = new FormData();
  form.append("file", new Blob([await readFile(path)]), path);
  const r = await fetch(`${API}/v1/media?kind=${kind}`, { method: "POST", headers: auth, body: form });
  const body = await r.json();
  if (!r.ok) throw new Error(body.error?.message ?? JSON.stringify(body));
  return body.id;
}

async function run(model, input) {
  const r = await fetch(`${API}/v1/executions`, {
    method: "POST",
    headers: { ...auth, "Content-Type": "application/json" },
    body: JSON.stringify({ model, input }),
  });
  const body = await r.json();
  if (!r.ok) throw new Error(body.error?.message ?? JSON.stringify(body.detail ?? body));
  return body;
}

const photo = await upload("shelf.jpg");
const masks = await run("sam3-segment", {
  image: photo,
  objects: [{ id: "box", text: "cardboard box" }],
});
console.log(masks.output.objects.map((o) => [o.id, o.box]), masks.cost);

Each model's exact input is in its catalog entry - GET /v1/catalog/sam3-segment returns input_schema (JSON Schema), a working example and the price. Models and prices lists them all.

3. Read the answer

{
  "id": "exec_...",
  "status": "succeeded",
  "model": "sam3-segment",
  "output": {
    "kind": "masks",
    "width": 640, "height": 480,
    "objects": [{
      "id": "box", "box": [380, 151, 541, 311], "score": 0.97,
      "media_id": "med_...", "storage_key": "orgs/.../mask-0.png"
    }],
    "not_found": [],
    "view_url": "https://api.factorialcompute.com/view#..."
  },
  "usage": {"objects": 1},
  "cost": {"usd": 0.0075, "complete": true}
}
  • output.kind says which shape the answer has: masks, image, depth, detections, mesh, tracks, transcript, text...
  • A file in an answer has a media_id (to pass to the next model) and a storage_key - download it with GET /v1/objects/{storage_key} and your key.
  • view_url opens the answer in a browser - the masks drawn on the picture, a 3D model you can turn - with no key. Anybody with the link can see it until it expires.
  • cost is what this run cost, in dollars.

4. Long runs

A run answers when it finishes. Video generation and 3D can take a minute or more, so for those send "wait": false: the answer is the run's id at once, with status: "queued". Then poll GET /v1/executions/{id} until status is succeeded, failed or canceled - or pass webhook_url to be told. In Python, f.run(..., wait=False) returns a handle; handle.wait() gives the answer.

When something is refused

A refusal has a status code and a sentence that says what to do:

{"error": {"code": "invalid_input",
           "message": "input.image must be an uploaded file, as {\"media_id\": \"med_...\"}. Upload it with POST /v1/media?kind=image; ..."}}

error.code is stable, for code to act on; error.message is for the person - or agent - reading it. A few older errors carry detail instead of error.

Before you spend

curl $API/v1/quote -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"model": "trellis-2", "input": {"image": "med_..."}}'

The quote runs the same checks as the run, so it is also the free way to check an input. In Python: f.run(..., dry_run=True). Add "max_cost_usd": 0.10 to a run and it is refused, before anything is spent, if it would cost more.