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

# Record that your patient dropped off the questionnaire

> Marks the intake session as dropped-off. Call this when a patient leaves without finishing so the drop-off is recorded against the session.



## OpenAPI

````yaml /openapi/public-openapi.json post /v1/instrument/abandon
openapi: 3.1.0
info:
  title: Purple API
  version: 0.0.0
servers:
  - url: https://api.dev.purplelabelmd.com
security: []
tags:
  - description: Sign a patient in and out and manage the browser session.
    name: Sessions & authentication
  - description: Run the server-driven intake questionnaire question by question.
    name: Intake
  - description: Address autocomplete for the intake flow.
    name: Addresses
  - description: Read the public status of a patient's enrollment.
    name: Enrollment
  - description: Track a client's onboarding progress.
    name: Onboarding
  - description: Read back the platform configuration stored for your brand.
    name: Brand configuration
  - description: Enable offerings for your brand and set their display copy.
    name: Offering configuration
  - description: Start and track payments for an order.
    name: Payments
  - description: Subscribe to platform events and manage delivery endpoints.
    name: Webhooks
paths:
  /v1/instrument/abandon:
    parameters:
      - $ref: '#/components/parameters/BrandIdHeader'
    post:
      tags:
        - Intake
      summary: Record that your patient dropped off the questionnaire
      description: >-
        Marks the intake session as dropped-off. Call this when a patient leaves
        without finishing so the drop-off is recorded against the session.
      operationId: abandonInstrument
      parameters:
        - $ref: '#/components/parameters/CorrelationIdHeader'
      requestBody:
        content:
          application/json:
            schema:
              properties:
                session_id:
                  type: string
              required:
                - session_id
              type: object
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InstrumentStep'
          description: abandonment recorded
        '404':
          $ref: '#/components/responses/EdgeInstrumentSessionUnknown'
        '429':
          $ref: '#/components/responses/EdgeRateLimited'
      security:
        - apiKey: []
