Empowering Restaurants through Integration

Follow

Create Order Validation

For a given location, Grubhub passes parameters that identify which menu items and options a
user wants to order (using the identifiers returned from the "Show Menu" call) along with a desired
ready time in the timezone of the location (ISO8601), a tip amount, user details, any stored
metadata (which may include other login credentials if an account was created previously), and a
discount amount (if applicable).

We expect a response with the final subtotal, applicable taxes, and the soonest time at which the
order may be available (in the local timezone of the location and ISO8601), which will be
presented to the user.

Grubhub may send more than one order validation request. This may occur in these situations:

The order validation request occurs after the diner has placed the order. Items within the current order
from the diner are assumed to be orderable as an ASAP or at a scheduled time. If the order
cannot be fulfilled, please send an error message. 

 

Request Endpoint

POST /locations/:provider_location_id/order_validations

 

Example Request Body - Grubhub Managed Delivery and/or Pickup

{
  "order_validation": {
"calculated_tax": 0, "desired_ready_time": "2016-03-11T11:44:08", "fulfillment_type": "pickup", "grubhub_order_number": "430515324805810", "items": [ { "item": { "category_id": "11124", "category_group_id": "6734384", "name": "Turkey Sandwich", "price": 679, "provider_id": "1324", "quantity": 1, "recipient_name": "Joe Smith", "metadata": { "integration_id_needed_for_validation": "1234" }, "special_instructions": "Hold the mayo!", "options": [ { "option": { "metadata": { "integration_id_needed_for_validation": "1111" }, "price": 100, "provider_id": "67478", "option_group_id": "585",
"option_group_display_order": 1, "quantity": 2 } }, { "option": { "metadaata": {}
"price": 0, "option_group_display_order": 1, "provider_id": "32791", "option_group_id": "596", "quantity": 1 } } ],
"tax_rate": "0.07"
} }, { "item": { "category_group_id": "6734384", "category_id": "75759", "name": "Slice of cake", "price": 199, "provider_id": "9876", "metadata": { "integration_id_needed_for_validation": "5678" }, "quantity": 2, "recipient_name": "Jane Smith", "options": [ { "option": {
"price": 0, "option_group_display_order": 2, "provider_id": "62459", "option_group_id": "334", "quantity": 1 } } ],
"tax_rate": "0.07" } } ], "location_time_zone": "America/New_York", "metadata": {}, "paid_via_ach": true,
"prepaid": true, "tip": 100, "merchant_funded_discount": 100, "special_instructions": "please double bag my order", "include_disposable_utensils": true, "user": { "born_at": "1986-03-11T11:44Z", "first_name": "joe", "gender": "male", "last_name": "smith", "email": "example@gmail.com", "password": "password123", "phone": "4445556666" }
"uuid": "5a3836800b1c0139c8d822da5a47e6c7" } }

 

Example Response Body - Grubhub Managed Delivery and/or Pickup 

{
"order_validation": {
"available_at": [
"2016-03-25T15:20:00Z",
"2016-03-25T15:40:00Z",
"2016-03-25T16:00:00Z",
"2016-03-25T16:20:00Z",
"2016-03-25T16:40:00Z",
"2016-03-25T17:00:00Z",
"2016-03-25T17:20:00Z",
"2016-03-25T17:40:00Z",
"2016-03-25T18:00:00Z"
],
"tax": 123,
"total": 978,
"tip": 100,
"soonest_available_at": "2016-03-11T11:44",
"merchant_funded_discount": 100,
"service_fee": 50,
"metadata": {
"authentication_token": "asdf232342453asdTSfs"
}
}
}

 

Example Request Schema

