# Pixta Partner API — copied from pixta-backend origin/main (7b350ef2) by scripts/sync-partner-api.mjs.
openapi: 3.0.3
info:
  title: Pixta Partner API
  version: "1.0.0"
  contact:
    name: Pixta Integrations
    email: suporte@pixta.me
    url: https://pixta.me
  description: |
    Public partner contract for integrating with Pixta as a **producer/operator**.

    Use this API to manage events and catalog, read orders and reports, look up buyers,
    and receive signed webhooks when orders are paid, canceled, or refunded.

    ## Authentication

    Pixta issues a **Partner API key** per integration (scoped to one or more producers).
    Send it on every request:

    ```
    Authorization: Bearer <api_key>
    ```

    Keys can be rotated by Pixta. Never embed keys in client-side apps or public repos.

    ## Base URL

    | Environment | URL |
    |-------------|-----|
    | Production | `https://api.pixta.me` |
    | Staging | `https://api.pixta.dev` |

    All paths below are under `/partner/v1`.

    ## Conventions

    - JSON request/response bodies (`Content-Type: application/json`).
    - UUIDs identify events, orders, products, and buyers unless noted.
    - Money fields are decimal strings in BRL (e.g. `"120.00"`) unless noted as numbers.
    - List endpoints that paginate use `page` (1-based) and `per_page` (default 25, max 100).
    - Errors: `401` unauthorized, `403` forbidden, `404` not found, `422` validation /
      business rule with `{ "error": "…" }` or `{ "errors": ["…"] }`.

    ## Webhooks

    Register HTTPS endpoints under **Webhooks**. Pixta POSTs JSON events with header
    `X-Pixta-Signature: sha256=<hex>` (HMAC-SHA256 of the raw body using your webhook secret).
    Respond with `2xx` within 10 seconds. Failed deliveries are retried with backoff.

    | Event | When |
    |-------|------|
    | `order.paid` | Order payment confirmed; tickets issued |
    | `order.canceled` | Paid order canceled (buyer/producer/admin) |
    | `order.refunded` | Refund completed (full or partial) |

    ## start.gg (tournament identity)

    When the event’s checkout form includes a **start.gg** field, order objects in list/get
    responses and in webhook `data.order` include `startgg` (see `StartggProfile`):

    | Field | Meaning |
    |-------|---------|
    | `user_id` | Numeric start.gg user id |
    | `player_id` | Player id |
    | `gamertag` | Gamer tag |
    | `slug` | Discriminator (e.g. `873d03cc`), not the `user/…` path |
    | `prefix` | Team tag |
    | `name` | Real name |
    | `email` | Email from start.gg |
    | `avatar_url` | Profile image URL |

    The value is the snapshot captured at checkout (OAuth). If the form has no start.gg field,
    `startgg` is omitted. The producer can limit the field to some ticket types (for example, players
    only): orders without those tickets omit `startgg` too.

x-tagGroups:
  - name: Getting started
    tags: [Auth]
  - name: Events & catalog
    tags: [Events, Products, Coupons]
  - name: Commerce
    tags: [Orders, Tickets]
  - name: Insights
    tags: [Reports, Buyers]
  - name: Realtime
    tags: [Webhooks]

servers:
  - url: https://api.pixta.me/partner/v1
    description: Production
  - url: https://api.pixta.dev/partner/v1
    description: Staging

security:
  - PartnerApiKey: []

tags:
  - name: Auth
    description: Verify credentials and inspect the integration scope.
  - name: Events
    description: Create and manage events for your producer(s).
  - name: Products
    description: Ticket lots / products on an event.
  - name: Coupons
    description: Discount and promoter coupons.
  - name: Orders
    description: Orders for an event (list, detail, cancel).
  - name: Tickets
    description: Issued tickets and door list (read).
  - name: Reports
    description: Event KPIs and sales analytics.
  - name: Buyers
    description: Buyer CRM for a producer (search and history).
  - name: Webhooks
    description: Subscribe to order lifecycle events.

