openapi: 3.1.0
info:
  title: We Playtest Games API
  version: 1.0.0
  description: |
    Public API for We Playtest Games — a marketplace connecting indie game developers with playtesters.
    Cost varies by duration and player count: 30min=$15, 1hr=$20, 2hr=$35, 3hr=$50 base (singleplayer). Multiplayer adds per-extra-player surcharge. Playtesters earn $10/hr per player. Max 8 players.
  contact:
    name: We Playtest Games
    email: team@weplaytestgames.com
    url: https://weplaytestgames.com
  license:
    name: Proprietary

servers:
  - url: https://app.weplaytestgames.com/api/v1
    description: Production

security:
  - bearerAuth: []

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key in format wpg_sk_{32 hex chars}

  schemas:
    Meta:
      type: object
      properties:
        requestId:
          type: string
          example: req_abc123
        timestamp:
          type: string
          format: date-time
          example: "2026-03-02T12:00:00Z"
        cursor:
          type: string
          nullable: true
        hasMore:
          type: boolean
        totalCount:
          type: integer

    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - UNAUTHORIZED
                - FORBIDDEN
                - NOT_FOUND
                - VALIDATION_ERROR
                - RATE_LIMITED
                - CONFLICT
                - PAYMENT_REQUIRED
                - IDEMPOTENCY_MISMATCH
                - INTERNAL_ERROR
                - SERVICE_UNAVAILABLE
            message:
              type: string
            details:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                  message:
                    type: string
        meta:
          $ref: '#/components/schemas/Meta'

    User:
      type: object
      properties:
        id:
          type: string
        email:
          type: string
          format: email
        role:
          type: string
          enum: [game_owner]
        name:
          type: string
        timezone:
          type: string
        emailVerified:
          type: boolean
        createdAt:
          type: string
          format: date-time

    Game:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        buildUrl:
          type: string
          format: uri
        tags:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time

    PlaytestRequest:
      type: object
      properties:
        id:
          type: string
        gameId:
          type: string
        visibility:
          type: string
          enum: [public, private]
        quantity:
          type: integer
        durationMinutes:
          type: integer
          enum: [30, 60, 120, 180]
          description: Duration in minutes
        playerCount:
          type: integer
          minimum: 1
          maximum: 8
          description: Number of players per session
        costPerSlotCents:
          type: integer
          description: Cost per slot in cents (stored at creation time)
        payoutCents:
          type: integer
          description: Payout per slot in cents (stored at creation time)
        instructions:
          type: string
        slotsOpen:
          type: integer
        slotsReserved:
          type: integer
        slotsCompleted:
          type: integer
        status:
          type: string
          enum: [active, completed, cancelled]
        createdAt:
          type: string
          format: date-time

    PlaytestSlot:
      type: object
      properties:
        id:
          type: string
        playtestId:
          type: string
        status:
          type: string
          enum: [open, reserved, submitted, accepted, rejected, expired, blocked, cancelled]
        reservedBy:
          type: string
          nullable: true
        reservedAt:
          type: string
          format: date-time
          nullable: true
        deadlineAt:
          type: string
          format: date-time
          nullable: true
        submittedAt:
          type: string
          format: date-time
          nullable: true

    Balance:
      type: object
      properties:
        balanceCents:
          type: integer
        currency:
          type: string
          example: USD

    Webhook:
      type: object
      properties:
        id:
          type: string
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
        isActive:
          type: boolean
        failureCount:
          type: integer
        createdAt:
          type: string
          format: date-time

