openapi: 3.1.0
info:
  title: IPM API
  version: 0.1.0-draft
  description: |
    Integration Plane Management (IPM).

    Contract notes:
    - API Gateway base path: `/ipm`
    - Org-gated for data routes: supply `x-orgcode`. Health (`GET /stat`) does not require `x-orgcode`.
    - Auth: either `x-session-guid` (user session) or `x-api-key` (org-bound service account).
    - Request context is required for write operations and captured for auditability.
    - Roles: `integration_view` for read-only, `integration_admin` for write operations (owner implied).
servers:
  - url: https://api.g3nretailstack.com/ipm
security:
  - sessionAuth: []
  - apiKeyAuth: []
components:
  securitySchemes:
    sessionAuth:
      type: apiKey
      in: header
      name: x-session-guid
    apiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
  parameters:
    OrgHeader:
      in: header
      name: x-orgcode
      required: true
      schema: { type: string }
    CostCentreHeader:
      in: header
      name: x-cccode
      required: false
      schema: { type: string }
  schemas:
    BuildMeta:
      type: object
      properties:
        build_major: { type: string }
        build_minor: { type: string }
        build_id: { type: string }
      required: [build_major, build_minor, build_id]
    Stats:
      type: object
      properties:
        call: { type: string }
        service: { type: string, enum: [ipm] }
        request_id: { type: string }
        timestamp_utc: { type: string, format: date-time }
        actor: { type: string }
        context_source:
          type: string
          enum: [session, api_key, operator, system]
        session_fingerprint:
          type: string
          description: Non-reversible SHA-256 fingerprint of the caller session for correlation.
        api_key_fingerprint:
          type: string
          description: Non-reversible SHA-256 fingerprint of the caller API key for correlation.
        orgcode: { type: string }
        cccode: { type: string }
        logical_guid: { type: string }
        channel_code: { type: string }
        roles:
          type: array
          items: { type: string }
        build:
          $ref: '#/components/schemas/BuildMeta'
      required: [call, service, timestamp_utc, build]
    ErrorMajor:
      type: object
      properties:
        tag: { type: string }
        message:
          type: object
          properties:
            en_US: { type: string }
      required: [tag, message]
    ErrorEnvelope:
      type: object
      properties:
        error_code: { type: string }
        http_status: { type: integer }
        retryable: { type: boolean }
        request_id: { type: string }
        trace_id: { type: string }
        major: { $ref: '#/components/schemas/ErrorMajor' }
        minor: { $ref: '#/components/schemas/ErrorMajor' }
        details: { type: object }
        conflict_snapshot: { type: object }
      required: [major]
    Envelope:
      type: object
      properties:
        success: { type: boolean }
        data: { type: object }
        error: { $ref: '#/components/schemas/ErrorEnvelope' }
        stats: { $ref: '#/components/schemas/Stats' }
        revision: { type: string }
      required: [success]
    SourceRef:
      type: object
      properties:
        kind: { type: string }
        id: { type: string }
      required: [kind, id]
    SourceRefs:
      type: array
      items: { $ref: '#/components/schemas/SourceRef' }
    RequestContext:
      type: object
      properties:
        orgcode: { type: string }
        logical_guid: { type: string }
        channel_code: { type: string }
        cccode: { type: string }
        actor: { type: string }
        roles:
          type: array
          items: { type: string }
        context_source:
          type: string
          enum: [session, api_key, operator, system]
        session_fingerprint: { type: string }
        api_key_fingerprint: { type: string }
      required: [orgcode, actor, context_source]
    WebhookFilter:
      type: object
      properties:
        service: { type: string }
        actions:
          type: array
          items: { type: string }
        channel_codes:
          type: array
          items: { type: string }
        logical_guids:
          type: array
          items: { type: string }
      required: [service]
    WebhookLastError:
      type: object
      properties:
        error_tag: { type: string }
        http_status: { type: integer }
    Webhook:
      type: object
      properties:
        webhook_id: { type: string }
        orgcode: { type: string }
        caption: { type: string }
        endpoint_url: { type: string }
        status: { type: string, enum: [active, paused, revoked] }
        event_filters:
          type: array
          items: { $ref: '#/components/schemas/WebhookFilter' }
        headers:
          type: object
          additionalProperties: { type: string }
        signing_algorithm: { type: string, enum: [hmac-sha256] }
        secret_last4: { type: string }
        request_context: { $ref: '#/components/schemas/RequestContext' }
        reason: { type: string }
        source_refs: { $ref: '#/components/schemas/SourceRefs' }
        status_request_context: { $ref: '#/components/schemas/RequestContext' }
        status_reason: { type: string }
        status_source_refs: { $ref: '#/components/schemas/SourceRefs' }
        consecutive_failures: { type: integer }
        last_success_at: { type: string, format: date-time }
        last_failure_at: { type: string, format: date-time }
        last_error: { $ref: '#/components/schemas/WebhookLastError' }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        revision: { type: string }
      required: [webhook_id, orgcode, endpoint_url, status, event_filters, signing_algorithm, secret_last4, created_at, updated_at, revision]
    WebhookCreateRequest:
      type: object
      properties:
        request_context: { $ref: '#/components/schemas/RequestContext' }
        reason: { type: string }
        source_refs: { $ref: '#/components/schemas/SourceRefs' }
        webhook_id: { type: string }
        caption: { type: string }
        endpoint_url: { type: string }
        status: { type: string, enum: [active, paused, revoked] }
        event_filters:
          type: array
          items: { $ref: '#/components/schemas/WebhookFilter' }
        headers:
          type: object
          additionalProperties: { type: string }
      required: [request_context, reason, source_refs, endpoint_url, event_filters]
    WebhookGetRequest:
      type: object
      properties:
        webhook_id: { type: string }
      required: [webhook_id]
    WebhookListRequest:
      type: object
      properties:
        limit: { type: integer }
        next_token: { type: object }
    WebhookStatusSetRequest:
      type: object
      properties:
        request_context: { $ref: '#/components/schemas/RequestContext' }
        reason: { type: string }
        source_refs: { $ref: '#/components/schemas/SourceRefs' }
        webhook_id: { type: string }
        status: { type: string, enum: [active, paused, revoked] }
        expected_revision: { type: string }
      required: [request_context, reason, source_refs, webhook_id, status, expected_revision]
    WebhookReplayRequest:
      type: object
      properties:
        request_context: { $ref: '#/components/schemas/RequestContext' }
        reason: { type: string }
        source_refs: { $ref: '#/components/schemas/SourceRefs' }
        webhook_id: { type: string }
        service: { type: string }
        from_utc: { type: string, format: date-time }
        to_utc: { type: string, format: date-time }
        limit: { type: integer }
        next_token: { type: object }
      required: [request_context, reason, source_refs, webhook_id]
    BulkJob:
      type: object
      properties:
        job_id: { type: string }
        job_type: { type: string, enum: [import, export] }
        target: { type: string, enum: [events] }
        format: { type: string, enum: [ndjson] }
        status: { type: string, enum: [awaiting_upload, queued, running, completed, failed] }
        input:
          type: object
          properties:
            bucket: { type: string }
            key: { type: string }
            content_type: { type: string }
            resume_from_line: { type: integer }
        output:
          type: object
          properties:
            bucket: { type: string }
            key: { type: string }
            record_count: { type: integer }
            next_token: { type: object }
            line_count: { type: integer }
            processed_count: { type: integer }
            error_count: { type: integer }
            skipped_lines: { type: integer }
        report:
          type: object
          properties:
            bucket: { type: string }
            key: { type: string }
            error_count: { type: integer }
        filter:
          type: object
          properties:
            service: { type: string }
            from_utc: { type: string, format: date-time }
            to_utc: { type: string, format: date-time }
            max_records: { type: integer }
            cursor: { type: object }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        revision: { type: string }
      required: [job_id, job_type, target, format, status, created_at, updated_at, revision]
    BulkImportCreateRequest:
      type: object
      x-idempotency-scope: orgcode+route+idempotency_key
      x-idempotency-retention-hours: 24
      x-idempotency-replay: return_original_response
      properties:
        request_context: { $ref: '#/components/schemas/RequestContext' }
        reason: { type: string }
        source_refs: { $ref: '#/components/schemas/SourceRefs' }
        job_id: { type: string }
        content_type: { type: string }
        idempotency_key: { type: string }
      required: [request_context, reason, source_refs]
    BulkImportCommitRequest:
      type: object
      properties:
        request_context: { $ref: '#/components/schemas/RequestContext' }
        reason: { type: string }
        source_refs: { $ref: '#/components/schemas/SourceRefs' }
        job_id: { type: string }
        expected_revision: { type: string }
      required: [request_context, job_id, expected_revision]
    BulkImportContinueRequest:
      type: object
      x-idempotency-scope: orgcode+route+idempotency_key
      x-idempotency-retention-hours: 24
      x-idempotency-replay: return_original_response
      properties:
        request_context: { $ref: '#/components/schemas/RequestContext' }
        reason: { type: string }
        source_refs: { $ref: '#/components/schemas/SourceRefs' }
        job_id: { type: string }
        new_job_id: { type: string }
        resume_from_line: { type: integer }
        idempotency_key: { type: string }
      required: [request_context, reason, source_refs, job_id]
    BulkExportCreateRequest:
      type: object
      x-idempotency-scope: orgcode+route+idempotency_key
      x-idempotency-retention-hours: 24
      x-idempotency-replay: return_original_response
      properties:
        request_context: { $ref: '#/components/schemas/RequestContext' }
        reason: { type: string }
        source_refs: { $ref: '#/components/schemas/SourceRefs' }
        job_id: { type: string }
        service: { type: string }
        from_utc: { type: string, format: date-time }
        to_utc: { type: string, format: date-time }
        max_records: { type: integer }
        cursor: { type: object }
        idempotency_key: { type: string }
      required: [request_context, reason, source_refs]
    BulkExportContinueRequest:
      type: object
      x-idempotency-scope: orgcode+route+idempotency_key
      x-idempotency-retention-hours: 24
      x-idempotency-replay: return_original_response
      properties:
        request_context: { $ref: '#/components/schemas/RequestContext' }
        reason: { type: string }
        source_refs: { $ref: '#/components/schemas/SourceRefs' }
        job_id: { type: string }
        new_job_id: { type: string }
        max_records: { type: integer }
        idempotency_key: { type: string }
      required: [request_context, reason, source_refs, job_id]
    BulkJobGetRequest:
      type: object
      properties:
        job_id: { type: string }
      required: [job_id]
    BulkJobListRequest:
      type: object
      properties:
        status: { type: string }
        job_type: { type: string }
        limit: { type: integer }
        next_token: { type: object }
    EventCatalogListRequest:
      type: object
      properties:
        service: { type: string }
        action: { type: string }
        action_prefix: { type: string }
        limit: { type: integer }
        next_token: { type: object }
    LifecycleListRequest:
      type: object
      properties:
        service: { type: string }
        name: { type: string }
    SpecimenGetRequest:
      type: object
      properties:
        name: { type: string }
    PaginationGetRequest:
      type: object
      properties: {}
    CdcListRequest:
      type: object
      properties:
        service: { type: string }
        logical_guid: { type: string }
        channel_code: { type: string }
        from_utc: { type: string, format: date-time }
        to_utc: { type: string, format: date-time }
        limit: { type: integer }
        next_token: { type: object }
    ExportContract:
      type: object
      properties:
        service: { type: string }
        stream: { type: string, enum: [events] }
        format: { type: string, enum: [ndjson] }
        partitioning:
          type: array
          items: { type: string }
        actions:
          type: array
          items: { type: string }
        schema_url: { type: string }
        schema_version: { type: string }
        redaction: { type: object }
      required: [service, stream, format, partitioning, actions]
    ExportContractListRequest:
      type: object
      properties:
        service: { type: string }
        limit: { type: integer }
        next_token: { type: object }
    KpiDimensions:
      type: object
      additionalProperties: true
    KpiSnapshot:
      type: object
      properties:
        kpi_id: { type: string }
        kpi_code: { type: string }
        scope: { type: string, enum: [org, facility] }
        logical_guid: { type: string }
        channel_code: { type: string }
        bucket: { type: string, enum: [hourly, daily, weekly, monthly, seasonal, fiscal] }
        bucket_start: { type: string, format: date-time }
        bucket_end: { type: string, format: date-time }
        value: { type: number }
        value_unit: { type: string }
        sample_count: { type: number }
        dimensions: { $ref: '#/components/schemas/KpiDimensions' }
        computed_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        revision: { type: string }
      required: [kpi_id, kpi_code, scope, bucket, bucket_start, bucket_end, value]
    KpiThreshold:
      type: object
      properties:
        comparator: { type: string, enum: [gt, gte, lt, lte, eq, neq] }
        value: { type: number }
      required: [comparator, value]
    KpiAlert:
      type: object
      properties:
        alert_id: { type: string }
        kpi_code: { type: string }
        value: { type: number }
        threshold: { $ref: '#/components/schemas/KpiThreshold' }
        scope: { type: string, enum: [org, facility] }
        logical_guid: { type: string }
        channel_code: { type: string }
        bucket: { type: string, enum: [hourly, daily, weekly, monthly, seasonal, fiscal] }
        bucket_start: { type: string, format: date-time }
        bucket_end: { type: string, format: date-time }
        dimensions: { $ref: '#/components/schemas/KpiDimensions' }
        title: { type: string }
        body: { type: string }
        team_guid: { type: string }
        inbox_service: { type: string, enum: [scm, ics, pcm] }
        inbox_notification_id: { type: string }
        delivery_status: { type: string }
        delivery_error: { type: object }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        revision: { type: string }
      required: [alert_id, kpi_code, value, threshold, scope, bucket, bucket_start, bucket_end]
    KpiRecordRequest:
      type: object
      x-idempotency-scope: orgcode+route+idempotency_key
      x-idempotency-retention-hours: 24
      x-idempotency-replay: return_original_response
      properties:
        request_context: { $ref: '#/components/schemas/RequestContext' }
        reason: { type: string }
        source_refs: { $ref: '#/components/schemas/SourceRefs' }
        idempotency_key: { type: string }
        policy_refs: { type: object }
        kpi:
          type: object
          properties:
            kpi_id: { type: string }
            kpi_code: { type: string }
            scope: { type: string, enum: [org, facility] }
            logical_guid: { type: string }
            channel_code: { type: string }
            bucket: { type: string, enum: [hourly, daily, weekly, monthly, seasonal, fiscal] }
            bucket_start: { type: string, format: date-time }
            bucket_end: { type: string, format: date-time }
            value: { type: number }
            value_unit: { type: string }
            sample_count: { type: number }
            dimensions: { $ref: '#/components/schemas/KpiDimensions' }
            computed_at: { type: string, format: date-time }
          required: [kpi_code, bucket, bucket_start, bucket_end, value]
      required: [request_context, reason, source_refs, kpi]
    KpiQueryRequest:
      type: object
      properties:
        kpi_code: { type: string }
        scope: { type: string, enum: [org, facility] }
        logical_guid: { type: string }
        channel_code: { type: string }
        bucket: { type: string, enum: [hourly, daily, weekly, monthly, seasonal, fiscal] }
        from_utc: { type: string, format: date-time }
        to_utc: { type: string, format: date-time }
        dimensions: { $ref: '#/components/schemas/KpiDimensions' }
        limit: { type: integer }
        next_token: { type: object }
      required: [kpi_code, bucket, from_utc, to_utc]
    KpiAlertRequest:
      type: object
      x-idempotency-scope: orgcode+route+idempotency_key
      x-idempotency-retention-hours: 24
      x-idempotency-replay: return_original_response
      properties:
        request_context: { $ref: '#/components/schemas/RequestContext' }
        reason: { type: string }
        source_refs: { $ref: '#/components/schemas/SourceRefs' }
        idempotency_key: { type: string }
        alert:
          type: object
          properties:
            alert_id: { type: string }
            kpi_code: { type: string }
            value: { type: number }
            threshold: { $ref: '#/components/schemas/KpiThreshold' }
            scope: { type: string, enum: [org, facility] }
            logical_guid: { type: string }
            channel_code: { type: string }
            bucket: { type: string, enum: [hourly, daily, weekly, monthly, seasonal, fiscal] }
            bucket_start: { type: string, format: date-time }
            bucket_end: { type: string, format: date-time }
            dimensions: { $ref: '#/components/schemas/KpiDimensions' }
            title: { type: string }
            body: { type: string }
            team_guid: { type: string }
            inbox_service: { type: string, enum: [scm, ics, pcm] }
            deliver_to_inbox: { type: boolean }
            notification_id: { type: string }
            priority: { type: string, enum: [low, medium, high, show_stopper] }
            act_by: { type: string, format: date-time }
            status: { type: string, enum: [inbox, archived] }
            state: { type: string, enum: [pending, completed, deferred] }
          required: [kpi_code, value, threshold, bucket, bucket_start, bucket_end]
      required: [request_context, reason, source_refs, alert]
