- a resumable chunked uploader for large WSIs,
- typed dataclasses for requests and responses,
- SSE streaming for live job status,
- an optional
AnnDataround-trip for results.
Install
Configuration
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.
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. Setif_not_exists=True on upload_file to skip the byte upload when the
platform already has the file:
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.
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:
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:
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:
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.
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, passwait=False. predict(...) returns a Job handle as soon as
upload and submission complete:
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 fromStrandError. Typed subclasses map HTTP codes 1:1 for
specific except clauses:
Catch unknown markers
Both modes ofpredict.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
Ifclient.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:
JobTimeoutError, InsufficientCreditsError,
and other StrandError subclasses raised mid-pipeline.
Public surface
See also
- SDK quickstart: upload, price, submit, wait, and download.
- REST API overview: the underlying HTTP surface.
- Pricing: how credits map to slides × markers.