openapi: 3.1.0
info:
  title: BALLDONTLIE Webhooks API
  version: "1.0.0"
  description: |
    Manage webhook endpoints for real-time sports event notifications.

    Webhooks deliver HTTP POST requests to your server when game events occur across
    NBA, MLB, NHL, NCAAB, and NCAAW. Instead of polling the API, register an endpoint URL
    and subscribe to the event types you care about.

    ## Authentication

    All requests require an API key passed via the `Authorization` header:

    ```
    Authorization: YOUR_API_KEY
    ```

    Webhook API access requires an ALL-ACCESS subscription ($299.99/mo).

    ## Rate Limits

    100 requests per minute per user across all webhook endpoints.

    ## Quick Start

    1. Create an endpoint with `POST /webhooks/v1/endpoints`
    2. Subscribe to event types (e.g., `nba.game.started`, `mlb.batter.home_run`)
    3. Store the `secret` from the response for signature verification
    4. Receive signed HTTP POST payloads at your URL as events occur
    5. Monitor delivery health with `GET /webhooks/v1/usage`

    ## Signature Verification

    Every webhook delivery includes three headers for verification:
    - `X-BDL-Webhook-Id` — unique event ID
    - `X-BDL-Webhook-Timestamp` — Unix timestamp
    - `X-BDL-Webhook-Signature` — HMAC-SHA256 signature (`v1=<hex>`)

    Verify by computing `HMAC-SHA256(secret, "${timestamp}.${body}")` and comparing.

servers:
  - url: https://api.balldontlie.io
    description: Production server

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: Your BALLDONTLIE API key

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
          description: Error message
      required:
        - error

    WebhookEndpoint:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique endpoint identifier
          example: "1ca7b516-fb3d-4e8c-b37f-567db7bd971e"
        url:
          type: string
          format: uri
          description: URL that receives webhook POST requests
          example: "https://example.com/webhooks"
        description:
          type: string
          nullable: true
          description: Human-readable label for this endpoint
          example: "NBA game alerts"
        active:
          type: boolean
          description: Whether the endpoint is receiving deliveries. Auto-disabled after 2 consecutive exhausted deliveries.
          example: true
        event_types:
          type: array
          items:
            type: string
          description: Event types this endpoint is subscribed to
          example: ["nba.game.started", "nba.game.ended", "nba.player.scored"]
        filters:
          type: object
          nullable: true
          description: Optional filters to narrow which events are delivered
        consecutive_failures:
          type: integer
          description: Number of consecutive delivery failures. Resets to 0 on successful delivery.
          example: 0
        disabled_at:
          type: string
          format: date-time
          nullable: true
          description: When the endpoint was auto-disabled due to consecutive failures
        created_at:
          type: string
          format: date-time
          example: "2026-02-20T17:53:20.565Z"
        updated_at:
          type: string
          format: date-time
          example: "2026-02-26T19:23:00.783Z"

    WebhookEndpointWithSecret:
      allOf:
        - $ref: '#/components/schemas/WebhookEndpoint'
        - type: object
          properties:
            secret:
              type: string
              description: HMAC-SHA256 signing secret. Only returned on create and rotate-secret. Store securely.
              example: "whsec_16c53e0118f24e07181c215670ff4274dcac869faeceb568c28d7efe38dab7fc"

    WebhookEvent:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique event identifier
          example: "3cf70202-d3ea-4422-a6f4-2a78eee406a2"
        type:
          type: string
          description: Event type
          example: "nba.player.scored"
        sport:
          type: string
          enum: [nba, mlb, nhl, ncaab, ncaaw]
          description: Sport this event belongs to
          example: "nba"
        game_id:
          type: integer
          nullable: true
          description: Game ID associated with this event
          example: 1283054
        payload:
          type: object
          description: Full event payload. Only included when fetching a single delivery.
          example:
            game:
              id: 1283054
            play:
              text: "LeBron James makes 3-point jump shot"
              type: "MadeFieldGoal"
              clock: "4:32"
              period: 2
              away_score: 45
              home_score: 52
              score_value: 3
            player:
              id: 237
              name: "LeBron James"
              team_id: 14
              position: "F"
            event_type: "nba.player.scored"
        created_at:
          type: string
          format: date-time
          example: "2026-02-26T19:23:30.468Z"

    WebhookDelivery:
      type: object
      properties:
        id:
          type: integer
          description: Delivery identifier
          example: 97041
        event_id:
          type: string
          format: uuid
          description: ID of the event being delivered
          example: "3cf70202-d3ea-4422-a6f4-2a78eee406a2"
        endpoint_id:
          type: string
          format: uuid
          description: ID of the endpoint this was delivered to
          example: "1ca7b516-fb3d-4e8c-b37f-567db7bd971e"
        status:
          type: string
          enum: [pending, delivering, delivered, failed, exhausted]
          description: |
            Delivery status:
            - `pending` — queued for delivery
            - `delivering` — currently being sent
            - `delivered` — successfully delivered (2xx response)
            - `failed` — delivery attempt failed, will retry
            - `exhausted` — all retry attempts used
          example: "delivered"
        attempts:
          type: integer
          description: Number of delivery attempts made
          example: 1
        max_attempts:
          type: integer
          description: Maximum retry attempts (3 for free, 5 for ALL-ACCESS)
          example: 5
        next_attempt_at:
          type: string
          format: date-time
          description: When the next retry attempt will occur
        last_response_status:
          type: integer
          nullable: true
          description: HTTP status code from the last delivery attempt
          example: 200
        last_response_body:
          type: string
          nullable: true
          description: Response body from the last delivery attempt (truncated to 1024 chars)
        last_error:
          type: string
          nullable: true
          description: Error message from the last failed attempt
        delivered_at:
          type: string
          format: date-time
          nullable: true
          description: When the event was successfully delivered
          example: "2026-02-26T19:23:30.776Z"
        duration_ms:
          type: integer
          nullable: true
          description: Round-trip delivery time in milliseconds
          example: 28
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        event:
          $ref: '#/components/schemas/WebhookEvent'

    WebhookEventType:
      type: object
      properties:
        type:
          type: string
          description: Event type identifier
          example: "nba.game.started"
        description:
          type: string
          description: Human-readable description
          example: "Game begins"
        sport:
          type: string
          enum: [nba, mlb, nhl, ncaab, ncaaw]
          description: Sport this event type belongs to
          example: "nba"
        available:
          type: boolean
          description: Whether your subscription tier has access to this event type
          example: true

    WebhookUsage:
      type: object
      properties:
        deliveries_this_month:
          type: integer
          description: Number of deliveries made in the current billing month
          example: 66925
        deliveries_limit:
          type: integer
          description: Maximum deliveries allowed per month
          example: 500000
        endpoints_count:
          type: integer
          description: Number of active endpoints
          example: 3
        endpoints_limit:
          type: integer
          description: Maximum endpoints allowed
          example: 10

    DeliveryPagination:
      type: object
      properties:
        next_cursor:
          type: integer
          nullable: true
          description: Cursor for the next page. Pass as `cursor` query parameter.
        per_page:
          type: integer
          description: Number of results per page
          example: 25

