SharpAPI Streaming API

Real-time SSE streaming

Operations 1

GET /stream Real-time SSE stream #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/sharpapi:sharpapi-streaming-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

sharpapi-streaming-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Sharp Streaming API
  version: 3.28.0
  description: Real-time sports betting odds API with +EV detection, arbitrage, middles, and low-hold opportunities.
  contact:
    name: SharpAPI Support
    url: https://sharpapi.io
    email: support@sharpapi.io
  license:
    name: Proprietary
    url: https://sharpapi.io/terms
  x-generated-at: '2026-10-13T20:02:34Z'
  x-commit-sha: 850f5cb
servers:
- url: https://api.sharpapi.io/api/v1
  description: Production
security:
- ApiKeyHeader: []
- BearerAuth: []
tags:
- name: Streaming
  description: Real-time SSE streaming
paths:
  /stream:
    get:
      operationId: stream
      summary: Real-time SSE stream
      description: 'Server-Sent Events stream for real-time odds and opportunity updates. Requires streaming access (WebSocket add-on, Enterprise tier, or active trial).


        Use `api_key` query parameter for authentication since `EventSource` cannot set headers.


        ## Channels


        | Channel | Description |

        |---------|-------------|

        | `opportunities` | EV, arbitrage, middles, low-hold updates (default) |

        | `odds` | Raw odds changes |

        | `all` | Both odds and opportunities |

        | `gamestate` | Live game state updates (Game State add-on or Enterprise) |


        ## Event Types


        | Event | Payload | Description |

        |-------|---------|-------------|

        | `connected` | `{filters, channels}` | Stream established |

        | `snapshot` | Array of items | Initial data dump (one per type) |

        | `snapshot:complete` | `{}` | All snapshots sent |

        | `odds:update` | `Odds` | Odds changed |

        | `odds:removed` | `{id, sportsbook}` | Selection removed |

        | `ev:detected` | `{opportunities: EVOpportunity[], count, type}` | New or updated +EV opportunities |

        | `ev:expired` | `{expired: id[], count, type}` | EV opportunities gone |

        | `arb:detected` | `{opportunities: ArbitrageOpportunity[], count, type}` | New or updated arbitrage |

        | `arb:expired` | `{expired: id[], count, type}` | Arbitrage gone |

        | `middles:detected` | `{opportunities: MiddleOpportunity[], count, type}` | New or updated middles |

        | `middles:expired` | `{expired: id[], count, type}` | Middles gone |

        | `low_hold:detected` | `{opportunities: LowHoldOpportunity[], count, type}` | New or updated low-hold |

        | `low_hold:expired` | `{expired: id[], count, type}` | Low-hold gone |

        | `gamestate:snapshot` | `{sport, events}` | Initial game state |

        | `gamestate:update` | `{sport, event_id, ...}` | Game state changed |

        | `heartbeat` | `{ts}` | Keep-alive (every 30s) |

        | `error` | `{message}` | Recoverable error |


        A `:detected` event carries new opportunities **and** updated versions of ones already sent: since 2026-09-26, an opportunity whose content changes (price, EV%, fair probability, leg odds, `is_suspended`, `quality_tier`) is re-sent under the same `id`. Upsert by `id`, and treat only an `id` you have not seen as new.


        Supports `Last-Event-ID` header for reconnection.'
      tags:
      - Streaming
      security:
      - ApiKeyQuery: []
      - ApiKeyHeader: []
      - BearerAuth: []
      parameters:
      - name: api_key
        in: query
        description: API key (for EventSource which cannot set headers)
        schema:
          type: string
      - name: channel
        in: query
        description: Stream channel
        schema:
          type: string
          enum:
          - opportunities
          - odds
          - all
          - gamestate
          default: opportunities
      - $ref: '#/components/parameters/sport'
      - $ref: '#/components/parameters/league'
      - $ref: '#/components/parameters/sportsbook'
      - $ref: '#/components/parameters/market'
      - $ref: '#/components/parameters/event_id'
      - $ref: '#/components/parameters/is_live'
      - $ref: '#/components/parameters/live'
      - $ref: '#/components/parameters/min_ev'
      - $ref: '#/components/parameters/min_profit'
      - name: state
        in: query
        description: US state code for deep-link routing only; does not filter odds or return state-specific prices. Recognises the 50 US state codes plus dc. When a recognised code is set, deep_link values in odds events include ?state=XX. Omitted, empty, and unsupported codes all emit no ?state= — unlike the REST odds and single-redirect endpoints, the stream handshake does not reject an unsupported code, but emits filter_warning after connected — and the redirect endpoint then applies its own pa default.
        schema:
          type: string
      responses:
        '200':
          description: SSE event stream
          content:
            text/event-stream:
              schema:
                type: string
                description: Server-Sent Events stream. Each event has `event:` type, `data:` JSON payload, and `id:` for reconnection.
              example: 'event: connected

                data: {"filters":{"channel":"opportunities","sport":null},"session_id":"abc123"}

                id: 1709993400000-1


                event: ev:detected

                data: {"id":"a1b2c3d4e5f67890","ev_percentage":4.2,"sportsbook":"draftkings","selection":"Los Angeles Lakers","market":"moneyline","sport":"basketball","league":"nba"}

                id: 1709993400000-2


                event: heartbeat

                data: {"ts":1709993430000}

                id: 1709993430000-3

                '
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Streaming access required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: streaming_not_available
                  message: Streaming requires WebSocket add-on or Enterprise tier
                  docs: https://sharpapi.io/pricing
