> ## Documentation Index
> Fetch the complete documentation index at: https://docs.strandai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Update a sample

> Atomically update any combination of an owned sample's display name, complete tag set, 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.

Atomically updates any combination of the sample's display name, complete tag set, and isotropic microns-per-pixel value. Returns the full owned sample detail, including inference-job history.

API keys use their existing organization access. OAuth access tokens must include both `samples:read` and `samples:write`, because the response contains read-only sample and job data.

The `tags` array accepts at most 1,000 raw items. Values are trimmed and lowercased, duplicate normalized values collapse, and the resulting set may contain at most 50 distinct tags.

When `mpp` is supplied and the effective value is countable, Strand enqueues a tissue recount even if the stored MPP is unchanged. If the update returns `500` because the recount could not be enqueued, the sample update has committed; retry the identical PATCH to enqueue the missing recount safely.


## OpenAPI

````yaml PATCH /samples/{id}
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: https://app.strandai.com/api/v1
    description: Production
security:
  - ApiKey: []
  - OAuth2: []
paths:
  /samples/{id}:
    patch:
      summary: Update a sample
      description: >-
        Atomically update any combination of an owned sample's display name,
        complete tag set, 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.
      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 add/remove the reserved public tag
          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'
      security:
        - ApiKey: []
        - OAuth2:
            - samples:read
            - samples:write
components:
  schemas:
    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.
        tags:
          type: array
          maxItems: 1000
          items:
            type: string
            pattern: ^[^\u0000]*$
          description: >-
            Complete desired tag set, with a transport-safety limit of 1,000 raw
            items. Each value is trimmed/lowercased, then must be 1–50
            characters with no commas or NUL characters; duplicates collapse
            after normalization and at most 50 distinct normalized tags are
            allowed.
        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.
    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
        - name
        - filename
        - status
        - fileSize
        - widthPx
        - heightPx
        - mpp
        - tags
        - createdAt
        - expiresAt
        - expiresAtSource
        - expiresInDays
        - willExpire
        - trashedAt
        - jobs
        - jobCount
        - segmentation
      properties:
        ownership:
          type: string
          enum:
            - mine
        id:
          type: string
          format: uuid
        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.
        tags:
          type: array
          items:
            type: string
          description: The sample's canonical tag strings, sorted alphabetically.
        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'
    Error:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
        message:
          type: string
        required:
          type: integer
          nullable: true
        retryAfterSeconds:
          type: integer
          nullable: true
    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
    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
  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

````