security:
  - ApiKeyAuth: []

paths:
  /webhooks/v1/event-types:
    get:
      operationId: listEventTypes
      summary: List available event types
      description: |
        Returns all event types across all sports. The `available` field indicates
        whether your subscription tier has access to each event type.

        Free tier has access to `nba.game.started` and `nba.game.ended` only.
        ALL-ACCESS tier has access to all 55+ event types.
      tags:
        - Event Types
      responses:
        "200":
          description: List of event types
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookEventType'
        "401":
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /webhooks/v1/endpoints:
    get:
      operationId: listEndpoints
      summary: List your webhook endpoints
      description: Returns all webhook endpoints for the authenticated user.
      tags:
        - Endpoints
      responses:
        "200":
          description: List of endpoints
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookEndpoint'

    post:
      operationId: createEndpoint
      summary: Create a webhook endpoint
      description: |
        Create a new webhook endpoint. The response includes a `secret` field —
        store it securely for signature verification. The secret is only returned
        on creation and when rotated.
      tags:
        - Endpoints
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - url
                - event_types
              properties:
                url:
                  type: string
                  format: uri
                  description: HTTPS URL that will receive webhook POST requests
                  example: "https://example.com/webhooks/bdl"
                description:
                  type: string
                  description: Human-readable label
                  example: "NBA live game alerts"
                event_types:
                  type: array
                  items:
                    type: string
                  description: Event types to subscribe to. Use `GET /webhooks/v1/event-types` to see available types.
                  example: ["nba.game.started", "nba.game.ended", "nba.player.scored"]
                filters:
                  type: object
                  nullable: true
                  description: Optional filters to narrow event delivery
      responses:
        "201":
          description: Endpoint created successfully. The `secret` is included in this response only.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/WebhookEndpointWithSecret'
        "400":
          description: Invalid request (bad URL, missing event_types, invalid event type)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "403":
          description: Endpoint limit reached or event type requires ALL-ACCESS
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /webhooks/v1/endpoints/{endpoint_id}:
    parameters:
      - name: endpoint_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Endpoint UUID

    get:
      operationId: getEndpoint
      summary: Get endpoint details
      description: Returns a single endpoint. Does not include the signing secret.
      tags:
        - Endpoints
      responses:
        "200":
          description: Endpoint details
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/WebhookEndpoint'
        "404":
          description: Endpoint not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    patch:
      operationId: updateEndpoint
      summary: Update a webhook endpoint
      description: |
        Update endpoint properties. All fields are optional — only provided fields are changed.
        To re-enable a disabled endpoint, set `active: true`.
      tags:
        - Endpoints
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                  description: New URL for the endpoint
                description:
                  type: string
                  description: New description
                active:
                  type: boolean
                  description: Enable or disable the endpoint
                event_types:
                  type: array
                  items:
                    type: string
                  description: Replace subscribed event types
                filters:
                  type: object
                  nullable: true
                  description: Replace event filters
      responses:
        "200":
          description: Updated endpoint
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/WebhookEndpoint'
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "404":
          description: Endpoint not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      operationId: deleteEndpoint
      summary: Delete a webhook endpoint
      description: Permanently deletes the endpoint and all associated delivery records.
      tags:
        - Endpoints
      responses:
        "200":
          description: Endpoint deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
                    example: true
        "404":
          description: Endpoint not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /webhooks/v1/endpoints/{endpoint_id}/rotate-secret:
    parameters:
      - name: endpoint_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Endpoint UUID

    post:
      operationId: rotateEndpointSecret
      summary: Rotate signing secret
      description: |
        Regenerate the HMAC-SHA256 signing secret. The old secret is immediately
        invalidated. Update your verification code before the next delivery arrives.
      tags:
        - Endpoints
      responses:
        "200":
          description: Endpoint with new secret
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/WebhookEndpointWithSecret'
        "404":
          description: Endpoint not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /webhooks/v1/endpoints/{endpoint_id}/test:
    parameters:
      - name: endpoint_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Endpoint UUID

    post:
      operationId: testEndpoint
      summary: Send a test event
      description: |
        Sends a signed test event to the endpoint URL. Use this to verify your
        server is correctly receiving and verifying webhook payloads. The test
        payload has `type: "test"` and `sport: "test"`.
      tags:
        - Endpoints
      responses:
        "200":
          description: Test result
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    description: Whether the endpoint responded with a 2xx status
                    example: true
                  status:
                    type: integer
                    description: HTTP status code from the endpoint
                    example: 200
                  error:
                    type: string
                    description: Error message if the request failed (timeout, connection refused, etc.)
        "404":
          description: Endpoint not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /webhooks/v1/endpoints/{endpoint_id}/deliveries:
    parameters:
      - name: endpoint_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: Endpoint UUID

    get:
      operationId: listDeliveries
      summary: List deliveries for an endpoint
      description: |
        Returns paginated delivery records for an endpoint, ordered by most recent first.
        Use cursor-based pagination to page through results.
      tags:
        - Deliveries
      parameters:
        - name: cursor
          in: query
          schema:
            type: integer
          description: Cursor from `meta.next_cursor` for pagination
        - name: per_page
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Number of results per page (max 100)
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, delivering, delivered, failed, exhausted]
          description: Filter deliveries by status
      responses:
        "200":
          description: Paginated list of deliveries
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookDelivery'
                  meta:
                    $ref: '#/components/schemas/DeliveryPagination'
        "404":
          description: Endpoint not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /webhooks/v1/deliveries/{delivery_id}:
    parameters:
      - name: delivery_id
        in: path
        required: true
        schema:
          type: integer
        description: Delivery ID

    get:
      operationId: getDelivery
      summary: Get delivery details
      description: |
        Returns a single delivery record with the full event payload included.
        Use this to inspect what was delivered and debug failed deliveries.
      tags:
        - Deliveries
      responses:
        "200":
          description: Delivery with full event payload
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/WebhookDelivery'
        "404":
          description: Delivery not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /webhooks/v1/deliveries/{delivery_id}/retry:
    parameters:
      - name: delivery_id
        in: path
        required: true
        schema:
          type: integer
        description: Delivery ID

    post:
      operationId: retryDelivery
      summary: Retry a delivery
      description: |
        Manually retry a failed or exhausted delivery. Resets attempts to 0 and
        sets status back to `pending` for immediate redelivery.

        Requires ALL-ACCESS subscription.
      tags:
        - Deliveries
      responses:
        "200":
          description: Delivery reset to pending
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/WebhookDelivery'
        "403":
          description: Manual retry requires ALL-ACCESS
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "404":
          description: Delivery not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /webhooks/v1/usage:
    get:
      operationId: getUsage
      summary: Get usage statistics
      description: |
        Returns current month delivery count, delivery limit, endpoint count,
        and endpoint limit for the authenticated user.
      tags:
        - Usage
      responses:
        "200":
          description: Usage statistics
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/WebhookUsage'

tags:
  - name: Event Types
    description: Browse available webhook event types across all sports
  - name: Endpoints
    description: Create, update, and manage webhook endpoints
  - name: Deliveries
    description: Monitor and retry webhook deliveries
  - name: Usage
    description: Track monthly usage and limits
