{"openapi":"3.1.0","info":{"title":"Strand AI Platform API","version":"v1","description":"REST surface for uploading WSIs, submitting Lattice inference jobs, and streaming results. All endpoints require an API key or Strand AI OAuth access token in the `Authorization: Bearer` header."},"servers":[{"url":"/api/v1","description":"Current host"}],"security":[{"ApiKey":[]},{"OAuth2":[]}],"components":{"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","bearerFormat":"sk-strand-XXXXXXXXXXXXXXXXXXXXXXXX"},"OAuth2":{"type":"oauth2","description":"Strand AI authorization-code flow with S256 PKCE, explicit organization consent, and RFC 8707 resource binding.","flows":{"authorizationCode":{"authorizationUrl":"https://auth.strandai.com/oauth2/auth","tokenUrl":"https://auth.strandai.com/oauth2/token","scopes":{"samples:read":"Read slide metadata and estimates","samples:write":"Upload and change slides","jobs:read":"Read run status","jobs:write":"Start, export, or cancel runs","results:read":"Read and download results","credits:spend":"Reserve and spend organization credits"}}}}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string"},"message":{"type":"string"},"required":{"type":"integer","nullable":true},"retryAfterSeconds":{"type":"integer","nullable":true}}},"UploadCreated":{"type":"object","required":["uploadId","gcsPath","existing"],"properties":{"uploadId":{"type":"string","format":"uuid"},"uploadUrl":{"type":"string","description":"Resumable upload URL. Present for a new upload; omitted when `existing` is true."},"gcsPath":{"type":"string"},"existing":{"type":"boolean","description":"True when `contentSha256` matched an existing active upload in the same organization."}}},"Upload":{"type":"object","required":["id","filename","fileSize","status","gcsPath"],"properties":{"id":{"type":"string","format":"uuid"},"filename":{"type":"string"},"fileSize":{"type":"string","description":"bytes, stringified bigint"},"status":{"type":"string","description":"uploading | upload_failed | deid_running | deid_failed | preprocessing | ready | preprocess_failed"},"gcsPath":{"type":"string"},"createdAt":{"type":"string","format":"date-time","nullable":true},"widthPx":{"type":"integer","nullable":true},"heightPx":{"type":"integer","nullable":true},"autoSegment":{"type":"boolean","description":"Resolved auto-segmentation decision for this upload (per-upload override, else org default, else true)."}}},"UploadList":{"type":"object","required":["uploads"],"properties":{"uploads":{"type":"array","items":{"$ref":"#/components/schemas/Upload"}},"nextCursor":{"type":"string","nullable":true,"description":"Opaque cursor for the next page, or null on last page"}}},"Estimate":{"type":"object","required":["dryRun","patchCount","markerCount","estimatedCredits","orgBalance","orgPending"],"properties":{"dryRun":{"type":"boolean","enum":[true]},"patchCount":{"type":"integer"},"markerCount":{"type":"integer"},"estimatedCredits":{"type":"integer"},"orgBalance":{"type":"integer"},"orgPending":{"type":"integer"}}},"Submission":{"type":"object","required":["jobId","reservedCredits","status"],"properties":{"jobId":{"type":"string","format":"uuid"},"reservedCredits":{"type":"integer"},"status":{"type":"string","enum":["queued"]}}},"SampleFieldList":{"type":"object","additionalProperties":false,"required":["count","fields"],"properties":{"count":{"type":"integer","description":"Number of definitions in `fields`."},"fields":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["id","key","label","valueType","origin","storage","writable","options"],"properties":{"id":{"type":"string","format":"uuid","description":"Stable definition id. This is the `DEFINITION_UUID` in a `field` filter on GET /samples."},"key":{"type":"string","description":"Stable lowercase key, unique within the organization."},"label":{"type":"string","description":"Human-readable name; not unique."},"valueType":{"type":"string","enum":["text","number","date","checkbox","single_select","multi_select","open_multi_select"],"description":"The field's declared value type, which decides the operators a `field` filter may use."},"origin":{"type":"string","enum":["system","custom"]},"storage":{"type":"string","enum":["value_table","core_column"]},"writable":{"type":"boolean","description":"Whether the generic field writer may set this field."},"options":{"type":["array","null"],"items":{"type":"string"},"description":"Allowed values for the select types; null otherwise."}}}}}},"MarkerList":{"type":"object","required":["fullPanel","count","markers"],"properties":{"fullPanel":{"type":"boolean","description":"Whether this account is entitled to the full marker panel. `false` = the public panel only."},"count":{"type":"integer","description":"Number of markers in `markers`."},"markers":{"type":"array","items":{"type":"object","required":["name","publicPanel"],"properties":{"name":{"type":"string","description":"Marker name to pass to predict."},"publicPanel":{"type":"boolean","description":"Whether this marker is part of the public panel."}}}}}},"SampleJob":{"type":"object","additionalProperties":false,"required":["id","status","progress","reservedCredits","markers","createdAt","startedAt","completedAt","errorMessage","resultsAvailable"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","description":"Lifecycle: `queued` → `dispatching` → `preprocessing` → `running` → `postprocessing` → terminal. Terminal values: `completed`, `partial_failed`, `failed`, and `cancelled`."},"progress":{"type":"number","nullable":true},"reservedCredits":{"type":"integer","nullable":true},"markers":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time","nullable":true},"startedAt":{"type":"string","format":"date-time","nullable":true},"completedAt":{"type":"string","format":"date-time","nullable":true},"errorMessage":{"type":"string","nullable":true},"resultsAvailable":{"type":"boolean"}}},"Results":{"type":"object","required":["resultUrl","expiresAt"],"properties":{"resultUrl":{"type":"string"},"resultBasePath":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}},"OmeTiffExport":{"type":"object","required":["status","format","sizeBytes","updatedAt"],"properties":{"status":{"type":"string","enum":["pending","running","ready","failed"]},"format":{"type":"string","enum":["ome-tiff"]},"sizeBytes":{"type":"integer","nullable":true},"downloadUrl":{"type":"string","format":"uri","description":"One-hour signed download URL. Present only when status is `ready`."},"downloadUrlExpiresAt":{"type":"string","format":"date-time","description":"Signed URL expiry. Present only when status is `ready`."},"error":{"type":"string","description":"Export failure message. Present only when status is `failed`."},"updatedAt":{"type":"string","format":"date-time","nullable":true}}},"ResultArchiveExport":{"type":"object","required":["status","format","sizeBytes","updatedAt"],"properties":{"status":{"type":"string","enum":["pending","running","ready","failed"]},"format":{"type":"string","enum":["ome-zarr-zip"]},"sizeBytes":{"type":"integer","nullable":true},"downloadUrl":{"type":"string","format":"uri","description":"One-hour signed download URL. Present only when status is `ready`."},"downloadUrlExpiresAt":{"type":"string","format":"date-time","description":"Signed URL expiry. Present only when status is `ready`."},"error":{"type":"string","description":"Present only when status is `failed`."},"updatedAt":{"type":"string","format":"date-time","nullable":true}}},"SegmentationState":{"type":"object","additionalProperties":false,"required":["status","retryable","creditCost","job","layer"],"properties":{"status":{"type":"string","enum":["not_started","queued","running","postprocessing","completed","failed","cancelled"]},"retryable":{"type":"boolean"},"creditCost":{"type":"integer","enum":[0],"description":"Segmentation is credit-free."},"job":{"type":"object","nullable":true,"additionalProperties":true},"layer":{"type":"object","nullable":true,"additionalProperties":true}}},"SegmentationManifest":{"type":"object","description":"Explicit per-cell contract. Mask pixels are uint32 local labels; morphology and marker Parquet tables join by instance_id. This is distinct from pixel-grid prediction matrices.","required":["schemaVersion","layerId","entityType","modelVersion","coordinateFrame","instanceCount","createdAt","artifacts"],"properties":{"schemaVersion":{"type":"string","enum":["1.0"]},"layerId":{"type":"string","format":"uuid"},"entityType":{"type":"string"},"modelVersion":{"type":"string"},"coordinateFrame":{"type":"string"},"instanceCount":{"type":"integer","nullable":true},"createdAt":{"type":"string","format":"date-time"},"artifacts":{"type":"object","additionalProperties":false,"required":["mask","cells","provenance"],"properties":{"mask":{"type":"object","nullable":true,"additionalProperties":false,"required":["format","dtype","baseUrl"],"properties":{"format":{"type":"string","enum":["ome-zarr-v3"]},"dtype":{"type":"string","enum":["uint32"]},"baseUrl":{"type":"string"}}},"cells":{"type":"object","nullable":true,"additionalProperties":false,"required":["format","morphologyUrl","markerExpressionBaseUrl","markers","joinKey","maskJoinKey","geometry","morphology","expression"],"properties":{"format":{"type":"string","enum":["parquet"]},"morphologyUrl":{"type":"string"},"markerExpressionBaseUrl":{"type":"string"},"markers":{"type":"array","items":{"type":"string"}},"joinKey":{"type":"string","enum":["instance_id"]},"maskJoinKey":{"type":"string","enum":["local_label"]},"geometry":{"type":"array","items":{"type":"string"},"description":"Per-cell centroid columns in the declared coordinate frame."},"morphology":{"type":"array","items":{"type":"string"},"description":"Per-cell shape measurements in instances.parquet."},"expression":{"type":"array","items":{"type":"string"},"description":"Per-cell marker summary columns, distinct from pixel-grid arrays."}}},"provenance":{"type":"object","nullable":true,"additionalProperties":false,"required":["format","url"],"properties":{"format":{"type":"string","enum":["json"]},"url":{"type":"string"}}}}},"downloads":{"type":"object","nullable":true,"additionalProperties":false,"description":"Short-lived, generation-pinned bearer links for the exact provenance and cell Parquet objects. Present only in result-export responses after organization authorization; absent from the standalone segmentation manifest.","required":["expiresAt","provenance","morphology","markers"],"properties":{"expiresAt":{"type":"string","format":"date-time"},"provenance":{"type":"object","additionalProperties":false,"required":["downloadUrl","sizeBytes","contentType"],"properties":{"downloadUrl":{"type":"string","format":"uri"},"sizeBytes":{"type":"integer","minimum":0},"contentType":{"type":"string"}}},"morphology":{"type":"object","additionalProperties":false,"required":["downloadUrl","sizeBytes","contentType"],"properties":{"downloadUrl":{"type":"string","format":"uri"},"sizeBytes":{"type":"integer","minimum":0},"contentType":{"type":"string"}}},"markers":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["marker","downloadUrl","sizeBytes","contentType"],"properties":{"marker":{"type":"string"},"downloadUrl":{"type":"string","format":"uri"},"sizeBytes":{"type":"integer","minimum":0},"contentType":{"type":"string"}}}}}}}},"ResultExport":{"type":"object","required":["schemaVersion","status","format","selection","expiresAt","retryable","error","artifacts"],"properties":{"schemaVersion":{"type":"string","enum":["1.0"]},"status":{"type":"string","enum":["pending","running","ready","failed"]},"format":{"type":"string","enum":["ome-zarr","ome-zarr-zip","ome-tiff"]},"selection":{"type":"object","additionalProperties":false,"properties":{"includeHe":{"type":"boolean"},"includeSegmentation":{"type":"boolean"},"includeOriginalUpload":{"type":"boolean"}}},"expiresAt":{"type":"string","format":"date-time","nullable":true},"retryable":{"type":"boolean"},"error":{"type":"string","nullable":true},"artifacts":{"type":"object","additionalProperties":false,"required":["prediction","he","segmentation","originalUpload"],"properties":{"prediction":{"type":"object","nullable":true,"additionalProperties":false,"required":["format","role","dataModel","stored","converted"],"properties":{"format":{"type":"string","enum":["ome-zarr-v3","ome-zarr-zip","ome-tiff"]},"role":{"type":"string","enum":["predicted-mif"]},"dataModel":{"type":"string","enum":["pixel-grid"]},"stored":{"type":"boolean"},"converted":{"type":"boolean"},"includesHe":{"type":"boolean"},"rootUrl":{"type":"string","nullable":true},"baseUrl":{"type":"string"},"downloadUrl":{"type":"string","nullable":true},"sizeBytes":{"type":"integer","nullable":true}}},"he":{"type":"object","nullable":true,"additionalProperties":false,"required":["format","role","rootUrl","baseUrl"],"properties":{"format":{"type":"string","enum":["ome-zarr-v3"]},"role":{"type":"string","enum":["processed-registered-he"]},"rootUrl":{"type":"string"},"baseUrl":{"type":"string"}}},"segmentation":{"allOf":[{"$ref":"#/components/schemas/SegmentationManifest"}],"nullable":true},"originalUpload":{"type":"object","nullable":true,"additionalProperties":false,"required":["status","role","filename","contentType","sizeBytes","expiresAt","downloadUrl","storedCopy","warning"],"properties":{"status":{"type":"string","enum":["ready","disabled","quarantined","not_ready","expired","deleted","unavailable"]},"role":{"type":"string","enum":["original-upload"]},"filename":{"type":"string"},"contentType":{"type":"string","nullable":true},"sizeBytes":{"type":"integer","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true},"downloadUrl":{"type":"string","nullable":true},"storedCopy":{"type":"string","enum":["uploaded","deidentified","unknown"]},"warning":{"type":"string","description":"Bearer-link warning: anyone with the URL can download the potentially multi-GB file until expiry."}}}}}}},"ExpirationUpdate":{"type":"object","description":"Provide exactly one of `expiresAt`, `neverExpire: true`, or `useOrgDefault: true`. `neverExpire: true` is sugar for `expiresAt: null` (custom). `useOrgDefault: true` clears the custom expiration and follows the org's default policy.","properties":{"expiresAt":{"type":"string","format":"date-time","nullable":true},"neverExpire":{"type":"boolean"},"useOrgDefault":{"type":"boolean"},"reason":{"type":"string","maxLength":500}}},"SampleFields":{"type":"object","additionalProperties":true,"description":"Unified field values keyed by stable definition key. Tags is a string array at `tags`; Public is a boolean at `public`. Values are validated against the organization's definitions."},"SamplePatch":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"name":{"type":"string","nullable":true,"maxLength":200,"pattern":"^[^\\u0000]*$","description":"Trimmed display name. Null or an empty string reverts to filename. NUL characters are rejected."},"fields":{"allOf":[{"$ref":"#/components/schemas/SampleFields"}],"minProperties":1,"description":"Complete values for the named fields, not deltas. Null clears a field. `fields.tags` is the complete desired tag set; `fields.public` is read-only and returns 403."},"mpp":{"oneOf":[{"type":"number","exclusiveMinimum":0,"maximum":100},{"type":"object","additionalProperties":false,"required":["x","y"],"properties":{"x":{"type":"number","exclusiveMinimum":0,"maximum":100},"y":{"type":"number","exclusiveMinimum":0,"maximum":100}}}],"description":"User-reported microns per pixel. Object axes must be equal; anisotropic input is rejected."}}},"Sample":{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"uuid"},"expiresAt":{"type":"string","format":"date-time","nullable":true},"expiresAtSource":{"type":"string","nullable":true,"enum":["org_default","custom",null]},"expirationChangedAt":{"type":"string","format":"date-time","nullable":true},"expirationChangedBy":{"type":"string","nullable":true},"batchId":{"type":"string","format":"uuid"}}},"OwnedSampleDetail":{"type":"object","additionalProperties":false,"description":"An owned sample's curated read model and its 50 newest inference jobs. `jobCount` is the uncapped total.","required":["ownership","id","appUrl","name","filename","status","fileSize","widthPx","heightPx","mpp","fields","createdAt","expiresAt","expiresAtSource","expiresInDays","willExpire","trashedAt","jobs","jobCount","segmentation"],"properties":{"ownership":{"type":"string","enum":["mine"]},"id":{"type":"string","format":"uuid"},"appUrl":{"type":"string","format":"uri","description":"Canonical browser URL for this owned sample."},"name":{"type":"string","nullable":true,"description":"Human-friendly display name, or null if unset (falls back to filename)."},"filename":{"type":"string","description":"Original uploaded filename."},"status":{"type":"string","description":"Lifecycle status: `uploading`, `upload_failed`, `deid_running`, `deid_failed`, `preprocessing`, `ready`, or `preprocess_failed`."},"fileSize":{"type":"string","description":"Uploaded file size in bytes (string-encoded 64-bit integer)."},"widthPx":{"type":"integer","nullable":true,"description":"Level-0 width in pixels, or null before the dimensions probe completes."},"heightPx":{"type":"integer","nullable":true,"description":"Level-0 height in pixels, or null before the dimensions probe completes."},"mpp":{"type":"number","nullable":true,"description":"Effective isotropic microns per pixel (user override → calibrated slide value), or null when the sample has no usable scale yet."},"fields":{"$ref":"#/components/schemas/SampleFields"},"createdAt":{"type":"string","format":"date-time","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true,"description":"When the sample moves to Trash. Null when it never expires."},"expiresAtSource":{"type":"string","nullable":true,"enum":["org_default","custom",null],"description":"`org_default` = derived from the org policy at upload time. `custom` = set by an explicit expiration call (a date or a never-expire pin). Null when the sample has no expiration and no org policy applies."},"expiresInDays":{"type":"integer","nullable":true,"description":"Whole days until expiry, clamped at 0 for a sample at/past its date but not yet swept. Null when the sample never expires."},"willExpire":{"type":"boolean","description":"True when the sample has an expiration date set."},"trashedAt":{"type":"string","format":"date-time","nullable":true,"description":"When the sample entered Trash, or null if it is still active. Trashed samples are permanently deleted 7 days after this time."},"jobs":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/SampleJob"}},"jobCount":{"type":"integer","minimum":0,"description":"Total inference-job count for the sample before the 50-job cap."},"segmentation":{"$ref":"#/components/schemas/SegmentationState"}}},"MineSampleListItem":{"type":"object","additionalProperties":false,"required":["ownership","id","name","filename","status","fileSize","fields","createdAt","appUrl"],"properties":{"ownership":{"type":"string","enum":["mine"]},"id":{"type":"string","format":"uuid","description":"Organization-authorized sample id."},"name":{"type":"string","nullable":true},"filename":{"type":"string"},"status":{"type":"string"},"fileSize":{"type":"string","description":"Decimal string for a 64-bit byte count."},"fields":{"$ref":"#/components/schemas/SampleFields"},"createdAt":{"type":"string","format":"date-time","nullable":true},"appUrl":{"type":"string","format":"uri","description":"Canonical browser URL."}}},"PublicSampleListItem":{"type":"object","additionalProperties":false,"required":["ownership","id","title","thumbnailUrl","appUrl"],"properties":{"ownership":{"type":"string","enum":["public"]},"id":{"type":"string","format":"uuid","description":"Safe public share UUID. Never an internal sample id."},"title":{"type":"string"},"thumbnailUrl":{"type":"string","description":"Authenticated v1 public thumbnail route."},"appUrl":{"type":"string","format":"uri","description":"Canonical browser URL."}}},"SampleList":{"type":"object","additionalProperties":false,"required":["items","nextCursor","appUrl"],"properties":{"items":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/MineSampleListItem"},{"$ref":"#/components/schemas/PublicSampleListItem"}],"discriminator":{"propertyName":"ownership","mapping":{"mine":"#/components/schemas/MineSampleListItem","public":"#/components/schemas/PublicSampleListItem"}}}},"nextCursor":{"type":"string","nullable":true,"description":"Opaque cursor for the next item, or null when exhausted."},"appUrl":{"type":"string","format":"uri","description":"Canonical browser URL for this exact scope and filter; pagination omitted."}}},"PublicSampleDetail":{"type":"object","additionalProperties":false,"description":"A public sample's curated detail. `id` is the public share UUID; the internal sample identity and job history are never exposed.","required":["ownership","id","appUrl","title","thumbnailUrl","geometry","viewer"],"properties":{"ownership":{"type":"string","enum":["public"]},"id":{"type":"string","format":"uuid"},"appUrl":{"type":"string","format":"uri","description":"Canonical browser URL for this public sample."},"title":{"type":"string"},"thumbnailUrl":{"type":"string","description":"v1-relative path to the sample's JPEG thumbnail."},"geometry":{"type":"object","required":["widthPx","heightPx","mppX","mppY"],"description":"Level-0 pixel dimensions and microns-per-pixel of the H&E image.","properties":{"widthPx":{"type":"integer"},"heightPx":{"type":"integer"},"mppX":{"type":"number","nullable":true},"mppY":{"type":"number","nullable":true}}},"viewer":{"type":"object","required":["pyramidUrl","markers"],"description":"How to read the pixel data: the OME-Zarr pyramid base path and the marker channels it carries.","properties":{"pyramidUrl":{"type":"string","description":"v1-relative base path of the OME-Zarr store (`GET /public/samples/{publicId}/zarr/{...path}`). The root manifest is `{pyramidUrl}/zarr.json`."},"markers":{"type":"array","description":"Live marker channels available in the pyramid.","items":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}}}}}}},"paths":{"/uploads":{"get":{"summary":"List uploads for the calling org","description":"Returns non-archived uploads newest-first. Cursor-paginated; pass the prior response's `nextCursor` to fetch the next page.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":100}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Upload list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadList"}}}},"400":{"description":"Invalid limit or cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Initiate a resumable upload","description":"Creates an organization-bound GCS resumable session. Slides must be de-identified before upload. Automated slide de-identification is off by default and runs only when enabled for the organization. It does not determine whether a slide satisfies HIPAA or any other legal de-identification standard, and it does not inspect or remove identifiers in tissue pixels or filenames. PUT the declared bytes to `uploadUrl`; when GCS finalizes the object, the platform automatically starts ingest preprocessing. There is no client finalize call. Poll `GET /uploads/{id}` until the sample leaves `uploading`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["filename","fileSize","contentType"],"properties":{"filename":{"type":"string"},"fileSize":{"type":"integer"},"contentType":{"type":"string"},"contentSha256":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"Optional client-computed SHA-256. On a match within the calling organization, the response returns the existing upload and skips byte transfer."},"autoSegment":{"type":"boolean","description":"Opt out of automatic cell segmentation for this upload. Defaults to the organization's `autoSegmentDefault` (true unless changed). When false, the slide is ingested and rendered but no segmentation job runs; marker runs still work but without cell-level readouts until segmented."},"mpp":{"oneOf":[{"type":"number","exclusiveMinimum":0,"maximum":100},{"type":"object","additionalProperties":false,"required":["x","y"],"properties":{"x":{"type":"number","exclusiveMinimum":0,"maximum":100},"y":{"type":"number","exclusiveMinimum":0,"maximum":100}}}],"description":"Optional user-reported microns per pixel, for callers that already know their slide's scale. Slides are isotropic: pass a number, or an { x, y } object whose axes are equal (anisotropic input is rejected). Persisted on the sample at creation and takes precedence over the slide's own calibrated value, so `POST /predict` works as soon as preprocessing finishes — no follow-up `PATCH /samples/{id}` needed. Ignored when `contentSha256` deduplicates to an existing sample (that sample's scale stands)."}}}}}},"responses":{"200":{"description":"Resumable upload URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadCreated"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/uploads/{id}":{"get":{"summary":"Fetch a single upload","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Upload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Upload"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Upload not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/markers":{"get":{"summary":"List available markers","description":"The entitlement-scoped catalog of markers this account may request. Credit-free. The list equals exactly what predict will accept: a self-signup account sees the public panel; a full-panel account sees the whole vocab. Use it to discover valid marker names upfront instead of trial-and-error against a 400.","responses":{"200":{"description":"The account's available markers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkerList"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sample-fields":{"get":{"summary":"List custom sample fields","description":"The calling organization's custom-field definitions. Credit-free. This is the discovery surface for the `field` filter on GET /samples: a caller cannot guess a definition's id, so it reads the catalog once and filters by the ids returned here. `key` is stable and unique within the organization; `label` is display text and is not unique.","responses":{"200":{"description":"The organization's custom-field definitions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SampleFieldList"}}}},"401":{"description":"Missing or invalid credential","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/predict":{"post":{"summary":"Price or submit a prediction","description":"Set `dryRun: true` to validate and price the prediction without writes or credit reservation (200). Omit it or set it to false to reserve credits atomically and enqueue preprocessing (202).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["uploadId","markers"],"properties":{"uploadId":{"type":"string","format":"uuid"},"markers":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":512},"model":{"type":"string","enum":["v0.4","v0.5","v0.7"],"description":"Optional explicit Lattice version. Only `v0.7` is dispatchable and is the current default; omit `model` to let the platform pick it. `v0.4`/`v0.5` are READ-ONLY as of 2026-06-23: they remain in this enum because they still appear on historical job records, but requesting one returns 400 `model_sunset` (with `currentDefault`). Legacy model strings return 400 `unknown_model`."},"dryRun":{"type":"boolean","default":false,"description":"When true, validates and returns the exact credit estimate without reserving credits, creating a job, or dispatching work."}}}}}},"responses":{"200":{"description":"Validated dry-run estimate with no side effects","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Estimate"}}}},"202":{"description":"Job accepted and credits reserved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Submission"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Insufficient credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"One or more requested markers are outside this account's panel (`marker_not_available`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Upload not ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/jobs/{id}":{"get":{"summary":"Get job status","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SampleJob"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/jobs/{id}/cancel":{"post":{"summary":"Cancel an in-flight job","description":"Cancels an active inference job and refunds its credit reservation. Active segmentation is owned by its fenced lifecycle and returns 409; terminal jobs return 400.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["cancelled"]}}}}}},"400":{"description":"Job already terminal","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Active segmentation is owned by its fenced lifecycle","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/jobs/{id}/stream":{"get":{"summary":"Stream job status (SSE)","description":"Server-Sent Events stream emitting JSON snapshots of `{status, progress, resultGcsPath}` on each pg_notify. Keep-alive heartbeats every 15s. Closes on terminal status.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"text/event-stream","content":{"text/event-stream":{"schema":{"type":"object","properties":{"event":{"type":"string"},"data":{"type":"string"},"id":{"type":"string"},"retry":{"type":"integer"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No job matches the given id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/jobs/{id}/results":{"get":{"summary":"Get signed download URL for results","description":"Returns a signed GCS URL for the OME-Zarr root metadata (`zarr.json`) plus the `resultBasePath` of the zarr store. Useful for clients that already have GCS credentials. SDK clients should generally prefer `GET /jobs/{id}/results/files/{path}` (API-key authenticated) for walking the tree.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Signed URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Results"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No job matches the given id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Job has not completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/jobs/{id}/exports":{"post":{"summary":"Request a format-driven result export","description":"One operation selects native OME-Zarr (no conversion), cached OME-Zarr ZIP, or cached OME-TIFF. Every format defaults to predicted mIF only; includeHe=true explicitly adds processed/registered H&E, with separately cached OME-TIFF variants. includeOriginalUpload=true separately requests the multi-GB source file stored by Strand (the stored de-identified copy when ingest de-identification ran), never the processed/registered H&E pyramid. Original retrieval is fail-closed when the runtime original-retrieval flag is disabled or the permanent source is unavailable. includeSegmentation attaches the latest uint32 mask and true per-cell geometry, morphology, and marker-expression manifest; segmentation jobs always include their own artifacts. OAuth requests conditionally require samples:read when segmentation artifacts are included. Predicted marker arrays remain pixel grids, not cell expressions. Requests are idempotent, credit-free, and failed asynchronous exports are retryable by repeating POST.","security":[{"ApiKey":[]},{"OAuth2":["jobs:write","results:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["format"],"properties":{"format":{"type":"string","enum":["ome-zarr","ome-zarr-zip","ome-tiff"]},"includeHe":{"type":"boolean","default":false,"description":"Include processed/registered H&E."},"includeSegmentation":{"type":"boolean","default":false,"description":"Include segmentation artifacts. Segmentation jobs always enable this selection. OAuth requests conditionally require samples:read when segmentation artifacts are included."},"includeOriginalUpload":{"type":"boolean","default":false,"description":"Request the separate multi-GB source file stored by Strand, not processed/registered H&E. If ingest de-identification ran, this is the stored de-identified copy. OAuth requests conditionally require samples:read when this option is true."}}}}}},"responses":{"200":{"description":"Native or cached result ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResultExport"}}}},"202":{"description":"Export pending or running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResultExport"}}}},"400":{"description":"Malformed request body or invalid selection value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"OAuth token lacks a required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Job has no completed results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Original-upload link issuance rate limit exceeded","headers":{"Retry-After":{"description":"Seconds until another issuance token is available","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"Get format-driven result export status","description":"Poll an asynchronous export and refresh expiring signed URLs. OAuth requests conditionally require samples:read when segmentation artifacts are included; segmentation jobs always include their own artifacts.","security":[{"ApiKey":[]},{"OAuth2":["jobs:read","results:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"format","in":"query","required":true,"schema":{"type":"string","enum":["ome-zarr","ome-zarr-zip","ome-tiff"]}},{"name":"includeHe","in":"query","description":"Include processed/registered H&E.","schema":{"type":"boolean","default":false}},{"name":"includeSegmentation","in":"query","description":"Include segmentation artifacts. Segmentation jobs always enable this selection. OAuth requests conditionally require samples:read when segmentation artifacts are included.","schema":{"type":"boolean","default":false}},{"name":"includeOriginalUpload","in":"query","description":"Request the separate multi-GB source file stored by Strand, not processed/registered H&E. If ingest de-identification ran, this is the stored de-identified copy. OAuth requests conditionally require samples:read when this option is true.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Result ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResultExport"}}}},"202":{"description":"Export pending or running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResultExport"}}}},"400":{"description":"Invalid selection value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"OAuth token lacks a required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Job or export not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Job has no completed results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Original-upload link issuance rate limit exceeded","headers":{"Retry-After":{"description":"Seconds until another issuance token is available","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/samples/{id}":{"get":{"summary":"Get an owned or public sample","description":"Resolve an org-owned sample id first, returning its curated detail and inference-job history. Otherwise resolve the same path value as a currently-public share UUID and return only public-safe detail.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Owned or public sample detail","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OwnedSampleDetail"},{"$ref":"#/components/schemas/PublicSampleDetail"}],"discriminator":{"propertyName":"ownership","mapping":{"mine":"#/components/schemas/OwnedSampleDetail","public":"#/components/schemas/PublicSampleDetail"}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Sample not found in this org","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a sample","description":"Atomically update any combination of an owned sample's display name, unified field values, and isotropic microns-per-pixel value. Public share ids are not patchable. OAuth callers need both `samples:read` and `samples:write` because the response includes full owned detail and job history. When countable `mpp` is supplied, an idempotent recount is enqueued even if the stored value is unchanged; after an enqueue-failure 500, retry the identical patch to heal the recount.","security":[{"ApiKey":[]},{"OAuth2":["samples:read","samples:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SamplePatch"}}}},"responses":{"200":{"description":"Updated owned sample detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OwnedSampleDetail"}}}},"400":{"description":"Body is not JSON, empty, or contains unknown fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid credential","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"OAuth token lacks samples:read or samples:write, or the patch attempts to write a read-only field such as Public","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Owned sample not found in this org","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A provided field is invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"The sample update committed but its MPP tissue recount could not be enqueued. Retry the identical patch to re-attempt the idempotent recount.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/samples/{id}/segmentation":{"get":{"summary":"Get cell-segmentation lifecycle","description":"Credit-free owned-sample state. Public sample handles are read-only and resolve as not found on this mutation-capable surface.","security":[{"ApiKey":[]},{"OAuth2":["samples:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Lifecycle state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentationState"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"OAuth token lacks a required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Owned sample not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Start or retry cell segmentation","description":"State-idempotent and credit-free. Commits a durable queued job intent before best-effort task delivery and returns its job ID. Reuses completed/active work, retries the exact latest failed attempt after a 60-second cooldown, and rejects non-ready, archived, cross-org, and public samples.","security":[{"ApiKey":[]},{"OAuth2":["samples:read","samples:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Existing completed or active work","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentationState"}}}},"202":{"description":"Start/retry queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentationState"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"OAuth token lacks a required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Owned sample not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Sample not ready or archived","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Retry cooldown","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Segmentation is not configured in this environment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/samples/{id}/segmentation/{layerId}/manifest":{"get":{"summary":"Get segmentation artifact manifest","description":"Returns mask OME-Zarr and true per-cell Parquet contracts. Marker-expression tables join morphology by instance_id; they are not pixel-grid AnnData.","security":[{"ApiKey":[]},{"OAuth2":["samples:read","results:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"layerId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Artifact manifest","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentationManifest"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"OAuth token lacks a required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Layer not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Layer not complete","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/samples/{id}/segmentation/{layerId}/files/{path}":{"get":{"summary":"Read one segmentation artifact file","description":"Bearer-authenticated, org-scoped byte proxy for the paths declared by the segmentation manifest.","security":[{"ApiKey":[]},{"OAuth2":["samples:read","results:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"layerId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"path","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Artifact bytes","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Invalid artifact path","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"OAuth token lacks a required scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Artifact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Storage proxy failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/samples/{id}/expiration":{"patch":{"summary":"Set sample expiration","description":"Set a sample to expire on a specific date, set it to never expire, or clear any custom expiration and follow the org default. Caller must be the sample's creator, an org owner/admin, or a Strand AI admin.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpirationUpdate"}}}},"responses":{"200":{"description":"Updated sample","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sample"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed for this sample","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Sample not found in this org","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/samples/expiration":{"patch":{"summary":"Set sample expiration (bulk)","description":"All-or-nothing: if any sample fails the permission gate the whole call is rejected before any writes. Max 500 IDs per call.","requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ExpirationUpdate"},{"type":"object","required":["sampleIds"],"properties":{"sampleIds":{"type":"array","minItems":1,"maxItems":500,"items":{"type":"string","format":"uuid"}}}}]}}}},"responses":{"200":{"description":"Bulk update result","content":{"application/json":{"schema":{"type":"object","required":["updated","batchId"],"properties":{"updated":{"type":"integer"},"batchId":{"type":"string","format":"uuid"}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"One or more samples are not in the API key's org","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/samples/{id}/restore":{"post":{"summary":"Restore a sample from Trash","description":"Available within the 7-day Trash window until permanent deletion starts. Brings the sample back to the active list and extends its expiration so it isn't immediately re-trashed. A deletion already in progress is irreversible and returns 409. Same permission gate as the expiration endpoints.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Restored sample","content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"uuid"},"trashedAt":{"type":"null"},"expiresAt":{"type":"string","format":"date-time","nullable":true}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not allowed for this sample","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Sample not found in this org","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Permanent deletion is already in progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"412":{"description":"Sample is not archived","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/jobs/{id}/results/files/{path}":{"get":{"summary":"Stream a single result file (API-key authenticated)","description":"Proxies a single file from the OME-Zarr result store. `path` is the path within the zarr store (e.g. `zarr.json`, `CD3/zarr.json`, `CD3/c/0/0/0`). Authenticated via the same bearer API key as the rest of `/api/v1/`; org-scoped to the job. Use this when walking the zarr tree from a client that does not have direct GCS credentials.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"path","in":"path","required":true,"description":"Path within the zarr store; slashes are not URL-encoded.","schema":{"type":"string"}}],"responses":{"200":{"description":"File bytes (binary for chunks, JSON for metadata).","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Job or file not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Job has not completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/samples":{"get":{"summary":"List samples","description":"List the calling organization's samples, the public cohort, or both. Cursor-paginated newest-first; `all` always emits mine first and de-duplicates owned public samples in favor of the mine representation. Credit-free.\n\nThe facet parameters are byte-identical to the ones the Strand web app puts in a filtered `/samples` URL, so a browser query string is a valid API query string and vice versa. Facets compose with AND; `status` and `workflow` values compose with OR; repeated `marker` and `field` values compose with AND. Tags is addressed through the predefined Tags field definition. The retired `tag` parameter is rejected with 400 rather than ignored. Unknown values, malformed clauses and over-cap arrays are also rejected.\n\nA public release supports title search (`q`) only. Under `scope=all` any other facet suppresses the public block; under `scope=public` it is a 400.","parameters":[{"name":"scope","in":"query","required":false,"schema":{"type":"string","enum":["mine","public","all"],"default":"mine"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":48}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","minLength":1},"description":"Opaque cursor returned as `nextCursor`; not bound to page size."},{"name":"q","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":200},"description":"Case-insensitive substring match against the sample's display name or filename. The only facet a public release can answer."},{"name":"status","in":"query","required":false,"style":"form","explode":true,"schema":{"type":"array","maxItems":5,"items":{"type":"string","enum":["ready","preprocessing","uploading","upload_failed","preprocess_failed"]}},"description":"Repeatable lifecycle-status filter. Values compose with OR."},{"name":"uploaded_from","in":"query","required":false,"schema":{"type":"string","format":"date","pattern":"^(?!0000-)\\d{4}-\\d{2}-\\d{2}$"},"description":"Inclusive lower bound on upload date in years 0001-9999, as a UTC calendar day. Must not be later than `uploaded_to`."},{"name":"uploaded_to","in":"query","required":false,"schema":{"type":"string","format":"date","pattern":"^(?!0000-)\\d{4}-\\d{2}-\\d{2}$"},"description":"Inclusive upper bound on upload date in years 0001-9999, as a UTC calendar day: the whole day is matched."},{"name":"workflow","in":"query","required":false,"style":"form","explode":true,"schema":{"type":"array","maxItems":3,"items":{"type":"string","enum":["ready_no_job","in_flight","has_results"]}},"description":"Repeatable run-state filter: `ready_no_job` (preprocessed, never submitted), `in_flight` (a non-terminal job exists), `has_results` (a completed marker prediction exists). Values compose with OR."},{"name":"marker","in":"query","required":false,"style":"form","explode":true,"schema":{"type":"array","maxItems":20,"items":{"type":"string","pattern":"^[^,:]{1,100}:(requested|completed)$"}},"description":"Repeatable per-marker state filter of the form `NAME:state`, where state is `requested` or `completed`. Marker names cannot contain `:` or `,`. Values compose with AND."},{"name":"field","in":"query","required":false,"style":"form","explode":true,"schema":{"type":"array","maxItems":20,"items":{"type":"string","pattern":"^[0-9a-fA-F-]{36}:(eq|contains|gte|lte)(:.+)$|^[0-9a-fA-F-]{36}:(is_empty|is_not_empty)$"}},"description":"Repeatable custom-field filter of the form `DEFINITION_UUID:op[:VALUE]`. `op` is one of `eq`, `contains`, `gte`, `lte`, `is_empty`, `is_not_empty`; the first four carry a value that is JSON-encoded and then URL-encoded (so `\"high\"`, `3`, `true`). Operators are checked against the definition's type. Org-defined fields have no meaning on the public cohort. Under `scope=all` a field facet returns matching organization-owned samples and suppresses public rows; under `scope=public` it is rejected. Values compose with AND."}],"responses":{"200":{"description":"A scoped sample page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SampleList"}}}},"400":{"description":"Invalid query or cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid credential","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/samples/{publicId}/zarr/{path}":{"get":{"summary":"Stream a public sample's OME-Zarr pyramid","description":"Authenticated byte proxy for a public sample's OME-Zarr store — the H&E image (`he/...`) and every pinned marker channel (`markers/{marker}/...`). The root manifest is `zarr.json`. This is the way to read the actual marker pixel data. Only the `he/` and `markers/` namespaces are served; everything else is denied. Never reserves or spends credits.","parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"path","in":"path","required":true,"description":"Path within the zarr store; slashes are not URL-encoded (e.g. `zarr.json`, `he/0/zarr.json`, `markers/CD3/c/0/0/0`).","schema":{"type":"string"}}],"responses":{"200":{"description":"File bytes (binary for chunks, JSON for metadata).","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid credential","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Public sample or file not found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}}}}}}},"/public/samples/{publicId}/thumbnail":{"get":{"summary":"Get a public sample's thumbnail","description":"Authenticated JPEG thumbnail for a public sample. Streamed through this release-keyed endpoint (never a signed GCS URL). Never reserves or spends credits.","parameters":[{"name":"publicId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"JPEG thumbnail bytes.","content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Missing or invalid credential","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No currently-public sample matches the given publicId","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}}}}}}}}}