components:
  parameters:
    event_id:
      name: event_id
      in: query
      description: 'Filter by event ID (comma-separated). `event_id` is the canonical name. The legacy spellings `event`, `events` and `event_ids` are still accepted, but a request that uses one is answered with `Deprecation: true` and `Warning: 299 - "filter alias(es) ... deprecated; use event_id"`. No `Sunset` date is sent. Send `event_id` to avoid those headers.'
      schema:
        type: string
    min_ev:
      name: min_ev
      in: query
      description: Minimum EV percentage
      schema:
        type: number
        default: 0
    is_live:
      name: is_live
      in: query
      description: 'Filter by live status. `is_live` is the canonical name. The legacy spelling `live` is still accepted, but a request that uses it is answered with `Deprecation: true` and `Warning: 299 - "filter alias(es) live deprecated; use is_live"`. Send `is_live` to avoid those headers.'
      schema:
        type: boolean
    sport:
      name: sport
      in: query
      description: Filter by sport (comma-separated)
      schema:
        type: string
      example: basketball
    min_profit:
      name: min_profit
      in: query
      description: Minimum profit percentage
      schema:
        type: number
        default: 0.5
    sportsbook:
      name: sportsbook
      in: query
      description: 'Filter by sportsbook (comma-separated). **Intentionally not enumerated.** This one parameter definition is shared by eleven operations, and they do not accept the same set of values, so no single `enum` would be correct for all of them. Nine of them validate the value and answer `400 invalid_filter` for one they do not accept; the other two do not reject an unrecognised value at all — `/splits/history` returns no rows for it and `/stream` opens the stream. See `/sportsbooks` for the book roster. The clearest per-operation difference is the synthetic `consensus` value: it is meaningful only on `/splits`, and every other operation that validates this filter rejects it.'
      schema:
        type: string
      example: draftkings,fanduel
    market:
      name: market
      in: query
      description: Filter by market type (comma-separated)
      schema:
        type: string
      example: moneyline,point_spread
    league:
      name: league
      in: query
      description: Filter by league (comma-separated)
      schema:
        type: string
      example: nba
    live:
      name: live
      in: query
      deprecated: true
      description: 'Deprecated alias of `is_live`, still accepted and behaving identically. A request using it is answered with `Deprecation: true` and `Warning: 299 - "filter alias(es) live deprecated; use is_live"`. No `Sunset` date is sent. Prefer `is_live`.'
      schema:
        type: boolean
  schemas:
    ErrorResponse:
      type: object
      required:
      - error
      properties:
        error:
          type: object
          required:
          - code
          - message
          properties:
            code:
              type: string
              enum:
              - missing_api_key
              - invalid_api_key
              - unauthorized
              - forbidden
              - tier_restricted
              - rate_limited
              - invalid_request
              - validation_error
              - not_found
              - streaming_not_available
              - stream_limit_exceeded
              - service_unavailable
              - internal_error
              description: Machine-readable error code
            message:
              type: string
              description: Human-readable error message
            docs:
              type: string
              format: uri
              description: Link to relevant documentation
            tier:
              type: string
              description: Current subscription tier
            required_tier:
              type: string
              description: Minimum tier required for this feature
            retry_after:
              type: integer
              description: Unix timestamp (ms) when request can be retried
            retryAfter:
              type: integer
              description: Seconds until the request can be retried — same instant as retry_after, different unit and casing
            max_books:
              type: integer
            requested_books:
              type: integer
  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            missing:
              summary: Missing API key
              value:
                error:
                  code: missing_api_key
                  message: API key required
                  docs: https://docs.sharpapi.io/en/authentication#authentication-methods
            invalid:
              summary: Invalid API key
              value:
                error:
                  code: invalid_api_key
                  message: Invalid API key
                  docs: https://docs.sharpapi.io/en/authentication#authentication-methods
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key passed in the `X-API-Key` header
    BearerAuth:
      type: http
      scheme: bearer
      description: API key passed as a Bearer token in the `Authorization` header
    ApiKeyQuery:
      type: apiKey
      in: query
      name: api_key
      description: API key as query parameter (for SSE/EventSource only)