paths:
  /me:
    get:
      operationId: partnerMe
      tags: [Auth]
      summary: Who am I
      description: Returns the partner integration name and the producers this key can access.
      responses:
        '200':
          description: Integration scope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PartnerMe'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /events:
    get:
      operationId: listEvents
      tags: [Events]
      summary: List events
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - name: status
          in: query
          schema:
            type: string
            enum: [draft, live, finished, cancelled]
          description: Filter by event status.
        - name: producer_id
          in: query
          schema:
            type: string
          description: Limit to one producer (UUID or slug) in the key’s scope.
      responses:
        '200':
          description: Paginated events.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventList'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createEvent
      tags: [Events]
      summary: Create event
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EventCreate'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Event'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/Unprocessable'

  /events/{event_id}:
    parameters:
      - $ref: '#/components/parameters/EventId'
    get:
      operationId: getEvent
      tags: [Events]
      summary: Get event
      responses:
        '200':
          description: Event detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Event'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: updateEvent
      tags: [Events]
      summary: Update event
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EventUpdate'
      responses:
        '200':
          description: Updated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Event'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'

  /events/{event_id}/cover:
    parameters:
      - $ref: '#/components/parameters/EventId'
    put:
      operationId: uploadEventCover
      tags: [Events]
      summary: Upload cover image
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
      responses:
        '200':
          description: Event with updated cover URLs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Event'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /events/{event_id}/products:
    parameters:
      - $ref: '#/components/parameters/EventId'
    get:
      operationId: listProducts
      tags: [Products]
      summary: List products (lots)
      responses:
        '200':
          description: Products.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Product'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      operationId: createProduct
      tags: [Products]
      summary: Create product (lot)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductCreate'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
        '422':
          $ref: '#/components/responses/Unprocessable'

  /events/{event_id}/products/{product_id}:
    parameters:
      - $ref: '#/components/parameters/EventId'
      - $ref: '#/components/parameters/ProductId'
    get:
      operationId: getProduct
      tags: [Products]
      summary: Get product
      responses:
        '200':
          description: Product.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: updateProduct
      tags: [Products]
      summary: Update product
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductUpdate'
      responses:
        '200':
          description: Updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'
        '422':
          $ref: '#/components/responses/Unprocessable'

  /events/{event_id}/coupons:
    parameters:
      - $ref: '#/components/parameters/EventId'
    get:
      operationId: listCoupons
      tags: [Coupons]
      summary: List coupons
      responses:
        '200':
          description: Coupons.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Coupon'
    post:
      operationId: createCoupon
      tags: [Coupons]
      summary: Create coupon
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CouponCreate'
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Coupon'
        '422':
          $ref: '#/components/responses/Unprocessable'

  /events/{event_id}/coupons/{coupon_id}:
    parameters:
      - $ref: '#/components/parameters/EventId'
      - name: coupon_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    patch:
      operationId: updateCoupon
      tags: [Coupons]
      summary: Update coupon
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CouponUpdate'
      responses:
        '200':
          description: Updated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Coupon'
        '404':
          $ref: '#/components/responses/NotFound'

  /events/{event_id}/orders:
    parameters:
      - $ref: '#/components/parameters/EventId'
    get:
      operationId: listOrders
      tags: [Orders]
      summary: List orders
      description: |
        Paid, pending, and canceled orders for the event (excludes unpaid carts still waiting
        and expired reservations). Newest first.
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - name: payment_status
          in: query
          schema:
            type: string
            enum: [pending, paid, cancelled]
        - name: since
          in: query
          schema:
            type: string
            format: date-time
          description: Only orders created at or after this instant (UTC).
      responses:
        '200':
          description: Paginated orders.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /events/{event_id}/orders/{order_id}:
    parameters:
      - $ref: '#/components/parameters/EventId'
      - $ref: '#/components/parameters/OrderId'
    get:
      operationId: getOrder
      tags: [Orders]
      summary: Get order
      description: Full order with items, buyer, tickets, and payment summary.
      responses:
        '200':
          description: Order detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '404':
          $ref: '#/components/responses/NotFound'

  /events/{event_id}/orders/{order_id}/cancel:
    parameters:
      - $ref: '#/components/parameters/EventId'
      - $ref: '#/components/parameters/OrderId'
    post:
      operationId: cancelOrder
      tags: [Orders]
      summary: Cancel a paid order
      description: |
        Cancels a paid order and starts the refund path (wallet credit and/or card refund,
        depending on how the order was paid). Resale orders may be refused.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
                  maxLength: 500
                  example: Buyer requested cancellation
      responses:
        '200':
          description: Order after cancellation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '404':
          $ref: '#/components/responses/NotFound'

  /events/{event_id}/tickets:
    parameters:
      - $ref: '#/components/parameters/EventId'
    get:
      operationId: listTickets
      tags: [Tickets]
      summary: List tickets
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PerPage'
        - name: q
          in: query
          schema:
            type: string
          description: Search by holder name, e-mail, or document.
      responses:
        '200':
          description: Paginated tickets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TicketList'

  /events/{event_id}/tickets/entrance:
    parameters:
      - $ref: '#/components/parameters/EventId'
    get:
      operationId: entranceList
      tags: [Tickets]
      summary: Door / entrance list
      description: Compact list for check-in operations (holders and check-in state).
      responses:
        '200':
          description: Entrance rows.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/EntranceTicket'

  /events/{event_id}/analytics:
    parameters:
      - $ref: '#/components/parameters/EventId'
    get:
      operationId: getEventAnalytics
      tags: [Reports]
      summary: Event analytics
      description: |
        Cached sales KPIs for the event (gross, fees, tickets sold, payment mix, etc.).
        Refresh is eventual; call again after a few minutes for latest totals.
      responses:
        '200':
          description: Analytics payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventAnalytics'
        '404':
          $ref: '#/components/responses/NotFound'

  /producers/{producer_id}/buyers:
    parameters:
      - $ref: '#/components/parameters/ProducerId'
    get:
      operationId: buyersDashboard
      tags: [Buyers]
      summary: Buyers dashboard
      description: Aggregate CRM metrics for buyers with paid orders on this producer.
      parameters:
        - name: month
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 12
          description: Month for birthday slice (default = current month).
        - name: limit
          in: query
          schema:
            type: integer
            default: 10
            maximum: 50
          description: Size of `best_buyers`.
      responses:
        '200':
          description: CRM dashboard.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BuyersDashboard'
        '404':
          $ref: '#/components/responses/NotFound'

  /producers/{producer_id}/buyers/search:
    parameters:
      - $ref: '#/components/parameters/ProducerId'
    get:
      operationId: searchBuyers
      tags: [Buyers]
      summary: Search buyers
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
            minLength: 2
          description: Name, e-mail, or document fragment.
      responses:
        '200':
          description: Matches.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/BuyerSummary'

  /producers/{producer_id}/buyers/{buyer_id}:
    parameters:
      - $ref: '#/components/parameters/ProducerId'
      - name: buyer_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      operationId: getBuyer
      tags: [Buyers]
      summary: Buyer detail
      description: Profile and paid order history with this producer.
      responses:
        '200':
          description: Buyer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BuyerDetail'
        '404':
          $ref: '#/components/responses/NotFound'

  /webhooks:
    get:
      operationId: listWebhooks
      tags: [Webhooks]
      summary: List webhook endpoints
      responses:
        '200':
          description: Registered endpoints.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookEndpoint'
    post:
      operationId: createWebhook
      tags: [Webhooks]
      summary: Register webhook endpoint
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookEndpointCreate'
      responses:
        '201':
          description: Created (includes `secret` once).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpointCreated'
        '422':
          $ref: '#/components/responses/Unprocessable'

  /webhooks/{webhook_id}:
    parameters:
      - name: webhook_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    delete:
      operationId: deleteWebhook
      tags: [Webhooks]
      summary: Delete webhook endpoint
      responses:
        '204':
          description: Deleted.
        '404':
          $ref: '#/components/responses/NotFound'

