apiquickstart

PhotonOdds API Quickstart: Your First Request

Make your first PhotonOdds API request, find an event, retrieve its odds, and follow cursor pagination without assuming fields or prices stay current.

Reviewed by the PhotonOdds data team
Market screens glowing above a notebook and pencil on a working desk
A working view for learning to read the board before it moves.

What do you need before the first request?

Create an API key in the Developer portal and keep it in a server-side environment variable. Send it in the X-API-Key header. The examples use https://api.photonodds.com/api/v1; substitute the base URL shown in your deployment's API documentation if it differs.

export PHOTONODDS_API_KEY="your-key-here"
curl --get "https://api.photonodds.com/api/v1/events" \
  --header "X-API-Key: $PHOTONODDS_API_KEY" \
  --data-urlencode "limit=20"

Keep the key out of browser code, source control, and logs. API authentication and rate limits covers capacity headers and recovery from 429 responses.

Run this from a terminal or server process that can read the environment variable. If you use a hosted job, put the same value in that platform's secret store and inject it at runtime. A browser request would expose the credential to anyone who can inspect the page or network traffic, so put your own server between the browser and PhotonOdds when building a public-facing view.

Check the HTTP status before treating the body as data. A successful response has a data field; failures use an error object. Keep the X-Request-ID response header when reporting a failed request so the request can be traced without copying a credential or an entire payload into a support message. If the key is invalid or the chosen route is outside its entitlement, changing the URL or retrying indefinitely will not solve the underlying access problem. Confirm the key and plan first.

How do you find the right event?

GET /events returns a data array of events. Each event has an id you can use on the event-specific routes. If you know a team or competition name, GET /events/search?q= narrows the discovery step; the query must contain at least three characters. For a repeatable integration, store the returned event ID rather than building one from team names.

curl --get "https://api.photonodds.com/api/v1/events/search" \
  --header "X-API-Key: $PHOTONODDS_API_KEY" \
  --data-urlencode "q=Arsenal" \
  --data-urlencode "limit=20"

Check sport, home, away, and starts_at before selecting an ID. A search term can match more than one event, and the available event set depends on the bookmakers in your key's scope.

The GET /events list is useful when your application needs a board rather than a particular fixture. You can narrow it by sport or competition and page through the results. Search is better when a person enters a name, but the text alone is not a stable identifier. Team names can recur across competitions, and an event may have a similar name to another event on the same day. Match at least the participants, competition, start time, and sport before persisting the id.

Persist the ID together with the context used to select it. That gives an operator enough information to diagnose a mismatch later, even if the original search term is forgotten. If a stored ID no longer resolves, do not silently substitute the first similarly named event. Return to discovery and require your matching rule to identify the replacement. A mistaken event association can make an otherwise well-formed odds response misleading.

If you need the list of supported sports, bookmakers, or markets to populate a filter control, use the corresponding reference routes (GET /sports, GET /bookmakers, and GET /markets) rather than hard-coding labels from a sample response. The values available to a particular key still depend on its scope; use the response your own key receives as the source for the integration.

How do you request odds for that event?

Pass the chosen ID to GET /events/{id}/odds. The response groups markets under bookmakers. Read each market's type, period, optional line, outcomes, and observed_at before comparing prices. A price is an observation; it may have changed by the time your application displays it.

curl "https://api.photonodds.com/api/v1/events/EVENT_ID/odds" \
  --header "X-API-Key: $PHOTONODDS_API_KEY"

Illustrative example. Your search returns an event with ID evt_example_123, sport football, and a start time that matches the fixture you intended. Replace EVENT_ID in the URL with that ID. The response contains a bookmaker's full-time moneyline market with a home quote of 2.10 observed at 14:32:00Z. Your display pairs that quote with the event and bookmaker name and shows the observation time. Before treating another bookmaker's 2.05 as a comparison, it confirms that both records describe the same event, period, market, and home outcome. These IDs, prices, and times are invented for illustration; they are not live data.

