Best Practice and FAQs

Best Practice Guide

This information provides guidance to avoid common mistakes during new integrations with the Brink API, based on actual support cases from vendor and customer integration teams.

Frequency
The best practice for frequency of calls for multi-tenant servers is

5 concurrent calls with a sleep of 2-3 minutes in between calls.

If your group is hosted on a single-tenant server, the frequency is

10 concurrent calls with a sleep of at least 1 minute in between calls.

It is not a matter about how many calls are made, but the frequency at which calls are made to not take down the API.

Publishing changes to location
Changes should be published on one location and once tested on a single location, they may be cascaded ahead to other locations. If changes are being done parallelly in multiple locations, single record changes must happen and batching/bulk operation is not recommended. Changes done in one location should not be cascaded ahead to more than a set of 500 locations.

Batching (performing multiple operations in a single request)
Whenever several save or delete operations are to be performed, the operations can be clubbed as a batched request, i.e. more than one operation can be performed in a single request.
However, it is recommended that batching is done adhering to the below guidelines:
- Batch requests should be sequential. If the system allows for a multi-threaded load, then recommended Request per minute (RPM) for batching should be followed.
- Batching or Bulk operation is not recommended for SavePriceChange and DeletePriceChange.
- Below is applicable for both End of Day and Immediate publish:
- Batch size for save endpoints (except SavePriceChanges) should not be greater than 100. RPM should not be more than 5 requests per minute.
- Batch size for delete endpoints (except DeletePriceChanges) should not be greater than 20. RPM should not be more than 5 requests per minute.

WSDL usage
Always try to cache the WSDLs. The WSDLs are on a sandbox server. In general never attempt to call it per API call but call a cached version of it to avoid an extra hop.

HTTPS vs HTTP
Always use https instead of http.
Using http causes the API to redirect, thus doubling up on the call, causing an excessive load on the server.

TLS
Brink only supports TLS version 1.1 or 1.2.

Result Codes
Result codes will always be returned for calls made to the following services:
  • HouseAccounts.svc
  • Kitchen.svc
  • Labor2.svc
  • Loyalty.svc
  • Loyalty2.svc
  • Ordering.svc
  • Sales2.svc
  • Settings2.svc

A ResultCode = 0 indicates that the call was successful. Please note the Result Codes to ensure that your call went through.
The Message field in the response will be populated and give further detail if the call is unsuccessful.

XML Calls
If you are using XML to submit API calls, the XML must be structured according to the order published in the WSDL.
Do not change the order of the fields in the request sent, or it will fail.

Settings Calls
It is unnecessary to continuously pull settings in Settings.svc and Settings2.svc if no changes have been made to the settings.
Always call GetLastModifiedTime in Settings2.svc before pulling settings again from either service.
Using this call won't indicate which settings have changed, but the DateTime value returned will indicate when settings have last been updated and can prompt a re-pull of settings.
I am getting an 'Access Denied' response - what does this mean?
If you are receiving an Access Denied from Brink in an API response or a Result Code = 4, then you should check the following things:
  • AccessToken - Is your AccessToken correct? Is it correctly in the HTTP Web request headers or the request body (depending on the service)?
  • LocationToken - Is your LocationToken correct? Is it correctly in the HTTP Web request headers or the request body (depending on the service)?
  • Endpoint - Is your endpoint pointing to the correct server? Is your endpoint https, not http?
  • Provisioning - Are you provisioned to access this location with your API account's AccessToken? If not, you can direct any token requests to:

  • Main Support at support.brinksoftware@partech.com or (800) 403-9027
I am getting successful response codes of '0' for my SubmitOrder calls, but I don't see my orders - what does this mean?
There is a setting in Settings Editor in Settings Editor > Options > Online Ordering > General:

Accept Orders When Master Offline

that when checked, will seemingly only orders to be submitted via the API successfully, but the Register may not receive these orders.

You can see if this option is checked, and then uncheck it if you prefer for orders to be rejected with the Register is offline.
I am getting successful response codes of '0' for my GetShifts calls, but no shifts are returned - what does this mean?
Check if the DateTime for the BusinessDate parameter that you are passing through in your request:
  • is in UTC time. You can check the Kind property of the DateTime and specify it to be UTC if not.
  • does not have any offset or time span specified. It should be the the BusinessDate with no time span or T00:00:00 in UTC.


