Papa John's API endpoint
Returns Papa John's restaurants in one of six international markets: Chile, Costa Rica, Guatemala, Panama, Portugal, and Spain -- either every restaurant near a coordinate, or one known restaurant looked up by id. Each restaurant carries its address, coordinates, phone, per-weekday opening hours split by fulfillment channel, accepted payment methods, delivery zones with their minimum-order and free-delivery thresholds, and live delivery/pickup open flags. Restaurants come back ranked by distance with no radius limit, five at a time by default; page and limit walk the rest, and the response reports total_count, the market's whole restaurant total. The returned id is what GET /papajohns/intl/menu prices against, and menu_id is what GET /papajohns/intl/ingredients reads. This covers those six markets only -- for India use GET /papajohns/india/stores, and for the US and Canada use GET /papajohns/directory, GET /papajohns/store, or GET /papajohns/nearby.
/papajohns/intl/storesThese six markets only. For India use
/papajohns/india/stores; for the US and Canada use/papajohns/directory,/papajohns/store, or/papajohns/nearby.
Query parameters:
market (required) — one of chile, costa-rica, guatemala, panama, portugal, spain.store_id (optional) — look one known restaurant up directly instead of
searching a coordinate. Supply either store_id, or both latitude and
longitude.latitude — between -90 and 90. Required unless store_id is supplied.longitude — between -180 and 180. Required unless store_id is supplied.fulfillment (optional, default delivery) — one of delivery (every
nearby restaurant) or pickup (only restaurants accepting pickup orders).page (optional, default 1) — one-based page of the distance-ranked
restaurants.limit (optional, default 5, max 100) — restaurants per page.
page and limit are both ignored for a store_id lookup and for
pickup, neither of which upstream paginates.Notable response behavior:
id on each restaurant is what
/papajohns/intl/menu prices against, and
menu_id is what
/papajohns/intl/ingredients reads.
Several restaurants usually share one menu_id.hours has one entry per weekday per fulfillment channel, with
dispatch_method naming the channel. Times are wall-clock in the
restaurant's own local time; upstream's placeholder date is dropped.payment_methods lists only the methods the restaurant actually accepts.delivery_zones carries each tier's delivery fee and its minimum-order and
free-delivery thresholds where the market publishes them.delivery_open/pickup_open false.count is how many this response carries, and page,
total_pages and total_count describe the rest of the ranking, so a first
page is distinguishable from the whole of it. total_count is the market's
entire restaurant total rather than a match count — for the same reason
there is no radius cut-off — so it is the same for every coordinate in a
market.limit returns more of the same ranking in one call rather than a
different one: the first five of limit=100 are the same five, in the same
order, that the default returns. Pages carry no duplicates.page past the last one returns a 404 naming the last page,
rather than an empty list. page below 1 is rejected as a 400.503; if the market carries no restaurants at all, it returns a 404.store_id lookup ignores fulfillment and returns exactly one store, or
a 404 if the market has no such restaurant. It has no search origin, so
latitude, longitude and fulfillment are omitted from the response
rather than reported as 0 — 0,0 is itself a real coordinate.store_id nor a coordinate pair returns a 400, as does
an unknown market or fulfillment or an out-of-range coordinate.| Field | Filled in, of 40 checked | Values in the sample |
|---|---|---|
| id | 40 | |
| name | 40 | “Vallejo Nájera” and 39 other values |
| menu_id | 40 | |
| address | 40 | |
| city | 40 | “Madrid” and 13 other values |
| region | 40 | Madrid (39), Pozuelo (1) |
| phone | 40 | |
| latitude | 40 | |
| longitude | 40 | |
| order_method | 40 | both in all 40 |
| delivery_open | 40 | true in 39 of 40 |
| pickup_open | 40 | true in 39 of 40 |
| delivery_wait_minutes | 40 | 30 |
| pickup_wait_minutes | 40 | 15 |
| future_orders | 40 | true in 40 of 40 |
| external_delivery | 40 | true in 37 of 40 |
| payment_methods | 40 | median 7 entries |
| delivery_zones | 40 | median 5 entries |
| hours | 39 | median 20 entries |
| map_url | 39 | maps.google.com |
| Parameter | Type | Required | Default | Description | Example |
|---|---|---|---|---|---|
| market | string | Yes | Market. One of: chile, costa-rica, guatemala, panama, portugal, spain Allowed values: chile, costa-rica, guatemala, panama, portugal, spain. | spain | |
| store_id | string | No | Look up one known restaurant by id instead of searching a coordinate. Supply either store_id, or both latitude and longitude. | 9 | |
| latitude | string | No | Latitude to search around. Required unless store_id is supplied. | 40.4168 | |
| longitude | string | No | Longitude to search around. Required unless store_id is supplied. | -3.7038 | |
| fulfillment | string | No | One of: delivery (default, every nearby restaurant), pickup (only restaurants accepting pickup orders). Ignored when store_id is supplied. Allowed values: delivery, pickup. | delivery | |
| page | integer | No | One-based page of the distance-ranked restaurants. Ignored for a store_id lookup and for pickup, neither of which is paginated. Minimum: 1. | 1 | |
| limit | integer | No | Restaurants per page, 1-100. Ignored for a store_id lookup and for pickup, neither of which is paginated. Minimum: 1. Maximum: 100. | 5 | |
| x-api-key (header) | string | Yes | API key required |
curl -X GET "https://api.crawlora.net/api/v1/papajohns/intl/stores?market=spain&fulfillment=delivery&page=1&limit=10" \ -H "x-api-key: $CRAWLORA_API_KEY"
{
"code": 200,
"msg": "OK",
"data": {
"market": "spain",
"fulfillment": "delivery",
"latitude": 40.4168,
"longitude": -3.7038,
"stores": [
{
"id": 9,
"name": "Vallejo Nájera",
"menu_id": 8,
"address": "Paseo de Juan Antonio Vallejo - Nájera Botas, 25, 28005",
"city": "Madrid",
"region": "Madrid",
"phone": "+34910493017",
"latitude": 40.401686,
"longitude": -3.708348,
"order_method": "both",
"delivery_open": true,
"pickup_open": true,
"delivery_wait_minutes": 30,
"pickup_wait_minutes": 15,
"future_orders": true,
"external_delivery": true,
"payment_methods": [
"apple_pay",
"bizum",
"cash",
"credit",
"direct_click_to_pay",
"google_pay",
"paypal"
],
"delivery_zones": [
{
"name": "La más fresca 6,99€",
"delivery_price": 0
}
],
"hours": [
{
"day": "monday",
"dispatch_method": "in_store",
"opens": "12:00:00",
"closes": "23:59:59"
}
]
}
],
"count": 5,
"page": 1,
"total_pages": 20,
"total_count": 97,
"source_url": "https://api.papajohns.es/v1/stores?latitude=40.4168&longitude=-3.7038",
"fetched_at": "2026-09-06T09:00:00Z"
}
}Request schema
No body schema
Response schema
#/definitions/papajohns.intlStoresResponseDoc
| Field | Type | Required | Enum | Bounds | Example | Description |
|---|---|---|---|---|---|---|
| code | integer | No | Code is the HTTP status code or a custom code used to indicate the result of the request @example 200 | |||
| data | papajohns.IntlStoresResponse | No | ||||
| data.count | integer | No | 5 | Count is how many restaurants this response carries. | ||
| data.fetched_at | string | No | ||||
| data.fulfillment | string | No | delivery | Fulfillment, Latitude and Longitude echo a coordinate search and are absent on a store_id lookup, which has no search origin. They are omitted rather than zeroed because 0,0 is itself a real coordinate. | ||
| data.latitude | number | No | 40.4168 | |||
| data.longitude | number | No | -3.7038 | |||
| data.market | string | No | spain | |||
| data.page | integer | No | 1 | Page, TotalPages and TotalCount describe the rest of the result set, so a caller can tell a first page from the whole of it. TotalCount is the market-wide restaurant total rather than a match count, because the search has no radius cut-off -- every restaurant in the market is ranked, and this is where in that ranking the response sits. All three are absent for a store_id lookup and for pickup, neither of which upstream paginates. | ||
| data.source_url | string | No | ||||
| data.stores | array | No | ||||
| data.stores[].address | string | No | ||||
| data.stores[].city | string | No | Madrid | |||
| data.stores[].delivery_open | boolean | No | true | |||
| data.stores[].delivery_wait_minutes | integer | No | 30 | |||
| data.stores[].delivery_zones | array | No | ||||
| data.stores[].delivery_zones[].delivery_price | number | No | 0 | |||
| data.stores[].delivery_zones[].minimum_order_for_delivery | number | No | ||||
| data.stores[].delivery_zones[].minimum_order_for_free_delivery | number | No | ||||
| data.stores[].delivery_zones[].name | string | No | Zona 1 | |||
| data.stores[].external_delivery | boolean | No | true | |||
| data.stores[].future_orders | boolean | No | true | |||
| data.stores[].hours | array | No | ||||
| data.stores[].hours[].closes | string | No | 23:59:59 | |||
| data.stores[].hours[].day | string | No | monday | |||
| data.stores[].hours[].dispatch_method | string | No | in_store | DispatchMethod is the fulfillment channel this window applies to, e.g. in_store or pj_delivery. | ||
| data.stores[].hours[].opens | string | No | 12:00:00 | |||
| data.stores[].id | integer | No | 9 | |||
| data.stores[].latitude | number | No | 40.401686 | |||
| data.stores[].longitude | number | No | -3.708348 | |||
| data.stores[].map_url | string | No | ||||
| data.stores[].menu_id | integer | No | 8 | MenuID identifies the catalog this restaurant serves. Several restaurants usually share one. | ||
| data.stores[].name | string | No | Vallejo Nájera | |||
| data.stores[].order_method | string | No | both | OrderMethod is the fulfillment the restaurant supports, as upstream reports it, e.g. both, delivery, or pick_up. | ||
| data.stores[].payment_methods | array | No | PaymentMethods lists only the methods this restaurant actually accepts. | |||
| data.stores[].phone | string | No | +34910493017 | |||
| data.stores[].pickup_open | boolean | No | true | |||
| data.stores[].pickup_wait_minutes | integer | No | 15 | |||
| data.stores[].region | string | No | Madrid | |||
| data.total_count | integer | No | 97 | |||
| data.total_pages | integer | No | 20 | |||
| msg | unknown | No | Msg is the message that describes the result of the request @example "Request successful" |
| Status | Description | Schema |
|---|---|---|
| 400 | Bad Request | #/definitions/app.Response |
| 404 | Not Found | #/definitions/app.Response |
| 429 | Too Many Requests | #/definitions/app.Response |
| 503 | Service Unavailable | #/definitions/app.Response |
Use environment variables for secrets and keep Crawlora API keys server-side.
curl -X GET "https://api.crawlora.net/api/v1/papajohns/intl/stores?market=spain&fulfillment=delivery&page=1&limit=10" \
-H "x-api-key: $CRAWLORA_API_KEY"