Open Patrol data API
You were given a token. This page says what it reads and how.
What it is
The API serves the facts Open Patrol publishes on its website, as data: which game stood in which place of which row on which casino's homepage on which day, plus the lists those facts refer to (markets, editions, casinos, studios, games, licences).
It serves facts, not figures. There are no shares, ranks or top lists. If you need a share, compute it from the placements. A figure you compute may differ from the one on the website, because the website applies its own counting rules.
It is read-only. Every request is a GET.
The token
Send it in one header on every request:
Authorization: Bearer <your token>
- The token starts with
opd_. Treat it like a password. - It is accepted in the header only. Do not put it in the address.
- A token comes with Market Patrol or Global Patrol, or is issued by us by hand. What it reads is your plan: a Market Patrol reads its studio in each market you bought, a Global Patrol its studio in every market we read, and several plans together read all of these. Daily Patrol holds no data. A token may have an expiry date.
- If you lose it, ask for a new one. The old one will be revoked. Nobody can read it back.
Base address
https://open.playpatrol.app/api/data/v1
GET /api/data/v1 itself lists the addresses below. It is the quickest way to check that your token
works.
Answers
Every JSON answer has the same shape:
{"data": [ ... ], "next": null}
data is a list of rows. next is the address of the next page, or null when there is none. To
read everything, request next until it is null. Use the address as given; do not build it
yourself.
Dates are YYYY-MM-DD. Addresses in the rows are absolute.
The seven addresses
/markets
The markets that have at least one published edition. No parameters.
| Key | Meaning |
|---|---|
code |
the market's code, upper case: ESP, or a US state's ISO 3166-2 code. This is how every other address names a market |
iso2 |
the two-letter country code |
name |
the market's name |
language |
the market's language |
timezone |
the market's time zone |
regulator |
the regulator whose licence register we read: name, register_url, read_on (the day we read it). null if we have not read one |
/markets/{geo}/licences
The licence list of one market, the same rows the market's page on the website shows. {geo} is the
market's code. No parameters. The list can be empty for a market whose register we have not read yet.
| Key | Meaning |
|---|---|
name |
the casino's name |
domain |
the casino's domain |
holder |
the licence holder, or null |
number |
the licence number, or null |
monitored |
true if we scan this casino's homepage in this market |
/editions
The published editions. An edition is one scan of one market. Parameter: market (optional).
| Key | Meaning |
|---|---|
market |
the market's code |
number |
the edition's number within its market |
scanned_on |
the day of the scan |
url |
the report's page on the website |
/casinos
The casinos that appear in a published edition. Parameter: market (optional). It chooses which
casinos are listed; a casino's row is the same either way.
| Key | Meaning |
|---|---|
domain |
the casino's domain. This is how placements name a casino |
name |
the casino's name as the website shows it |
markets |
the codes of the markets the casino was read in, among those your token covers |
licence |
number, issuer (may be empty), checked_on (may be null), register_url (may be null). The whole key is null if we hold no licence number, or if the casino has no page on the website |
url |
the casino's page on the website, or null if it has none |
/providers
The game studios in our catalogue that appear in a published edition, as the studio a casino showed or as the maker of a game it showed, so the list may hold a studio whose games casinos show only under other names. No parameters.
| Key | Meaning |
|---|---|
id |
the studio's id. This is how games and placements name a studio |
name |
the studio's name |
url |
the studio's page on the website, or null if it has none |
group |
the studio group the studio belongs to, or is: id (the group's own studio id), name (the group's bare name, "Evolution", as the group's page prints it) and url (the group's page on the website, or null). null for a studio in no group |
/games
The games in our catalogue that appear in a published edition. Parameters: provider (a studio's id,
optional), limit (default 1000, at most 5000), after (taken from next).
| Key | Meaning |
|---|---|
id |
the game's id. This is how placements name a game |
title |
the game's title as the website shows it |
provider_id |
the id of the game's studio |
url |
the game's page on the website, or null if it has none |
/placements
The placements themselves. One row is one game in one place of one row of one casino's homepage, in one edition.
| Key | Meaning |
|---|---|
market |
the market's code |
edition |
the edition's number |
scanned_on |
the day this placement was recorded |
casino |
the casino's domain |
row |
the position of the row on the homepage |
row_name |
the row's title as the casino wrote it |
position |
the game's position in that row, as recorded. Positions may have gaps |
game_id |
the game's id in our catalogue, or null if the game is not in it |
game |
the game's title as the website spells it; when game_id is null, the recorded text in that spelling |
provider_id |
the studio's id in our catalogue, or null if the studio is not in it |
provider |
the studio's name. When provider_id is null, the text the scan recorded, or null when the scan could not identify the studio: a name without an id is a studio our catalogue does not hold, and both null means not identified |
capture |
the address of our screenshot of that casino's homepage in that edition, or null |
page_tiles |
the count of tiles recorded on that casino's page in that edition, as /evidence/{casino}/{day} prints it; no share is served |
maker_id |
the id of the studio our catalogue records as the game's maker, or null when the catalogue does not name one |
maker |
that studio's name, or null with maker_id. It is the studio a game's page names as its maker, and it differs from provider where the casino showed a publisher's name |
Nothing is left out: a placement whose studio we could not identify is served too, with a null id.
A placement's provider is the studio the casino showed for that game, which may be a publisher
rather than the studio that made it; the provider filter reads the same studio. maker_id and
maker name the studio that made it, as a game's page does; no filter reads them. A game's
provider_id in the games list is the studio our catalogue records as its maker.
Filters. All optional, and they combine.
| Parameter | Value |
|---|---|
market |
a market's code |
edition |
an edition's number. Only together with market |
casino |
a casino's domain |
provider |
a studio's id |
game |
a game's id |
from, to |
scan dates, inclusive, YYYY-MM-DD |
A casino, studio or game we do not know gives an empty list, not an error.
Order. Always the same: by market, then edition number, then the order the placements were recorded.
Paging. limit is 1000 by default and at most 5000. Follow next until it is null. For a full
walk of a large market, use limit=5000, or use CSV. A page can occasionally hold a few rows more
than limit. A walk never skips a row and never repeats one. A limit above the maximum is a 400.
Placements as CSV
Add format=csv to /placements. You get the same rows as a table: one header row with the fifteen
keys above, in that order, then one line per placement. Empty cells stand for null.
limitmay go up to 50000 in CSV, so one edition usually fits in one file.- The next page's address is in the
Linkresponse header, markedrel="next". No such header means this was the last page. - If the last line of a file starts with
#INCOMPLETE, the file was cut short on our side. The status is still200, because the first rows had already been sent, so check the last line of every file. Discard such a file and fetch that page again. - In CSV only, a casino, row, game, studio or maker name whose first character, after any leading spaces, is
=,+,-or@, or that starts with a tab, is preceded by an apostrophe ('), so that a spreadsheet does not run it as a formula. JSON carries the name unaltered. If you need exact names, use JSON.
Opening the CSV in a spreadsheet
The file is UTF-8 without a byte-order mark.
- Google Sheets: File > Import > Upload. It reads the file correctly.
- LibreOffice: open the file and choose the character set UTF-8 in the import dialog.
- Excel: do not open the file by double-click, or accented letters will be garbled. Use Data > From Text/CSV and choose the file origin "65001: Unicode (UTF-8)".
- If a name shows a leading apostrophe, that is the protection described above.
Use it from an AI assistant (MCP)
The same facts can be read by an AI assistant that speaks the Model Context Protocol (MCP). The assistant calls tools; seven of them are the addresses above, and answer with the same rows. Two more serve Patrol Rank, Open Patrol's own ranking of studios and games, as the ranking pages print it.
https://open.playpatrol.app/mcp
Connecting with a token
Send the token in the Authorization header, exactly as for the addresses above. In Claude Code:
claude mcp add --transport http open-patrol https://open.playpatrol.app/mcp \
--header "Authorization: Bearer <your token>"
Any other client that can send a header to a remote MCP server works the same way. As above, the token is accepted in the header only, never in the address.
Connecting by signing in
An assistant that can sign in does not need a token. It sends you to Open Patrol, you sign in with the address your monitoring is under and allow the access, and the assistant is connected. The steps for each assistant are at open.playpatrol.app/connect.
The page you allow shows the assistant's name, as it names itself, and where it will send you back to. Allow it only if you started the connection just now. The assistant reads what your plan covers on the day it asks, and nothing it can do changes anything. You can disconnect each assistant on its own from your account page, under Data access, MCP, and doing so does not touch your token. An assistant that has not been used for 30 days has to be signed in again.
Every request to /mcp is a POST with a JSON body. Any other method is a 405.
The fourteen tools
| Tool | Mirrors | Arguments |
|---|---|---|
list_markets |
/markets |
none |
list_licences |
/markets/{geo}/licences |
market (required) |
list_editions |
/editions |
market |
list_casinos |
/casinos |
market |
list_providers |
/providers |
none |
list_games |
/games |
provider, limit, after |
list_placements |
/placements |
market, edition, casino, provider, game, from, to, max_page_position, limit, after |
patrol_rank |
the Patrol Rank pages | kind (studios, games or groups, required), market, month, limit, after |
rank_of |
the Patrol Rank pages | provider or game (one of them), month |
publisher_partners |
a publisher's page | publisher or maker (one of them), market, limit, after |
group_profile |
a studio group's page | group (required), month |
market_month |
a market's month report | market (required), month, limit, after |
casino_studios |
a casino's page | casino (required), month |
list_news |
the newsroom | month, market, studio, limit, after |
The arguments mean what the parameters of the same name mean above. An argument a tool does not know is refused, never ignored. There is no CSV here.
An answer is one block of JSON text with the column names once and then one list of values per row:
{"columns": ["market", "edition", "scanned_on", "..."], "rows": [["ESP", 3, "2026-10-02", "..."]], "next_after": null}
The columns are the keys of the address the tool mirrors, in the same order. Nested keys (regulator,
licence) stay objects inside their cell. list_providers adds aggregator, the address of a
publisher's page where it has one.
list_placements adds one column at the end, page_position: the game's place on the whole casino
homepage in that edition, every row and every studio counted, so 1 is the first game on the page.
max_page_position keeps only the placements at or above that place. Neither exists at the
/placements address.
patrol_rank answers one ranking: the studios or the games of one market, or of all markets when
market is left out. Next to the rows it says which ranking it is (ranking, for example
UK Patrol Rank), how a place is decided (rule), the scans it counts (period), the public page that
prints it (page) and the months that have one (months; pass one as month). Without month it is
the month the public page shows now. rank_of answers one studio's or one game's place in every
market's ranking where it is ranked, and across all markets, one row each, labelled the same way. Quote
these as Open Patrol's Patrol Rank: they are our ranking, not a recorded fact. patrol_rank with
kind groups answers one market's studio groups and independent studios as that market's report
prints them, each group with its brands; market is required there. rank_of names a studio's group
and the group's place in each market (group, group_rank).
The last five tools each answer what one public page prints. publisher_partners names the studios a
publisher brings to casinos, with their placements and markets, or, given a maker, the publishers
that bring that studio. group_profile is a studio group's page under the group's bare name: one month (the month the page
shows, the newest month every market has read, which may still be under way, or the month you pass;
the answer names it), the group's Group Patrol Rank and share that month, its studios with their own
Patrol Rank, the markets they were on show in and the groups around it.
market_month is a market's month report: casinos read, placements per scan, games and studios seen,
the new games by the day each was first seen and the repeats. casino_studios is a casino's page: the
studios on its homepage, each with its placements and share, and every studio seen there. list_news
lists what the newsroom published, newest first, with the markets and studios each item is about.
What you can read
Every tool returns only what your plan covers, as the API does, and list_markets and
list_licences too: the market list is the markets your plan touches, and the licence list of a
market your plan does not cover is refused. Your plan is read at each request: something you buy
today is covered on the next call. When nothing you bought is current any more, the token is answered
401, here as at the API, until you buy again; the same token then works again. A token issued to you
directly reads the markets it was issued for.
A market's Patrol Rank is served for a market your plan covers; the ranking across all markets is served to every plan, as its page is public. The same holds for a group's or a publisher's figures: the totals are every plan's, and every list by market names only the markets your plan covers. A month report and a casino's page are served for a market your plan covers. A news item about a market your plan does not cover is left out; an item about no market is listed for every plan.
Without a market argument, a data tool returns the rows your plan covers and says what that is in a
covers key, a list such as ["market GBR"]. When you name a market, studio, casino or game your plan
does not cover, the answer is not an error: it is one plain sentence - the API's own message, for
example Your plan does not cover that market. - and no rows. A plan that covers nothing answers
Your plan does not cover any data.
Paging
list_games, list_placements, patrol_rank, market_month, publisher_partners and list_news return a next_after value, or null on the last page. To continue,
pass it back as after, unchanged. limit has a default and a maximum:
| Tool | Default | At most |
|---|---|---|
list_placements |
100 | 120 |
list_games |
200 | 200 |
patrol_rank |
100 | 200 |
market_month |
100 | 120 |
publisher_partners |
25 | 25 |
list_news |
20 | 25 |
These pages are smaller than the data API's, because an assistant reads every row into its working memory. Filter as narrowly as you can. The other tools return the whole list in one answer.
Refusals
A request the tool cannot read, such as an argument it does not know or a malformed value, is an
answer marked as an error. It carries the same sentence and the same status the matching address
gives, in the body. A request without a usable token is a 401 with a WWW-Authenticate header, and a
request from a web page is a 403.
The minute
The limit of 120 requests a minute (see Limits) is counted per customer, across the token on his account page and every assistant he uses, here and at the addresses above together. Every message the assistant sends counts, including the ones it sends to set up the connection.
Limits
120 requests a minute: per account for the token on your account page and every assistant you
connect, counted across both doors together (see The minute), and per token for a token issued to you
directly. Beyond that you get a 429 with a Retry-After header: wait that
many seconds before the next request.
Status codes
| Status | Meaning |
|---|---|
200 |
the rows |
400 |
a parameter we do not know, or a value we cannot read. The body names it in parameter |
401 |
the token is missing, wrong, revoked or expired, or nothing you bought is current. The answer is the same in every case |
403 |
you named a market, studio, casino or game your plan does not cover (scope_not_purchased) |
404 |
no such address |
405 |
any method but GET; told only to a valid token |
429 |
too many requests; see Limits |
500 |
something went wrong on our side |
503 |
temporarily unavailable. Wait for the time in Retry-After and try again |
Every error is JSON: {"error": "..."}. The one exception is the 403, which says what is not
covered in a second key:
{"error": "scope_not_purchased", "message": "Your plan does not cover that market."}
The message names one of four things your filters asked for - market, studio (the provider
filter), casino or game - or, when each is covered on its own but not together, combination of filters; an edition is named as its market. A casino is judged by the markets it is listed in or
was recorded in, and a game by its studio, never by whether any rows match.
An unknown parameter is refused, never ignored. A misspelt filter gives a 400, not a full unfiltered
answer.
If your plan does not cover everything, every list shows only what it covers, without an error: a
market plan its markets, a studio plan that studio's rows in every market, a Daily Patrol pair only
that studio's rows at that casino. Several of these together read each one whole, never mixed: a pair
and a market are that pair's rows plus everything in that market, not the pair's studio across the
market. Only naming something outside your plan explicitly gives a 403: a market, a studio, a
casino or a game. A malformed parameter is still a 400 first.
Examples
Set the token once:
export TOKEN='opd_...'
The markets:
curl -H "Authorization: Bearer $TOKEN" https://open.playpatrol.app/api/data/v1/markets
The first page of Spain's placements, at the largest JSON page size:
curl -H "Authorization: Bearer $TOKEN" "https://open.playpatrol.app/api/data/v1/placements?market=ESP&limit=5000"
Spain's placements as a CSV file, with the response headers saved so you can read Link:
curl -H "Authorization: Bearer $TOKEN" -D headers.txt -o placements.csv "https://open.playpatrol.app/api/data/v1/placements?market=ESP&format=csv&limit=50000"
What is deliberately absent
- Shares, ranks, top lists and comparisons between scans. Compute them from the placements.
- Anything the website does not show.
Changes
v1 is part of the address. We may add keys and addresses to v1, so ignore keys you do not know.
We will not remove a key or change what one means within v1.