The GetShifts call specifically has built-in logic that will not return shifts data if the BusinessDate parameter is not in UTC, and we recommend for our integrators to always submit DateTime objects in UTC.
Can I make an API call for a range of dates?
There are only a select number of calls that allow you to retrieve data for a range of dates:
Otherwise, if you need to retrieve data for multiple dates, you will have to make a loop that will call the API with those business dates individually.
Can I make an API call for multiple locations?
There are only a select number of calls that allow you to retrieve data from an entire group of locations:
Otherwise, if you need to retrieve data for multiple locations, you will have to call the API with their respective LocationTokens individually.
I am interested in live dashboarding - how can I accomplish this?
Brink highly recommends making 5-10 concurrent calls with a sleep of 2-3 minutes as best practice, but to further avoid any latency issues or redundant calls, we propose a method of making calls to the API to only obtain data that has changed since the last call.

Live dashboarding or live data calls include:
  • GetOrders in Sales2.svc - using the ModifiedTime field
  • GetShifts in Labor2.svc - using the ModifiedTime field
  • Any calls in Settings.svc and Settings2.svc - using the GetLastModifiedTime call

This method allows you to retrieve data that has only been added or modified since, and including, the ModifiedTime you specify, in the case of the GetOrders and GetShifts calls.
In the case of Settings.svc and Settings2.svc, it is unnecessary to continuously pull settings if no changes have been made. Using the GetLastModifiedTime call won't indicate which settings have changed, but the DateTime value returned will indicate when settings have last been updated and can prompt a re-pull of settings.

If you are interested in live dashboarding or live data, please contact:

API Support at api.support2@brinkpos.net

to assist you with that integration.
How do I make a SaveEmployees call to Settings2.svc?
You can save new employees, retrieve existing employees, and update employee information with the SaveEmployees call to Settings2.svc.

The structure of the SaveEmployees request and response is outlined here in the Settings2.svc documentation.
The structure of the Employee type is outlined here.

Create: Add one or more new Employee(s)
You can add one or more Employees to Brink by populating the Employees array in the SaveEmployees call with Employee objects.

The creation of any object within the SaveEmployees call is denoted with a negative integer Id of at least -1 for the first object created, and decrementing for each new object in the request (e.g. -2, -3, etc.)
                            Employees = [
	Employee:
		Id = -1,
		DisplayName = "John Doe",
		FirstName = "John",
		Jobs:
			EmployeeJob:
				Id = -2,
				JobId = 101,
				PayRate = 10,
				SecurityLevelId = 1,
		LastName = "Doe"

	Employee:
		Id = -3,
		DisplayName = "Jane Doe",
		FirstName = "Jane",
		Jobs:
			EmployeeJob:
				Id = -4,
				JobId = 202,
				PayRate = 20,
				SecurityLevelId = 1,
		LastName = "Doe"
]
                    

John Doe and Jane Doe were created in Brink because their Employee objects were denoted with IDs of -1 and -3, respectively.
Because EmployeeJob objects were also created under each Employee, those also were denoted with IDs of -2 and -4, keeping in the increasingly negative sequential order within the request.
When you retrieve Employees through the GetEmployees call in Settings2.svc, these ID fields will be populated with unique Brink-assigned Ids for the Employee or EmployeeJob object that was created.

The minimally required fields to save a new employee are Id, DisplayName, FirstName, and LastName.

Please note that Employee IDs are unique on a per-location basis. For example, if Employee John Doe works at multiple locations, he will have a different EmployeeId at each location.
Pushing the same Employee IDs across multiple locations is not possible, and you would have to create the employee at each separate location.
Read: Retrieve all existing employees
You can retrieve all existing employees from Brink for a location with the GetEmployees call, outlined here in the Settings2.svc documentation.

It is important to make this call before updating an employee, since the response will return the IDs that you need to target in order to update an Employee, as well as the existing data you'll have to repopulate in your request.
                            Collection = [
	Employee:
		Id = 10001,
		DisplayName = "John Doe",
		FirstName = "John",
		Jobs:
			EmployeeJob:
				Id = 10002,
				JobId = 101,
				PayRate = 10,
				SecurityLevelId = 1,
		LastName = "Doe"

	Employee:
		Id = 10003,
		DisplayName = "Jane Doe",
		FirstName = "Jane",
		Jobs:
			EmployeeJob:
				Id = 10004,
				JobId = 202,
				PayRate = 20,
				SecurityLevelId = 1,
		LastName = "Doe"
]

                    

If the SaveEmployees call made in the previous section was successful, then the two Employees, John Doe and Jane Doe, will be returned in the GetEmployees response.
(Note: Fields have been omitted from this example for clarity, which are outlined here.)

As you can see, John Doe has a unique EmployeeId of 10001 and the unique ID of the EmployeeJob under him is 10002.
Likewise, Jane Doe has a unique EmployeeId of 10003 and the unique ID of the EmployeeJob under her is 10004.
Update: Update the information of an existing Employee
You can update one or more existing employees in Brink by populating the Employees array in the SaveEmployees call with Employee objects distinguished by the EmployeeId.

