# REST Endpoint

`💁 since version 1.7` Odds tables load their odds in the visitor's browser from a REST endpoint on your own website:

```text
GET /wp-json/dinodds/v1/odds
```

The page itself only contains an empty table with a loading animation, so page caches (a caching plugin, your host or a CDN) never keep outdated odds.

* **Public.** No login or API key is needed, because visitors' browsers call it.
* **Read-only.** It only renders the odds already saved on your website. It never contacts the feed server and never returns your Client ID.
* **Cache-friendly.** It is a plain `GET` request with a cache header, see [Caching](https://docs.affkraft.com/dinodds/rest-api#caching).

If your site doesn't use pretty permalinks, the route is `/?rest_route=/dinodds/v1/odds`.

## Parameters

| Parameter  | Required | Description                                                                                           |
| ---------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `league`   | Yes      | League slug, e.g. `nfl`. Letters, numbers, `-` and `_`, up to 32 characters.                         |
| `bookies`  | No       | Comma-separated sportsbook slugs. Empty means all sportsbooks in the feed.                            |
| `style`    | No       | `1` or `2`. Defaults to the [Display tab](https://docs.affkraft.com/dinodds/colors) value.                                     |
| `layout`   | No       | `top` or `inline`. Defaults to the Display tab value.                                                 |
| `type`     | No       | `spread`, `moneyline` or `total`. Defaults to the Display tab value.                                  |
| `selector` | No       | `true` or `false`, for the `html` field. Defaults to the Display tab value.                           |
| `away`     | No       | Away team, for a single match. Needs `home` too.                                                      |
| `home`     | No       | Home team, for a single match. Needs `away` too.                                                      |
| `class`    | No       | Extra CSS class for the table wrapper.                                                                |
| `part`     | No       | What markup to return: `matches` (default, only the matches fragment), `table` (the complete table) or `none` (no markup, useful with `games=true`). |
| `games`    | No       | `true` also returns the league's upcoming games (used by the block's match picker). Default `false`.  |

A missing `league`, a value that isn't allowed (for example `style=3`), or an array parameter such as `bookies[]=x` gets a `400` response (`rest_invalid_param` or `rest_missing_callback_param`).

## Response

A JSON object. It contains `matches` (the default) or `html` (with `part=table`), never both; with `part=none` it has neither.

```json
{
  "matches": "...",
  "style": "1",
  "type": "spread",
  "lastUpdated": 1793100000,
  "games": [{ "away": "Buffalo Bills", "home": "Houston Texans", "date": "2026-11-01" }]
}
```

| Field         | Description                                                                                      |
| ------------- | ------------------------------------------------------------------------------------------------ |
| `matches`     | Default. Only the matches markup, which the front-end script puts into the page's table.        |
| `html`        | Only with `part=table`: the complete table (wrapper, header and matches).                        |
| `style`       | The style used (`1` or `2`).                                                                      |
| `type`        | The odds type used.                                                                              |
| `lastUpdated` | Unix time of the last successful feed update (`0` if the feed was never loaded).                 |
| `games`       | Only with `games=true`: upcoming games of the league, with team names and dates (no prices).     |

An unknown league, or a league with no upcoming matches, still gives `200` with the "no matches available" markup.

## Caching

The response has a `Cache-Control: public` header with `max-age` and `s-maxage` set to the time until the next scheduled feed update (at least 60 seconds, at most one hour). Browsers and CDNs can keep it until fresh odds are due. See [Troubleshooting](https://docs.affkraft.com/dinodds/troubleshooting#caching).

## Legacy `admin-ajax.php` request

Before 1.7, odds were loaded through `admin-ajax.php`. The action is now `dinodds_fetch_odds`, and the old name `fetch-odds` still works as an alias for cached pages. The request is still answered, so pages that were cached with the old markup keep working, and the front-end script falls back to it if the REST request fails. New code should use the REST endpoint.
