Skip to content

Jobs

Predictions, designs and embeddings all create a job. This is how you track it, read its output, and clean it up. Every call sends the key of the account that submitted the job (see Authentication); another account's job id answers 404.

The Job object

{
  "object": "job",
  "id": "9daefed38ec5b505b80f76f9a9e87323",
  "kind": "predict",              // "predict" | "design" | "embed"
  "status": "running",            // queued | running | succeeded | failed | canceled
  "name": "myprotein",
  "model": "boltz2",              // null for designs
  "protocol": null,               // set for designs
  "progress": 0.5,                // 0..1, or null when indeterminate
  "stage": "trunk",               // human-readable current step
  "done": 0, "total": 1,          // sub-units completed / total
  "params": {},                   // the params the job was submitted with
  "waiting_for_device": true,     // present only while nothing is free to run it
  "queue": { "reason": "all_chips_busy",
             "accounts_ahead": 3,
             "yours": { "position": 1, "of": 1 },
             "fleet": { "chips": 128, "busy": 127, "out_of_service": 1,
                        "accounts_waiting": 41 } },
  "attempt": 2,                   // present only on a retry: the box running it stopped answering
  "error": null,                  // why it failed, or what a succeeded job could not deliver
  "partial": { "unit": "designs", "requested": 12, "delivered": 10,
               "shards_failed": 1, "shards": 6 },  // present only on a short delivery
  "created_at": "2026-09-02T20:12:08Z",
  "started_at":  "...",
  "finished_at": null,
  "results_ready": false,
  "links": { "self": "...", "results": "...", "archive": "...", "logs": "...", "usage": "..." }
}

links.usage is what the job was charged, once it has finished: the billed seconds and amount in dollars (amount_micros in millionths of one), the same record GET /v1/usage lists. It answers 404 while the job is still queued or running, and answers as soon as the job has ended. A job canceled before it started, or one that failed, costs 0.

A job can succeed with less than it was asked for. A design runs as several shards, and a prediction of several structures folds each on its own. When some of that work fails, the job still ends succeeded with everything that did finish, and it carries partial: what you asked for, what you got, and for a design how many shards failed. error says the same in a sentence, with the first failure's reason. The results directory is named for the designs it holds (final_10_designs/). Only the work that finished is billed. Submit again for the rest.

When a structure runs again

If the box folding a structure stops answering (it hung, or its agent died), the structure runs again from the start on another box, and its progress starts over. The job then carries "attempt": 2 (a job of several structures carries it on that structure's entry in targets). A box that is restarted on purpose hands its structures back first; they start over too, without counting as an attempt. A retry is not billed twice: you pay for the run that produced the result. A structure that loses a second box this way fails with a message saying so, and nothing is charged for it.

While a job waits

queue is present while any part of the job waits for a chip. It says why and who is ahead, never how long: how long the work ahead takes is not known until it ends.

field meaning
reason all_chips_busy; no_galaxy_online, nothing is serving right now and the job starts when something is; or account_backlog, your own earlier jobs run first
accounts_ahead accounts the next free chip goes to before yours
yours this job's place among your own waiting jobs, which run in the order you sent them
fleet chips online, busy and out of service, and how many accounts have work waiting

The next free chip goes to the account holding the fewest chips, so a new account's first job starts on the next chip that frees however long the queue is. How that is decided and audited is in Models and limits. GET /v1/cluster answers the same fleet counts without a job.

A queued job and a running one are different states: a running job reports its stage, done and total instead.

Email when it ends

A queue can be longer than you want to watch. Signed in, you can ask for one email when the job succeeds or fails, sent to your account's address:

curl -s -H "Authorization: Bearer $JAPANFOLD_API_KEY" -X POST https://api.japanfold.aiand.com/v1/jobs/$JOB/notify -d '{"lang": "en"}'   # or "ja"

The job then carries "email_when_done": true. An account without an email address gets 409, and so does a job that has already ended. A cancelled job sends nothing. The workbench offers this once a job has waited a minute.

Wait for one job

curl -s -H "Authorization: Bearer $JAPANFOLD_API_KEY" https://api.japanfold.aiand.com/v1/jobs/$JOB

returns the job's state now. Add ?wait=<seconds> (at most 60) and the request is held until the job finishes or the time is up, whichever comes first. The body is the same job either way, so a loop on it needs no sleep: each call returns the moment the job is done.

while :; do
  S=$(curl -s -H "Authorization: Bearer $JAPANFOLD_API_KEY" "https://api.japanfold.aiand.com/v1/jobs/$JOB?wait=60" \
      | python3 -c 'import sys,json; print(json.load(sys.stdin).get("status"))')
  case $S in queued|running) ;; *) break ;; esac
done
echo "$S"

The CLI does the same with japanfold wait $JOB. The header Prefer: wait=60 is equivalent and also works on a create (see Predictions). The hold is a preference: when the service is already holding many requests it answers at once with Retry-After, and Preference-Applied: wait on a response says the hold happened.

List your jobs

GET /v1/jobs lists every job on the account, whichever key or browser submitted it.

curl -s -H "Authorization: Bearer $JAPANFOLD_API_KEY" \
  "https://api.japanfold.aiand.com/v1/jobs?limit=20"
{ "object": "list", "data": [ { "object": "job", ... } ],
  "has_more": false, "next_cursor": null }

Paginated cursor-style: limit defaults to 20 and clamps to 100, and next_cursor goes back as ?cursor=... for the next page.

Cancel, delete

curl -s -H "Authorization: Bearer $JAPANFOLD_API_KEY" -X POST   https://api.japanfold.aiand.com/v1/jobs/$JOB/cancel   # stop a queued/running job
curl -s -H "Authorization: Bearer $JAPANFOLD_API_KEY" -X DELETE https://api.japanfold.aiand.com/v1/jobs/$JOB          # delete the job and its data