In your request, include all Employee fields that you intend to update with new values, along with the fields populated with the already existing data.
If you include fields with no value or exclude fields altogether in your request, you will overwrite any existing values for those Employee fields.
Likewise, if you do not include all existing EmployeeJobs in a Jobs array, they will be overwritten as well.
                            Employees = [
	Employee:
		Id = 10001,
		DisplayName = "John D.",
		FirstName = "John",
		Jobs:
			EmployeeJob:
				Id = 10002,
				JobId = 101,
				PayRate = 15,
				SecurityLevelId = 1,
		LastName = "Doe"

	Employee:
		Id = 10003,
		DisplayName = "Jane Doe",
		FirstName = "Jane",
		Jobs:
			EmployeeJob:
				Id = 10004,
				JobId = 202,
				PayRate = 20,
				SecurityLevelId = 1,
			EmployeeJob:
				Id = -1,
				JobId = 303,
				PayRate = 30,
				SecurityLevelId = 1,
		LastName = "Doe"
]
                    

The first part of the above SaveEmployees request is targeting the Employee with an EmployeeId of 10001, John Doe.
We are updating his DisplayName to John D. and PayRate to 15, so these fields contain updated values in this request.
The other fields should also populated with their existing values in this request.

The second part of the request is targeting the Employee with an EmployeeId of 10003, Jane Doe.
We are updating her Jobs array by adding a new EmployeeJob denoted with an EmployeeJobId of -1, while retaining her original job so she will be able to clock in under either Job.
Suppose that Jane has been working as Shift Lead (EmployeeJobId: 202) and has been promoted to now also work shifts as a Manager (EmployeeJobId: 303).
Both of these EmployeeJobs must be included in her Jobs array in the request, otherwise her original EmployeeJob will be overwritten.

You can confirm that the update was successful by the response from your SaveEmployees call and by making a GetEmployees call and see if the fields are populated with the correct values.
Delete: Removing an employee
Once an Employee is created in Brink, the Employee object cannot be deleted and will always be returned via the GetEmployees call.

There are two options in which you can "remove" an Employee:
1) You can target an Employee and replace their fields with null or other string values, effectively overwriting their information.
2) You can filter out Employees whose Terminated fields have a value of true.

                            Employees = [
	Employee:
		Id = 10001,
		DisplayName = "REMOVED",
		FirstName = "REMOVED",
		LastName = "REMOVED"

	Employee:
		Id = 10003,
		DisplayName = "Jane Doe",
		FirstName = "Jane",
		Jobs:
			EmployeeJob:
				Id = 10004,
				JobId = 202,
				PayRate = 20,
				SecurityLevelId = 1,
			EmployeeJob:
				Id = 10005,
				JobId = 303,
				PayRate = 30,
				SecurityLevelId = 1,
		LastName = "Doe"
		Terminated = true,
		TerminationDate = "2019-01-01T00:00:00"
]
                    

The above SaveEmployees request is targeting the two Employees with an EmployeeId of 10001, John Doe, and EmployeeId of 10003, Jane Doe.

All of John Doe's personal information has been removed and his Jobs array has been emptied through the request. However, the Employee object with EmployeeId: 10001 will remain, as it cannot be deleted.

Jane Doe's personal information will still persist in Brink, but she is recognized as terminated through the Terminated field being set to true.
Populating the TerminationDate for a terminated Employee is optional, but recommended.
How do I make a SubmitOrder call to Ordering.svc?
You can submit a future order to a location through the API using the SubmitOrder call to Ordering.svc

Submitting a Basic Order
There are three parts to SubmitOrder request: Options, Order, and SuppressConfirmationEmail.
1. Options - this will contain the field CalculateItemPrice in which you should specify either true or false in order to indicate whether the Order should be calculated with default pricing or with custom pricing outlined in the SubmitOrder request.
2. Order - the structure of an NewOrder object is outlined here.
3. SuppressConfirmation - you should specify true or false in order to indicate a confirmation email should be suppressed upon completion of the request.

                            Options:
	CalculateItemPrice = true

Order:
	DestinationId = 1,
	FutureOrder:
		PickupTime = "2019-01-01T20:00:00",
		OffsetMinutes = 0,
	Items:
		NewOrderItem:
			Id = 1,
			ItemId = 12345,
			DestinationId = 1,
	Name = "Online Order"

SuppressConfirmationEmail = true
                    

