openapi: 3.1.0
info:
  title: PMC API
  version: 0.1.0-draft
  description: |
    Product Merchandising Control (PMC).

    Contract notes:
    - API Gateway base path: `/pmc`
    - All data routes are org-gated: supply `x-orgcode`. `GET /build/stats` does not require `x-orgcode`.
    - Auth: either `x-session-guid` (user session) or `x-api-key` (org-bound service account). Headers are canonical; body `session_guid`/`api_key` accepted for compatibility on POST.
    - Optional cost attribution: provide `x-cccode` (or request field `cccode`). Format `^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$`, canonicalized to uppercase; invalid values (or body/header mismatch) return HTTP 400.
    - Roles:
      - Read endpoints require `pmc_view` (or owner implied; also accepts `pvv`/Viewer and `pma`/Admin synonyms).
      - Write endpoints require `pmc_publish` (or owner implied; also accepts `pma`/Admin synonyms).
    - Core model:
      - A PMC product identity is `PVM variant × OFM sales channel` (sales channel implies logical facility + market/locale).
      - Publishing creates an immutable revision record with required `reason` (publish-time stamping).
      - Exactly one revision may be online at a time for a given product (or none).
    - Auto-offline:
      - When upstream PVM containers become ineligible (status != active), PMC clears the online revision (drift window: minutes).
