Skip to content

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.

POST /analysis-jobs/pendingqueuedprocessingcompletedfailedcancelled dispatchedworker startedresult storedsolver error orno convergenceran past 2×expected durationtimed outtimed out
A job's statuses. Job states says what each one means and who is holding a job that waits.

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, 200 with Idempotency-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.