API Basics
PAR Ordering APIs are organized around REST. The API has predictable resource-oriented endpoints, returns JSON-encoded responses, accepts JSON bodies for POST / PUT requests, and uses standard HTTP response codes, authentication and verbs.
Data Format
Resource IDs
All PAR Ordering resources come with a UUID as an indentifier, which is guaranteed to be unique for the resource type.
In cases where unique identifiers are generated by third-party systems (such as the POS system or a Loyalty platform), they are returned as generated by the third-party system, and may not follow a UUID format. Please review response parameter documentation of the API Reference for more details.
Notation
The snake_case notation is used for naming JSON resources and their attributes, for example serving_times, price_level.
Date & Time Format
All dates and timestamps are represented according to RFC-3339 standard with the extended notation (i.e. hyphen & dot separated). For example, 2020-11-05 is used for dates and 2020-11-05T18:35:52 for timestamps.
Timezones
All timestamps are always returned in UTC by the API and have to be converted by the API client. The Venue and Brand resources include a Timezone object, which can be used, in order to know into which timezone a timestamp needs to be converted to.
Amounts
All prices and order related charges are represented in minor currency units (i.e. cents) as integer values. For example, 0.35 USD will be represented as 35, 10 GBP as 1000. Currencies that have no minor currency units (i.e. currency exponent is 0), such as ISK or JPY, are also represented with a x100 factor applied, for example 10 JPY will be 1000.
Authentication
The PAR Ordering API uses three pieces of authentication, and which ones you need depends on the call you are making. Every request carries an Application key that identifies your app. Calls that act on behalf of a signed-in customer also carry that customer's token. A small number of endpoints belong to your integration rather than to any individual customer, and those additionally need an OAuth client token.
Application
The Application HTTP header identifies your application and is required for all API calls to PAR Ordering. Using only this authentication header you will be able to make all API requests that do not require a customer context, such as fetching details about a Venue (Store / Location):
Request
{
"method": "get",
"url": "https://api-public-demo.menu.app/api/venues/31f3769a-33ce-40d9-8f22-8d910db4fd11",
"headers": {
"X-Request-ID": "69da3547-204b-4093-a225-54e084c24215",
"Application": "f3a90488ffee32c3acb6fcd0ca417cf6",
"Api-Version": "4.78.0"
}
}
You will receive your Application key as part of the integration kick-off process from your Integration Manager. Note that your Application key will be different per environment, such as from Playground to Production, but also between Production environments (e.g. US vs. EU), so make sure to set this up as a configurable variable.
Customer Account
For API calls that require a customer context, such as fetching all delivery addresses for a customer, additionally the Authorization HTTP header with a JWT token has to be provided:
Request
{
"method": "get",
"url": "https://api-public-demo.menu.app/api/customer-accounts/dacd5ded-ad3b-4155-89d2-0748b589ce71/delivery-addresses",
"headers": {
"X-Request-ID": "69da3547-204b-4093-a225-54e084c24215",
"Application": "f3a90488ffee32c3acb6fcd0ca417cf6",
"Api-Version": "4.78.0",
"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS1wdWJsaWMtcGxheWdyb3VuZC5tZW51LmFwcC9hcGkvY3VzdG9tZXJzL3JlZnJlc2giLCJpYXQiOjE2OTEyMzk4MzQsImV4cCI6MTY5MTI0Mzc0NSwibmJmIjoxNjkxMjQwMTQ1LCJqdGkiOiJvSGw3aXVCanRLcjc1c0dTIiwic3ViIjoiNTA5MDYzNyIsInBydiI6ImNjMzI5MjFhMTU0ODBhMTE3ZDliYmM3MmMwZTEyNTZhNjg1MjQ1OGIiLCJhcHBsaWNhdGlvbl9pZCI6MjAxNSwic2Vzc2lvbl9pZCI6MjY2MTd9.5IqDOv5HHd1ddyojBMp63zVnl50VbbKEuhXFWbv25V8"
}
}
A JWT token can be obtained by going through the Sign In or Sign Up Flow. If your customers already exist in an external identity provider such as Punchh, you can exchange their token for a PAR Ordering one instead — see STS Token Exchange Login and the SSO Accounts Synchronization Guide.
The Access Token is typically valid for 1 minute (60 seconds), while the Refresh Token is valid for 30 days (2628000 seconds). TTLs (time-to-live) are returned as part of the Token object.
Ordering API Client
A few endpoints belong to your integration rather than to a customer, and they need a machine-to-machine token in addition to the Application key. Retrieving an order by its ID and managing your webhook configuration both work this way.
Request the token from POST /oauth/tokens using HTTP Basic authentication with the client_id and client_secret your Integration Manager issued, and the client_credentials grant. Scopes are space-separated: order covers order retrieval and webhooks-management covers webhook configuration. If you ask for no scope at all you will be given webhooks-management.
Request
{
"method": "post",
"url": "https://api-public-demo.menu.app/api/oauth/tokens",
"headers": {
"X-Request-ID": "69da3547-204b-4093-a225-54e084c24215",
"Application": "f3a90488ffee32c3acb6fcd0ca417cf6",
"Authorization": "Basic base_64_encode(client_id:client_secret)",
"Content-Type": "application/x-www-form-urlencoded"
},
"body": {
"grant_type": "client_credentials",
"scope": "order webhooks-management"
}
}
Response
{
"status": "OK",
"code": 200,
"data": {
"oauth_token": {
"access_token": "0ca417cf6f3a90488ffee32c3acb6fcd",
"token_type": "bearer",
"scope": "order webhooks-management",
"expires_in": 86399
}
}
}
Send the token back on X-API-Authorization, not on Authorization — that header is reserved for the customer token, and the two can appear on the same request. You also need X-Ordering-API, which tells us to handle the call as an Ordering API request; without it the scope check never runs and the endpoint will refuse you.
Request
{
"method": "get",
"url": "https://api-public-demo.menu.app/api/webhook-configs",
"headers": {
"X-Request-ID": "69da3547-204b-4093-a225-54e084c24215",
"Application": "f3a90488ffee32c3acb6fcd0ca417cf6",
"Api-Version": "4.78.0",
"X-API-Authorization": "Bearer 0ca417cf6f3a90488ffee32c3acb6fcd",
"X-Ordering-API": true
}
}
Tokens last 24 hours by default. You can retire one early by posting the access_token to POST /oauth/tokens/revoke with the same Basic credentials.
Cart 2.0 does not use this token. Creating a cart, updating it, checking out and placing the order need only the Application key, plus the customer's Authorization token once they have signed in.
API Requests & Responses
Requests
- Resources and Resource IDs are specified as part of the URL. Based on the hierarchy of resources, resources and resource IDs can be nested.
- For each request, the
Api-VersionHTTP header has to be specified - refer to Versioning for more information. Send it as a string, for example"4.78.0". If you leave it out we fall back to a very old default version, which is almost certainly not the behavior you built against, so treat it as required even though the request will not be rejected without it. - For each request, a unique
X-Request-IDHTTP header has to be specified. This ID is used for idempotency, as well as to allow for easy debugging. We recommend using a UUIDv4 generator to generate a unique Request ID.
For example to get all details of a Venue (Store / Location):
Request
{
"method": "get",
"url": "https://api-public-demo.menu.app/api/venues/31f3769a-33ce-40d9-8f22-8d910db4fd11",
"headers": {
"X-Request-ID": "69da3547-204b-4093-a225-54e084c24215",
"Application": "f3a90488ffee32c3acb6fcd0ca417cf6",
"Api-Version": "4.78.0"
}
}
- For POST / PUT requests, JSON is accepted in the request body, for which the
Content-TypeHTTP header has to be set toapplication/json. Send it even when the request has no body — calls such as cart checkout take no payload but are still rejected with415 Only JSON content type allowedif the header is missing.
For example to place an order:
Request
{
"method": "post",
"url": "https://api-public-demo.menu.app/api/cart/{cart_id}/order",
"headers": {
"X-Request-ID": "69da3547-204b-4093-a225-54e084c24215",
"Application": "f3a90488ffee32c3acb6fcd0ca417cf6",
"Api-Version": "4.78.0",
"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS1wdWJsaWMtcGxheWdyb3VuZC5tZW51LmFwcC9hcGkvY3VzdG9tZXJzL3JlZnJlc2giLCJpYXQiOjE2OTEyMzk4MzQsImV4cCI6MTY5MTI0Mzc0NSwibmJmIjoxNjkxMjQwMTQ1LCJqdGkiOiJvSGw3aXVCanRLcjc1c0dTIiwic3ViIjoiNTA5MDYzNyIsInBydiI6ImNjMzI5MjFhMTU0ODBhMTE3ZDliYmM3MmMwZTEyNTZhNjg1MjQ1OGIiLCJhcHBsaWNhdGlvbl9pZCI6MjAxNSwic2Vzc2lvbl9pZCI6MjY2MTd9.5IqDOv5HHd1ddyojBMp63zVnl50VbbKEuhXFWbv25V8",
"Content-Type": "application/json"
},
"body": {
"customer_info": {
"first_name": "Alex",
"last_name": "Rivera",
"email": "alex.rivera@example.com",
"phone_number": "+13125550143"
},
"payment_info": {
"payment_init_hash": "1c2dc93bc422ffbfb82c932f2d376dd7"
}
}
}
Responses
Responses are always JSON-encoded and have a predictable set of keys:
{
"code": 200,
"status": "OK",
"data": {
"*entity_name*": {
"*attribute_name*": "*attribute_value*"
}
}
}
For example, this is what getting details of a specific Venue (Store / Location) would look like:
Request
curl --request GET \
--url https://api-public-demo.menu.app/api/venues/31f3769a-33ce-40d9-8f22-8d910db4fd11 \
--header 'Application: f3a90488ffee32c3acb6fcd0ca417cf6' \
--header 'X-Request-ID: 69da3547-204b-4093-a225-54e084c24215'
Response
{
"status": "OK",
"code": 200,
"data": {
"venue": {
"id": "31f3769a-33ce-40d9-8f22-8d910db4fd11",
"uuid": "31f3769a-33ce-40d9-8f22-8d910db4fd11",
"name": "Kauwela Austin 1 Brink",
"code": "3181",
"currency_id": "dbf418a1-3ee5-4bad-b6a0-ff49f8db77e3",
"language_id": 1,
"external_dlc_id": null,
"brand_id": "de3a0527-7e4c-4888-91de-71b6d29efa74",
"timezone": {
"name": "America/Indiana/Indianapolis",
"offset": "-04:00"
},
"description": "",
"kiosk_receipt_footer": "",
"imprint": "",
"welcome_message": "",
"translations": {
"description": "",
"kiosk_receipt_footer": null,
"welcome_message": null
},
"address": "2111 Chicon St",
"state": 1,
"city": "Austin",
"zip": "78722",
"latitude": 30.2825159,
"longitude": -97.7242909,
"tax_number": "",
"phone": "723888237"
}
}
}
The response above is shortened; a real venue payload carries considerably more fields.
API Endpoints
The examples in this documentation call Demo.
| Environment | API Endpoint |
|---|---|
| Demo | https://api-public-demo.menu.app |
Other environment hosts are given to you by your PAR representative. Data and authentication do not carry from one environment to another.
Localization & Translation
The PAR Ordering platform was architected with i18n as a core concept, and as such supports content in an unlimited number of languages, as configured by the brand in the CMS / Management Center.
In order for content to be returned in a specific language, you can specify the Content-Language HTTP Header in your API request. If content has been translated into that language through the CMS / Management Center, it will be returned in the specified language. Should the specified language not be available, the API will fallback to the default language that has been configured for the Brand.
When sending a Content-Language, translated content will be contained in a translations object within every resource that contains translated attributes:
"manual_location_inputs": [
{
"id": "464d8458-043d-4bdf-afae-ce01cd896a21",
"name": "San Francisco",
"translations": {
"name": "San Francisco"
}
}
]
Translations apply to PAR Ordering, discount and manual locations related API calls.
Sorting
For many resources PAR Ordering allows for sorting, which allows brands to decide the order in which they want to present certain content (such as categories, subcategories and items) to their customers. Items in API responses are always returned already sorted and no custom sorting logic should be applied by the API client.
Versioning
The PAR Ordering API is versioned, in order to ensure backwards compability. With every API request, the Api-Version HTTP header has to be passed, which specifies the API Version that was used to build the integration.
The API Version may be used by PAR Ordering, in order to transform requests / responses, to ensure backwards compatibility. Breaking changes from one API version to another are documented in the API Changelog.
API Versions follow Release Versions documented in our Release Notes.
Backwards Compatibility
PAR Ordering ensures backwards compability for all request / response attributes that are documented. API endpoints may accept and / or return additional attributes that are not documented, though these should not be relied upon by your application, since they may change without notice. If you believe there is an additional request / response parameter that is essential to your implementation, please consult your Integration Manager on whether it is advisable to use, and if so we will adjust the API documentation.
In case PAR Ordering has to make a breaking change, which cannot be kept backwards compatible through API Versioning, you will receive a notice and due time to update your application.
Error Handling
The API signals errors via standard HTTP status codes. This means that successful responses use a 2xx status code, while errors are reported with 4xx or 5xx codes.
Standard HTTP Error Codes
| Status Code | Status Message | Description |
|---|---|---|
| 400 | Bad Request | Request could not be understood by the server due to malformed syntax |
| 401 | Unauthorized | Client must authenticate itself to get the requested response |
| 403 | Forbidden | User might not have necessary permissions for the resource, or may need an account |
| 404 | Not Found | Requested resource could not be found but may be available in the future |
| 405 | Method Not Allowed | Request method is not supported for the requested resource |
| 415 | Unsupported Media Type | Request entity has a media type which the server or the resource does not support |
| 422 | Validation Error | Some of the required fields are missing from the request body |
| 426 | Upgrade Required | Client must update its application to the newest one |
| 429 | Too Many Requests | Too many requests sent in a given amount of time |
| 500 | Internal Server Error | An unexpected condition was encountered and a specific message is not suitable |
Next to the HTTP status, the response body carries more detail. Read the two fields carefully, because they do not mean what their names suggest at first glance. code is the PAR Ordering error code, not the HTTP status, and status carries a short description of what went wrong rather than the HTTP reason phrase. Look the code up in the Error Codes reference to decide what to show the customer.
{
"status": "Invalid pickup time",
"code": 2001,
"data": {
"info_message": {
"title": "Invalid pickup time 2026-09-19 05:30:00",
"body": ""
},
"message": "There was an interruption and we couldn't do what you wanted us to do.",
"error_id": "d54309f9af2e4514b9c39beb857f6a1d"
}
}
info_message.title is written for a person and is safe to surface to the customer; message is a generic fallback and is the same for every error, so do not rely on it to tell failures apart. When error_id is populated, quote it to support — it is how we find your exact request in our logs.
Not every error uses info_message. Where a failure is about specific items, the offending resources come back in data instead. Checkout does this when something in the cart is no longer available, listing the product UUIDs so you can mark them in the customer's basket:
{
"status": "Bad Request",
"code": 400,
"data": {
"products": [
"7618da71-a36d-42d0-a9ec-395631d456cf",
"0fb3da7b-9002-41d5-90ce-4b4e1ac9c7c4"
],
"context": null,
"message": "There was an interruption and we couldn't do what you wanted us to do.",
"error_id": ""
}
}
Validation failures are a third shape. They come back as 422 with the offending fields listed under data.validations, each with the rule that failed and a message you can show:
{
"status": "Unprocessable Content",
"code": 422,
"data": {
"validations": {
"latitude": [{ "rule": "Required", "message": "The latitude field is required." }],
"longitude": [{ "rule": "Required", "message": "The longitude field is required." }]
},
"message": "There was an interruption and we couldn't do what you wanted us to do.",
"error_id": ""
}
}
Handle all three shapes: check for info_message, then validations, then whatever else data contains. Treat error_id as optional, since it is empty on some errors. And note from the examples above that code does not always match the HTTP status — a 415 response carries "code": 400 in its body — so branch on the HTTP status for transport-level handling and on code for business logic.
Next Steps
Ready to make your first API calls? Then head on to First Steps