servers:
  - url: https://api.g3nretailstack.com/pmc
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 }
  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: [pmc] }
        request_id: { type: string }
        timestamp_utc: { type: string, format: date-time }
        build: { $ref: '#/components/schemas/BuildMeta' }
        actor: { type: string }
        context_source:
          type: string
          enum: [session, api_key, operator, system]
        session_fingerprint:
          type: string
          description: Non-reversible SHA-256 fingerprint of a caller-provided `session_guid` (never an auth credential).
        api_key_fingerprint:
          type: string
          description: Non-reversible SHA-256 fingerprint of a caller-provided `api_key` (never an auth credential).
        orgcode: { type: string }
        cccode: { type: string }
        logical_guid: { type: string }
        channel_code: { type: string }
        roles:
          type: array
          items: { type: string }
      required: [call, service, timestamp_utc, build]
    ErrorMessage:
      type: object
      properties:
        en_US: { type: string }
      required: [en_US]
    ErrorTag:
      type: object
      properties:
        tag: { type: string }
        message: { $ref: '#/components/schemas/ErrorMessage' }
      required: [tag, message]
    Error:
      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/ErrorTag' }
        minor: { $ref: '#/components/schemas/ErrorTag' }
        details: {}
      required: [major]
    Envelope:
      type: object
      properties:
        success: { type: boolean }
        data: {}
        stats: { $ref: '#/components/schemas/Stats' }
        error: { $ref: '#/components/schemas/Error' }
        revision: { type: string }
      required: [success]
    NextToken:
      type: object
      description: Opaque cursor; pass back as JSON in `next_token`.
      additionalProperties: true

    PmcGroupBucket:
      type: object
      properties:
        value: { type: string }
        count: { type: integer }
      required: [value, count]

    PmcProduct:
      type: object
      properties:
        product_id: { type: string }
        product_key: { type: string }
        variant_id: { type: string }
        variant_code: { type: string }
        sku: { type: string }
        style_id: { type: string }
        style_code: { type: string }
        brand_id: { type: string }
        category_id: { type: string }
        category_code: { type: string }
        department_id: { type: string }
        department_code: { type: string }
        division_id: { type: string }
        division_code: { type: string }
        primary_vendor_id: { type: string }
        primary_manufacturer_id: { type: string }
        channel_guid: { type: string, description: OFM sales channel GUID }
        channel_code: { type: string }
        market_code: { type: string }
        logical_guid: { type: string }
        locale_codes: { type: array, items: { type: string } }
        default_locale_code: { type: string }
        identifier_keys: { type: array, items: { type: string } }
        alias_keys: { type: array, items: { type: string } }
        barcode_values:
          type: array
          description: Barcode digit strings (GTIN/UPC/EAN), snapshot at publish time.
          items: { type: string }
        pack_refs:
          type: array
          items: { $ref: '#/components/schemas/PackRef' }
        taxonomy_version: { type: string }
        biz_unit_code: { type: string }
        region_code: { type: string }
        country_code: { type: string }
        online_rev: { type: integer }
        online_rev_id: { type: string }
        online_reason_code: { type: string }
        offline_reason_code: { type: string }
        max_rev: { type: integer }
        last_publish_reason: { type: string }
        last_publish_run_id: { type: string }
        last_publish_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        revision: { type: string }

    PmcProductRevision:
      type: object
      properties:
        product_id: { type: string }
        product_key: { type: string }
        rev: { type: integer }
        rev_id: { type: string }
        online: { type: boolean }
        publish_run_id: { type: string }
        publish_reason: { type: string }
        published_at: { type: string, format: date-time }
        actor: { type: string }
        session_fingerprint: { type: string }
        api_key_fingerprint: { type: string }
        pack_refs:
          type: array
          items: { $ref: '#/components/schemas/PackRef' }
        taxonomy_version: { type: string }
        biz_unit_code: { type: string }
        region_code: { type: string }
        country_code: { type: string }
        pvm:
          type: object
          description: Published snapshot (schema may evolve; treat as payload with best-effort stability).
          additionalProperties: true

    PublishProfile:
      type: object
      properties:
        channel_code: { type: string }
        required_sku: { type: boolean }
        required_identifier_types:
          type: array
          items: { type: string }
        required_alias_tags:
          type: array
          items: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        revision: { type: string }

    PublishRun:
      type: object
      properties:
        run_id: { type: string }
        status: { type: string, enum: [running, completed, canceled] }
        reason: { type: string }
        pack_refs:
          type: array
          items: { $ref: '#/components/schemas/PackRef' }
        taxonomy_version: { type: string }
        biz_unit_code: { type: string }
        region_code: { type: string }
        country_code: { type: string }
        manifest_bucket: { type: string }
        manifest_prefix: { type: string }
        manifest_schema_version: { type: integer }
        manifest_parts: { type: integer }
        selector:
          type: object
          additionalProperties: true
        channels:
          type: object
          additionalProperties: true
        pvm_next_token: {}
        counts:
          type: object
          additionalProperties: true
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        revision: { type: string }

    PackRef:
      type: object
      properties:
        pack_id: { type: string }
        pack_version: { type: integer }
        pack_kind: { type: string }
        content_hash: { type: string }
        content_size_bytes: { type: integer }
        content_type: { type: string }

    ContentPack:
      type: object
      properties:
        pack_id: { type: string }
        pack_version: { type: integer }
        pack_kind: { type: string }
        pack_status: { type: string, enum: [active, pending_upload] }
        content_hash: { type: string }
        content_size_bytes: { type: integer }
        content_type: { type: string }
        content_encoding: { type: string }
        referenced: { type: boolean }
        referenced_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        revision: { type: string }

    PackPresign:
      type: object
      properties:
        upload_url: { type: string }
        method: { type: string }
        headers:
          type: object
          additionalProperties: true
        expires_at: { type: string, format: date-time }
        upload_token: { type: string }

    ProductPointer:
      type: object
      properties:
        product_key: { type: string }
        pointer_kind: { type: string, enum: [staged, preview, fallback] }
        rev: { type: integer }
        rev_id: { type: string }
        reason: { type: string }
        effective_at: { type: string, format: date-time }
        expires_at: { type: string, format: date-time }
        set_at: { type: string, format: date-time }
        set_by: { type: string }
        updated_at: { type: string, format: date-time }
        revision: { type: string }

    ManifestPart:
      type: object
      properties:
        run_id: { type: string }
        manifest_part: { type: integer }
        manifest_bucket: { type: string }
        manifest_key: { type: string }
        manifest_hash: { type: string }
        manifest_size_bytes: { type: integer }
        manifest_schema_version: { type: integer }
        created_at: { type: string, format: date-time }