The event odds route is a good first inspection call because it exposes the bookmaker grouping. In a production view, decide which fields are required for each row and reject a row when its market identity is incomplete. Odds values arrive as strings; parse them deliberately when a calculation needs a number, rather than relying on implicit conversion in a UI template. Keep the original value and source timestamp with any derived figure so a later audit can distinguish what the API returned from what your application computed.

For one best quote per outcome across the selected bookmakers, the API response-shapes guide explains GET /events/{id}/odds/best and its required line parameter for spread, total, and Asian-handicap markets. The best-odds route answers a narrower question than the complete event response. Choose it when you need that view, and retain the quoted bookmaker and observed_at rather than displaying a price without its source.

How do you page through a larger result set?

List and search responses include meta.has_more and, when another page exists, meta.cursor. Request the next page with cursor=<meta.cursor> while keeping the same filters. Continue until has_more is false. Treat the cursor as an opaque value; do not decode or construct it yourself. If a cursor is rejected, restart the query from its first page and reconcile records by event ID.

For GET /odds, pagination moves across events, even when an event contains several bookmaker or market records. When you supply explicit event_ids to that bulk route, do not combine them with a cursor; the route rejects that combination. Polling and the change feed describes a different cursor pattern for ongoing updates.

An integration that pages through events should treat a page as a unit of work. Fetch it, validate its response, upsert each event by ID, and then save the next cursor. If the process stops before the cursor is saved, repeating the page should update the same event records instead of duplicating them. If the process saves the cursor first and fails while writing the page, it can skip records. That ordering matters more than the programming language or database you choose.

Keep the original filter values with the saved cursor. The server binds a cursor to its filter set; changing a sport or bookmaker filter during pagination calls for a fresh first page. Cursors are temporary traversal state, not permanent event identifiers or a historical archive. For a user interface, a fresh filtered request is clearer than trying to reuse a cursor after a filter control changes.

The REST change feed also returns meta.cursor, but it represents progress through updates rather than an event-list page. Do not share one cursor variable between an event listing and a change consumer. Give each job its own stored cursor, filter set, and last successful processing time. A cursor can tell you where to continue; it does not prove that the observation is still current.

What should your first production check cover?

Run the same sequence with a small scope before scheduling it. Confirm that the key works, your search selects the intended event, and the event odds response contains the market and bookmakers you expect. Log status codes, request duration, event IDs, and observation times while keeping the API key and any sensitive response data out of routine logs. A successful HTTP response can still be the wrong fixture or an empty market for your application, so validate the contents separately from transport success.

Exercise three ordinary failure paths: no matching event, no odds for a chosen event, and a request that exceeds the key's hourly capacity. The UI or job should explain an empty result without filling it with a previous price, and it should respect Retry-After after a 429. A bounded retry policy prevents one failed request from turning into an unending loop. If the service runs continuously, add an operator-visible signal when it has not completed a successful refresh within your chosen interval.

Finally, decide how your application uses observed_at. The server's response time and the bookmaker observation time answer different questions. A freshly received response can contain a record observed earlier. Define a maximum age appropriate to your view, mark or omit records beyond it, and show the observation time when a person needs to judge freshness. This is an application policy rather than an assurance that any displayed quote remains available.

Put the first integration into practice

Review the Odds API overview for access options and reference documentation, then create a key in the Developer portal. Fetch a small event page, select an event ID, and inspect its odds. The API use cases guide shows how to turn that first request into a comparison view, change monitor, or recorded market review. Validate the market and observation time in your own application before using the data for analysis or an alert. An alert is an observation aid, not a bet instruction.

Your first milestone can be modest: one stored event ID, one validated odds response, and a visible timestamp. Once that works, add pagination or a change feed only if the application requires broader or more frequent coverage. Each additional route brings a distinct state to store and validate; building outward from one trustworthy request makes those later decisions easier to explain and test.

18+ only. Betting carries risk. Odds data and signals do not guarantee an outcome or a return.

18+ only. Betting carries risk. PhotonOdds provides analytical and educational tools, not a promise of profit or a recommendation to place a bet. If gambling is causing harm, see Responsible Gambling.