The GoTab Loyalty API is an events-driven API to be consumed by a third party loyalty program.
GoTab Loyalty API Dependencies:

Event Types:
INQUIRE:
The inquire event type will have a payload that includes the event_type: INQUIRE. It will also include the lookup value the customer entered. This data may be a phone number, an email, or a customer loyalty number depending on how the lookup_type value has been configured for your specific integration. If there are any items currently in the customers cart that are unpaid for it will also include that.
Here is an example INQUIRE event type request:
{
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Authorization": "example_auth_header_123",
"x-api-key": "null"
},
"json": true,
"body": {
"tab_data": {
"tab_id": "14165",
"tab_uuid": "O2oFAC7fXeYNEWmmOBFZr_4S",
"location_id": "1019",
"name": "Austin Michael",
"spots": null,
"status": "PENDING",
"total": 1166,
"subtotal": 1100,
"tax": 66,
"balance_due": 1166,
"created": "2025-04-01T11:00:53.247Z",
"orders": [],
"items": [],
"adjustments": [],
"payments": [],
"customers": {},
"tab_metadata": null
},
"event_type": "INQUIRE",
"lookup_value": "+16082139090",
"location_id": "1019"
}
}
In order to indicate that a customer was found in your system please respond with a 200 status code. It is expected that even when there are no offers or points currently avaialble for a customer we will get a 200 status code with this JSON in the response:
{
"loyalty_points": [],
"offers": []
}
In the case where the customer is eligible for offers or points here are a few examples of what your JSON responses may look like:
Offers and Points
{
"loyalty_points": [
{
"type_display_name": "Loyalty Points",
"type": "points",
"total": 100,
"available": 100,
"value": 100,
"conversion_rate": 1
}
],
"offers": [
{
"name": "Offer Program Name",
"offers": [
{
"offer_id": "12344",
"name": "Free Drink",
"description": "This is good for any free drink",
"amount": 5,
"type": "tab_discount",
"exclusive_offer": false,
"group_exclusive_offer": false,
"auto_apply": false,
"allow_partial_use": false
}
]
}
]
}
Just Points
{
"loyalty_points": [
{
"type_display_name": "Loyalty Points",
"type": "points",
"total": 100,
"available": 100,
"value": 100,
"conversion_rate": 1
}
],
"offers": []
}
Just Offers
{
"loyalty_points": [ ],
"offers": [
{
"name": "Offer Program Name",
"offers": [
{
"offer_id": "12344",
"name": "Free Drink",
"description": "This is good for any free drink",
"amount": 5,
"type": "tab_discount",
"exclusive_offer": false,
"group_exclusive_offer": false,
"auto_apply": false,
"allow_partial_use": false
}
]
}
]
}
export const LoyaltyOffersSchema = z.object({
loyalty_points: z.array(LoyaltyPointSchema),
offers: z.array(OfferGroupSchema),
});
const LoyaltyPointSchema = z.object({
type_display_name: z.string(),
type: z.string(),
total: z.number().positive({ message: 'Point total must be greater than 0' }),
available: z.number(),
value: z.number().positive({ message: 'Point value must be greater than 0' }),
conversion_rate: z.number().positive({ message: 'Point conversion rate must be greater than 0' }),
});
const OfferGroupSchema = z.object({
name: z.string(),
offers: z.array(OfferSchema),
});
const OfferSchema = z.object({
offer_id: z.string(),
name: z.string(),
description: z.string(),
amount: z.number().positive({ message: 'Offer amount must be greater than 0' }),
type: z.string(),
target_id: z.string().optional(),
expiration_date: z.string().optional(),
exclusive_offer: z.boolean(),
group_exclusive_offer: z.boolean(),
auto_apply: z.boolean(),
allow_partial_use: z.boolean(),
});
In order to indicate a customer was not found in your system please respond with a status code 404 and a JSON response that looks like the following. Please note that the message is up to you. If you want to indicate that the customer should take some specific action please do so, but keep it succinct. Mainly, just be aware that any message you include will end up as an alert in our UI.
{
"message": "A membership was not found for that phone number."
}
For any other error please response with a 400 status code and an appropriate message.
ADD_CUSTOMER:
If the INQUIRE event returns no account our UI will update to show a sign up form for the loyalty program. This will allow a guest to provide their look up value, as well as opt in to recieve any necessary marketing materials. Once this form is submitted it will create a request with an event_type: CREATE. This will also include the relevant customer data. Following this CREATE event our systen will trigger another INQUIRE event with the new customer's data to see if they are elgible for any offers or rewards based on their current cart or as a first time customer.
REDEEM:
ACCRUAL:
The ACCRUAL event type will be an asynchronous request that will be triggered for each tab at a location as they are closed in GoTab. It will basically act as an export of the tab's sales data and will include the data of the customers associated with the tab.
We intentionally send an ACCRUAL event type request for every closedd tab. This may include data for customers who are not currently using your loyalty program. The idea here is that you could still store the data and in the event a customer signs up for the loyalty program after having been at the location many times prior they may get credit for those previous visits. This would depend on the limitations of your program and whether the operator configured it to behave that way.
Here's an example request for an ACCRUAL event type request
{
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Authorization": "example_auth_header_123",
"x-api-key": "null"
},
"json": true,
"body": {
"tab_data": {
"tab_id": "9200",
"tab_uuid": "tOp_3qizc55ojTehKtoGGKZc",
"location_id": "100800",
"name": "Test User",
"spots": ["109104"],
"status": "CLOSED",
"total": 1619,
"subtotal": 1295,
"tax": 130,
"balance_due": 0,
"created": "2025-02-17T13:10:51.759Z",
"orders": [
{
"tax": 130,
"name": "Test User",
"notes": null,
"total": 1619,
"placed": "2025-02-17T13:10:59.358228-05:00",
"status": "SCHEDULED",
"spot_id": 109104,
"user_id": null,
"zone_id": 16866,
"order_id": 72970599,
"subtotal": 1295,
"scheduled": "2025-02-17T13:10:59.358228-05:00",
"spot_name": "Bar 101",
"x_spot_id": null,
"x_zone_id": null,
"zone_name": "Bar",
"zone_type": null,
"address_id": null,
"dispatched": null,
"display_id": null,
"order_uuid": "or_WjMDstvFo3HGAC2Ef1qZcvBd",
"customer_id": 21569877,
"dropoff_eta": null,
"location_id": 100800,
"spot_user_id": null,
"spot_url_name": "bar-101",
"zone_group_id": 2425,
"zone_group_type": "DINING",
"spot_agent_configs": null,
"zone_agent_configs": null
}
],
"items": [
{
"fee": false,
"tax": 130,
"notes": {},
"comped": false,
"status": "OPEN",
"tab_id": 9200,
"x_name": "BBQ Brisket",
"created": "2025-02-17T13:10:54.209875",
"item_id": 16828,
"options": {},
"order_id": 72970599,
"prepared": null,
"recalled": null,
"subtotal": 1295,
"tax_rate": 0.1,
"discounts": false,
"prep_time": 0,
"x_item_id": null,
"dispatched": null,
"product_id": 17680342,
"router_ids": [2203],
"unit_price": 1295,
"x_metadata": null,
"x_quantity": 1,
"category_id": 55060,
"product_tags": ["60employee"],
"product_type": "DEFAULT",
"product_uuid": "prd_1L4pqUUNkJlDC8cV2d~EtweQ",
"x_product_id": null,
"adjust_reason": null,
"category_name": "Lunch Sandwiches",
"item_subtotal": 1295,
"order_rule_id": null,
"product_delay": null,
"x_item_details": {},
"x_product_name": null,
"product_options": {},
"tax_rate_detail": [
{
"rate": 0.1,
"tax_id": 801,
"weight": 1,
"tax_name": "Tax"
}
],
"true_unit_price": 1295,
"true_x_quantity": 1,
"x_price_level_id": null,
"product_base_price": 1295,
"x_product_base_price": null,
"order_rule_discount_percentage": null
}
],
"adjustments": [],
"payments": [
{
"amount": 1619,
"tip": 130,
"name": "TEST USER",
"autograt": 194,
"created": "2025-02-17T13:10:58.447506-05:00",
"customer_id": 21569877
}
],
"customers": {
"tabOwnerIsPOSUser": false,
"tabOwnerCustomerId": "21569877",
"allCustomersOnTab": [
{
"tab_id": "9200",
"customer_id": "21569877",
"created": "2025-02-17T13:10:51.759Z",
"tab_viewed": "2025-02-17T13:10:51.851Z",
"handle": "+16082139087",
"protocol": "s",
"email": null
}
]
},
"tab_metadata": null
},
"event_type": "ACCRUAL"
}
}
A 200 response would look like the following (note the id which we store in our system for referential integrity and reconciliation):
{
"message": "success",
"id": "hl123hlj12h31"
}
A 4xx response in addition to having the relevant status code should include a message that we log out on our side in order to debug:
{
"message": "Tab missing customer data."
}