paths:
  /build/stats:
    get:
      summary: Build stats
      description: Lightweight health/build response for PMC.
      x-route-class: Tier B
      x-qps-target: 300
      x-concurrency-target: 500
      x-latency-p95-ms: 300
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          service: { type: string }
                          op: { type: string }
                          timestamp_utc: { type: string, format: date-time }

  /product/get:
    get:
      summary: Get PMC product
      description: |
        Get PMC product. Auth is via headers; org identity uses x-orgcode or body placement as documented.
        Org-scoped reads may return 404 for non-associated callers (anti-enumeration). Route class Tier C
        (p95 150ms, p99 400ms).
      x-route-class: Tier C
      x-qps-target: 2500
      x-concurrency-target: 3000
      x-latency-p95-ms: 150
      x-latency-p99-ms: 400
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - in: query
          name: product_id
          schema: { type: string }
        - in: query
          name: variant_id
          schema: { type: string }
        - in: query
          name: channel_guid
          schema: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          product: { $ref: '#/components/schemas/PmcProduct' }
                          online_revision: { $ref: '#/components/schemas/PmcProductRevision' }

  /product/list:
    get:
      summary: List PMC products (one filter at a time)
      description: |
        Lists products by a single high-level dimension (or org-wide if no filter). Use `online_state=online|offline|both` to select online/offline/both products (legacy: `only_online=true` → `online_state=online`).
        Exact-match only: all filters are strict equality after service-side canonicalization (trim; codes uppercased; alias values whitespace-collapsed + uppercased).
        Pagination uses an opaque JSON `next_token`.
      x-route-class: Tier C
      x-qps-target: 2000
      x-concurrency-target: 2500
      x-latency-p95-ms: 150
      x-latency-p99-ms: 400
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - in: query
          name: online_state
          schema: { type: string, enum: [online, offline, both], default: both }
        - in: query
          name: only_online
          schema: { type: boolean, default: false }
          deprecated: true
        - in: query
          name: variant_id
          schema: { type: string }
        - in: query
          name: style_id
          schema: { type: string }
        - in: query
          name: brand_id
          schema: { type: string }
        - in: query
          name: vendor_id
          schema: { type: string }
        - in: query
          name: manufacturer_id
          schema: { type: string }
        - in: query
          name: category_id
          schema: { type: string }
        - in: query
          name: department_id
          schema: { type: string }
        - in: query
          name: division_id
          schema: { type: string }
        - in: query
          name: channel_guid
          schema: { type: string }
        - in: query
          name: logical_guid
          schema: { type: string }
        - in: query
          name: style_code
          schema: { type: string }
        - in: query
          name: category_code
          schema: { type: string }
        - in: query
          name: department_code
          schema: { type: string }
        - in: query
          name: division_code
          schema: { type: string }
        - in: query
          name: channel_code
          schema: { type: string }
        - in: query
          name: market_code
          schema: { type: string }
        - in: query
          name: locale_code
          schema: { type: string }
        - in: query
          name: default_locale_code
          schema: { type: string }
        - in: query
          name: variant_code
          schema: { type: string }
        - in: query
          name: sku
          schema: { type: string }
        - in: query
          name: identifier_type
          schema: { type: string }
        - in: query
          name: identifier_value
          schema: { type: string }
        - in: query
          name: alias_tag
          schema: { type: string }
        - in: query
          name: alias_value
          schema: { type: string }
        - in: query
          name: barcode_value
          schema: { type: string }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 256, default: 8 }
        - in: query
          name: next_token
          schema: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items: {}
                          next_token: { $ref: '#/components/schemas/NextToken' }

  /product/search:
    get:
      summary: Search PMC products (partial match)
      description: |
        Partial-match search for operational workflows (prefix/contains/regex) backed by the PMC search plane (OpenSearch Serverless).
        Results are org-gated and returned as PMC product heads (query-authoritative; PMC does not consult PVM for queries).
        Use `online_state=online|offline|both` to filter by online pointer.
        Pagination uses an opaque JSON `next_token` (search-after cursor).
        Guardrails:
        - `mode=contains` requires `q` length ≥ 3; `mode=prefix` requires ≥ 2.
        - `field=barcode` requires digits-only `q` length ≥ 4.
        - `mode=regex` requires `pmc_publish` (or owner) and is applied to the normalized lowercased search text.
      x-route-class: Tier C
      x-qps-target: 2000
      x-concurrency-target: 2500
      x-latency-p95-ms: 150
      x-latency-p99-ms: 400
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - in: query
          name: q
          required: true
          schema: { type: string }
        - in: query
          name: field
          schema:
            type: string
            enum: [any, sku, code, barcode, identifier, alias]
            default: any
        - in: query
          name: mode
          schema: { type: string, enum: [prefix, contains, regex], default: contains }
        - in: query
          name: online_state
          schema: { type: string, enum: [online, offline, both], default: both }
        - in: query
          name: channel_guid
          schema: { type: string }
        - in: query
          name: logical_guid
          schema: { type: string }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 256, default: 8 }
        - in: query
          name: next_token
          schema: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items: {}
                          next_token: { $ref: '#/components/schemas/NextToken' }

  /product/group:
    post:
      summary: Group PMC products by dimension (bounded)
      description: |
        Returns counts grouped by a single dimension (bounded). If `values` is provided, counts are returned for those exact values.
        When `values` is omitted, returns top buckets up to `limit` (default 50).
        For category/department/division group-by (IDs or codes), require `channel_guid`, `channel_code`, or `logical_guid`.
        Uses PMC rollup table for bounded `values[]` when available; falls back to the PMC search plane (OpenSearch Serverless) for top buckets or missing rollups.
        Results are org-gated and honor `online_state`.
      x-route-class: Tier C
      x-qps-target: 2000
      x-concurrency-target: 2500
      x-latency-p95-ms: 150
      x-latency-p99-ms: 400
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                dimension:
                  type: string
                  enum:
                    [
                      variant_id,
                      style_id,
                      brand_id,
                      vendor_id,
                      manufacturer_id,
                      category_id,
                      department_id,
                      division_id,
                      channel_guid,
                      logical_guid,
                      style_code,
                      category_code,
                      department_code,
                      division_code,
                      channel_code,
                      market_code,
                      locale_code,
                      default_locale_code,
                      biz_unit_code,
                      region_code,
                      country_code
                    ]
                values:
                  type: array
                  items: { type: string }
                online_state:
                  type: string
                  enum: [online, offline, both]
                  default: both
                channel_guid: { type: string }
                channel_code: { type: string }
                logical_guid: { type: string }
                limit:
                  type: integer
                  minimum: 1
                  maximum: 256
                  default: 50
                session_guid: { type: string }
                api_key: { type: string }
                cccode: { type: string }
              required: [dimension]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          dimension: { type: string }
                          online_state: { type: string }
                          channel_guid: { type: string }
                          channel_code: { type: string }
                          logical_guid: { type: string }
                          groups:
                            type: array
                            items: { $ref: '#/components/schemas/PmcGroupBucket' }

  /product/revision/list:
    get:
      summary: List product revisions
      description: |
        List product revisions. Auth is via headers; org identity uses x-orgcode or body placement as
        documented. Paginated with limit/next_token (default 8; clamp 1–256). 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: 300
      x-concurrency-target: 500
      x-latency-p95-ms: 300
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - in: query
          name: product_id
          schema: { type: string }
        - in: query
          name: variant_id
          schema: { type: string }
        - in: query
          name: channel_guid
          schema: { type: string }
        - in: query
          name: online_state
          schema: { type: string, enum: [online, offline, both], default: both }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 256, default: 8 }
        - in: query
          name: next_token
          schema: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items: { $ref: '#/components/schemas/PmcProductRevision' }
                          next_token: { $ref: '#/components/schemas/NextToken' }

  /product/online/set:
    post:
      summary: Set online revision
      description: |
        Sets which revision is online for a product. Requires `expected_revision` for optimistic concurrency.
        Only one revision may be online (or none).
      x-route-class: Tier D
      x-qps-target: 1000
      x-concurrency-target: 2000
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/OrgHeader' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                product_id: { type: string }
                variant_id: { type: string }
                channel_guid: { type: string }
                expected_revision: { type: string }
                rev: { type: integer }
                rev_id: { type: string }
                reason_code: { type: string }
              required: [expected_revision, rev, rev_id]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          run_id: { type: string }
                          status: { type: string }
                          revision: { type: string }
                          pvm_next_token: {}
                          processed: { type: integer }
                          publish_ok: { type: integer }
                          publish_skipped: { type: integer }
                          skipped_reasons:
                            type: object
                            additionalProperties: true
                          manifest_part: { type: integer }
                          manifest_schema_version: { type: integer }
                          manifest_key: { type: string }
                          manifest_hash: { type: string }
                          results:
                            type: array
                            items:
                              type: object
                              additionalProperties: true

  /product/online/clear:
    post:
      summary: Clear online revision (set none online)
      description: |
        Clear online revision (set none online). Auth is via headers; org identity uses x-orgcode or body
        placement as documented. Route class Tier D (p95 300ms, p99 600ms).
      x-route-class: Tier D
      x-qps-target: 1000
      x-concurrency-target: 2000
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/OrgHeader' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                product_id: { type: string }
                variant_id: { type: string }
                channel_guid: { type: string }
                expected_revision: { type: string }
                reason_code: { type: string }
              required: [expected_revision]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Envelope' }

  /product/pointer/get:
    get:
      summary: Get product pointer (staged/preview/fallback)
      description: |
        Get product pointer (staged/preview/fallback). Auth is via headers; org identity uses x-orgcode or
        body placement as documented. 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: 300
      x-concurrency-target: 500
      x-latency-p95-ms: 300
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - in: query
          name: pointer_kind
          required: true
          schema: { type: string, enum: [staged, preview, fallback] }
        - in: query
          name: product_id
          schema: { type: string }
        - in: query
          name: variant_id
          schema: { type: string }
        - in: query
          name: channel_guid
          schema: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          pointer:
                            anyOf:
                              - $ref: '#/components/schemas/ProductPointer'
                              - type: 'null'

  /product/pointer/set:
    post:
      summary: Set product pointer (staged/preview/fallback)
      description: |
        Set product pointer (staged/preview/fallback). Auth is via headers; org identity uses x-orgcode or
        body placement as documented. 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: 1000
      x-concurrency-target: 2000
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/OrgHeader' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                pointer_kind: { type: string, enum: [staged, preview, fallback] }
                product_id: { type: string }
                variant_id: { type: string }
                channel_guid: { type: string }
                rev: { type: integer }
                rev_id: { type: string }
                reason: { type: string }
                effective_at: { type: string, format: date-time }
                expires_at: { type: string, format: date-time }
                expected_revision: { type: string }
              required: [pointer_kind, rev, rev_id]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          pointer: { $ref: '#/components/schemas/ProductPointer' }

  /product/pointer/clear:
    post:
      summary: Clear product pointer (staged/preview/fallback)
      description: |
        Clear product pointer (staged/preview/fallback). Auth is via headers; org identity uses x-orgcode or
        body placement as documented. Route class Tier D (p95 300ms, p99 600ms).
      x-route-class: Tier D
      x-qps-target: 1000
      x-concurrency-target: 2000
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/OrgHeader' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                pointer_kind: { type: string, enum: [staged, preview, fallback] }
                product_id: { type: string }
                variant_id: { type: string }
                channel_guid: { type: string }
                expected_revision: { type: string }
              required: [pointer_kind, expected_revision]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Envelope' }

  /pack/create:
    post:
      summary: Create content pack (inline JSON or presigned upload)
      description: |
        Create content pack (inline JSON or presigned upload). Auth is via headers; org identity uses
        x-orgcode or body placement as documented. 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: 1000
      x-concurrency-target: 2000
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/OrgHeader' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                pack_id: { type: string }
                pack_version: { type: integer }
                pack_kind: { type: string }
                content_type: { type: string }
                content_encoding: { type: string }
                payload: {}
                content_md5: { type: string }
                size_bytes: { type: integer }
              required: [pack_kind, content_type]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          pack: { $ref: '#/components/schemas/ContentPack' }
                          presign: { $ref: '#/components/schemas/PackPresign' }

  /pack/complete:
    post:
      summary: Complete content pack upload (finalize)
      description: |
        Complete content pack upload (finalize). Auth is via headers; org identity uses x-orgcode or body
        placement as documented. Route class Tier D (p95 300ms, p99 600ms).
      x-route-class: Tier D
      x-qps-target: 1000
      x-concurrency-target: 2000
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/OrgHeader' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                pack_id: { type: string }
                pack_version: { type: integer }
                upload_token: { type: string }
                expected_revision: { type: string }
              required: [pack_id, pack_version, upload_token, expected_revision]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Envelope' }

  /pack/get:
    get:
      summary: Get content pack
      description: |
        Get content pack. Auth is via headers; org identity uses x-orgcode or body placement as documented.
        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: 300
      x-concurrency-target: 500
      x-latency-p95-ms: 300
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - in: query
          name: pack_id
          required: true
          schema: { type: string }
        - in: query
          name: pack_version
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          pack: { $ref: '#/components/schemas/ContentPack' }

  /pack/list:
    get:
      summary: List content pack versions
      description: |
        List content pack versions. Auth is via headers; org identity uses x-orgcode or body placement as
        documented. Paginated with limit/next_token (default 8; clamp 1–256). 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: 300
      x-concurrency-target: 500
      x-latency-p95-ms: 300
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - in: query
          name: pack_id
          required: true
          schema: { type: string }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 256, default: 8 }
        - in: query
          name: next_token
          schema: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items: { $ref: '#/components/schemas/ContentPack' }
                          next_token: { $ref: '#/components/schemas/NextToken' }

  /publish-profile/get:
    get:
      summary: Get publishability profile (per channel_code)
      description: |
        Get publishability profile (per channel_code). Auth is via headers; org identity uses x-orgcode or
        body placement as documented. 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: 300
      x-concurrency-target: 500
      x-latency-p95-ms: 300
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - in: query
          name: channel_code
          required: true
          schema: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          channel_code: { type: string }
                          profile: { $ref: '#/components/schemas/PublishProfile' }

  /publish-profile/set:
    post:
      summary: Set publishability profile (per channel_code)
      description: |
        Creates or updates the publishability profile for a `channel_code`.
        Updating an existing profile requires `expected_revision`.
      x-route-class: Tier A
      x-qps-target: 50
      x-concurrency-target: 200
      x-latency-p95-ms: 500
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/OrgHeader' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                channel_code: { type: string }
                expected_revision: { type: string }
                required_sku: { type: boolean }
                required_identifier_types:
                  type: array
                  items: { type: string }
                required_alias_tags:
                  type: array
                  items: { type: string }
              required: [channel_code]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Envelope' }

  /publish/run/start:
    post:
      summary: Start a publish run (client-driven)
      description: |
        Starts a publish run. The client drives the run by calling `/publish/run/step` until status becomes `completed`.
        Publishing all variants requires `confirm_all=true`.
        If `variant_id` is provided, `style_id` is required (PVM variant lookup is style-scoped).
        Selector semantics are exact match only (no partial/prefix text search).
      x-route-class: Tier D
      x-qps-target: 1000
      x-concurrency-target: 2000
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/OrgHeader' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                reason: { type: string }
                confirm_all: { type: boolean }
                variant_id: { type: string }
                style_id: { type: string }
                brand_id: { type: string }
                vendor_id: { type: string }
                manufacturer_id: { type: string }
                category_id: { type: string }
                control_type: { type: string }
                service_flag: { type: boolean }
                kit_flag: { type: boolean }
                pack_refs:
                  type: array
                  items: { $ref: '#/components/schemas/PackRef' }
                taxonomy_version: { type: string }
                biz_unit_code: { type: string }
                region_code: { type: string }
                country_code: { type: string }
                logical_guid: { type: string }
                channel_guid: { type: string }
                channel_code: { type: string }
                market_code: { type: string }
              required: [reason]
              dependentRequired:
                variant_id: [style_id]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data: { $ref: '#/components/schemas/PublishRun' }

  /publish/run/get:
    get:
      summary: Get publish run
      description: |
        Get publish run. Auth is via headers; org identity uses x-orgcode or body placement as documented.
        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: 300
      x-concurrency-target: 500
      x-latency-p95-ms: 300
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - in: query
          name: run_id
          required: true
          schema: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data: { $ref: '#/components/schemas/PublishRun' }

  /publish/run/step:
    post:
      summary: Process next page of publish run
      description: |
        Process next page of publish run. Auth is via headers; org identity uses x-orgcode or body placement
        as documented. Route class Tier D (p95 300ms, p99 600ms).
      x-route-class: Tier D
      x-qps-target: 1000
      x-concurrency-target: 2000
      x-latency-p95-ms: 300
      x-latency-p99-ms: 600
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/OrgHeader' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                run_id: { type: string }
                expected_revision: { type: string }
                limit: { type: integer, minimum: 1, maximum: 256, default: 8 }
              required: [run_id, expected_revision]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Envelope' }

  /publish/run/cancel:
    post:
      summary: Cancel publish run
      description: |
        Cancel publish run. Auth is via headers; org identity uses x-orgcode or body placement as
        documented. Route class Tier A (p95 500ms).
      x-route-class: Tier A
      x-qps-target: 50
      x-concurrency-target: 200
      x-latency-p95-ms: 500
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters: [{ $ref: '#/components/parameters/OrgHeader' }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                run_id: { type: string }
                expected_revision: { type: string }
              required: [run_id, expected_revision]
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Envelope' }

  /publish/run/manifest/list:
    get:
      summary: List publish run manifest parts
      description: |
        List publish run manifest parts. Auth is via headers; org identity uses x-orgcode or body placement
        as documented. Paginated with limit/next_token (default 8; clamp 1–256). 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: 300
      x-concurrency-target: 500
      x-latency-p95-ms: 300
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - in: query
          name: run_id
          required: true
          schema: { type: string }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 256, default: 8 }
        - in: query
          name: next_token
          schema: { type: string }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          items:
                            type: array
                            items: { $ref: '#/components/schemas/ManifestPart' }
                          next_token: { $ref: '#/components/schemas/NextToken' }

  /publish/run/manifest/presign:
    get:
      summary: Get a presigned URL for a manifest part
      description: |
        Get a presigned URL for a manifest part. Auth is via headers; org identity uses x-orgcode or body
        placement as documented. 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: 300
      x-concurrency-target: 500
      x-latency-p95-ms: 300
      security: [{ sessionAuth: [] }, { apiKeyAuth: [] }]
      parameters:
        - $ref: '#/components/parameters/OrgHeader'
        - in: query
          name: run_id
          required: true
          schema: { type: string }
        - in: query
          name: part
          required: true
          schema: { type: integer }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Envelope' }