paths:
  /stat:
    get:
      summary: Health check
      description: |
        Health check. Tenant routes require x-orgcode; /stat health checks do not. Org-scoped reads may
        return 404 for non-associated callers (anti-enumeration). Route class Tier A (p95 500ms).
      x-route-class: Tier A
      x-qps-target: 5
      x-concurrency-target: 2
      x-latency-p95-ms: 500
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          service: { type: string }
                          status: { type: string }
  /event/catalog/list:
    post:
      summary: List event catalog entries
      description: |
        List event catalog entries. Tenant routes require x-orgcode; /stat health checks do not. Paginated
        with limit/next_token (default 8; clamp 1–256). Route class Tier B (p95 300ms).
      x-route-class: Tier B
      x-qps-target: 50
      x-concurrency-target: 25
      x-latency-p95-ms: 300
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EventCatalogListRequest' }
      responses:
        '200':
          description: Event catalog list
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              type: object
                              properties:
                                service: { type: string }
                                action: { type: string }
                                schema_url: { type: string }
                                schema_version: { type: string }
                                redaction: { type: object }
                          next_token: { type: object }
  /export/contract/list:
    post:
      summary: List export contracts aligned to event streams
      description: |
        List export contracts aligned to event streams. Tenant routes require x-orgcode; /stat health checks
        do not. Paginated with limit/next_token (default 8; clamp 1–256). Route class Tier B (p95 300ms).
      x-route-class: Tier B
      x-qps-target: 50
      x-concurrency-target: 25
      x-latency-p95-ms: 300
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ExportContractListRequest' }
      responses:
        '200':
          description: Export contract list
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          contracts:
                            type: array
                            items: { $ref: '#/components/schemas/ExportContract' }
                          next_token: { type: object }
  /kpi/query:
    post:
      summary: Query KPI snapshots
      description: |
        Query KPI snapshots. Tenant routes require x-orgcode; /stat health checks do not. Route class Tier C
        (p95 1200ms, p99 2500ms).
      x-route-class: Tier C
      x-qps-target: 25
      x-concurrency-target: 10
      x-latency-p95-ms: 1200
      x-latency-p99-ms: 2500
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/KpiQueryRequest' }
      responses:
        '200':
          description: KPI query results
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          series:
                            type: array
                            items: { $ref: '#/components/schemas/KpiSnapshot' }
                          next_token: { type: object }
  /kpi/record:
    post:
      summary: Record KPI snapshot
      description: |
        Record KPI snapshot. Tenant routes require x-orgcode; /stat health checks do not. If this updates a
        revisioned record, expected_revision is required (428 if missing; 409 on mismatch). Route class Tier
        D (p95 1500ms, p99 3000ms).
      x-route-class: Tier D
      x-qps-target: 10
      x-concurrency-target: 5
      x-latency-p95-ms: 1500
      x-latency-p99-ms: 3000
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/KpiRecordRequest' }
      responses:
        '200':
          description: KPI record created
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          kpi: { $ref: '#/components/schemas/KpiSnapshot' }
  /kpi/alert:
    post:
      summary: Emit KPI alert (optionally sends inbox notification)
      description: |
        Emit KPI alert (optionally sends inbox notification). Tenant routes require x-orgcode; /stat health
        checks do not. Route class Tier D (p95 1500ms, p99 3000ms).
      x-route-class: Tier D
      x-qps-target: 10
      x-concurrency-target: 5
      x-latency-p95-ms: 1500
      x-latency-p99-ms: 3000
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/KpiAlertRequest' }
      responses:
        '200':
          description: KPI alert recorded
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          alert: { $ref: '#/components/schemas/KpiAlert' }
  /lifecycle/list:
    post:
      summary: List lifecycle/state-machine entries
      description: |
        List lifecycle/state-machine entries. Tenant routes require x-orgcode; /stat health checks do not.
        Paginated with limit/next_token (default 8; clamp 1–256). Route class Tier B (p95 300ms).
      x-route-class: Tier B
      x-qps-target: 50
      x-concurrency-target: 25
      x-latency-p95-ms: 300
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/LifecycleListRequest' }
      responses:
        '200':
          description: Lifecycle list
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items:
                              type: object
                              properties:
                                name: { type: string }
                                service: { type: string }
                                states:
                                  type: array
                                  items: { type: string }
                                transitions:
                                  type: array
                                  items:
                                    type: object
                                    properties:
                                      from: { type: string }
                                      to: { type: string }
                                      rule: { type: string }
                          version: { type: string }
  /specimen/get:
    post:
      summary: Fetch specimen payloads
      description: |
        Fetch specimen payloads. Tenant routes require x-orgcode; /stat health checks do not. Org-scoped
        reads may return 404 for non-associated callers (anti-enumeration). Route class Tier B (p95 300ms).
      x-route-class: Tier B
      x-qps-target: 50
      x-concurrency-target: 25
      x-latency-p95-ms: 300
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SpecimenGetRequest' }
      responses:
        '200':
          description: Specimen payloads
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          name: { type: string }
                          specimen: { type: object }
                          specimens: { type: object }
  /pagination/get:
    post:
      summary: Fetch pagination conventions
      description: |
        Fetch pagination conventions. Tenant routes require x-orgcode; /stat health checks do not.
        Org-scoped reads may return 404 for non-associated callers (anti-enumeration). Route class Tier B
        (p95 300ms).
      x-route-class: Tier B
      x-qps-target: 50
      x-concurrency-target: 25
      x-latency-p95-ms: 300
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PaginationGetRequest' }
      responses:
        '200':
          description: Pagination conventions
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Envelope' }
  /webhook/create:
    post:
      summary: Create a webhook subscription
      description: |
        Create a webhook subscription. Tenant routes require x-orgcode; /stat health checks do not. If this
        updates a revisioned record, expected_revision is required (428 if missing; 409 on mismatch). Route
        class Tier D (p95 300ms, p99 600ms).
      x-route-class: Tier D
      x-qps-target: 20
      x-concurrency-target: 10
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookCreateRequest' }
      responses:
        '200':
          description: Webhook created
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        allOf:
                          - { $ref: '#/components/schemas/Webhook' }
                          - type: object
                            properties:
                              signing_secret: { type: string }
  /webhook/get:
    post:
      summary: Get a webhook
      description: |
        Get a webhook. Tenant routes require x-orgcode; /stat health checks do not. Org-scoped reads may
        return 404 for non-associated callers (anti-enumeration). Route class Tier B (p95 300ms).
      x-route-class: Tier B
      x-qps-target: 50
      x-concurrency-target: 25
      x-latency-p95-ms: 300
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookGetRequest' }
      responses:
        '200':
          description: Webhook get
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          webhook: { $ref: '#/components/schemas/Webhook' }
  /webhook/list:
    post:
      summary: List webhooks
      description: |
        List webhooks. Tenant routes require x-orgcode; /stat health checks do not. Paginated with
        limit/next_token (default 8; clamp 1–256). Route class Tier B (p95 300ms).
      x-route-class: Tier B
      x-qps-target: 50
      x-concurrency-target: 25
      x-latency-p95-ms: 300
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookListRequest' }
      responses:
        '200':
          description: Webhook list
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items: { $ref: '#/components/schemas/Webhook' }
                          next_token: { type: object }
  /webhook/status/set:
    post:
      summary: Update webhook status
      description: |
        Update webhook status. Tenant routes require x-orgcode; /stat health checks do not. If this updates
        a revisioned record, expected_revision is required (428 if missing; 409 on mismatch). Route class
        Tier D (p95 300ms, p99 600ms).
      x-route-class: Tier D
      x-qps-target: 20
      x-concurrency-target: 10
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookStatusSetRequest' }
      responses:
        '200':
          description: Webhook status updated
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          webhook: { $ref: '#/components/schemas/Webhook' }
  /webhook/replay:
    post:
      summary: Replay webhook deliveries from the event store
      description: |
        Replay webhook deliveries from the event store. Tenant routes require x-orgcode; /stat health checks
        do not. Route class Tier D (p95 500ms, p99 1000ms).
      x-route-class: Tier D
      x-qps-target: 5
      x-concurrency-target: 2
      x-latency-p95-ms: 500
      x-latency-p99-ms: 1000
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookReplayRequest' }
      responses:
        '200':
          description: Webhook replay triggered
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          webhook_id: { type: string }
                          delivered: { type: integer }
                          failed: { type: integer }
                          next_token: { type: object }
  /cdc/list:
    post:
      summary: List CDC events
      description: |
        List CDC events. Tenant routes require x-orgcode; /stat health checks do not. Paginated with
        limit/next_token (default 8; clamp 1–256). Route class Tier C (p95 150ms, p99 400ms).
      x-route-class: Tier C
      x-qps-target: 100
      x-concurrency-target: 50
      x-latency-p95-ms: 150
      x-latency-p99-ms: 400
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CdcListRequest' }
      responses:
        '200':
          description: CDC list
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items: { type: object }
                          next_token: { type: object }
  /bulk/import/create:
    post:
      summary: Create a bulk import job
      description: |
        Create a bulk import job. Tenant routes require x-orgcode; /stat health checks do not. If this
        updates a revisioned record, expected_revision is required (428 if missing; 409 on mismatch). Route
        class Tier D (p95 300ms, p99 600ms).
      x-route-class: Tier D
      x-qps-target: 10
      x-concurrency-target: 5
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BulkImportCreateRequest' }
      responses:
        '200':
          description: Bulk import job created
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          job: { $ref: '#/components/schemas/BulkJob' }
                          upload_url: { type: string }
  /bulk/import/continue:
    post:
      summary: Continue a bulk import job from the last processed line
      description: |
        Continue a bulk import job from the last processed line. Tenant routes require x-orgcode; /stat
        health checks do not. Route class Tier D (p95 300ms, p99 600ms).
      x-route-class: Tier D
      x-qps-target: 10
      x-concurrency-target: 5
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BulkImportContinueRequest' }
      responses:
        '200':
          description: Bulk import continuation job created
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          job: { $ref: '#/components/schemas/BulkJob' }
  /bulk/import/commit:
    post:
      summary: Commit a bulk import job after upload
      description: |
        Commit a bulk import job after upload. Tenant routes require x-orgcode; /stat health checks do not.
        If this updates a revisioned record, expected_revision is required (428 if missing; 409 on
        mismatch). Route class Tier D (p95 300ms, p99 600ms).
      x-route-class: Tier D
      x-qps-target: 10
      x-concurrency-target: 5
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BulkImportCommitRequest' }
      responses:
        '200':
          description: Bulk import committed
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          job: { $ref: '#/components/schemas/BulkJob' }
  /bulk/export/create:
    post:
      summary: Create a bulk export job
      description: |
        Create a bulk export job. Tenant routes require x-orgcode; /stat health checks do not. If this
        updates a revisioned record, expected_revision is required (428 if missing; 409 on mismatch). Route
        class Tier D (p95 300ms, p99 600ms).
      x-route-class: Tier D
      x-qps-target: 10
      x-concurrency-target: 5
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BulkExportCreateRequest' }
      responses:
        '200':
          description: Bulk export job created
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          job: { $ref: '#/components/schemas/BulkJob' }
  /bulk/export/continue:
    post:
      summary: Continue a bulk export job using the previous cursor
      description: |
        Continue a bulk export job using the previous cursor. Tenant routes require x-orgcode; /stat health
        checks do not. Route class Tier D (p95 300ms, p99 600ms).
      x-route-class: Tier D
      x-qps-target: 10
      x-concurrency-target: 5
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - $ref: '#/components/parameters/CostCentreHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BulkExportContinueRequest' }
      responses:
        '200':
          description: Bulk export continuation job created
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          job: { $ref: '#/components/schemas/BulkJob' }
  /bulk/job/get:
    post:
      summary: Get bulk job status
      description: |
        Get bulk job status. Tenant routes require x-orgcode; /stat health checks do not. Org-scoped reads
        may return 404 for non-associated callers (anti-enumeration). Route class Tier B (p95 300ms).
      x-route-class: Tier B
      x-qps-target: 50
      x-concurrency-target: 25
      x-latency-p95-ms: 300
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BulkJobGetRequest' }
      responses:
        '200':
          description: Bulk job get
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          job: { $ref: '#/components/schemas/BulkJob' }
                          download_url: { type: string }
                          report_url: { type: string }
  /bulk/job/list:
    post:
      summary: List bulk jobs
      description: |
        List bulk jobs. Tenant routes require x-orgcode; /stat health checks do not. Paginated with
        limit/next_token (default 8; clamp 1–256). Route class Tier B (p95 300ms).
      x-route-class: Tier B
      x-qps-target: 50
      x-concurrency-target: 25
      x-latency-p95-ms: 300
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BulkJobListRequest' }
      responses:
        '200':
          description: Bulk job list
          content:
            application/json:
              schema:
                allOf:
                  - { $ref: '#/components/schemas/Envelope' }
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items: { $ref: '#/components/schemas/BulkJob' }
                          next_token: { type: object }
