Skip to main content
The Python SDK wraps the REST API and adds:
  • a resumable chunked uploader for large WSIs,
  • typed dataclasses for requests and responses,
  • SSE streaming for live job status,
  • an optional AnnData round-trip for results.
See the SDK quickstart for the end-to-end developer path.

Install

Requires Python 3.12+. If your environment can’t reach PyPI, install from the GitHub mirror:

Configuration

Resource servers acting for a user can pass a short-lived Strand AI OAuth token instead: Client(access_token=token). An explicit access token takes precedence over API-key arguments and STRAND_API_KEY.

Full pipeline

client.predict(...) is callable: one blocking call runs upload, submit, wait, and download.
It returns a PredictResult with job_id, status, credits_used, marker_outputs (paths under output_dir), and results (a JobResults handle for selective reads). It raises JobFailedError if the job fails, JobTimeoutError if the deadline elapses, and surfaces InsufficientCreditsError when the org balance cannot reserve the run. Pass on_progress=lambda stage, frac: ... to follow the four stages ("upload", "submit", "wait", "download"). frac is always a float in [0.0, 1.0]: 0.0 at stage start, 1.0 at stage end, with intermediate values where available (such as upload byte progress).

Uploads

upload_file initiates a resumable session, streams 8 MiB chunks directly to GCS, then polls the upload resource until the authenticated OBJECT_FINALIZE event starts ingest. There is no finalize request. Dimensions can remain None while ingest preprocessing continues; call uploads.get(upload.id) to refresh them. Tune transfer chunks with chunk_size. list pages are newest-first and stable under inserts. Pass the response’s next_cursor back as cursor= to get the next page.

Skip re-uploads with content-hash dedup

Agentic and batch workflows often re-run on the same WSI. Set if_not_exists=True on upload_file to skip the byte upload when the platform already has the file:
The SDK streams a sha256 of the local file and posts it on the upload-init request. If a non-archived sample in your org already has that hash, the existing Upload is returned and no bytes are transferred. On a miss the upload proceeds normally and the hash is stored for next time. On modern hardware, hashing a 600 MB WSI takes one or two seconds. Leave the default (False) when the upload should run unconditionally.

Samples

client.samples.list() uses one cursor-paginated surface for owned and public samples. The default scope is "mine"; use "public" for the curated cohort or "all" for owned samples followed by public samples.
List items are a discriminated union. MineSampleSummary carries the owned filename, status, size, tags, and creation time. PublicSampleSummary carries the public title, thumbnail, tags, and curated metadata. Both expose the canonical .id; discriminate with .ownership. client.samples.get(id) follows the same identity contract:
Owned detail includes the 50 newest prediction jobs plus job_count, the total matching history. Public detail remains a download-capable PublicSample handle. Its bytes use the authenticated public OME-Zarr routes. Use one patch to rename an owned sample, replace its complete tag set, and/or set isotropic microns per pixel:
Omitted fields are not changed. name=None is distinct from omission. tags has set semantics: pass the complete desired set, not an add/remove delta. The user-reported mpp takes precedence over embedded slide metadata. See Set slide pixel size for the REST and R equivalents.

Cell segmentation

Cell segmentation runs per sample, not per prediction job, and costs no credits. Uploads segment automatically unless the organization or the upload opts out; segment starts segmentation on a sample that has none and retries a failed one.
segment is idempotent: completed or in-flight work is returned as-is, and a failed layer can be retried after the server’s 60-second cooldown. Public samples are read-only and cannot be segmented. When state.layer reports a materialized layer, read its artifacts:
The mask is a uint32 label OME-Zarr. features/instances.parquet carries instance_id, local_label, geometry, and morphology; per-marker Parquet files carry mean and median expression joined on instance_id. These are the only per-cell outputs. The AnnData returned by job.download_results() is a pixel-grid matrix of marker predictions, not per-cell expression.

Predict

Selecting a model

predict.submit and predict(...) accept an optional model=:
  • "v0.7": current dispatchable version and default.
Omit model to let the platform pick "v0.7". The older "v0.4" and "v0.5" labels can appear on historical jobs but are sunset for new submissions.
PredictResult.model and JobStatus.model always carry the canonical v0.X label the platform actually ran. Jobs from before the versioning rollout may surface as "v0.1", a sunset legacy label: readable on historical jobs, not dispatchable. The retired 2026 "v10*" ids were removed entirely and are rejected with a 400.

Submit without waiting

A full pipeline run blocks for 15+ minutes. To submit without waiting for results, pass wait=False. predict(...) returns a Job handle as soon as upload and submission complete:
Static typing follows the wait flag: wait=True returns PredictResult and wait=False returns Job, so IDE completions stay correct without a runtime check.

Jobs

Cancel is atomic: the job’s status flips to cancelled, the credit reservation is refunded, and any markers already written stay on the sample. Cancellation leaves the GPU worker running and ignores its remaining outputs. Calling cancel on a job that is already completed, failed, or cancelled raises BadRequestError. client.jobs.cancel(job_id) is a top-level shortcut for cancelling by id from another process.

Errors

All errors inherit from StrandError. Typed subclasses map HTTP codes 1:1 for specific except clauses:

Catch unknown markers

Both modes of predict.submit validate marker names against the platform’s panel before reserving credits or queueing a job. Unknown names surface as UnknownMarkerError:

Recover from a failed pipeline without re-uploading

If client.predict(...) raises after the upload step succeeded, the resulting upload_id is attached to the error so you can resume the job without paying to re-upload the WSI:
The same attribute appears on JobTimeoutError, InsufficientCreditsError, and other StrandError subclasses raised mid-pipeline.

Public surface

See also