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.kindsays 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 astorage_key- download it withGET /v1/objects/{storage_key}and your key. view_urlopens 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.costis 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.