components:
  securitySchemes:
    PartnerApiKey:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: Partner API key issued by Pixta.

  parameters:
    EventId:
      name: event_id
      in: path
      required: true
      schema:
        type: string
      description: Event UUID or slug.
      example: festa-de-verao-2026
    ProductId:
      name: product_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    OrderId:
      name: order_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    ProducerId:
      name: producer_id
      in: path
      required: true
      schema:
        type: string
      description: Producer UUID or slug.
      example: coletivo-exemplo
    Page:
      name: page
      in: query
      schema:
        type: integer
        minimum: 1
        default: 1
    PerPage:
      name: per_page
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25

  responses:
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Resource not found or outside this key’s scope.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unprocessable:
      description: Validation or business rule failure.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorList'

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
          example: Not found
    ErrorList:
      type: object
      properties:
        error:
          type: string
        errors:
          type: array
          items:
            type: string

    PartnerMe:
      type: object
      properties:
        name:
          type: string
          example: Acme Ticketing Integration
        producers:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              slug:
                type: string
              name:
                type: string

    Money:
      type: string
      description: Amount in BRL with two decimal places.
      example: "120.00"

    EventList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/EventSummary'
        page:
          type: integer
        per_page:
          type: integer
        total:
          type: integer

    EventSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        status:
          type: string
          enum: [draft, live, finished, cancelled]
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
          nullable: true
        producer:
          type: object
          properties:
            id:
              type: string
              format: uuid
            slug:
              type: string
            name:
              type: string
        cover_url:
          type: string
          format: uri
          nullable: true

    Event:
      allOf:
        - $ref: '#/components/schemas/EventSummary'
        - type: object
          properties:
            bio:
              type: string
              nullable: true
              description: HTML description.
            color:
              type: string
              nullable: true
              example: '#ff639f'
            ask_for_cpf:
              type: boolean
            ask_for_rg:
              type: boolean
            credit_card_enabled:
              type: boolean
            tips_enabled:
              type: boolean
            resale_enabled:
              type: boolean
            waitlist_enabled:
              type: boolean
            venue:
              type: object
              nullable: true
              properties:
                name:
                  type: string
                city:
                  type: string
                  nullable: true
                address:
                  type: string
                  nullable: true
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time

    EventCreate:
      type: object
      required: [producer_id, name, starts_at]
      properties:
        producer_id:
          type: string
          description: UUID or slug of a producer in the key’s scope.
        name:
          type: string
          example: Festa de Verão 2026
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
        bio:
          type: string
        color:
          type: string
        ask_for_cpf:
          type: boolean
          default: true
        credit_card_enabled:
          type: boolean
          default: true

    EventUpdate:
      type: object
      properties:
        name:
          type: string
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
          nullable: true
        bio:
          type: string
        color:
          type: string
        status:
          type: string
          enum: [draft, live, finished, cancelled]
        ask_for_cpf:
          type: boolean
        ask_for_rg:
          type: boolean
        credit_card_enabled:
          type: boolean
        tips_enabled:
          type: boolean
        resale_enabled:
          type: boolean
        waitlist_enabled:
          type: boolean

    Product:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          example: 1º Lote
        amount:
          $ref: '#/components/schemas/Money'
        maximum_quantity:
          type: integer
          example: 500
        available_quantity:
          type: integer
          example: 320
        sell_starts_at:
          type: string
          format: date-time
          nullable: true
        sell_ends_at:
          type: string
          format: date-time
          nullable: true
        hidden:
          type: boolean
          description: Not listed publicly; still sellable with a secret link.
        product_type:
          type: string
          enum: [ticket, deliverable, other]
          example: ticket

    ProductCreate:
      type: object
      required: [name, amount, maximum_quantity]
      properties:
        name:
          type: string
        amount:
          $ref: '#/components/schemas/Money'
        maximum_quantity:
          type: integer
          minimum: 1
        sell_starts_at:
          type: string
          format: date-time
        sell_ends_at:
          type: string
          format: date-time
        hidden:
          type: boolean
          default: false

    ProductUpdate:
      type: object
      properties:
        name:
          type: string
        amount:
          $ref: '#/components/schemas/Money'
        maximum_quantity:
          type: integer
        sell_starts_at:
          type: string
          format: date-time
          nullable: true
        sell_ends_at:
          type: string
          format: date-time
          nullable: true
        hidden:
          type: boolean

    Coupon:
      type: object
      properties:
        id:
          type: string
          format: uuid
        code:
          type: string
          example: PROMO10
        name:
          type: string
          nullable: true
        discount_type:
          type: string
          enum: [percent, amount]
        discount_value:
          type: string
          example: "10"
        active:
          type: boolean
        usage_limit:
          type: integer
          nullable: true

    CouponCreate:
      type: object
      required: [code, discount_type, discount_value]
      properties:
        code:
          type: string
        name:
          type: string
        discount_type:
          type: string
          enum: [percent, amount]
        discount_value:
          type: string
        usage_limit:
          type: integer
        active:
          type: boolean
          default: true

    CouponUpdate:
      type: object
      properties:
        name:
          type: string
        discount_value:
          type: string
        usage_limit:
          type: integer
          nullable: true
        active:
          type: boolean

    OrderList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/OrderSummary'
        page:
          type: integer
        per_page:
          type: integer
        total:
          type: integer

    OrderBuyer:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          nullable: true
        email:
          type: string
          format: email
        document:
          type: string
          nullable: true
          description: CPF/CNPJ or foreign document (digits/letters as stored).

    OrderItem:
      type: object
      properties:
        id:
          type: string
          format: uuid
        product_id:
          type: string
          format: uuid
        product_name:
          type: string
        quantity:
          type: integer
        unit_price:
          $ref: '#/components/schemas/Money'
        subtotal:
          $ref: '#/components/schemas/Money'
        holders:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              document_cpf:
                type: string
                nullable: true
              document_rg:
                type: string
                nullable: true

    OrderSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        payment_status:
          type: string
          enum: [pending, paid, cancelled, expired, waiting]
        payment_method:
          type: string
          nullable: true
          enum: [pix, credit_card, mercadopago, mercadopago_pix, null]
        amount:
          $ref: '#/components/schemas/Money'
        product_amount:
          $ref: '#/components/schemas/Money'
        fees_amount:
          $ref: '#/components/schemas/Money'
        discount_amount:
          $ref: '#/components/schemas/Money'
        tip_amount:
          $ref: '#/components/schemas/Money'
        paid_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        tickets_count:
          type: integer
        buyer:
          $ref: '#/components/schemas/OrderBuyer'
        items:
          type: array
          items:
            $ref: '#/components/schemas/OrderItem'
        coupon_code:
          type: string
          nullable: true
        startgg:
          allOf:
            - $ref: '#/components/schemas/StartggProfile'
          nullable: true
          description: |
            Present when the event checkout form has a `startgg` field. Snapshot of the buyer's
            linked start.gg account (frozen on the form answer when available).

    StartggProfile:
      type: object
      description: start.gg identity captured at OAuth / checkout (tournament registration fields).
      properties:
        user_id:
          type: string
          description: Numeric start.gg user id.
          example: '999'
        player_id:
          type: string
          nullable: true
          example: '42'
        gamertag:
          type: string
          nullable: true
          example: Ace
        slug:
          type: string
          nullable: true
          description: start.gg discriminator (e.g. `873d03cc`), not the `user/…` path.
          example: 873d03cc
        prefix:
          type: string
          nullable: true
          description: Team tag / prefix.
          example: TEAM
        name:
          type: string
          nullable: true
          description: Real name on start.gg.
          example: Ace Real
        email:
          type: string
          format: email
          nullable: true
        avatar_url:
          type: string
          format: uri
          nullable: true

    Order:
      allOf:
        - $ref: '#/components/schemas/OrderSummary'
        - type: object
          properties:
            tickets:
              type: array
              items:
                $ref: '#/components/schemas/Ticket'
            refund:
              type: object
              nullable: true
              properties:
                status:
                  type: string
                  enum: [pending, completed, manual_required]
                amount:
                  $ref: '#/components/schemas/Money'

    Ticket:
      type: object
      properties:
        id:
          type: string
          format: uuid
        order_id:
          type: string
          format: uuid
        product_name:
          type: string
          nullable: true
        holder_name:
          type: string
          nullable: true
        status:
          type: string
          enum: [valid, checked_in, cancelled, transferred]
        checked_in_at:
          type: string
          format: date-time
          nullable: true

    TicketList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Ticket'
        page:
          type: integer
        per_page:
          type: integer
        total:
          type: integer

    EntranceTicket:
      type: object
      properties:
        id:
          type: string
          format: uuid
        holder_name:
          type: string
          nullable: true
        product_name:
          type: string
          nullable: true
        checked_in:
          type: boolean
        checked_in_at:
          type: string
          format: date-time
          nullable: true

    EventAnalytics:
      type: object
      properties:
        cached_at:
          type: string
          format: date-time
          nullable: true
        gross_sales:
          $ref: '#/components/schemas/Money'
        net_to_producer:
          $ref: '#/components/schemas/Money'
        fees:
          $ref: '#/components/schemas/Money'
        tickets_sold:
          type: integer
        orders_paid:
          type: integer
        by_payment_method:
          type: object
          additionalProperties:
            type: object
            properties:
              count:
                type: integer
              amount:
                $ref: '#/components/schemas/Money'
        resale:
          type: object
          nullable: true
          properties:
            sold_count:
              type: integer
            volume:
              $ref: '#/components/schemas/Money'
            open_listings:
              type: integer
            waitlist_count:
              type: integer

    BuyersDashboard:
      type: object
      properties:
        total_buyers:
          type: integer
        recurring_buyers:
          type: integer
        average_ticket:
          $ref: '#/components/schemas/Money'
        best_buyers:
          type: array
          items:
            $ref: '#/components/schemas/BuyerSummary'
        birthdays_this_month:
          type: array
          items:
            $ref: '#/components/schemas/BuyerSummary'

    BuyerSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          nullable: true
        email:
          type: string
          format: email
        total_spent:
          $ref: '#/components/schemas/Money'
        orders_count:
          type: integer

    BuyerDetail:
      allOf:
        - $ref: '#/components/schemas/BuyerSummary'
        - type: object
          properties:
            document:
              type: string
              nullable: true
            cellphone:
              type: string
              nullable: true
            birthdate:
              type: string
              format: date
              nullable: true
            city:
              type: string
              nullable: true
            orders:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  event_name:
                    type: string
                  amount:
                    $ref: '#/components/schemas/Money'
                  paid_at:
                    type: string
                    format: date-time
                    nullable: true
                  payment_status:
                    type: string

    WebhookEndpoint:
      type: object
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
          example: https://partner.example.com/hooks/pixta
        events:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventName'
        active:
          type: boolean
        created_at:
          type: string
          format: date-time

    WebhookEndpointCreate:
      type: object
      required: [url, events]
      properties:
        url:
          type: string
          format: uri
          description: HTTPS URL that accepts POST.
        events:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/WebhookEventName'
        active:
          type: boolean
          default: true

    WebhookEndpointCreated:
      allOf:
        - $ref: '#/components/schemas/WebhookEndpoint'
        - type: object
          required: [secret]
          properties:
            secret:
              type: string
              description: |
                Shared secret for `X-Pixta-Signature`. Shown **only** at creation —
                store it securely.

    WebhookEventName:
      type: string
      enum: [order.paid, order.canceled, order.refunded]

    # --- Outbound webhook payloads (what Pixta POSTs to your URL) ---

    WebhookEnvelope:
      type: object
      description: |
        Body of every webhook POST. Verify `X-Pixta-Signature: sha256=<hex>` where
        `<hex>` = HMAC-SHA256(raw_body, webhook_secret) encoded as lowercase hex.
      required: [id, type, created_at, data]
      properties:
        id:
          type: string
          format: uuid
          description: Delivery id (idempotent — ignore duplicates).
        type:
          $ref: '#/components/schemas/WebhookEventName'
        created_at:
          type: string
          format: date-time
        data:
          $ref: '#/components/schemas/WebhookOrderData'

    WebhookOrderData:
      type: object
      description: |
        Payload under `data`. `order` may include `startgg` when the event form requires start.gg
        (same shape as list/get orders).
      properties:
        order:
          $ref: '#/components/schemas/OrderSummary'
        event:
          type: object
          properties:
            id:
              type: string
              format: uuid
            slug:
              type: string
            name:
              type: string
        producer:
          type: object
          properties:
            id:
              type: string
              format: uuid
            slug:
              type: string
            name:
              type: string
        refund:
          type: object
          nullable: true
          description: Present on `order.refunded` (and sometimes `order.canceled`).
          properties:
            amount:
              $ref: '#/components/schemas/Money'
            status:
              type: string
              enum: [pending, completed, manual_required]