{
"type": "object",
"properties": {
"order_validation": {
"type": "object",
"properties": {
"calculated_tax": {
"type": "integer",
"description": "The calculated tax for when tax is not available from the provider."
},
"desired_ready_time": {
"type": [ "string", "null" ],
"description": "An iso8601 datestamp in UTC of the desired ready time from the diner"
},
"force_prices": {
"type": "boolean",
"description": "A indicator for the provider to use the forced price from Grubhub."
},
"fulfillment_type": {
"type": "string",
"description": "Set to pickup for grubhub managed deliveries and diner pickup, or set to delivery for self-delivery"
},
"grubhub_order_number": {
"type": "string"
"description": "A unique Grubhub identifier for an order"
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"item": {
"type": "object",
"properties": {
"category_id": {
"type": "string",
"description": "The corresponding category identifier that the item belongs to"
},
"category_group_id": {
"type": [ "string", "null" ],
"description": "The corresponding category group identifier that the category belongs to. This is only supported for Agency Applications."
},
"metadata": {
"type": "object",
"description": "Additional data about the item from the menu."
},
"name": {
"type": "string",
"description": "The name of the item."
},
"options": {
"type": "array",
"items": {
"type": "object",
"properties": {
"option": {
"type": "object",
"properties": {
"metadata": {
"type": "object",
"description": "Additional data about the option from the menu."
},
"option_group_display_order": {
"type": "integer",
"description": "The display order for the option group."
},
"option_group_id": {
"type": "string",
"description": "The provider ID for the option group."
},
"price": {
"type": "integer",
"description": "The price per single unit."
},
"provider_id": {
"type": "string",
"description": "The provider id from the menu"
},
"quantity": {
"type": "integer",
"description": "The quantity of the selected option"
}
},
"required": [
"metadata",
"option_group_display_order",
"option_group_id",
"price",
"provider_id",
"quantity"
]
}
},
"required": [
"option"
]
}
},
"price": {
"type": "integer",
"description": "The price per single unit"
},
"provider_id": {
"type": "string",
"description": "The provider id from the menu"
},
"quantity": {
"type": "integer",
"description": "The quantity of the selected item"
},
"recipient_name": {
"type": [ "string", "null" ],
"description": "The name of the diner. Applicable for Agency Application only. Set as null for Grubhub Marketplace."
},
"special_instructions": {
"type": [ "string", "null" ],
"description": "The special instructions set by the diner for an item. Sent only if item special instructions is enabled on the menu"
},
"tax_rate": {
"type": "string",
"description": "The tax rate from the menu."
}
},
"required": [
"category_id",
"category_group_id",
"metadata",
"name",
"options",
"price",
"provider_id",
"quantity",
"recipient_name",
"special_instructions",
"tax_rate"
]
}
},
"required": [
"item"
]
}
},
"location_time_zone": {
"type": "string",
"description": "The location's time zone"
},
"merchant_funded_discount": {
"type": "integer",
"description": "The discount amount from Grubhub."
},
"include_disposable_utensils": {
"type": "boolean",
"description": "A flag indicating if the diner requests for utensils."
},
"metadata": {
"type": "object",
"description": "Additional data about the order. Grubhub will return the metadata from the order validation response."
},
"paid_via_ach": {
"type": "boolean",
"description": "A flag indicating that the order was paid via ACH"
},
"prepaid": {
"type": "boolean",
"description": "A flag indicating the order was prepaid."
},
"special_instructions": {
"type": [ "string", "null" ],
"description": "Special instructions from the diner, including driver and pickup instructions"
},
"tip": {
"type": "integer"
},
"user": {
"type": "object",
"properties": {
"born_at": {
"type": "string"
},
"email": {
"type": "string"
},
"first_name": {
"type": "string"
},
"gender": {
"type": "string"
},
"last_name": {
"type": "string"
},
"password": {
"type": "string"
},
"phone": {
"type": "string"
}
},
"required": [
"born_at",
"email",
"first_name",
"gender",
"last_name",
"password",
"phone"
]
},
"uuid": {
"type": "string",
"description": "Unique identifier for each payload"
}
},
"required": [
"desired_ready_time",
"fulfillment_type",
"items",
"location_time_zone",
"merchant_funded_discount",
"include_disposable_utensils",
"metadata",
"paid_via_ach",
"prepaid",
"special_instructions",
"tip",
"user",
"uuid"
]
}
},
"required": [
"order_validation"
]
}

 

Example Request Body - Self-Delivery

{
  "order_validation": {
"calculated_tax": 0, "desired_ready_time": "2016-03-11T11:44:08", "fulfillment_type": "delivery",
"force_prices": false, "grubhub_order_number": "443213323803422", "delivery_fee": 300, "delivery_address": { "street_address": "One Federal St", "extended_address": "Floor 6", "locality": "Boston", "region": "MA", "postal_code": "02110", "latitude": 42.356257, "longitude": -71.057225
"delivery_instructions": null }, "items": [ { "item": { "category_id": "11124", "category_group_id": "6734384", "name": "Turkey Sandwich", "price": 679, "provider_id": "1324", "quantity": 1, "recipient_name": "Joe Smith", "metadata": { "integration_id_needed_for_validation": "1234" }, "special_instructions": "extra mayo", "options": [ { "option": { "metadata": { "integration_id_needed_for_validation": "1111" }, "price": 100, "option_group_display_order": 2, "provider_id": "67478", "option_group_id": "585", "quantity": 2 } }, { "option": { "price": 0, "option_group_display_order": 2, "provider_id": "32791", "option_group_id": "596", "quantity": 1 } } ] } }, { "item": { "category_group_id": "6734384", "category_id": "75759", "name": "Slice of cake", "price": 199, "provider_id": "9876", "metadata": { "integration_id_needed_for_validation": "5678" }, "quantity": 2, "recipient_name": "Jane Smith", "options": [ { "option": { "price": 0, "option_group_display_order": 5, "provider_id": "62459", "option_group_id": "334", "quantity": 1 } } ] } } ], "location_time_zone": "America/New_York", "metadata": {}, "paid_via_ach": true,
"prepaid": true, "tip": 100, "merchant_funded_discount": 100, "special_instructions": "buzzer does not work, call phone number please", "include_disposable_utensils": false, "user": { "born_at": "1986-03-11T11:44Z", "first_name": "joe", "gender": "male", "last_name": "smith", "email": "joe.smith@example.com", "password": "password123", "phone": "4445556666" },
"uuid": "3bff033014c3013921b85e87feee5a7c"
} }

 

