> ## Documentation Index
> Fetch the complete documentation index at: https://developer.hooper.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# Submit a video

> Places a credit hold, creates the session and queues it for processing. Responds immediately with the session in `queued` state; you are notified through a webhook (`session.processed` / `session.failed`) when it finishes, or you can poll `GET /v1/sessions/{id}`.

Send an `Idempotency-Key` header to make retries safe: the same key with the same body returns the original response for 24 hours.



## OpenAPI

````yaml /openapi.json post /v1/sessions
openapi: 3.1.0
info:
  title: Hooper API
  version: 1.0.0
  description: >-
    Submit basketball game video and receive highlights, shots and per-player
    box scores.


    All endpoints require `Authorization: Bearer hk_prod_…`. Money fields are
    integer **cents**. Timestamps are Unix seconds. Every list — top-level or
    embedded — is `{ object: "list", data, has_more, total_count }`.
servers:
  - url: https://api.hooper.gg
    description: Production (accepts hk_prod_ keys)
security:
  - apiKey: []
tags:
  - name: Uploads
    description: Resumable video uploads to Hooper storage.
  - name: Sessions
    description: >-
      A session is one submitted game and everything the pipeline produced for
      it.
  - name: Billing
    description: Prepaid credits, metered per second of video.
  - name: Webhooks
    description: Signed HTTP callbacks when a session finishes.
  - name: Events
    description: The log of every event Hooper has emitted for your organization.