Cancel answers with the job's status and finished_at. A queued job ends at once. A running job ends when its run has stopped, usually within a few seconds, and the cancel waits up to 15 s for that, so the answer normally reads canceled and the job's usage answers in the next call. If the run is slower, the answer reads running and its detail names GET /v1/jobs/$JOB?wait=60, which returns when it has ended. canceled is false for a job that had already ended.

A running job cannot be deleted. DELETE cancels it and answers 409; the run stops, the job reads canceled, and a second DELETE then removes it and its files. A canceled job is billed for the chip time it ran, 0 if it was still queued; GET /v1/jobs/$JOB/usage says which once the job reads canceled.

Results

Once results_ready (or status is succeeded), read the scores and the list of downloadable artifacts:

curl -s -H "Authorization: Bearer $JAPANFOLD_API_KEY" https://api.japanfold.aiand.com/v1/jobs/$JOB/results

Ask earlier and you get {"job_id":"...","object":"results","ready":false,"status":"queued"}, so ready is safe to poll on directly.

You may not need this call. A succeeded job read by its id (GET /v1/jobs/{id}, including the poll that saw it finish) already carries its artifacts, the same list as below, and download, one shell command that fetches every file into the current directory with the key from JAPANFOLD_API_KEY:

{ "status": "succeeded", "results_ready": true,
  "artifacts": [ { "path": "target_1.cif", "type": "structure", "url": "/v1/jobs/.../artifacts/target_1.cif" } ],
  "download": "curl -fsSL -H \"Authorization: Bearer $JAPANFOLD_API_KEY\" --create-dirs -o target_1.cif https://api.japanfold.aiand.com/v1/jobs/.../artifacts/target_1.cif" }

A job with more than 20 files lists them only here, and its download fetches and unzips the archive instead. Read /results for the scores.

A prediction result, from the 33-residue Boltz-2 fold in the quickstart:

{
  "object": "results",
  "job_id": "...",
  "kind": "predict",
  "ready": true,
  "rows": [
    { "id": "target_1", "status": "ok",
      "confidence_score": 0.827157,
      "complex_plddt": 0.877438, "complex_iplddt": 0.877438,
      "complex_pde": 0.431932, "complex_ipde": 0.0,
      "ptm": 0.626033, "iptm": 0.0,
      "protein_iptm": 0.0, "ligand_iptm": 0.0,
      "chains_ptm": { "0": 0.626033 },
      "pair_chains_iptm": { "0": { "0": 0.626033 } },
      "runtime_s": 8.6 }
  ],
  "artifacts": [
    { "path": "target_1.cif", "target": "target_1", "type": "structure",
      "url": "/v1/jobs/.../artifacts/target_1.cif" }
  ],
  "archive_url": "/v1/jobs/.../archive"
}
  • rows: one row per target. confidence_score is the headline number, a weighted blend of the others. The rest are the AlphaFold-family metrics: plDDT (plddt, complex_plddt) is predicted local accuracy per residue, pTM (ptm) the predicted TM-score for the whole structure, ipTM (iptm) the same restricted to a chain-chain interface, and PDE (complex_pde, complex_ipde) the predicted distance error. All are 0-1, higher is better except the error terms. Which fields are present depends on the model: the block above is Boltz-2's, an ESMFold-2 row instead carries plddt (mean, 0-1), ptm, n_residues, n_chains, samples and msa, and a Boltz-2 affinity run adds the affinity fields (Predictions → Affinity).
  • A rough read: fold complex_plddt > 0.7, interface iptm > 0.5. ipTM scores an interface, so on a single-chain input it comes back 0.0 rather than absent, as above.
  • A design result carries designs instead of rows, ranked for BoltzGen and unranked for RFdiffusion3 and PXDesign. An embed result carries sequences; see Embeddings → Results.
  • artifacts[].url and archive_url are paths under the base URL. Prefix them with https://api.japanfold.aiand.com.

Download artifacts

One file. The API answers with a redirect to a short-lived link and the file comes straight from storage, so pass -L. requests and urllib follow the redirect on their own; httpx needs follow_redirects=True:

curl -sL -H "Authorization: Bearer $JAPANFOLD_API_KEY" https://api.japanfold.aiand.com/v1/jobs/$JOB/artifacts/target_1.cif -o target_1.cif

The link is good for a few minutes and for that one file. Do not forward your API key to it: curl, requests and httpx already drop it when the host changes, but Python's urllib.request does not, and the storage layer refuses a request carrying two credentials.

Send Accept: application/json and you get the link instead of being redirected to it, as {"url": ..., "size": ..., "expires_in": ...}. That is for a page in a browser, which cannot follow this redirect: a cross-origin request redirected on to a third origin arrives at storage with no origin, and the fetch is refused. Fetch the url yourself in a second request. A file storage cannot hand over itself is streamed by the API either way, so check the content type.

Everything as a zip. The archive is built as it is sent, so there is no Content-Length and no progress bar:

curl -s -H "Authorization: Bearer $JAPANFOLD_API_KEY" https://api.japanfold.aiand.com/v1/jobs/$JOB/archive -o results.zip
unzip -oq results.zip -d results

Logs

Plain-text run log, useful while a job runs or to debug a failure:

curl -s -H "Authorization: Bearer $JAPANFOLD_API_KEY" https://api.japanfold.aiand.com/v1/jobs/$JOB/logs

Retention

A job and its result files are kept for 30 days after it finishes, then deleted. A deleted or expired id returns 404. Download what you want to keep.