components:
  parameters:
    BrandIdHeader:
      description: >-
        Opaque brand id (brd_...). Validated to belong to the resolved client
        (§2).
      in: header
      name: X-Brand-Id
      required: true
      schema:
        pattern: ^brd_[A-Za-z0-9][A-Za-z0-9_-]*$
        type: string
    CorrelationIdHeader:
      description: >-
        An optional id you send to tie this request to your own logs. Send one
        and it is echoed back unchanged; omit it and one is assigned for you.
        Either way the id is returned in the X-Correlation-Id response header on
        every response, including errors, so you can match a response to the
        request that produced it.
      in: header
      name: X-Correlation-Id
      required: false
      schema:
        maxLength: 128
        pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]*$
        type: string
  schemas:
    InstrumentStep:
      description: >-
        The result of a resolve, next, or abandon call — the session identity,
        the current question (or null when finished), the status, any flags
        (e.g. ship_state_mismatch), and any validation issues. On completion it
        may carry the entry-link hand-off.
      properties:
        blocked:
          $ref: '#/components/schemas/BlockedOutcome'
        flags:
          items:
            type: string
          type: array
        handoff:
          $ref: '#/components/schemas/EntryHandoff'
        issues:
          items:
            $ref: '#/components/schemas/ControlIssue'
          type: array
        journey_id:
          pattern: ^jny_[A-Za-z0-9][A-Za-z0-9_-]*$
          type: string
        node:
          oneOf:
            - $ref: '#/components/schemas/RenderedNode'
            - type: 'null'
        progress:
          $ref: '#/components/schemas/StepProgress'
        session_id:
          type: string
        status:
          description: >-
            `active` (a node is presented), `complete` (server-authoritative
            completion), `abandoned` (patient drop-off), or `blocked` — a
            terminal, server-authoritative refusal to proceed (a required
            consent was declined or could not be affirmed). On `blocked`, `node`
            is null and `blocked` carries the honest stop-screen outcome.
          enum:
            - active
            - complete
            - abandoned
            - blocked
          type: string
      required:
        - session_id
        - journey_id
        - status
      type: object
    BlockedOutcome:
      description: >-
        The honest stop-screen outcome. Present only when `status` is `blocked`.
        Carries the machine-readable reason, the node at which the block
        occurred, the consent scopes that were not granted (codes, never patient
        data), and a support-routing hint. The stop-screen copy is a
        presentation concern (the intake kit renders it); no patient data ever
        rides here. No order lock ever forms without a positive affirmation
        where one is required.
      properties:
        code:
          description: stable reason code (e.g. `consent_declined`)
          type: string
        node_id:
          description: the node at which the block occurred (the consent node id)
          type: string
        support_route:
          description: >-
            a stable support-routing key (edge owns portal→GHL support routing);
            never PII
          type: string
        ungranted_scopes:
          description: consent scopes not granted — codes, never patient data
          items:
            type: string
          type: array
      required:
        - code
        - node_id
      type: object
    EntryHandoff:
      additionalProperties: false
      description: >-
        Post-completion hand-off carried when a session that began from an entry
        link completes. Present only when `status` is `complete` and the session
        carried a validated entry context. `redirect` holds a brand-allowlisted
        https URL; `promo` is an opaque pass-through token validated downstream;
        `test` is the per-client test-mode flag. Never carries patient data.
      properties:
        promo:
          maxLength: 256
          type: string
        redirect:
          maxLength: 2048
          type: string
        test:
          type: boolean
      type: object
    ControlIssue:
      properties:
        code:
          type: string
        conflicting_codes:
          description: >-
            chore-refuse-contradictory-answer (additive; paired with
            `exclusive_codes`). The option code(s) the exclusive selection
            contradicted — the other half of "which options clashed". When the
            submission carried only exclusive options, they contradict each
            other and both lists name them.
          items:
            type: string
          type: array
        exclusive_codes:
          description: >-
            chore-refuse-contradictory-answer (additive; present ONLY on `code:
            renderer.exclusive_violation`, absent on every other issue). The
            exclusive ("none of these"-class) option code(s) the refused
            submission carried. A contradictory selection on a clinical-safety
            question is a SERVE defect, not a client nicety: the server refuses
            it with a 422 and never a 200-advance, and the refusal must say
            WHICH selections contradicted so the client can correct the exact
            ones. These are the caller's OWN submitted codes echoed back to the
            caller.
          items:
            type: string
          type: array
        message:
          type: string
      required:
        - code
        - message
      type: object
    RenderedNode:
      description: >-
        A node as presented to the client — PRESENTATION ONLY. The client never
        owns the sequence, controls, or option order (those come from the
        compiled bundle; a brand theme cannot alter them). Brand theme/copy,
        when applied, ride alongside as presentation overlay.
      properties:
        computed_stub:
          type: boolean
        consent_version:
          description: >-
            the consent version served on a `consent` node. The client echoes it
            on the affirmation; it rides into the recorded consent fact as
            provenance. The MD/counsel-locked affirmation copy rides `content`;
            brand can never supply it.
          type: string
        content:
          additionalProperties:
            type: string
          description: >-
            Named display-content slots for a display node — MD-locked copy
            carried in the compiled instrument bundle, versioned and reviewed
            with the instrument. Brand config themes presentation only and can
            never supply or override these slots. The `copy` field is the
            separate question-headline channel; its authoritative source is
            likewise the MD-locked instrument bundle, and the brand overlay can
            never override it.
          type: object
        control:
          type: string
        copy:
          description: >-
            The question headline, as a single string, served from the MD-locked
            question prompt in the compiled instrument bundle (versioned and
            reviewed with the instrument). For a question node this is its only
            source — the per-brand copy overlay can never supply or override it
            (theming yes, wording never). Absent is valid; the renderer falls
            back deterministically (for example a formatted fact code).
          type: string
        display:
          enum:
            - static
            - computed
          type: string
        fact:
          type: string
        grants:
          description: >-
            the consent scopes an affirmative answer grants on a `consent` node.
            Codes, never labels; nothing branches on the copy text.
          items:
            type: string
          type: array
        interstitial:
          description: >-
            Marks a display node as an interstitial moment for skin treatment
            (the tint pair is reserved for these). Presentation flag only; never
            carried on question nodes.
          type: boolean
        kind:
          description: >-
            `question` (collects a fact), `display` (MD-locked informational
            node, no answer channel), or `consent` (a consent-capture
            affirmation surface — binds no fact; its MD/counsel-locked copy
            rides `content`, its `consent_version` + `grants` ride alongside).
          enum:
            - question
            - display
            - consent
          type: string
        may_auto_advance:
          type: boolean
        media:
          $ref: '#/components/schemas/MediaConfig'
        node_id:
          type: string
        offering_refs:
          items:
            type: string
          type: array
        option_codes:
          items:
            type: string
          type: array
        options:
          description: >-
            Per-option presentation hints for this question. When present, it
            lists the same codes in the same order as `option_codes` — option
            order comes from the questionnaire and cannot be altered by theming.
            When absent, render from `option_codes` alone.
          items:
            $ref: '#/components/schemas/RenderedOption'
          type: array
        prefill:
          enum:
            - collect
            - suppress
            - confirm
          type: string
        prefilled_value: {}
        reassurance:
          description: >-
            A single hedged reassurance line for a computed display panel,
            served from the MD-locked instrument content and reviewed with it.
            The line is SELECTED server-side by where the primary computed value
            falls against the served zone (below / in_zone / above) — never
            served statically — and is PRESENT only when the matching band is
            authored and the band could be determined; otherwise it is ABSENT
            (fail to neutral). Display copy only; it carries no eligibility
            language and defers every decision to the clinician.
          type: string
        required:
          type: boolean
        section_id:
          type: string
        theme:
          additionalProperties: true
          type: object
        values:
          additionalProperties: {}
          description: >-
            Server-supplied display values keyed by fact/derivation CODE (codes,
            never label text). Derived codes are computed server-side on read
            from registered pure derivations — the client renders these values
            and never computes anything of record. A display node whose primary
            referenced values cannot all be resolved is never emitted
            (server-skipped, fail-soft). Ancillary echoed values (for example
            the height and weight behind a computed number) ride this same map
            alongside the primary value; an echoed value that cannot be resolved
            is omitted, and never blocks the node.
          type: object
        zone:
          $ref: '#/components/schemas/DisplayZone'
      required:
        - node_id
        - kind
        - section_id
      type: object
    StepProgress:
      description: >-
        Server-computed progress metadata, present on every resolve/next step:
        computed from the live effective plan — never client-guessed.
        `position_estimate` is the honest fraction of the plan completed (0..1);
        display nodes participate in the fraction. Whether and how it renders
        (thin persistent bar, step ring, interstitial pacing, none) is
        brand-theme presentation.
      properties:
        position_estimate:
          description: honest fraction of the effective plan completed, 0..1
          maximum: 1
          minimum: 0
          type: number
        scope:
          description: '`instrument`, or a subflow id once named optional sub-flows ship'
          type: string
        section_label:
          description: >-
            optional server-supplied section label; omitted while bundles carry
            none
          type: string
      required:
        - scope
        - position_estimate
      type: object
    EdgeProblem:
      description: RFC 7807 problem+json error body.
      properties:
        detail:
          type: string
        status:
          type: integer
        title:
          type: string
        type:
          format: uri-reference
          type: string
      required:
        - type
        - title
        - status
      type: object
    MediaConfig:
      description: >-
        Capture configuration for a file-capture question. When absent, the
        question is a standard photo or document upload. Presentation and
        capture-bounds only — the answer is an upload reference and nothing
        branches on the media content; faces and voices are never matched,
        recognized, or graphed.
      properties:
        accept:
          description: MIME allowlist (e.g. video/mp4, video/webm)
          items:
            type: string
          type: array
        capture_mode:
          enum:
            - upload
            - record
            - record_or_upload
          type: string
        consent_copy:
          description: >-
            DELIVERY-163 — the PLAIN DISPLAY TEXT the member must be shown
            before any affirmative action, pinned to `consent_version`. The
            portal renders THIS EXACT text; it is not a key, not a template, and
            the client never substitutes its own wording — consent to copy the
            server did not serve is not consent. Optional on the contract
            because non-video captures may not gate on consent at all, but
            REQUIRED (nonempty) for a managed video capture alongside
            `consent_version` and `consent_fact`: all three, or no grant and no
            recorded consent. Authored and served through the same MediaConfig,
            so the text the member reads and the version the server records can
            never drift apart.
          type: string
        consent_fact:
          description: >-
            fact code the consent acknowledgment projects to (discrete auditable
            consent fact)
          type: string
        consent_version:
          description: >-
            the version OF RECORD for the consent copy below. The server checks
            the echoed version against this one and records it on the
            acknowledgment; there is NO versionless fallback — a managed video
            capture whose node omits this refuses rather than recording a
            consent that cannot say which copy was agreed to.
          type: string
        facing:
          description: advisory front/rear camera hint; not enforced
          enum:
            - user
            - environment
          type: string
        kind:
          enum:
            - image
            - video
          type: string
        max_bytes:
          type: integer
        max_duration_s:
          type: number
        min_duration_s:
          type: number
        require_consent:
          description: gate capture on a consent-to-record acknowledgment (video ⇒ true)
          type: boolean
        resumable:
          type: boolean
      required:
        - kind
      type: object
    RenderedOption:
      description: >-
        One selectable option with its presentation hints — presentation only,
        carried alongside `option_codes`. An `exclusive` option (a "none of
        these"-class choice) cannot be combined with any sibling — selecting it
        should clear the other selections, and the server rejects an answer that
        carries an exclusive code together with another selection (`422`, the
        session does not advance). A banded choice (for example a blood-pressure
        or frequency range) may carry a `classification_code` alongside its
        stable option code.
      properties:
        classification_code:
          description: >-
            classification code carried by a banded choice alongside its stable
            option code
          type: string
        code:
          description: the stable option code submitted as the answer value
          type: string
        exclusive:
          description: >-
            marks a "none of these"-class option that cannot be combined with
            any sibling selection
          type: boolean
        label:
          description: >-
            MD-locked display label for this option, carried in the compiled
            instrument bundle and resolved by the stable option code (never
            positional). Presentation only — the code remains the submitted
            answer value and the sole binding; consumers fall back to the code
            when no label is present.
          type: string
      required:
        - code
      type: object
    DisplayZone:
      description: >-
        A designed-panel band descriptor for a computed display node: a scale
        from `scale_min` to `scale_max` with a highlighted band from `zone_min`
        to `zone_max`, named by `zone_label`. The client places the patient's
        marker and the highlighted band for the picture. Display copy and
        presentation only — a zone carries no eligibility language and never
        asserts eligibility; the server remains authoritative on jurisdiction
        and eligibility.
      properties:
        scale_max:
          description: high end of the rendered scale axis
          type: number
        scale_min:
          description: low end of the rendered scale axis
          type: number
        zone_label:
          description: label naming the highlighted band (display copy; never an outcome)
          type: string
        zone_max:
          description: high bound of the highlighted band (within the scale)
          type: number
        zone_min:
          description: low bound of the highlighted band (within the scale)
          type: number
      required:
        - scale_min
        - scale_max
        - zone_min
        - zone_max
        - zone_label
      type: object
  responses:
    EdgeInstrumentSessionUnknown:
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/EdgeProblem'
      description: >-
        The questionnaire session named by `session_id` is not available: it
        never existed, or it has expired. Sessions live in the serving process,
        so a deployment retires the ones in flight. Start a fresh questionnaire
        (`/instrument/resolve`) and collect the answers again — retrying the
        same `session_id` will never succeed. The answer is identical for a
        malformed, an expired, and an unknown session id, so it carries no
        signal about which.
    EdgeRateLimited:
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/EdgeProblem'
      description: >-
        Too many requests. You have exceeded the request rate allowed for this
        endpoint. Wait the number of seconds given in the Retry-After response
        header, then retry the request.
      headers:
        Retry-After:
          description: Seconds to wait before you retry.
          schema:
            type: integer
        X-Correlation-Id:
          description: The correlation id for this request, echoed back.
          schema:
            type: string
  securitySchemes:
    apiKey:
      description: 'Per-client API key (M2M). Presented as `Authorization: Bearer <key>`.'
      scheme: bearer
      type: http

````