paths:
  /auth/register:
    post:
      tags: [Auth]
      summary: Register a new account
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password, role, name]
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                  minLength: 8
                role:
                  type: string
                  enum: [game_owner]
                name:
                  type: string
                timezone:
                  type: string
      responses:
        '200':
          description: Account created
        '422':
          description: Validation error

  /auth/register/api-key:
    post:
      tags: [Auth]
      summary: Register and get API key in one step
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password, role, name]
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                role:
                  type: string
                  enum: [game_owner]
                name:
                  type: string
                keyName:
                  type: string
      responses:
        '200':
          description: Account created with API key

  /auth/me:
    get:
      tags: [Auth]
      summary: Get current user profile
      responses:
        '200':
          description: User profile

  /auth/profile:
    patch:
      tags: [Auth]
      summary: Update profile
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                timezone:
                  type: string
      responses:
        '200':
          description: Updated profile

  /info/categories:
    get:
      tags: [Info]
      summary: List all game categories
      responses:
        '200':
          description: Categories list

  /info/devices:
    get:
      tags: [Info]
      summary: List all hardware devices
      responses:
        '200':
          description: Devices list

  /info/platforms:
    get:
      tags: [Info]
      summary: List all distribution platforms
      responses:
        '200':
          description: Platforms list

  /games:
    get:
      tags: [Games]
      summary: List my games
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
        - name: cursor
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Games list
    post:
      tags: [Games]
      summary: Create a game
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, description, buildUrl, tags]
              properties:
                name:
                  type: string
                description:
                  type: string
                buildUrl:
                  type: string
                  format: uri
                tags:
                  type: array
                  items:
                    type: string
                  minItems: 1
      responses:
        '200':
          description: Game created

  /games/{gameId}:
    get:
      tags: [Games]
      summary: Get game details
      parameters:
        - name: gameId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Game details
    patch:
      tags: [Games]
      summary: Update a game
      parameters:
        - name: gameId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Updated game

  /games/{gameId}/playtests:
    get:
      tags: [Playtests]
      summary: List playtest requests for a game
      parameters:
        - name: gameId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Playtests list
    post:
      tags: [Playtests]
      summary: Create a playtest request
      parameters:
        - name: gameId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [visibility, quantity]
              properties:
                visibility:
                  type: string
                  enum: [public, private]
                quantity:
                  type: integer
                  minimum: 1
                  maximum: 100
                durationMinutes:
                  type: integer
                  enum: [30, 60, 120, 180]
                  description: Playtest duration in minutes (default 60)
                playerCount:
                  type: integer
                  minimum: 1
                  maximum: 8
                  description: Number of players per session (default 1)
                notesForTesters:
                  type: string
                instructions:
                  type: string
      parameters:
        - name: X-Payment-Method
          in: header
          schema:
            type: string
            enum: [x402]
          description: Set to "x402" for crypto payment via x402 protocol
        - name: X-Payment-Chain
          in: header
          schema:
            type: string
            enum: [base, ethereum, solana]
          description: Preferred blockchain (default base)
        - name: X-Payment-Currency
          in: header
          schema:
            type: string
            enum: [USDC, ETH, SOL]
          description: Preferred currency (default USDC)
        - name: X-Payment
          in: header
          schema:
            type: string
          description: 'JSON with tx hash for retry: {"txHash":"0x...","chain":"base","currency":"USDC"}'
      responses:
        '201':
          description: Playtest created
        '402':
          description: Payment required (x402 or insufficient credit)

  /slots/{slotId}/accept:
    post:
      tags: [Playtests]
      summary: Accept a submission
      parameters:
        - name: slotId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                rating:
                  type: integer
                  minimum: 1
                  maximum: 5
      responses:
        '200':
          description: Submission accepted

  /slots/{slotId}/reject:
    post:
      tags: [Playtests]
      summary: Reject a submission
      parameters:
        - name: slotId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason:
                  type: string
                rating:
                  type: integer
                  minimum: 1
                  maximum: 5
      responses:
        '200':
          description: Submission rejected

  /billing/balance:
    get:
      tags: [Billing]
      summary: Get credit balance
      responses:
        '200':
          description: Balance

  /billing/credit:
    post:
      tags: [Billing]
      summary: Purchase credit via Stripe
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amountCents]
              properties:
                amountCents:
                  type: integer
                  minimum: 2000
                  maximum: 1000000
                returnUrl:
                  type: string
                  format: uri
      responses:
        '200':
          description: Checkout URL

  /billing/credit/x402:
    post:
      tags: [Billing]
      summary: Top up credit via x402 crypto payment
      description: |
        Uses the x402 HTTP payment protocol. Send X-Payment-Method: x402 header
        to receive 402 with payment instructions. After on-chain payment, retry
        with X-Payment header containing tx hash.
      parameters:
        - name: X-Payment-Method
          in: header
          schema:
            type: string
            enum: [x402]
          description: Set to "x402" for initial payment request
        - name: X-Payment-Chain
          in: header
          schema:
            type: string
            enum: [base, ethereum, solana]
          description: Preferred blockchain (default base)
        - name: X-Payment-Currency
          in: header
          schema:
            type: string
            enum: [USDC, ETH, SOL]
          description: Preferred currency (default USDC)
        - name: X-Payment
          in: header
          schema:
            type: string
          description: 'JSON with tx hash for retry: {"txHash":"0x...","chain":"base","currency":"USDC"}'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amountCents]
              properties:
                amountCents:
                  type: integer
                  minimum: 500
                  maximum: 1000000
      responses:
        '200':
          description: Payment confirmed, credit added
        '402':
          description: Payment required — X-Payment header contains payment instructions

  /webhooks:
    get:
      tags: [Webhooks]
      summary: List webhooks
      responses:
        '200':
          description: Webhooks list
    post:
      tags: [Webhooks]
      summary: Create a webhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
                    enum:
                      - slot.reserved
                      - slot.submitted
                      - slot.accepted
                      - slot.rejected
                      - slot.expired
                      - payout.completed
                      - payout.failed
                      - payment.completed
                      - payment.failed
                      - chat.message
      responses:
        '200':
          description: Webhook created (includes secret)
