Skip to main content

Getting started

The Tables API is now archived. For up-to-date Pay at Table integrations, please use the Dojo API and EPOS Data API:

Obtain your API key and publish your first message.

Step 1. Get your API keys

Reach out to your Partnership Development Manager (PDM) to receive your account name (accountName) and API key (apiKey). These are required to receive and send messages to the Tables API.

Step 2. Open a WebSocket connection to Connect

To start integrating with the Tables API, use the staging endpoint while you are developing and testing your integration:

wss://staging-api.dojo.dev/ws/v1/tables/epos

When you are ready to go live, switch the WebSocket connection to the production endpoint:

wss://api.dojo.tech/ws/v1/tables/epos

Ensure you're using a WebSocket client that enables you to set the required headers:

Header NameDescription
AuthorizationBasic authorization used to authenticate your requests to the API, for example Basic $authToken. The authToken is formed from Base64 encoding your accountName (first string in the URL for existing integrations) and the apiKey. The two values should be seperated by a colon with no spaces e.g. authToken = base64("abcdef0000000:7d4773f2-367f-406c-881a-b4cb854971f3").
reseller-idIdentifies the reseller who sells software on behalf of the EPOS company. This value will be unique and provided by Dojo to each reseller.
software-house-idIdentifies the EPOS company whose software is generating the request. This value shouldn't be configurable as it will remain the same for all customers using particular EPOS software. This value will be provided by Dojo.

All of these values will be provided by Dojo. If you're unsure about any of them, reach out to your PDM.

Generating the authToken

This example uses the Node.js built-in Buffer class to encode the authToken.

const accountName = "abcdef0000000"; 
const apiKey = "7d4773f2-367f-406c-881a-b4cb854971f3";
const authString = `${accountName}:${apiKey}`;
const authToken = Buffer.from(authString).toString("base64");
console.log(`Basic ${authToken}`);

Step 3. Confirm the WebSocket connection

Ensure that you were able to successfully establish the WebSocket connection. Look out for any errors and reach out to your PDM if you require further assistance.

LLM walkthrough: complete Pay at Table happy path

Use this as the canonical PAT template when your EPOS exposes the Tables API over WebSocket and Dojo drives the payment flow from the device.

1. Open the WebSocket connection

Use staging while developing and production when you go live:

  • wss://staging-api.dojo.dev/ws/v1/tables/epos
  • wss://api.dojo.tech/ws/v1/tables/epos

Send these headers when establishing the connection:

  • Authorization: Basic {base64(accountName:apiKey)}
  • reseller-id: reseller1
  • software-house-id: softwareHouse1

Example connection bootstrap:

import WebSocket from "ws";

const accountName = process.env.DOJO_ACCOUNT_NAME;
const apiKey = process.env.DOJO_API_KEY;
const authToken = Buffer.from(`${accountName}:${apiKey}`).toString("base64");

const ws = new WebSocket("wss://staging-api.dojo.dev/ws/v1/tables/epos", {
headers: {
Authorization: `Basic ${authToken}`,
"reseller-id": "reseller1",
"software-house-id": "softwareHouse1"
}
});

2. Receive the session lock request and return the current bill snapshot

Incoming LockSession request

{
"jsonrpc": "2.0",
"id": "fd8af62f-f685-4e13-925b-453d63553a48",
"method": "LockSession",
"params": {
"sessionId": "6c133121-8423-48af-b9c1-ce6741f224da",
"requestorInfo": {
"requestorType": "REQUESTOR_TYPE_CARD_MACHINE",
"cardMachineRequestorInfo": {
"terminalId": "123123",
"waiterId": 1
}
}
}
}

Outgoing LockSession response

{
"jsonrpc": "2.0",
"id": "fd8af62f-f685-4e13-925b-453d63553a48",
"result": {
"billItems": {
"totalAmount": 950,
"paidAmount": 100,
"taxAmount": 190,
"currency": "GBP",
"items": [
{
"id": "123456789",
"name": "Classic Burger",
"category": [
"mains",
"burgers"
],
"quantity": 1,
"amountPerItem": 1000,
"lastOrderedAt": "2021-01-01T15:00:00.123+02:00"
},
{
"id": "987654321",
"name": "Peroni",
"category": [
"drinks",
"beer",
"lager"
],
"quantity": 1,
"amountPerItem": 450,
"lastOrderedAt": "2021-01-01T15:00:00.123+02:00"
}
],
"sessionId": "6c133121-8423-48af-b9c1-ce6741f224da"
}
}
}