Example Response Body (Self-Delivery)

{
  "order_validation": {
    "available_at": [
      "2016-03-25T15:20:00Z",
      "2016-03-25T15:40:00Z",
      "2016-03-25T16:00:00Z",
      "2016-03-25T16:20:00Z",
      "2016-03-25T16:40:00Z",
      "2016-03-25T17:00:00Z",
      "2016-03-25T17:20:00Z",
      "2016-03-25T17:40:00Z",
      "2016-03-25T18:00:00Z"
    ],
    "delivery_fee": 300,
    "tax": 123,
    "total": 978,
    "tip": 100,
    "soonest_available_at": "2016-03-11T11:44",
    "provider_funded_discount": 200,
    "merchant_funded_discount": 100,
    "service_fee": 0,
    "metadata": {
      "authentication_token": "asdf232342453asdTSfs"
    }
  }
}

 

Example Response Schema

{
"type": "object",
"properties": {
"order_validation": {
"type": "object",
"properties": {
"available_at": {
"type": [
"array",
"null"
],
"items": [
{
"description": "An iso8601 datestamp in UTC of the available times",
"type": "string"
}
]
},
"tax": {
"description": "Tax amount in whole cent amount",
"type": "integer"
},
"total": {
"description": "Subtotal in whole cent amount",
"type": "integer"
},
"tip": {
"type": "integer"
},
"merchant_funded_discount": {
"type": "integer"
},
"service_fee": {
"type": "integer"
},
"soonest_available_at": {
"description": "An iso8601 datestamp in UTC of the soonest available time",
"type": "string"
},
"metadata": {
"description": "Additional data about the item. Any keys prefixed with integration_ will be passed back to the provider during order validation and submission",
"type": "object"
}
},
"required": [
"available_at",
"tax",
"total",
"tip",
"merchant_funded_discount",
"service_fee",
"soonest_available_at"
]
}
},
"required": [
"order_validation"
]
}

 

Payment

All Grubhub orders are prepaid and paid via ACH (Automated Clearing House). Grubhub does not send any payment information. Please consult with your Grubhub Account Advisor for information regarding payment reconciliation.

 

Amounts

All amounts are always in cents.

 

Handling Nested Options

Please note that Grubhub will return options in a "flattened" fashion in our ordering requests. This means that all options selected within an item will be returned on the same level, including the nested options. This will be returned by display order within the option group and display order within the option. The order of the options is similar to what is displayed and selected by the diner.

Grubhub returns the parent option group as option_group_id , which can help identify the nested option leveling.

 

Negative Options

Grubhub does not return any option groups or options that were NOT selected by the diner. We suggest adding a separate option group for negative options such, as "Remove Toppings".

 

Handling Time Values

There are four options for how you pass back the date-time:

 

Order Types

Grubhub Managed

In the payload, fulfillment_type is always "pickup" (even for delivery orders) as Grubhub treats all managed orders the same. Orders are treated as pickup since they are picked up by either the diner or our Grubhub drivers.

Self-Delivery

These are orders fulfilled by the restaurant. Grubhub sends fulfillment_type as "delivery" for self-delivery orders and uses "pickup" for pickup orders.


Handling Tips

Grubhub should only pass in tip values for places that accept tips, as identified on the locations endpoint. If Grubhub passes in a tip for a place that does not accept tips, please return the tip attribute to us as "null". We interpret that as 0 for the transaction in question - it also triggers us to change our settings for that location's ability to accept tips.


Total, Tax, Tip and Merchant Funded Discounts

Reminder: all values are always in cents.

tax

The sales tax on the order. Grubhub charges the diner the tax returned from the order validation. Since order validation occurs AFTER the diner has placed the order, Grubhub displays an estimated tax based on the tax_rate provided in the menu. This estimated tax amount should match the tax sent by the provider. Please update tax_rate in the menu to have the appropriate tax rate.

tip

The tip the user submitted, i.e. what we sent in to you can mostly just be echoed back to us. If the site does not allow tips, or the tip fails, return as "null". This is either an integration issue or a config issue as to whether or not a location accepts tips (should be defined on the menu for the location).

