Analysis jobs¶
A job is one analysis run on one image. You submit it, and PoreStore runs it on hardware suited to it. When it completes, its result is stored and can be read any number of times. You can cancel a job until it starts running. PoreStore cancels one itself when it waits or runs far past its limits.
A diffusivity or conductivity job cancelled at its time limit while processing completes if its result arrives later.
Submit a job¶
POST /api/v1/teams/{team_id}/analysis-jobs/
| Field | Type | Required | Notes |
|---|---|---|---|
image_id |
UUID | yes | A ready image in this team. |
analysis_type |
string | yes | A slug from /analysis-types/. |
parameters |
object | yes | Validated against that analysis's parameter schema. See the parameter reference. |
Optional header: Idempotency-Key. See below.
curl --fail-with-body -sS -X POST "$PORESTORE_HOST/api/v1/teams/$PORESTORE_TEAM_ID/analysis-jobs/" \
-H "Authorization: Api-Key $PORESTORE_API_KEY" \
-H "Idempotency-Key: porosity-campaign-sample-17" \
-H 'Content-Type: application/json' \
--data '{
"image_id": "9d2b6f0e-1c3a-4e5b-8f7d-2a4c6e8b0d1f",
"analysis_type": "diffusivity",
"parameters": {
"direction": "z",
"target_relative_error": 0.001,
"solver_tolerance": 0.00001,
"queue_name": "tortuosity-jl",
"compute_tier": "auto"
}
}'
Response 202: the new job.
Response 200 with header Idempotency-Replayed: true: a replay of a job you already submitted with this key.
Errors
| Status | Where | Why |
|---|---|---|
400 |
details.<parameter> |
A parameter is missing, out of range or not allowed for the chosen backend, or the chosen compute tier can't take an image this size. |
400 |
details.image_id |
The image isn't ready yet. |
400 |
details.image |
The image isn't segmented: it's greyscale, holds labels, or has two values without a 0. |
400 |
details.submission |
No tier fits the image, the image can't be analysed (for example, its voxels can't be read, or it's all pore or all solid for permeability), or the job couldn't be queued. |
403 |
analysis_unavailable |
The analysis isn't released to you on this team. |
404 |
not_found |
No such image in this team. |
409 |
idempotency_conflict |
This key was already used for a different request. |
Idempotency¶
A network timeout leaves you with a real question: did the job get created? Send an Idempotency-Key header (any string up to 255 characters, unique per intended job) and you can retry safely:
- Same key, same request: you get the original job back,
200withIdempotency-Replayed: true. Nothing is dispatched again. - Same key, different request:
409 idempotency_conflict. "Different" is judged after parameters are normalised, so sending a default explicitly or leaving it out counts as the same request. - New key: a new job.
Keys are scoped to the team, so any authorised member's retry with the same key finds the same job. A UUID per job works well, and so does a deterministic name such as campaign-7/sample-17/permeability-x if you want reruns of a script to be no-ops.
Without a key, don't blindly retry a timed-out submission. List the team's jobs first and look for a match.
List jobs¶
GET /api/v1/teams/{team_id}/analysis-jobs/
Response 200: {"results": [job, …]}, with every job in the team. Not paginated, and the order isn't guaranteed.
Get a job¶
GET /api/v1/teams/{team_id}/analysis-jobs/{job_id}/
Response 200: the job. Poll this every 5–10 seconds until status is completed, failed or cancelled.
Cancel a job¶
POST /api/v1/teams/{team_id}/analysis-jobs/{job_id}/cancel/
Cancels a job that hasn't started running, pending or queued. No body.
Response 200: the job, now cancelled. A job that starts in the moment the cancel arrives is stopped by it.
Errors
| Status | Where | Why |
|---|---|---|
403 |
analysis_unavailable |
The job's analysis isn't released to you on this team. |
404 |
not_found |
No such job in this team. |
409 |
job_started |
The job is already running, so it can't be cancelled. |
409 |
job_ended |
The job has already finished. |
503 |
cancel_unconfirmed |
The cancel couldn't be confirmed yet. Try again in a moment. |
Get a result¶
GET /api/v1/teams/{team_id}/analysis-jobs/{job_id}/result/
Response 200
{
"job_id": "1b7e…",
"metrics": {
"solution": { "…": "the analysis's outputs, in the image's units" }
}
}
What's inside solution depends on the analysis. Results lists every analysis's keys and units.
Errors
| Status | Where | Why |
|---|---|---|
400 |
details.job |
The job isn't completed yet. |
404 |
not_found |
No such job in this team. |
The job object¶
{
"id": "1b7e5c2a-8d4f-4e1b-9a3c-6f2d8e0b4a7c",
"image_id": "9d2b6f0e-1c3a-4e5b-8f7d-2a4c6e8b0d1f",
"analysis_type": "diffusivity",
"status": "completed",
"parameters": {
"direction": "z",
"target_relative_error": 0.001,
"solver_tolerance": 1e-05,
"queue_name": "tortuosity-jl",
"compute_tier": "auto",
"backend": "gpu",
"…": "…"
},
"progress_percentage": 100,
"error_message": "",
"queue_reason": null,
"created_at": "2026-10-10T14:10:05.120331+00:00",
"started_at": "2026-10-10T14:10:06.004512+00:00",
"completed_at": "2026-10-10T14:12:41.775018+00:00",
"selected_compute_tier": "gpu-48gb-rtx-8000",
"solver_converged": true,
"solver_iterations_completed": 214,
"solver_wall_time_seconds": null,
"solver_convergence_telemetry": {},
"solver_version": "0.3.0",
"worker_image_tag": "…",
"worker_image_digest": "sha256:…",
"worker_application_revision": "…",
"compute_hardware": "Quadro RTX 8000"
}
This job asked for Auto, and when it was dispatched a 48 GB RTX 8000 GPU was idle and fitted the image, so it ran there.
| Field | Meaning |
|---|---|
image_id |
The image the job ran on, or null once that image has been deleted. The job and its result stay. |
status |
pending, queued, processing, completed, failed or cancelled. New values may be added. A job is queued until its work starts running, and processing while it runs. A job that fails and is retried automatically stays queued or processing until its last attempt ends. |
queue_reason |
While the job is pending or queued, why it waits, when known: behind_other_jobs (the workers that run it are busy) or scarce_hardware (the hardware it needs is in short supply, which can take minutes to hours). A job that can't start in time to finish within a day fails. null otherwise. New values may be added. |
parameters |
The normalised parameters the job ran with, including defaults filled in, routing fields such as backend, and the solver settings fixed at submission. Jobs started in the app, such as segmentation, are listed too, with their settings as the app saved them. A Basic Thresholding job (segmentation) saves the Invert (keep above threshold) box as operation_params.keep_above_threshold: true when voxels above the threshold became pore. Older jobs hold invert_threshold instead, which means the opposite: true when voxels at or below the threshold became pore. |
progress_percentage |
A coarse 0–100 indicator. Don't use it to predict run time. |
error_message |
Why the job failed or was cancelled, in plain words. |
started_at, completed_at |
When the job was picked up to run and when it finished. Their difference is the wall-clock time, including queueing for hardware and worker start-up. |
selected_compute_tier |
The compute tier the job ran on, such as gpu-48gb-a40-a6000. For an Auto job this is where it actually ran. null for analyses without tiers. |
solver_converged |
For iterative solvers, whether every convergence criterion passed. null for analyses that don't iterate. |
solver_iterations_completed |
Iterations the solver ran. |
solver_convergence_telemetry |
Per-check convergence history, where the solver reports it. |
solver_version, worker_image_tag, worker_image_digest, worker_application_revision |
Provenance: exactly which solver build produced the result. Keep them with your results if you need to reproduce a number later. |
compute_hardware |
The GPU or CPU the job ran on, as reported when it ran, such as NVIDIA A40. null until then. |
solver_wall_time_seconds |
Always null. It's kept for compatibility. |