GraphQL API introduction
Learn how to query the GoTab GraphQL API — from your first request to efficient filtering with fiscalDay.
The GoTab GraphQL API lives at https://gotab.io/api/graph and uses the same Bearer token as the REST API. This guide walks through making your first query, reading catalog and tab data, and filtering efficiently — including the indexing patterns that keep queries fast.
For a comparison of when to use GraphQL versus REST, see the overview page.
What is GraphQL?
Section titled “What is GraphQL?”If you haven’t used GraphQL before: it’s a query language where you describe exactly the data you want, and the server returns precisely that — no more, no less. Instead of hitting multiple REST endpoints and stitching results together, you write one query that fetches nested data in a single request.
Every GoTab GraphQL request is a POST to the same URL. You send a query string in the request body, and the server returns a JSON object with your results under a data key.
GraphQL is best for reading — catalog sync, reporting, order history, and anything that benefits from fetching related data together. REST is still the right tool for actions like creating tabs.
Making your first query
Section titled “Making your first query”curl -X POST https://gotab.io/api/graph \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{ "query": "{ locationsList { locationUuid name } }" }'Response:
{ "data": { "locationsList": [ { "locationUuid": "loc_abc123", "name": "Main Street" }, { "locationUuid": "loc_def456", "name": "Downtown" } ] }}All results are under data. If something goes wrong, an errors array appears alongside data — your data fields will be null for anything that failed.
Using the GraphQL Explorer
Section titled “Using the GraphQL Explorer”The interactive explorer at docs.gotab.io/api-reference/graphql lets you browse the full schema, autocomplete field names, and run queries live against your sandbox.
To authenticate:
- Open the Headers panel in the explorer.
- Add:
Authorization: Bearer YOUR_TOKEN - Run any query — results appear in the right panel.
Use your sandbox credentials here so you’re not hitting production while exploring.
Two query styles: List queries vs single-record lookups
Section titled “Two query styles: List queries vs single-record lookups”You’ll notice the schema has two forms for most resources:
tabsList(...)— returns an array, supports filtering and paginationtab(tabId: BigInt!)ortabByTabUuid(tabUuid: String!)— returns a single record by ID
Use list queries when pulling sets of data for reporting or sync. Use single-record lookups when you already have an ID and just need the details for that one thing.
# Single record lookup{ tabByTabUuid(tabUuid: "nIJHITr9GU1U9zNCakTdk9iA") { tabUuid status total created }}Fetching catalog data
Section titled “Fetching catalog data”Menus, categories, and products are each queryable as flat top-level lists. Filter them by locationId (the numeric ID, not the UUID) using the condition argument:
{ menusList(condition: { locationId: 12345 }) { menuId name startTime endTime }}To get categories for a specific menu:
{ categoriesList(condition: { locationId: 12345 }) { categoryId label xCategoryId }}To get products:
{ productsList(condition: { locationId: 12345 }) { productId name price available description }}Fetching tab and order data
Section titled “Fetching tab and order data”tabsList returns tabs for a location. Filter by locationId using condition:
{ tabsList(condition: { locationId: 12345 }) { tabUuid status total created fiscalDay ordersList { orderId orderUuid status total itemsList { name quantity unitPrice subtotal } } }}Filtering efficiently — use fiscalDay, not timestamp ranges
Section titled “Filtering efficiently — use fiscalDay, not timestamp ranges”GoTab organizes transactional data around fiscal days — date strings in YYYY-MM-DD format that represent a location’s business day (which may not align with midnight UTC). The fiscalDay field is indexed; arbitrary timestamp ranges are not.
The pattern that works:
{ tabsList( condition: { locationId: 12345 } filter: { fiscalDay: { equalTo: "2024-01-15" } } ) { tabUuid status total fiscalDay }}For a date range, use greaterThanOrEqualTo and lessThanOrEqualTo together:
{ tabsList( condition: { locationId: 12345 } filter: { fiscalDay: { greaterThanOrEqualTo: "2024-01-01" lessThanOrEqualTo: "2024-01-31" } } ) { tabUuid status total fiscalDay created }}The same applies to ordersList and ledger queries — filter on fiscalDay first, then narrow further if needed.
What is a fiscal day?
Section titled “What is a fiscal day?”A fiscal day is the date GoTab assigns to a business transaction, based on when the location’s business day started — not UTC midnight. A tab opened at 11:30 PM and closed at 1:00 AM the next calendar day will both belong to the same fiscal day. If you’re building a daily report, always query by fiscalDay rather than trying to calculate the right UTC window.
You can look up which fiscal day a specific timestamp belongs to using:
{ goFiscalDay( _locationId: 12345 _utcTimestamp: "2024-01-15T02:30:00Z" )}This returns the fiscalDay date string for that moment at that location.
Ledger entries for reporting and reconciliation
Section titled “Ledger entries for reporting and reconciliation”For payment-level reporting (tip amounts, payment methods, refunds, order source), use ledgerEntriesList or realTimeLedgerEntriesList rather than querying tabs directly.
ledgerEntriesList— settled data, best for end-of-day reports and accounting exportsrealTimeLedgerEntriesList— reflects live state including open tabs, useful for dashboards
Both support fiscalDay filtering:
{ ledgerEntriesList( condition: { locationId: 12345 } filter: { fiscalDay: { equalTo: "2024-01-15" } } ) { tabUuid total tax tip fiscalDay created }}Identifying order source (server vs customer-placed)
Section titled “Identifying order source (server vs customer-placed)”The pointOfInteraction field on the Payment type indicates whether an order was placed at a server terminal (POS) or by a customer via QR:
{ paymentsList( condition: { locationId: 12345 } filter: { fiscalDay: { equalTo: "2024-01-15" } } ) { paymentId amount tip pointOfInteraction created }}"SERVER"— placed through the POS by a staff member"CONSUMER"— placed by a guest via QR or web
Pagination
Section titled “Pagination”For large result sets, use first to limit the page size and offset for simple offset pagination:
{ tabsList( condition: { locationId: 12345 } filter: { fiscalDay: { equalTo: "2024-01-15" } } first: 50 offset: 0 ) { tabUuid status total }}Increment offset by first on each subsequent request to page through results. For cursor-based pagination (more efficient for large datasets), see Pagination.
Querying employees
Section titled “Querying employees”The field for GoTab staff members is employeesList, not usersList:
{ employeesList(condition: { locationId: 12345 }) { employeeUuid: userRoleUuid name roleName }}The condition vs filter argument — what’s the difference?
Section titled “The condition vs filter argument — what’s the difference?”Both arguments narrow results, but they work differently:
-
conditionmatches rows where a field equals an exact value. It maps directly to indexed columns and is always fast. Use it to scope to a location:condition: { locationId: 12345 }. -
filtersupports range operators (greaterThanOrEqualTo,lessThanOrEqualTo,equalTo,in, etc.) and can combine multiple conditions. Use it for date ranges and non-equality checks.
The most efficient queries combine both — condition to hit the index, filter to narrow within it:
tabsList( condition: { locationId: 12345 } # index hit filter: { fiscalDay: { equalTo: "2024-01-15" } } # narrow within)Avoid using filter alone for location scoping — always put locationId in condition.
Common errors
Section titled “Common errors”| Code | Cause | Action |
|---|---|---|
401 | Token expired | Refresh with your refresh_token and retry |
403 | Token valid but no access to that location | Check allowed_location_ids on the credential, or re-run the OAuth authorization flow for that location |
| Timeout / empty result | Query missing fiscalDay filter on a large table | Add filter: { fiscalDay: { equalTo: "..." } } to scope the query |
| Schema error on field name | Field doesn’t exist (e.g. usersList) | Check the GraphQL Explorer for the correct field name |
For general error handling patterns, see Error Handling.