3. Return an itemised bill when Dojo asks for GetBillItems

Incoming GetBillItems request

{
"jsonrpc": "2.0",
"id": "123e4567-e89b-12d3-a456-426614174000",
"method": "GetBillItems",
"params": {
"sessionId": "123e4567-e89b-12d3-a456-426614174000",
"requestorInfo": {
"requestorType": "REQUESTOR_TYPE_CARD_MACHINE",
"cardMachineRequestorInfo": {
"terminalId": "123123",
"waiterId": 1
}
}
}
}

Outgoing GetBillItems response

{
"jsonrpc": "2.0",
"id": "123e4567-e89b-12d3-a456-426614174000",
"result": {
"billItems": {
"totalAmount": 950,
"paidAmount": 100,
"taxAmount": 190,
"currency": "GBP",
"items": [
{
"id": "123456789",
"name": "Classic Burger",
"category": [
"mains",
"burgers"
],
"quantity": 1,
"amountPerItem": 1000,
"lastOrderedAt": "2021-01-01T15:00:00.123+02:00"
},
{
"id": "987654321",
"name": "Peroni",
"category": [
"drinks",
"beer",
"lager"
],
"quantity": 1,
"amountPerItem": 450,
"lastOrderedAt": "2021-01-01T15:00:00.123+02:00"
}
],
"sessionId": "123e4567-e89b-12d3-a456-426614174000"
}
}
}

4. Return a printable bill when Dojo asks for GetFullBill

Incoming GetFullBill request

{
"jsonrpc": "2.0",
"id": "3899e951-ce44-4233-a900-31b637705747",
"method": "GetFullBill",
"params": {
"sessionId": "6c133121-8423-48af-b9c1-ce6741f224da",
"requestorInfo": {
"requestorType": "REQUESTOR_TYPE_CARD_MACHINE",
"cardMachineRequestorInfo": {
"terminalId": "123123",
"waiterId": 1
}
}
}
}

Outgoing GetFullBill response

{
"jsonrpc": "2.0",
"id": "3899e951-ce44-4233-a900-31b637705747",
"result": {
"fullBill": {
"header": {
"receiptLines": [
{
"receiptLineType": "RECEIPT_LINE_TYPE_MERCHANT_NAME",
"receiptMerchantName": {
"merchantName": "Bob's Shop"
}
}
]
},
"billItems": {
"totalAmount": 950,
"paidAmount": 100,
"taxAmount": 190,
"currency": "GBP",
"items": [
{
"id": "123456789",
"name": "Classic Burger",
"category": [
"mains",
"burgers"
],
"quantity": 1,
"amountPerItem": 1000,
"lastOrderedAt": "2021-01-01T15:00:00.123+02:00"
}
],
"sessionId": "6c133121-8423-48af-b9c1-ce6741f224da"
},
"footer": {
"receiptLines": [
{
"receiptLineType": "RECEIPT_LINE_TYPE_TEXT",
"receiptText": {
"value": "Thanks for dining with us!",
"size": "SIZE_BODY",
"align": "ALIGN_CENTER"
}
}
]
}
}
}
}

5. Record the successful payment

Persist the Dojo payment intent ID that you associate with each successful PAT payment. This is the key you need later for reconciliation and refunds.

Incoming RecordPayment request