service_fee

Fee to the user applied by an ordering provider. Set as 0 if not applicable.

delivery_fee

Fee to the user for self-delivery orders. Set as 0 if not applicable.

merchant_funded_discount

If the merchant runs a discount through the Grubhub Platform, we post the total discount value here, so it can be used in tax calculations. Set as 0 if not applicable.

total

The amount spent on food not including the tax or tip and specifically excluding any merchant_funded_discount. As an example, a $10 sandwich with a $2 tip and $0.70 sales tax, $0.45 service fee and a $1 merchant_funded_discount would have the following values:

The total charged to the consumer (which is not expected to be returned in the API to us) is $12.15. The total that Grubhub pays to the merchant is $12.15 since the $1 merchant funded discount is funded by the merchant.

 

Utensils Support

By default, all Grubhub orders have include_disposable_utensils set to "true". When a diner opts out of receiving utensils via the Grubhub Marketplace UI, the order payloads reflect this by sending include_disposable_utensils as "false" . This is sent on both the Order Validation and Order Submission request payload.

 

Ready Times & Scheduling of Orders

Further information on ready times and order scheduling:

Note that when the order is placed to be picked up ASAP (desired_ready_time = null), we expect the soonest_available_at time to you as a parseable time-stamp in the local time of that location. This is the most useful time for us to receive and what we show to the user.

If the merchant does not give an actual ready time that can be returned to us as a date-time, please include the ready_at attribute with a value of "null". We only want the most accurate value if a real time is returned, otherwise, Grubhub subs one in based on our best "guesstimate". You returning null is our cue to do that. If the merchant says "ASAP", return "null". If they say "In 10 minutes" or another bit of text you cannot parse, just return "null".

 

Available At Times

For merchants that support order scheduling but require orders to be scheduled for particular time slots, an array of available_at times should be returned. Only available time slots after the desired ready time and within 24 hours of the validation time should be returned in the available_at array.

If ordering is available for any time after the soonest_available_at time, an available_at array should be returned.

The available_at array should be null for providers that do not support order scheduling.

Available time zones should be returned either as local timestamps (YYYY-MM-DDTHH:mm) or timestamps with a timezone (YYYY-MMDDTHH:mmZ).

 

Add-Ons

Grubhub has it's own merchant-controlled logic for upsells, there is no need to return to us existing upsell logic unless specifically requested by the merchant.

 

Optional Cart_ID Optimization

Use if you would like to include a cart_id, or some other identifier that you get back after the validate order call. To make the submit order call faster, you can echo that information back to Grubhub in the metadata response attribute, and we will send it along on the proceeding submit call. For example, if you sent us back the following on the validate call:

{
"metadata": {
"order_user_token": "a694d3bc-151b-48b0-bb92-a557106b0019",
"order_cart_token": "d1e19c7b-e63a-4fb4-8896-912d12e1d15d"
}
}

you would get it back on the submit call and could use it to pull back up the cart without recreating it from scratch. This might save a lot of time on the submit call.

Some things to note if you take this approach:

 

Errors

When errors occur, we expect a response in the following format:

{
"error": {
"type": "provider",
"message": "ITEM NOT AVAILABLE ERROR: Italian B.M.T.",
"error_details": {
"failed_items": [
{
"provider_id": "/Cart/Product2.aspx?ID=30106&gID=3",
"name": "Italian B.M.T."
}
]
}
}
}

Site Errors: 422 HTTP status code

The underlying merchant ordering site renders an error that would have been displayed to the user to explain something like "Sorry, this location is closed right now" or "Oh no! We're out of guacamole". In this case, please return the actual error message the site renders to the user, an error code of 422.

Item Or Option Unavailable Errors: 422 HTTP status code

This is a subset of the case above, but should be handled with the following criteria in
mind:

Integration Errors: 500 HTTP status code

If your service cannot interact with the merchant for some reason, please pass back a relevant message to help us understand what's going wrong along with an error code 500. Grubhub displays its own error message to the user.

This should include the scenario when the underlying merchant is non-responsive, i.e. perhaps their location ordering provider is down, in which case your integration lets Grubhub know so we can communicate to the user.

Parameter Errors: 500 HTTP status code

If we send you parameters that do not match your expectations and a message telling
us the error. This error will not be shown to the user, but may be used for informing us
we need to refresh our menu for the location. Send an error code of 500.

Menu Not found: 404 Error Code

If Grubhub attempts to issue a call and the merchant/location is no longer available from the provider (whether temporarily or permanently), subsequent requests from Grubhub for actions on that merchant should return a 404.

Service Down

If your service is down, Grubhub returns a generic error to the user, Error code 500.

Timeout Threshold

Order validation request expects a response within 25 seconds.