At the very minimum, an Order is required to have 4 fields populated: DestinationId, FutureOrder, Items, and Name.
1. DestinationId - this field should be populated with the Id of the Destination to which this Order will be sent.
2. FutureOrder - because all orders submitted via the API are considered future orders, this field requires a PickupTime. This is because the Register uses this DateTime to determine whether this is a FutureDateOrder (a future order that is placed at least one day or more in advance), and when to open and/or send the Order to the kitchen, depending on the amount of time required for prep.
3. Items - at least one NewOrderItem is required in this array.
  • The creation of objects within the SubmitOrder call is denoted with a positive integer Id of at least 1 for the first object created, and incrementing for each new object in the request (e.g. 1, 2) - unlike the SaveEmployees request which uses decrementing, negative integers.
  • The ItemId needs to map to the Id of the Item that is being ordered.
  • EachNewOrderItem also needs to have DestinationId to which it will be sent. This typically should be the same DestinationId at Order-level.
4. Name - a name for the Order is required to distinguish the Order on the register.
Submitting a Combo Order
When submitting a combo order, the Ids of the NewOrderItem within the Order, Item, and Component must all be validated against the Items Settings, retrievable by GetItems.

Suppose that you want to submit an order for an API Combo, which has an ItemId = 111. We'll refer to this as the Parent Item.

There are three components to this API Combo Item: Sandwich, Side, and Drink.
1. Sandwich - ComponentId = 200 has two options:
  • Burger - ItemId = 201
  • Chicken Sandwich - ItemId = 202

2. Side - ComponentId = 300 has two options:
  • Fries - ItemId = 301
  • Tots - ItemId = 302

3. Drink - ComponentId = 400 has two options:
  • Soft Drink - ItemId = 401
  • Juice - ItemId = 402

If you wanted to submit a combo order for a Burger, Fries, and Soft Drink, then the Items in your Order in the request should be formatted like this:
                            Items:
	NewOrderItem:
		Description = "API Combo",
		Id = 1,
		ItemId = 111,
		ComboItems:
			NewOrderItem:
				Description = "Burger",
				Id = 2,
				ItemId = 201,
				ComponentId = 200,
				DestinationId = 1,
			NewOrderItem:
				Description = "Fries",
				Id = 3,
				ItemId = 301,
				ComponentId = 300,
				DestinationId = 1,
			NewOrderItem:
				Description = "Soft Drink",
				Id = 4,
				ItemId = 401,
				ComponentId = 400,
				DestinationId = 1,
		DestinationId = 1
                            

The Parent Item, API Combo, will have the 3 nested items within its ComboItems array.

Each of these 3 Child Items should have the ComponentId that indicates which component of the combo the item fulfills (Sandwich, Side, or Drink) and the ItemId of the Item (Burger, Fries, and Soft Drink) that was selected to fulfill that corresponding component.

Each creation of a NewOrderItem, whether it is a Parent Item or Child Item, will still require an incrementing Id, as well as the DestinationId for the item.

Submitting an attached payment with an Order
  • Supports multiple NewOrderPayment in the Payments array.

There are additional fields associated with specific payment types, so the NewOrderPayment type must be specified when attaching a payment. These types are: NewCheckPayment, NewCreditCardPayment, NewExternalPayment , NewGiftCardPayment, and NewGiftCertificatePayment.

Below is an example of an Order with a NewCreditCardPayment:

                            Order:
	DestinationId = 1,
	FutureOrder:
		PickupTime = "2019-01-01T20:00:00",
		OffsetMinutes = 0,
	Items:
		NewOrderItem:
			Id = 1,
			ItemId = 12345,
			DestinationId = 1,
	Name = "Online Order",
	Payments:
		NewOrderPayment (of type "NewCreditCardPayment"):
			Amount = 10.00,
			Id = 2,
			TenderId = 5,
			TipAmount = 2.00,
			AccountNumber = 1234123412341234,
			City = "New York",
			Country = "US",
			CVV = "321",
			ExpirationDate = "0120",
			NameOnCard = "CustomerName",
			PostalCode = "12345",
			State = "CA",
			StreetAddress = "123 Brink St."
                    
All NewOrderPayment objects have the same base fields, Amount, Id (inline with the incrementally increasing IDs within the order), TenderId (which corresponds to the payment tender type Id retrievable through GetTenders), and TipAmount if any is attached.
The rest of the fields are specific to the NewCreditCardPayment type, which can be submitted if there is a Credit Card processor set up for the location to accept online payments.

Below is an example of an Order with a NewExternalPayment:
                            	Payments:
		NewOrderPayment (of type "NewExternalPayment"):
			Amount = 10.00,
			Id = 2,
			TenderId = 6,
			TipAmount = 2.00,
			ReferenceDetails:
				PaymentReferenceDetail:
					Name = "CardType",
					Value = "Visa"
                    
External payments are processed externally by an integrator and submitted as a NewExternalPayment type and external tender - this is for the Register to know that no other payments need to be processed for the order.