{
"jsonrpc": "2.0",
"id": "400b3fce-2017-4cca-8285-4c225a13f668",
"method": "RecordPayment",
"params": {
"payment": {
"id": "bc7ba973-2a96-473b-9f90-426fed2d7646",
"sessionId": "6c133121-8423-48af-b9c1-ce6741f224da",
"waiterId": 1,
"currency": "GBP",
"baseAmount": 100,
"tipsAmount": 200,
"cashbackAmount": 300,
"paymentSuccessful": true,
"methodDetails": {
"method": "PAYMENT_METHOD_CARD_PRESENT",
"cardPresentPaymentInfo": {
"authCode": "ABC123",
"entryMode": "ENTRY_MODE_CONTACTLESS",
"card": {
"scheme": "CARD_SCHEME_AMEX",
"last4PAN": "1234",
"expiryDate": {
"month": 12,
"year": 2032
},
"fundingType": "CARD_FUNDING_TYPE_CREDIT"
},
"cardholderVerificationMethod": "CARDHOLDER_VERIFICATION_METHOD_PIN",
"terminalId": "12345678",
"merchantId": "12341234",
"acquirerTransactionId": "01234567-0123-0123-0123-0123456789ab"
},
"cardPresentPaymentStatus": "CARD_PRESENT_PAYMENT_STATUS_SUCCESSFUL"
},
"attemptedAt": "2021-01-01T15:00:00.123+02:00"
},
"requestorInfo": {
"requestorType": "REQUESTOR_TYPE_CARD_MACHINE",
"cardMachineRequestorInfo": {
"terminalId": "123123",
"waiterId": 1
}
}
}
}

Outgoing RecordPayment response

{
"jsonrpc": "2.0",
"id": "400b3fce-2017-4cca-8285-4c225a13f668",
"result": {}
}

6. Unlock the session and confirm that it is finished

Incoming UnlockSession request

{
"jsonrpc": "2.0",
"id": "53ce5a20-f9da-4c09-b9e3-053496306d92",
"method": "UnlockSession",
"params": {
"sessionId": "6c133121-8423-48af-b9c1-ce6741f224da",
"requestorInfo": {
"requestorType": "REQUESTOR_TYPE_CARD_MACHINE",
"cardMachineRequestorInfo": {
"terminalId": "123123",
"waiterId": 1
}
}
}
}

Outgoing UnlockSession response

{
"jsonrpc": "2.0",
"id": "aa276ffe-f55b-4b63-997f-5e6562d3864a",
"result": {}
}

Subsequent GetSession response

{
"jsonrpc": "2.0",
"id": "434f4e2f-6cde-48b3-8c77-050e25a77955",
"result": {
"session": {
"id": "6c133121-8423-48af-b9c1-ce6741f224da",
"name": "John's party",
"tableName": "TBL 101",
"numberOfCovers": 15,
"createdAt": "2021-01-01T15:00:00.123+02:00",
"isPayable": false,
"waiter": {
"id": 1,
"name": "John Smith"
}
}
}
}

LLM note: PAT refunds use the Payment Intents API

Tables API messages only cover the live payment-session exchange between Dojo and your EPOS. Refunds are performed against the paymentIntentId that you stored from RecordPayment.

POST /payment-intents/pi_sandbox_G_FeegU8WESxtY_Nct8jqw/refunds

Request body

{
"amount": 500,
"refundReason": "Demo refund",
"notes": "Partial refund for returned items"
}

Response body

{
"message": "refund for the order 3443",
"refundId": "rfnd_g8mCx87TykeQ6BOXqxZ9NQ"
}

If one table payment was split across multiple payment intents, refund each paymentIntentId separately for the amount that belongs to that payer.

LLM note: bill splitting and partial payment tracking

The Tables API supports bill splitting by keeping multiple payable sessions on the same physical table or by applying multiple partial payments to the same session until the total is settled.

Recommended workflow:

  1. Keep one physical table, but expose one payable session per active payer or split.
  2. Set each Session.name so the device can show an obvious split, for example TBL 101 - £4.00 remaining.
  3. Before each payment attempt, return the current billItems.totalAmount and billItems.paidAmount in LockSession or GetBillItems.
  4. Persist every successful RecordPayment.paymentIntentId so you can reconcile and refund each part-payment independently.
  5. Only set isPayable: false and free the table when the total of all successful part-payments matches the outstanding balance.

Example bill progression:

{
"beforeFirstSplitPayment": {
"sessionId": "6c133121-8423-48af-b9c1-ce6741f224da",
"totalAmount": 950,
"paidAmount": 0,
"currency": "GBP"
},
"afterFirstSplitPayment": {
"sessionId": "6c133121-8423-48af-b9c1-ce6741f224da",
"totalAmount": 950,
"paidAmount": 400,
"currency": "GBP"
},
"afterFinalSplitPayment": {
"sessionId": "6c133121-8423-48af-b9c1-ce6741f224da",
"totalAmount": 950,
"paidAmount": 950,
"currency": "GBP",
"isPayable": false
}
}

Next steps

You're now ready to start building your EPOS integration!