| Title: | Client for the Strand AI Platform API |
|---|---|
| Description: | R client for the Strand AI Platform 'REST' API. Provides functions to upload Whole Slide Images via 'GCS' resumable upload, submit 'Lattice' H&E to multiplex protein inference jobs, and download 'OME-Zarr' predictions as 'SpatialExperiment' objects from Bioconductor. Designed for spatial-omics research workflows in pharma and academic labs. |
| Authors: | Strand AI [aut, cre, cph] |
| Maintainer: | Strand AI <[email protected]> |
| License: | Apache License (>= 2) |
| Version: | 0.10.0 |
| Built: | 2026-09-20 06:48:55 UTC |
| Source: | https://github.com/Strand-AI/strand-sdk-r |
stats::predict genericDispatches predict(client, image_path, markers, ...) to strand_run(),
so the standard R generic works on a strand_client. See strand_run()
for the full argument list and return shape — model = and wait = are
both forwarded.
## S3 method for class 'strand_client' predict(object, image_path, markers, ...)## S3 method for class 'strand_client' predict(object, image_path, markers, ...)
object |
A |
image_path |
Path to a local WSI file. |
markers |
Character vector of markers to predict. |
... |
Additional arguments passed to |
A strand_predict_result list when wait = TRUE, or a
strand_job handle when wait = FALSE; see strand_run().
Constructs a client object that bundles authentication, base URL, and
a shared httr2 request template. The returned list is passed to every
other function in this package.
strand_client(api_key = NULL, base_url = NULL, timeout = 60, user_agent = NULL)strand_client(api_key = NULL, base_url = NULL, timeout = 60, user_agent = NULL)
api_key |
Bearer API key ( |
base_url |
Platform base URL (without |
timeout |
Per-request timeout in seconds. |
user_agent |
Override the default User-Agent string. |
Default base URL is https://app.strandai.com. The /api/v1/ path is
appended automatically; pass the root site URL, not the API root.
An object of class strand_client: a list with api_key,
base_url, api_root, timeout, and user_agent.
## Not run: client <- strand_client() # reads STRAND_API_KEY client <- strand_client(api_key = "sk-strand-...", base_url = "http://localhost:3000") ## End(Not run)## Not run: client <- strand_client() # reads STRAND_API_KEY client <- strand_client(api_key = "sk-strand-...", base_url = "http://localhost:3000") ## End(Not run)
Walks the OME-Zarr result store via the API-key-authenticated proxy at
/api/v1/jobs/{id}/results/files/{path}, assembles the per-marker arrays
into one matrix (rows = markers, columns = pixels), and returns a
SpatialExperiment::SpatialExperiment() object.
strand_download_results(job, path = NULL, level = 0L, markers = NULL)strand_download_results(job, path = NULL, level = 0L, markers = NULL)
job |
A |
path |
Optional output directory. When set, downloads the store to
disk and returns the directory path; no |
level |
Pyramid level to read; |
markers |
Optional character vector restricting which multiscales to
load. Defaults to every multiscale except |
For large slides this materializes a dense n_markers x (H * W) matrix in
memory; pass path to mirror the zarr store to disk instead and read
selectively with Rarr.
A SpatialExperiment with assay = "expression" and pixel
coordinates in spatialCoords(). When path is supplied, the directory
path is returned invisibly.
## Not run: job <- strand_predict(client, upload$id, c("CD3", "CD8")) strand_job_wait(job) spe <- strand_download_results(job) ## End(Not run)## Not run: job <- strand_predict(client, upload$id, c("CD3", "CD8")) strand_job_wait(job) spe <- strand_download_results(job) ## End(Not run)
Download a generated result export
strand_export_download( job, format, path, include_segmentation = FALSE, timeout = Inf, poll_interval = 2 )strand_export_download( job, format, path, include_segmentation = FALSE, timeout = Inf, poll_interval = 2 )
job |
A |
format |
One of |
path |
Destination file. |
include_segmentation |
Attach the latest segmentation artifact manifest. |
timeout |
Maximum seconds to wait; |
poll_interval |
Seconds between status requests. |
Normalized destination path, invisibly.
Get format-driven result export status
strand_export_get(job, format, include_he = NULL, include_segmentation = FALSE)strand_export_get(job, format, include_he = NULL, include_segmentation = FALSE)
job |
A |
format |
One of |
include_he |
Include source H&E. OME-TIFF always includes H&E. |
include_segmentation |
Attach the latest segmentation artifact manifest. |
A refreshed result export manifest.
Native OME-Zarr is returned without conversion. OME-Zarr ZIP and OME-TIFF are asynchronous, cached exports. The response manifest explicitly records H&E and segmentation/cell-expression contents.
strand_export_request( job, format, include_he = NULL, include_segmentation = FALSE )strand_export_request( job, format, include_he = NULL, include_segmentation = FALSE )
job |
A |
format |
One of |
include_he |
Include source H&E. OME-TIFF always includes H&E. |
include_segmentation |
Attach the latest segmentation artifact manifest. |
A result export manifest.
Calls POST /api/v1/jobs/{id}/cancel, then fetches and returns the updated
job status. Cancellation refunds the reserved credits. The server returns
400 if the job is already terminal.
strand_job_cancel(job)strand_job_cancel(job)
job |
A |
The updated job status list with status = "cancelled".
## Not run: job <- strand_predict(client, upload$id, c("CD3", "CD8")) status <- strand_job_cancel(job) ## End(Not run)## Not run: job <- strand_predict(client, upload$id, c("CD3", "CD8")) status <- strand_job_cancel(job) ## End(Not run)
Fetch the latest status snapshot for a job
strand_job_get(client, job_id)strand_job_get(client, job_id)
client |
A |
job_id |
Job identifier (or a |
A list with the job's current state.
Polls GET /api/v1/jobs/{id} on a fixed interval and returns the terminal
status. (The platform also exposes an SSE stream at /jobs/{id}/stream; the
R SDK uses polling for compatibility — there is no widely-used SSE client in
base R, and httr2 streaming is awkward to integrate with R CMD check.)
strand_job_wait(job, timeout = Inf, poll_interval = 2, progress = FALSE)strand_job_wait(job, timeout = Inf, poll_interval = 2, progress = FALSE)
job |
A |
timeout |
Max seconds to wait. |
poll_interval |
Seconds between status polls. |
progress |
If |
The terminal job status list. status = "completed" means every
requested marker delivered; "partial_failed" means some markers
delivered (results are downloadable) and the rest terminally failed after
server-side retries — it returns normally rather than erroring, so check
$status. Only a "failed" terminal status (nothing delivered) raises a
strand_job_failed_error.
List the markers your account can request, scoped to your entitlement: a
self-signup account sees the public panel; a full-panel account sees the
whole vocabulary. Credit-free. The names returned are exactly what
strand_predict() accepts in both submit and dry-run modes.
strand_markers_list(client)strand_markers_list(client)
client |
A |
A named list with full_panel (logical; whether the account holds
the full-panel entitlement), markers (a character vector of marker
names), and public_markers (the subset that is part of the public panel).
## Not run: client <- strand_client() catalog <- strand_markers_list(client) catalog$markers ## End(Not run)## Not run: client <- strand_client() catalog <- strand_markers_list(client) catalog$markers ## End(Not run)
Updates any combination of display name, complete user-editable tag set,
and physical pixel size in one request. NULL arguments are omitted.
Use clear_name = TRUE to send JSON null and restore the filename as the
display name. Passing tags = character(0) clears all user-editable tags.
strand_patch_sample( client, sample_id, name = NULL, tags = NULL, mpp = NULL, clear_name = FALSE )strand_patch_sample( client, sample_id, name = NULL, tags = NULL, mpp = NULL, clear_name = FALSE )
client |
A |
sample_id |
UUID of an owned sample. |
name |
New display name, or |
tags |
Character vector containing the complete desired tag set,
|
mpp |
Isotropic microns per pixel, greater than 0 and at most 100, or
|
clear_name |
Send JSON |
The refreshed owned sample record, including jobs and job_count;
see strand_samples_get().
## Not run: strand_patch_sample(client, sample_id, name = "Baseline biopsy") strand_patch_sample(client, sample_id, tags = c("baseline", "responding")) strand_patch_sample(client, sample_id, tags = character(0), mpp = 0.26) strand_patch_sample(client, sample_id, clear_name = TRUE) ## End(Not run)## Not run: strand_patch_sample(client, sample_id, name = "Baseline biopsy") strand_patch_sample(client, sample_id, tags = c("baseline", "responding")) strand_patch_sample(client, sample_id, tags = character(0), mpp = 0.26) strand_patch_sample(client, sample_id, clear_name = TRUE) ## End(Not run)
Calls POST /api/v1/predict. The default mode atomically reserves credits
and creates a job. With dry_run = TRUE, the same marker, model, and sample
validation runs without reserving credits or creating a job.
strand_predict(client, upload_id, markers, model = NULL, dry_run = FALSE)strand_predict(client, upload_id, markers, model = NULL, dry_run = FALSE)
client |
A |
upload_id |
Upload identifier (from |
markers |
Character vector of marker names (e.g. |
model |
Optional explicit Lattice version. |
dry_run |
When |
With dry_run = FALSE, a strand_job list with id,
reserved_credits, and client. With dry_run = TRUE, a list with
dry_run = TRUE, patch_count, marker_count, estimated_credits,
org_balance, and org_pending.
## Not run: estimate <- strand_predict( client, upload$id, c("CD3", "CD8"), dry_run = TRUE ) message("approximately ", estimate$estimated_credits, " credits") job <- strand_predict(client, upload$id, c("CD3", "CD8")) strand_job_wait(job) job <- strand_predict(client, upload$id, c("CD3", "CD8"), model = "v0.7") ## End(Not run)## Not run: estimate <- strand_predict( client, upload$id, c("CD3", "CD8"), dry_run = TRUE ) message("approximately ", estimate$estimated_credits, " credits") job <- strand_predict(client, upload$id, c("CD3", "CD8")) strand_job_wait(job) job <- strand_predict(client, upload$id, c("CD3", "CD8"), model = "v0.7") ## End(Not run)
Available within the 7-day Trash window. Brings the sample back to the
active list and extends its expiration so it isn't immediately
re-trashed. Caller must have the same permissions required for
strand_set_expiration().
strand_restore(client, sample_id)strand_restore(client, sample_id)
client |
A |
sample_id |
UUID of the sample. |
The restored sample payload (id, archivedAt, expiresAt).
## Not run: client <- strand_client() strand_restore(client, sample_id) ## End(Not run)## Not run: client <- strand_client() strand_restore(client, sample_id) ## End(Not run)
[C, H, W] numeric arrayDownload a single multiscale as a raw [C, H, W] numeric array
strand_results_to_array(job, name = NULL, level = 0L, root_meta = NULL)strand_results_to_array(job, name = NULL, level = 0L, root_meta = NULL)
job |
A |
name |
Name of the multiscale to read (e.g. |
level |
Pyramid level to read; |
root_meta |
Optional pre-fetched root group metadata (internal use). |
A list with array (3-D [C, H, W]) and meta (parsed zarr.json).
SpatialExperiment from a [C, H, W] arrayBuild a SpatialExperiment from a [C, H, W] array
strand_results_to_spatial_experiment(array, markers, base_path = NA_character_)strand_results_to_spatial_experiment(array, markers, base_path = NA_character_)
array |
3-D array |
markers |
Character vector of marker / channel names; length equals C. |
base_path |
Optional GCS path; stored in |
A SpatialExperiment with assay = "expression" and pixel coords
in spatialCoords.
Orchestrates upload → submit → (optional) wait → (optional) download. With
wait = TRUE (default) returns when the job reaches a terminal state. With
wait = FALSE, returns the strand_job handle as soon as upload + submit
complete so the caller can drive strand_job_wait() /
strand_download_results() later. All sub-operations use the same exported
primitives (strand_upload_file(), strand_predict(), strand_job_wait(),
strand_download_results()) so callers can drop down a level whenever they
need finer control.
strand_run( client, image_path, markers, model = NULL, wait = TRUE, timeout_sec = 1800, output_dir = NULL, poll_interval_sec = 5, on_progress = NULL, auto_segment = NULL )strand_run( client, image_path, markers, model = NULL, wait = TRUE, timeout_sec = 1800, output_dir = NULL, poll_interval_sec = 5, on_progress = NULL, auto_segment = NULL )
client |
A |
image_path |
Path to a local WSI file (SVS / TIFF / NDPI / ...). |
markers |
Character vector of markers to predict (e.g.
|
model |
Optional explicit model id; see |
wait |
When |
timeout_sec |
Max seconds to wait for the job to finish; defaults to 30 minutes. |
output_dir |
Optional output directory. When provided, the entire OME-Zarr result store is mirrored under this directory. |
poll_interval_sec |
Seconds between status polls while waiting. |
on_progress |
Optional callback |
auto_segment |
Opt out of automatic cell segmentation for the uploaded
slide. |
When wait = TRUE, a list with class strand_predict_result
containing: job_id, status, credits_used, marker_outputs (named
list mapping marker name → on-disk directory when output_dir is
provided, else an empty list), output_dir, and job (the underlying
strand_job handle). When wait = FALSE, the strand_job handle
directly (same shape as strand_predict()).
## Not run: client <- strand_client() result <- strand_run( client, "biopsy.ome.tiff", markers = c("HER2", "CD8", "PD1"), output_dir = "./outputs/" ) cat("used", result$credits_used, "credits\n") # Choose a version explicitly: result <- strand_run(client, "biopsy.ome.tiff", c("CD8", "Ki67"), model = "v0.7") # Fire-and-forget — upload + submit only, then drive the job later: job <- strand_run(client, "biopsy.ome.tiff", c("CD8"), wait = FALSE) status <- strand_job_wait(job) spe <- strand_download_results(job) # The S3 generic also works: predict(client, ...) result <- predict(client, "biopsy.ome.tiff", markers = c("HER2")) ## End(Not run)## Not run: client <- strand_client() result <- strand_run( client, "biopsy.ome.tiff", markers = c("HER2", "CD8", "PD1"), output_dir = "./outputs/" ) cat("used", result$credits_used, "credits\n") # Choose a version explicitly: result <- strand_run(client, "biopsy.ome.tiff", c("CD8", "Ki67"), model = "v0.7") # Fire-and-forget — upload + submit only, then drive the job later: job <- strand_run(client, "biopsy.ome.tiff", c("CD8"), wait = FALSE) status <- strand_job_wait(job) spe <- strand_download_results(job) # The S3 generic also works: predict(client, ...) result <- predict(client, "biopsy.ome.tiff", markers = c("HER2")) ## End(Not run)
Fetches either an owned sample by sample id or a public sample by share id.
The mandatory ownership field identifies the returned branch. Owned
samples include up to the 50 newest inference jobs and the uncapped
job_count; public samples never expose job history.
strand_samples_get(client, sample_id)strand_samples_get(client, sample_id)
client |
A |
sample_id |
Owned sample UUID or public share UUID. |
For an owned sample, a named list with ownership = "mine", sample
identity and lifecycle fields, jobs, and job_count. Each job contains
id, status, progress, reserved_credits, markers, timestamps,
error_message, and results_available. For a public sample, a named list
with ownership = "public", canonical share id, title, tags, public
metadata, geometry, markers, thumbnail_url, and pyramid_url.
## Not run: client <- strand_client() sample <- strand_samples_get(client, sample_id) if (sample$ownership == "mine") { message(sample$job_count, " inference jobs") } else { print(sample$markers) } ## End(Not run)## Not run: client <- strand_client() sample <- strand_samples_get(client, sample_id) if (sample$ownership == "mine") { message(sample$job_count, " inference jobs") } else { print(sample$markers) } ## End(Not run)
Lists owned samples, Strand's public cohort, or both through one
cursor-paginated collection. Public and owned records use distinct shapes;
inspect each item's mandatory ownership field before using branch fields.
strand_samples_list( client, scope = "mine", limit = 48L, cursor = NULL, tag = NULL )strand_samples_list( client, scope = "mine", limit = 48L, cursor = NULL, tag = NULL )
client |
A |
scope |
Exactly one of |
limit |
Items per page, from 1 through 100. |
cursor |
Opaque cursor returned as |
tag |
Optional exact tag filter. |
A named list with items and next_cursor. Every item has
ownership and canonical id. Owned items also have name, filename,
status, numeric file_size, tags, and created_at. Public items also
have title, thumbnail_url, tags, and public metadata.
## Not run: client <- strand_client() page <- strand_samples_list(client, scope = "public", tag = "tcga-coad") ids <- vapply(page$items, function(sample) sample$id, character(1)) ## End(Not run)## Not run: client <- strand_client() page <- strand_samples_list(client, scope = "public", tag = "tcga-coad") ids <- vapply(page$items, function(sample) sample$id, character(1)) ## End(Not run)
Get cell-segmentation state
strand_segmentation_get(client, sample_id)strand_segmentation_get(client, sample_id)
client |
A |
sample_id |
An owned sample UUID. Public sample handles are read-only. |
Lifecycle state with latest job/layer metadata.
Get segmentation artifact manifest
strand_segmentation_manifest(client, sample_id, layer_id)strand_segmentation_manifest(client, sample_id, layer_id)
client |
A |
sample_id |
An owned sample UUID. Public sample handles are read-only. |
layer_id |
Segmentation layer UUID from the lifecycle response. |
Explicit mask, geometry/morphology, marker-expression, and provenance schema.
Free and idempotent: completed or active work is reused, failed work can be retried after the documented cooldown, and no credits are reserved.
strand_segmentation_start(client, sample_id)strand_segmentation_start(client, sample_id)
client |
A |
sample_id |
An owned sample UUID. Public sample handles are read-only. |
Lifecycle state and polling URL.
Pin a sample to a specific expiration date, set it to never expire, or clear any custom expiration and follow the org's default policy.
strand_set_expiration( client, sample_id, expires_at = NULL, never_expire = FALSE, use_org_default = FALSE, reason = NULL )strand_set_expiration( client, sample_id, expires_at = NULL, never_expire = FALSE, use_org_default = FALSE, reason = NULL )
client |
A |
sample_id |
UUID of the sample. |
expires_at |
A |
never_expire |
If |
use_org_default |
If |
reason |
Optional governance reason (10-500 chars). |
The updated sample payload (id, expiresAt, expiresAtSource, ...).
## Not run: client <- strand_client() strand_set_expiration(client, sample_id, never_expire = TRUE, reason = "Active research project — keep until publication") ## End(Not run)## Not run: client <- strand_client() strand_set_expiration(client, sample_id, never_expire = TRUE, reason = "Active research project — keep until publication") ## End(Not run)
All-or-nothing: if any sample fails the permission gate (caller is not the sample creator, an org owner/admin, or a Strand AI admin), the whole call returns a 403 with no rows touched.
strand_set_expiration_bulk( client, sample_ids, expires_at = NULL, never_expire = FALSE, use_org_default = FALSE, reason = NULL )strand_set_expiration_bulk( client, sample_ids, expires_at = NULL, never_expire = FALSE, use_org_default = FALSE, reason = NULL )
client |
A |
sample_ids |
Character vector of sample UUIDs (length 1-500). |
expires_at |
A |
never_expire |
If |
use_org_default |
If |
reason |
Optional governance reason (10-500 chars). |
A list with updated (integer count) and batchId (UUID).
## Not run: client <- strand_client() strand_set_expiration_bulk(client, c(uuid1, uuid2, uuid3), never_expire = TRUE) ## End(Not run)## Not run: client <- strand_client() strand_set_expiration_bulk(client, c(uuid1, uuid2, uuid3), never_expire = TRUE) ## End(Not run)
Performs the event-driven resumable upload flow:
POST /api/v1/uploads — create a resumable session
PUT <uploadUrl> — stream the file to GCS in 8 MiB chunks
Poll GET /api/v1/uploads/{id} until GCS OBJECT_FINALIZE starts ingest
strand_upload_file( client, path, content_type = NULL, chunk_size = 8L * 1024L * 1024L, progress = FALSE, auto_segment = NULL, mpp = NULL )strand_upload_file( client, path, content_type = NULL, chunk_size = 8L * 1024L * 1024L, progress = FALSE, auto_segment = NULL, mpp = NULL )
client |
A |
path |
Path to a local file (SVS / TIFF / NDPI / ...). |
content_type |
Override the auto-detected MIME type. |
chunk_size |
Bytes per chunk. Must be a positive multiple of 256 KiB (the GCS resumable upload requirement). Defaults to 8 MiB. |
progress |
If |
auto_segment |
Opt out of automatic cell segmentation for this upload.
|
mpp |
Optional user-reported microns per pixel, persisted when the upload is created. Must be greater than 0 and at most 100. This creation-time value takes precedence over embedded slide calibration. |
A list with class strand_upload containing id, gcs_path,
upload_url, width_px, height_px, status.
## Not run: client <- strand_client() upload <- strand_upload_file(client, "slide.svs", progress = TRUE, mpp = 0.26) ## End(Not run)## Not run: client <- strand_client() upload <- strand_upload_file(client, "slide.svs", progress = TRUE, mpp = 0.26) ## End(Not run)
Fetch a single upload by id
strand_uploads_get(client, upload_id)strand_uploads_get(client, upload_id)
client |
A |
upload_id |
Upload identifier (UUID). |
A strand_upload list as documented in strand_uploads_list().
Raises strand_not_found_error if no upload with that id exists for the
calling org.
## Not run: u <- strand_uploads_get(client, "11111111-1111-1111-1111-111111111111") ## End(Not run)## Not run: u <- strand_uploads_get(client, "11111111-1111-1111-1111-111111111111") ## End(Not run)
Fetches one page of uploads, newest-first. Filters out archived uploads;
use strand_restore() to bring archived rows back into the active set.
strand_uploads_list(client, limit = 100L, cursor = NULL)strand_uploads_list(client, limit = 100L, cursor = NULL)
client |
A |
limit |
Page size (1-200). Defaults to 100. |
cursor |
Opaque cursor from a prior response's |
A list with class strand_upload_list containing:
uploads: list of strand_upload entries (each carries id,
filename, file_size, status, gcs_path, width_px, height_px,
created_at).
next_cursor: cursor for the next page, or NULL on the last page.
## Not run: page <- strand_uploads_list(client, limit = 20) for (u in page$uploads) message(u$id, " ", u$filename) ## End(Not run)## Not run: page <- strand_uploads_list(client, limit = 20) for (u in page$uploads) message(u$id, " ", u$filename) ## End(Not run)