# Brizz Model C — Verification Provider contract v1 (PARTNER_CODES_SPEC.md §16.12).
# OPPOSITE DIRECTION to the Partner API: here BRIZZ IS THE CLIENT and the PARTNER
# IMPLEMENTS THIS ENDPOINT. Brizz publishes the spec (platform-defined callback,
# RFC 7662-style); the partner builds an endpoint that satisfies it.
# Additive changes only within v1; breaking changes mint v2.
openapi: 3.0.3
info:
  title: Brizz Model C — Verification Provider
  version: "1.0"
  description: |
    The HTTPS endpoint YOU host and Brizz calls, live, during checkout, to ask
    whether one of your codes is valid. Your answer decides whether the discount
    applies.

    **You are the server.** Brizz calls this on every checkout where one of your
    codes is entered, with a customer waiting — so it must be online, fast and
    authenticated. Default timeout 1500 ms (configurable 100–5000 ms); Brizz also
    caps connect at 5 s and total at 10 s. Slower than the timeout is treated as
    unavailable, so no discount is applied and checkout continues.

    **Authentication (Brizz to you).** Brizz presents a bearer credential as
    `Authorization: Bearer <secret>` OR `X-API-Key: <secret>` — those two header
    names only. Brizz can mint the secret and reveal it once, or present one you
    supply. Verify it in constant time.

    **Optional HMAC.** When signing is enabled, Brizz adds `X-Brizz-Timestamp`,
    `X-Brizz-Request-Id` and `X-Brizz-Signature: v1=<hex>` — HMAC-SHA256 over
    `{timestamp}.{request_id}.{raw_body}` (more than one `v1=` during a secret
    rotation). Verify against the RAW body; reject if `|now - timestamp| > 300 s`.

    **Codes Brizz will send.** Uppercased, trimmed, `[A-Z0-9-]`, 4-32 chars,
    carrying a routing prefix `[A-Z0-9]{2,16}-`. A longer or non-conforming code
    is rejected by Brizz locally and never reaches you.

    **Response contract.** Exactly HTTP 200, `application/json`, at most 64 KiB,
    with a strict boolean `approved`. Anything else — another status, a non-JSON
    body, a timeout, a truthy string or number — is treated as unavailable.

    **A verification is not a redemption.** Approving reserves nothing on your
    side; the customer may still abandon checkout. Learn the real outcome from the
    `code.redeemed` webhook or the reconciliation report (Partner API), both of
    which carry the code and your `authorization_ref`.

    **No customer PII is sent.** You issued the code to a specific person, so the
    code is already your user reference.
servers:
  - url: https://your-endpoint.example
    description: The URL you host and register with Brizz (per campaign/route).
paths:
  /your/verification/path:
    post:
      summary: Verify one code (Brizz calls you)
      description: >
        Brizz asks whether `code` is valid for this campaign. Answer yes or no
        from your own records. The path is whatever URL you register with Brizz.
      operationId: verifyCode
      security:
        - bearerSecret: []
        - apiKey: []
      parameters:
        - { name: X-Brizz-Timestamp, in: header, required: false, schema: { type: string }, description: "Unix seconds; present when HMAC signing is enabled." }
        - { name: X-Brizz-Request-Id, in: header, required: false, schema: { type: string, format: uuid }, description: "Echoes body.request_id; present when signing is enabled." }
        - { name: X-Brizz-Signature, in: header, required: false, schema: { type: string, example: "v1=9a3b" }, description: "One or more v1= HMAC digests; present when signing is enabled." }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/VerificationRequest" }
      responses:
        "200":
          description: >
            Your verdict. MUST be exactly 200 + application/json + a strict
            boolean `approved`, at most 64 KiB. Any other status or body shape
            is treated as unavailable (no discount; checkout continues).
          content:
            application/json:
              schema: { $ref: "#/components/schemas/VerificationResponse" }
components:
  securitySchemes:
    bearerSecret:
      type: http
      scheme: bearer
      description: "Authorization: Bearer <secret> — the credential Brizz presents."
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: "Alternative to Bearer; one or the other, never both."
  schemas:
    VerificationRequest:
      type: object
      required: [code, campaign_uuid, request_id]
      properties:
        code: { type: string, minLength: 4, maxLength: 32, example: "PARTNER-AB12", description: "Uppercased, trimmed, [A-Z0-9-]." }
        campaign_uuid: { type: string, format: uuid }
        request_id: { type: string, format: uuid, description: "Correlation id; also in X-Brizz-Request-Id when signing is on. Log it." }
    VerificationResponse:
      type: object
      required: [approved]
      properties:
        approved:
          type: boolean
          description: Strict JSON boolean. A truthy string or number is rejected as malformed.
        authorization_ref:
          type: string
          maxLength: 128
          nullable: true
          description: >
            Optional opaque, non-PII reference for your own reconciliation.
            Echoed back later in the code.redeemed webhook and the report. An
            over-length value is DROPPED entirely (never truncated).
        reason:
          type: string
          nullable: true
          enum: [INVALID, EXPIRED, ALREADY_USED, NOT_ELIGIBLE, REVOKED]
          description: >
            Optional, closed vocabulary, matched case/space/hyphen/dot-insensitively.
            A value outside the set is replaced with OTHER_<12 hex>, never stored
            raw. Never shown to the customer.
      example:
        approved: false
        reason: ALREADY_USED