# OpenAPI 3.0: use x-webhooks (Redoc / Redocly). Same payloads as WebhookEnvelope.
x-webhooks:
  orderPaid:
    post:
      operationId: webhookOrderPaid
      tags: [Webhooks]
      summary: order.paid
      description: |
        Sent when an order becomes paid and tickets are issued.
        Header `X-Pixta-Signature: sha256=<hmac_hex>`.
        When the event checkout form has a start.gg field, `data.order.startgg` carries the
        buyer’s linked profile (`StartggProfile`).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookEnvelope'
                - type: object
                  properties:
                    type:
                      enum: [order.paid]
            example:
              id: 7c2e1a90-0b1c-4d2e-9f30-1234567890ab
              type: order.paid
              created_at: '2026-10-13T22:15:00Z'
              data:
                order:
                  id: 11111111-1111-4111-8111-111111111111
                  payment_status: paid
                  payment_method: pix
                  amount: '132.00'
                  paid_at: '2026-10-13T22:14:58Z'
                  buyer:
                    id: 22222222-2222-4222-8222-222222222222
                    name: Maria Silva
                    email: maria@example.com
                  startgg:
                    user_id: '999'
                    player_id: '42'
                    gamertag: Ace
                    slug: 873d03cc
                    prefix: TEAM
                    name: Ace Real
                    email: ace@example.com
                    avatar_url: https://images.start.gg/ace.png
                event:
                  id: 33333333-3333-4333-8333-333333333333
                  slug: festa-de-verao-2026
                  name: Festa de Verão 2026
      responses:
        '200':
          description: Acknowledge receipt.
  orderCanceled:
    post:
      operationId: webhookOrderCanceled
      tags: [Webhooks]
      summary: order.canceled
      description: Sent when a paid order is canceled.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookEnvelope'
                - type: object
                  properties:
                    type:
                      enum: [order.canceled]
      responses:
        '200':
          description: Acknowledge receipt.
  orderRefunded:
    post:
      operationId: webhookOrderRefunded
      tags: [Webhooks]
      summary: order.refunded
      description: Sent when a refund completes (full or partial).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: '#/components/schemas/WebhookEnvelope'
                - type: object
                  properties:
                    type:
                      enum: [order.refunded]
      responses:
        '200':
          description: Acknowledge receipt.