paths:
  /v1/sessions:
    post:
      tags:
        - Sessions
      summary: Submit a video
      description: >-
        Places a credit hold, creates the session and queues it for processing.
        Responds immediately with the session in `queued` state; you are
        notified through a webhook (`session.processed` / `session.failed`) when
        it finishes, or you can poll `GET /v1/sessions/{id}`.


        Send an `Idempotency-Key` header to make retries safe: the same key with
        the same body returns the original response for 24 hours.
      operationId: createSession
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSessionRequest'
      responses:
        '201':
          description: Session queued
          headers:
            X-Hooper-Cost-Cents:
              description: >-
                Cents held for this job (estimate; corrected when the job
                settles).
              schema:
                type: integer
            X-Hooper-Balance-Cents:
              description: Available balance after the hold.
              schema:
                type: integer
            Idempotent-Replayed:
              description: >-
                Present and `true` when this is a replay of an earlier request
                with the same Idempotency-Key.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: Insufficient credits
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InsufficientCreditsError'
        '409':
          description: Upload not finished, or the organization has no group yet
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: >-
        Any string up to 255 characters. Replaying the same key with the same
        body within 24h returns the original response; a different body returns
        400 `idempotency_key_reused`.
      schema:
        type: string
  schemas:
    CreateSessionRequest:
      type: object
      required:
        - source
      properties:
        source:
          $ref: '#/components/schemas/SourceInput'
        group:
          type: string
          description: >-
            A group under your organization. Defaults to the organization's
            default group.
          example: grp_412
        metadata:
          type: object
          additionalProperties:
            type: string
          description: >-
            Up to 20 string key/values (keys ≤ 40 chars, values ≤ 500). Echoed
            on the session and inside every event — use it to carry your own
            ids.
          example:
            external_match_id: abc-123
        duration_seconds:
          type: integer
          minimum: 1
          description: >-
            Optional. Improves the credit hold estimate; the real charge is
            always the measured length.
        options:
          $ref: '#/components/schemas/SessionOptionsInput'
    Session:
      type: object
      description: >-
        One submitted game. The same shape everywhere it appears — single read,
        list item, webhook payload. Highlights, shots and players are embedded
        (first 100 of each); their `url` pages the rest.
      properties:
        id:
          type: string
          example: sess_18234
        object:
          type: string
          const: session
        created:
          type: integer
          example: 1756224000
        status:
          $ref: '#/components/schemas/SessionStatus'
        group:
          type: string
          example: grp_412
        source:
          type: object
          properties:
            type:
              type: string
              enum:
                - upload
                - url
                - veo
            url:
              type: string
              nullable: true
        metadata:
          type: object
          additionalProperties:
            type: string
        run_version:
          type: integer
          nullable: true
        duration_seconds:
          type: integer
          nullable: true
          example: 5412
        cost_cents:
          type: integer
          nullable: true
          example: 752
        highlights:
          allOf:
            - $ref: '#/components/schemas/ListEnvelope'
            - type: object
              properties:
                data:
                  type: array
                  items:
                    $ref: '#/components/schemas/Highlight'
        shots:
          allOf:
            - $ref: '#/components/schemas/ListEnvelope'
            - type: object
              properties:
                data:
                  type: array
                  items:
                    $ref: '#/components/schemas/Shot'
        players:
          allOf:
            - $ref: '#/components/schemas/ListEnvelope'
            - type: object
              properties:
                data:
                  type: array
                  items:
                    $ref: '#/components/schemas/Player'
        totals:
          $ref: '#/components/schemas/BoxScore'
        video:
          type: object
          properties:
            url:
              type: string
              nullable: true
            preview_url:
              type: string
              nullable: true
            1080p:
              type: string
              nullable: true
            720p:
              type: string
              nullable: true
            360p:
              type: string
              nullable: true
            width:
              type: integer
              nullable: true
            height:
              type: integer
              nullable: true
            fps:
              type: number
              nullable: true
        artifacts:
          type: object
          description: Raw model output (JSONL) behind the shots and highlights.
          properties:
            actions:
              type: string
              nullable: true
            balls:
              type: string
              nullable: true
            hoops:
              type: string
              nullable: true
            clusters:
              type: string
              nullable: true
        error:
          type: object
          nullable: true
          description: Set when `status` is `failed`.
          properties:
            code:
              type: string
              enum:
                - no_shots
                - no_hoops
                - no_court
                - video_too_large
                - source_invalid
                - source_unavailable
                - timeout
                - unknown
            message:
              type: string
    InsufficientCreditsError:
      type: object
      properties:
        error:
          type: object
          properties:
            type:
              type: string
              const: insufficient_credits
            code:
              type: string
              const: insufficient_credits
            message:
              type: string
            needed_cents:
              type: integer
              example: 752
            available_cents:
              type: integer
              example: 100
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - code
            - message
          properties:
            type:
              type: string
              enum:
                - invalid_request_error
                - authentication_error
                - insufficient_credits
                - rate_limit_error
                - api_error
            code:
              type: string
              example: parameter_invalid
            message:
              type: string
            param:
              type: string
              description: The request field the error refers to, when there is one.
              example: source.url
    SourceInput:
      type: object
      description: >-
        Where the video comes from. `type` is optional — it is derived from the
        URL (a Veo link is always routed to Veo).
      properties:
        type:
          type: string
          enum:
            - upload
            - url
            - veo
        upload_id:
          type: string
          description: 'For `upload`: the id from POST /v1/uploads.'
          example: up_3f9a1c2b7e5d4a08
        url:
          type: string
          description: >-
            For `url` (a public https MP4/MOV) or `veo` (a public Veo match page
            or CDN link).
          example: https://app.veo.co/matches/20260826-acme-vs-north/
    SessionOptionsInput:
      type: object
      properties:
        session_type:
          type: string
          enum:
            - solo
            - 1v1
            - 2v2
            - 3v3
            - 4v4
            - 5v5
          default: 5v5
        session_mode:
          type: string
        point_system:
          type: integer
        court_type:
          type: string
          example: ncaa_men
        camera_setup:
          type: string
          enum:
            - stationary
            - panning
        recording_setup:
          type: string
        has_jerseys:
          type: boolean
          default: false
        num_players:
          type: integer
        recording_team_name:
          type: string
        opponent_team_name:
          type: string
    SessionStatus:
      type: string
      enum:
        - queued
        - processing
        - processed
        - failed
    ListEnvelope:
      type: object
      required:
        - object
        - data
        - has_more
        - total_count
      properties:
        object:
          type: string
          const: list
        has_more:
          type: boolean
        total_count:
          type: integer
        url:
          type: string
          description: 'Embedded lists only: the endpoint that pages the full set.'
          example: /v1/sessions/sess_18234/highlights
    Highlight:
      type: object
      description: A clip cut around one shot, in several renditions.
      properties:
        id:
          type: string
          example: hl_9f2c…
        object:
          type: string
          const: highlight
        shot:
          type: string
          description: The shot this clip is for.
          example: shot_a1b2…
        shot_index:
          type: integer
          nullable: true
        length_seconds:
          type: number
          example: 8.5
        is_lowlight:
          type: boolean
        is_manual:
          type: boolean
        player:
          type: string
          nullable: true
          description: The scorer, as a `plr_` id from the session's `players`.
          example: plr_5
        video:
          type: object
          properties:
            default:
              type: string
              nullable: true
            horizontal:
              type: string
              nullable: true
            1080p:
              type: string
              nullable: true
            720p:
              type: string
              nullable: true
            480p:
              type: string
              nullable: true
            360p:
              type: string
              nullable: true
            240p:
              type: string
              nullable: true
            144p:
              type: string
              nullable: true
        preview:
          type: object
          properties:
            default:
              type: string
              nullable: true
            horizontal:
              type: string
              nullable: true
        created:
          type: integer
          nullable: true
    Shot:
      type: object
      properties:
        id:
          type: string
          example: shot_a1b2…
        object:
          type: string
          const: shot
        index:
          type: integer
          nullable: true
        start_seconds:
          type: number
          nullable: true
        end_seconds:
          type: number
          nullable: true
        made:
          type: boolean
          nullable: true
        points:
          type: integer
          nullable: true
          description: 0 when missed; 1 for a free throw; 2 or 3 otherwise.
        type:
          type: string
          example: fieldgoal
        is_three_point:
          type: boolean
          nullable: true
        is_free_throw:
          type: boolean
        is_fouled:
          type: boolean
        location:
          type: object
          description: Normalized (0–1) coordinates.
          properties:
            shot_image_normed:
              type: array
              items:
                type: number
              nullable: true
            shot_world_normed:
              type: array
              items:
                type: number
              nullable: true
            hoop_image_normed:
              type: array
              items:
                type: number
              nullable: true
        excitement_score:
          type: number
          nullable: true
        players:
          type: object
          properties:
            scorer:
              type: string
              nullable: true
              example: plr_5
            assister:
              type: string
              nullable: true
            rebounder:
              type: string
              nullable: true
        highlight:
          type: string
          nullable: true
          description: The clip for this shot, when one was cut.
          example: hl_9f2c…
    Player:
      type: object
      properties:
        id:
          type: string
          example: plr_5
        object:
          type: string
          const: player
        name:
          type: string
          nullable: true
        jersey:
          type: string
          nullable: true
        user_id:
          type: string
          nullable: true
        team_id:
          type: integer
          nullable: true
        stats:
          $ref: '#/components/schemas/BoxScore'
    BoxScore:
      type: object
      properties:
        points:
          type: integer
        fg_2pt_att:
          type: integer
        fg_2pt_make:
          type: integer
        fg_3pt_att:
          type: integer
        fg_3pt_make:
          type: integer
        ft_att:
          type: integer
        ft_make:
          type: integer
        assist:
          type: integer
        rebound:
          type: integer
  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Missing, invalid, revoked or wrong-environment API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: More than 60 requests per minute for this key
      headers:
        Retry-After:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: hk_prod_… / hk_stg_…
      description: >-
        API key issued by Hooper for your organization. Keys are
        environment-tagged: `hk_prod_` keys work against production, `hk_stg_`
        keys against staging.

````