Point of Sale
Point of Sale
Tables and open tabs for on-location selling. A tab is a running order, not a document: it carries no number, is never fiscalized and holds no payments. It becomes an invoice through the normal POST /invoices flow.
The lifecycle of a tab is:
1. POST /pos/tabs opens one, optionally on a table.
2. POST /pos/tabs/{id}/items adds to it. Appends to an open tab are additive: concurrent additions serialize and merge quantities on matching lines. New appends return 409 already_claimed while a payment claim is active. Retrying a previously accepted append_id returns the current tab without adding the items again.
3. POST /pos/tabs/{id}/claim reserves it for one device before payment, so a second device cannot issue the same tab and produce a duplicate invoice and duplicate fiscal record. Claims expire after five minutes.
4. Issue the invoice however you normally would, then POST /pos/tabs/{id}/close with the invoice_id. Close is idempotent, which is what makes a lost response after a successful issue recoverable.
Edits and removals use PATCH /pos/tabs/{id} with expected_version and get a 409 carrying the current tab if another device changed it first.
To keep devices in a venue in sync, poll GET /pos/tabs and send the previous ETag back as If-None-Match; an unchanged venue answers 304.
The Point of Sale object
Attributes
{
"id": "ptab_65f1a2b3c4d5e6f7a8b9c0d5",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"table_id": "ptbl_65f1a2b3c4d5e6f7a8b9c0d1",
"status": "open",
"items": [
{
"line_id": "line_7f3a",
"item_id": "item_65f1a2b3c4d5e6f7a8b9c0d3",
"name": "Glass of red wine",
"quantity": 2,
"unit_gross_price": 5.5,
"discount_percent": null,
"tax_rates": null,
"note": null,
"added_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"added_at": "2026-07-31T20:12:00.000Z"
}
],
"note": null,
"customer_id": null,
"opened_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"updated_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"claimed_by_device": null,
"claimed_at": null,
"invoice_id": null,
"version": 3,
"opened_at": "2026-07-31T20:05:00.000Z",
"issued_at": null,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T20:05:00.000Z",
"updated_at": "2026-07-31T20:12:00.000Z"
}Create a point-of-sale table
/pos/tablesCreate a point-of-sale table
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Body parameters
Table name as staff refer to it — "1", "12", "Terrace 3".
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.createTable({
label: "4",
sort_order: 4
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"id": "ptbl_65f1a2b3c4d5e6f7a8b9c0d1",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"label": "4",
"sort_order": 4,
"is_active": true,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T18:00:00.000Z",
"updated_at": "2026-07-31T18:00:00.000Z"
}Open a tab
/pos/tabsOpen a tab
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Body parameters
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.createTab({
table_id: "ptbl_65f1a2b3c4d5e6f7a8b9c0d1"
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"id": "ptab_65f1a2b3c4d5e6f7a8b9c0d5",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"table_id": "ptbl_65f1a2b3c4d5e6f7a8b9c0d1",
"status": "open",
"items": [],
"note": null,
"customer_id": null,
"opened_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"updated_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"claimed_by_device": null,
"claimed_at": null,
"invoice_id": null,
"version": 1,
"opened_at": "2026-07-31T20:05:00.000Z",
"issued_at": null,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T20:05:00.000Z",
"updated_at": "2026-07-31T20:12:00.000Z"
}Enrol a staff member on a shared till
/pos/operators/enrolA staff member signs in on a shared device with their own email and password and receives a token bound to this business, which the device keeps so that the sales they ring up carry their own fiscal operator identity. Credentials are verified exactly as at POST /users/login; the person must already be a member of the entity. Requires the device's own signed-in user session for the same entity; neither an operator token nor an API key can enrol anyone, and the route is rate-limited like sign-in. The token lives thirty days and only works on this business's point-of-sale surface. Choosing who is at the register afterwards is the device's job — this replaces server-side PINs.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Body parameters
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.enrolPosOperator({
email: "luka@example.com",
password: "correct horse battery staple"
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
Bearer token for the switched-to operator.
{
"id": "tok_65f1a2b3c4d5e6f7a8b9c0db",
"user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d8",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"ttl": 2592000,
"scope": "pos_operator",
"created_at": "2026-09-08T09:15:00.000Z"
}Switch the acting operator
/pos/operators/switchVerifies the PIN and returns a token for that member, so the sales they ring up carry their own fiscal operator identity. The calling session must administer this business, and the member switched to must not: the token acts as them and carries their permissions, so a three-digit PIN is not a way for one member to become another. Removing the member, or changing their role on this business or its account, ends the token immediately. Repeated failures lock the PIN with an escalating backoff.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Body parameters
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.switchOperator({
user_id: "usr_65f1a2b3c4d5e6f7a8b9c0d8",
pin: "482"
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
Bearer token for the switched-to operator.
{
"id": "tok_65f1a2b3c4d5e6f7a8b9c0da",
"user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d8",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"ttl": 31536000,
"scope": "pos_operator",
"created_at": "2026-07-31T20:25:00.000Z"
}List point-of-sale tables
/pos/tablesList point-of-sale tables
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Query parameters
Include deactivated tables.
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.listTables({
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"data": [
{
"id": "ptbl_65f1a2b3c4d5e6f7a8b9c0d1",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"label": "1",
"sort_order": 1,
"is_active": true,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T18:00:00.000Z",
"updated_at": "2026-07-31T18:00:00.000Z"
},
{
"id": "ptbl_65f1a2b3c4d5e6f7a8b9c0e1",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"label": "2",
"sort_order": 2,
"is_active": true,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T18:00:00.000Z",
"updated_at": "2026-07-31T18:00:00.000Z"
},
{
"id": "ptbl_65f1a2b3c4d5e6f7a8b9c0e2",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"label": "Terrace 1",
"sort_order": 3,
"is_active": true,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T18:00:00.000Z",
"updated_at": "2026-07-31T18:00:00.000Z"
}
]
}List point-of-sale tabs
/pos/tabsReturns the full set of tabs in the requested statuses. Send the previous ETag back as If-None-Match to get a 304 when nothing changed — this is the intended polling path for keeping devices in a venue in sync.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
ETag from a previous response. Returns 304 when the tab set is unchanged.
Query parameters
Comma-separated statuses to include. Defaults to open and issuing.
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.listTabs({
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"data": [
{
"id": "ptab_65f1a2b3c4d5e6f7a8b9c0d5",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"table_id": "ptbl_65f1a2b3c4d5e6f7a8b9c0d1",
"status": "open",
"items": [
{
"line_id": "line_7f3a",
"item_id": "item_65f1a2b3c4d5e6f7a8b9c0d3",
"name": "Glass of red wine",
"quantity": 2,
"unit_gross_price": 5.5,
"discount_percent": null,
"tax_rates": null,
"note": null,
"added_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"added_at": "2026-07-31T20:12:00.000Z"
}
],
"note": null,
"customer_id": null,
"opened_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"updated_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"claimed_by_device": null,
"claimed_at": null,
"invoice_id": null,
"version": 3,
"opened_at": "2026-07-31T20:05:00.000Z",
"issued_at": null,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T20:05:00.000Z",
"updated_at": "2026-07-31T20:12:00.000Z"
}
]
}Export daily takings
/pos/reports/daily/exportThe daily takings report as a flat spreadsheet (Excel or CSV): one row per breakdown line, with section, label, detail and amount columns, mirroring the layout the on-device export already produces. Sourced from the same report as GET /pos/reports/daily; no separate aggregation.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Query parameters
The day to report on, by document date.
Export format
Possible values: "xlsx", "csv"
Default: "xlsx"
Language for the exported column headers. Defaults to the entity locale when omitted.
Possible values: "en", "de", "sl", "it", "fr", "es", "pt", "nl", "pl", "hr", "sv", "fi", "et", "bg", "cs", "sk", "nb", "is", "hu", "null"
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const blob = await sdk.pointOfSale.exportPosDailyReport({
date: "2026-08-15"
}, {
entity_id: "YOUR_ENTITY_ID"
});
const arrayBuffer = await blob.arrayBuffer();
const { writeFile } = await import('node:fs/promises');
await writeFile('download.bin', Buffer.from(arrayBuffer));
console.log('Saved download.bin');Export monthly takings
/pos/reports/monthly/exportThe monthly takings report as a flat spreadsheet (Excel or CSV), with the same section/label/detail/amount layout as the daily export plus a day section. Sourced from the same report as GET /pos/reports/monthly; no separate aggregation.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Query parameters
The month to report on, by document date.
Export format
Possible values: "xlsx", "csv"
Default: "xlsx"
Language for the exported column headers. Defaults to the entity locale when omitted.
Possible values: "en", "de", "sl", "it", "fr", "es", "pt", "nl", "pl", "hr", "sv", "fi", "et", "bg", "cs", "sk", "nb", "is", "hu", "null"
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const blob = await sdk.pointOfSale.exportPosMonthlyReport({
month: "2026-08"
}, {
entity_id: "YOUR_ENTITY_ID"
});
const arrayBuffer = await blob.arrayBuffer();
const { writeFile } = await import('node:fs/promises');
await writeFile('download.bin', Buffer.from(arrayBuffer));
console.log('Saved download.bin');Daily takings
/pos/reports/dailyWhat the venue took on one day, split by payment type, tax rate, item category and operator.
This is a business report, not a fiscal one: Slovenia and Croatia confirm every cash invoice with the authority as it is issued, so there is no daily close to file and nothing here is submitted anywhere. Voided documents are counted separately and excluded from the totals. Operator attribution comes from the fiscal record, which is where the acting operator is stored.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Query parameters
The day to report on, by document date.
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.dailyReport({
date: "2026-08-15"
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"date": "2026-08-15",
"currency_code": "EUR",
"totals": {
"invoices": 42,
"voided": 1,
"net": 1011.89,
"tax": 222.61,
"gross": 1234.5
},
"by_payment_type": [
{
"type": "cash",
"invoices": 30,
"total": 800
},
{
"type": "card",
"invoices": 12,
"total": 434.5
}
],
"by_tax_rate": [
{
"rate": 22,
"base": 1011.89,
"amount": 222.61
}
],
"by_category": [
{
"category": "wine",
"quantity": 61,
"total": 700
},
{
"category": "food",
"quantity": 24,
"total": 534.5
}
],
"by_operator": [
{
"operator": "Ana",
"invoices": 42,
"total": 1234.5
}
]
}Monthly takings
/pos/reports/monthlyWhat the venue took over one calendar month, split by payment type, tax rate, item category, operator and day.
This is a business report, not a fiscal one: Slovenia and Croatia confirm every cash invoice with the authority as it is issued, so there is no monthly close to file and nothing here is submitted anywhere. Voided documents are counted separately and excluded from the totals. by_day gives the same split a daily report would give for each day that had at least one sale, so the month can be read as a whole or day by day.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Query parameters
The month to report on, by document date.
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.getPosMonthlyReport({
month: "2026-08"
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"month": "2026-08",
"date_from": "2026-08-01",
"date_to": "2026-08-31",
"currency_code": "EUR",
"totals": {
"invoices": 640,
"voided": 12,
"net": 15234.1,
"tax": 3351.5,
"gross": 18585.6
},
"by_payment_type": [
{
"type": "cash",
"invoices": 420,
"total": 11800
},
{
"type": "card",
"invoices": 220,
"total": 6785.6
}
],
"by_tax_rate": [
{
"rate": 22,
"base": 15234.1,
"amount": 3351.5
}
],
"by_category": [
{
"category": "wine",
"quantity": 910,
"total": 10500
},
{
"category": "food",
"quantity": 640,
"total": 8085.6
}
],
"by_operator": [
{
"operator": "Ana",
"invoices": 640,
"total": 18585.6
}
],
"by_day": [
{
"date": "2026-08-01",
"invoices": 20,
"net": 480,
"tax": 105.6,
"gross": 585.6
},
{
"date": "2026-08-15",
"invoices": 42,
"net": 1011.89,
"tax": 222.61,
"gross": 1234.5
}
]
}List staff who can operate this point of sale
/pos/operatorsList staff who can operate this point of sale
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.listOperators({
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"data": [
{
"user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"name": "Ana",
"email": "ana@winebar.example",
"role": "admin",
"has_pin": true
},
{
"user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d8",
"name": "Luka",
"email": "luka@winebar.example",
"role": "editor",
"has_pin": false
}
]
}Get a tab
/pos/tabs/{id}Get a tab
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Path parameters
Unique resource identifier. Prefix depends on the resource, for example inv_, cus_, or ent_.
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.getTab('POINTOFSALE_ID', {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"id": "ptab_65f1a2b3c4d5e6f7a8b9c0d5",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"table_id": "ptbl_65f1a2b3c4d5e6f7a8b9c0d1",
"status": "open",
"items": [
{
"line_id": "line_7f3a",
"item_id": "item_65f1a2b3c4d5e6f7a8b9c0d3",
"name": "Glass of red wine",
"quantity": 2,
"unit_gross_price": 5.5,
"discount_percent": null,
"tax_rates": null,
"note": null,
"added_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"added_at": "2026-07-31T20:12:00.000Z"
}
],
"note": null,
"customer_id": null,
"opened_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"updated_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"claimed_by_device": null,
"claimed_at": null,
"invoice_id": null,
"version": 3,
"opened_at": "2026-07-31T20:05:00.000Z",
"issued_at": null,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T20:05:00.000Z",
"updated_at": "2026-07-31T20:12:00.000Z"
}Update a point-of-sale table
/pos/tables/{id}Update a point-of-sale table
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Path parameters
Unique resource identifier. Prefix depends on the resource, for example inv_, cus_, or ent_.
Body parameters
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.updateTable('POINTOFSALE_ID', {
label: "Terrace 1",
sort_order: 7
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"id": "ptbl_65f1a2b3c4d5e6f7a8b9c0d1",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"label": "Terrace 1",
"sort_order": 7,
"is_active": true,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T18:00:00.000Z",
"updated_at": "2026-07-31T18:00:00.000Z"
}Replace a tab's contents
/pos/tabs/{id}Replacing write for edits and removals, guarded by expected_version. A 409 means another device changed the tab first and carries the current tab.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Path parameters
Unique resource identifier. Prefix depends on the resource, for example inv_, cus_, or ent_.
Body parameters
Version the client last saw. The request is rejected with 409 if the tab changed since, and the response carries the current tab.
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.updateTab('POINTOFSALE_ID', {
items: [
{
line_id: "line_7f3a",
item_id: "item_65f1a2b3c4d5e6f7a8b9c0d3",
name: "Glass of red wine",
quantity: 2,
unit_gross_price: 5.5,
discount_percent: null,
tax_rates: null,
note: null,
added_by_user_id: "usr_65f1a2b3c4d5e6f7a8b9c0d4",
added_at: "2026-07-31T20:12:00.000Z"
}
],
expected_version: 3
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"id": "ptab_65f1a2b3c4d5e6f7a8b9c0d5",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"table_id": "ptbl_65f1a2b3c4d5e6f7a8b9c0d1",
"status": "open",
"items": [
{
"line_id": "line_7f3a",
"item_id": "item_65f1a2b3c4d5e6f7a8b9c0d3",
"name": "Glass of red wine",
"quantity": 2,
"unit_gross_price": 5.5,
"discount_percent": null,
"tax_rates": null,
"note": null,
"added_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"added_at": "2026-07-31T20:12:00.000Z"
}
],
"note": null,
"customer_id": null,
"opened_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"updated_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"claimed_by_device": null,
"claimed_at": null,
"invoice_id": null,
"version": 4,
"opened_at": "2026-07-31T20:05:00.000Z",
"issued_at": null,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T20:05:00.000Z",
"updated_at": "2026-07-31T20:12:00.000Z"
}Set an operator PIN
/pos/operators/{user_id}/pinSets the PIN used to switch to this member on a shared device. Callers may set their own PIN; entity admins may set anyone's. The PIN identifies who is acting, it does not grant access: the device must already hold a valid session for the same entity. Exactly three digits, so a till can act on the last one without a confirm step.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Path parameters
Body parameters
Exactly three digits.
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.setOperatorPin('USER_ID', {
pin: "482"
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Delete a point-of-sale table
/pos/tables/{id}Delete a point-of-sale table
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Path parameters
Unique resource identifier. Prefix depends on the resource, for example inv_, cus_, or ent_.
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
await sdk.pointOfSale.deleteTable('POINTOFSALE_ID', {
entity_id: "YOUR_ENTITY_ID"
});
console.log('Deleted successfully');Cancel a tab
/pos/tabs/{id}Send expected_version when cancelling because the tab looked empty: a poll-based view can be seconds stale, and without the guard a round another device just added would be discarded. Omit it to cancel regardless, which is what an operator picking "Cancel tab" means.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Path parameters
Unique resource identifier. Prefix depends on the resource, for example inv_, cus_, or ent_.
Query parameters
Refuse with 409 unless the tab is still at this version.
Remove an operator PIN
/pos/operators/{user_id}/pinRemove an operator PIN
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Path parameters
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
await sdk.pointOfSale.clearOperatorPin('USER_ID', {
entity_id: "YOUR_ENTITY_ID"
});
console.log('Deleted successfully');Add items to a tab
/pos/tabs/{id}/itemsAppends to an open tab are additive: concurrent additions serialize and merge quantities on matching lines. New appends return 409 already_claimed while a payment claim is active. Retrying a previously accepted append_id returns the current tab without adding the items again.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Path parameters
Unique resource identifier. Prefix depends on the resource, for example inv_, cus_, or ent_.
Body parameters
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.appendTabItems('POINTOFSALE_ID', {
items: [
{
line_id: "line_7f3a",
item_id: "item_65f1a2b3c4d5e6f7a8b9c0d3",
name: "Glass of red wine",
quantity: 2,
unit_gross_price: 5.5,
discount_percent: null,
tax_rates: null,
note: null,
added_by_user_id: "usr_65f1a2b3c4d5e6f7a8b9c0d4",
added_at: "2026-07-31T20:12:00.000Z"
}
]
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"id": "ptab_65f1a2b3c4d5e6f7a8b9c0d5",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"table_id": "ptbl_65f1a2b3c4d5e6f7a8b9c0d1",
"status": "open",
"items": [
{
"line_id": "line_7f3a",
"item_id": "item_65f1a2b3c4d5e6f7a8b9c0d3",
"name": "Glass of red wine",
"quantity": 4,
"unit_gross_price": 5.5,
"discount_percent": null,
"tax_rates": null,
"note": null,
"added_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"added_at": "2026-07-31T20:12:00.000Z"
}
],
"note": null,
"customer_id": null,
"opened_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"updated_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"claimed_by_device": null,
"claimed_at": null,
"invoice_id": null,
"version": 4,
"opened_at": "2026-07-31T20:05:00.000Z",
"issued_at": null,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T20:05:00.000Z",
"updated_at": "2026-07-31T20:12:00.000Z"
}Reserve a tab for payment
/pos/tabs/{id}/claimMarks the tab as being paid on one device so a second device cannot issue it at the same time, which would produce a duplicate invoice and a duplicate fiscal record. Claims expire after five minutes so a crashed device cannot lock a table.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Path parameters
Unique resource identifier. Prefix depends on the resource, for example inv_, cus_, or ent_.
Body parameters
Stable per-device identifier, so the claiming device can recognise its own claim after a restart.
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.claimTab('POINTOFSALE_ID', {
device_id: "9C2E5F10-7A44-4E0B-9D2C-1B8E3A6F0C41"
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"id": "ptab_65f1a2b3c4d5e6f7a8b9c0d5",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"table_id": "ptbl_65f1a2b3c4d5e6f7a8b9c0d1",
"status": "issuing",
"items": [
{
"line_id": "line_7f3a",
"item_id": "item_65f1a2b3c4d5e6f7a8b9c0d3",
"name": "Glass of red wine",
"quantity": 2,
"unit_gross_price": 5.5,
"discount_percent": null,
"tax_rates": null,
"note": null,
"added_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"added_at": "2026-07-31T20:12:00.000Z"
}
],
"note": null,
"customer_id": null,
"opened_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"updated_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"claimed_by_device": "9C2E5F10-7A44-4E0B-9D2C-1B8E3A6F0C41",
"claimed_at": "2026-07-31T20:20:00.000Z",
"invoice_id": null,
"version": 4,
"opened_at": "2026-07-31T20:05:00.000Z",
"issued_at": null,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T20:05:00.000Z",
"updated_at": "2026-07-31T20:12:00.000Z"
}Release a payment claim
/pos/tabs/{id}/releaseHands a claimed tab back. Only the device holding the claim may release it — otherwise the claim would be decorative, since any device could free a tab another is paying and take it over. An expired claim may be cleared by anyone.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Path parameters
Unique resource identifier. Prefix depends on the resource, for example inv_, cus_, or ent_.
Body parameters
The device releasing the claim. Must match the device holding it unless the claim has expired.
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.releaseTab('POINTOFSALE_ID', {
device_id: "9C2E5F10-7A44-4E0B-9D2C-1B8E3A6F0C41"
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"id": "ptab_65f1a2b3c4d5e6f7a8b9c0d5",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"table_id": "ptbl_65f1a2b3c4d5e6f7a8b9c0d1",
"status": "open",
"items": [
{
"line_id": "line_7f3a",
"item_id": "item_65f1a2b3c4d5e6f7a8b9c0d3",
"name": "Glass of red wine",
"quantity": 2,
"unit_gross_price": 5.5,
"discount_percent": null,
"tax_rates": null,
"note": null,
"added_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"added_at": "2026-07-31T20:12:00.000Z"
}
],
"note": null,
"customer_id": null,
"opened_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"updated_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"claimed_by_device": null,
"claimed_at": null,
"invoice_id": null,
"version": 5,
"opened_at": "2026-07-31T20:05:00.000Z",
"issued_at": null,
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T20:05:00.000Z",
"updated_at": "2026-07-31T20:12:00.000Z"
}Close a tab against an issued invoice
/pos/tabs/{id}/closeIdempotent: re-closing a tab with the same invoice returns it unchanged. This is what makes a lost response after a successful issue recoverable rather than stranding the table.
Header parameters
Entity ID on which the request is made. Auto-selected when only one entity exists, required when multiple entities exist.
Path parameters
Unique resource identifier. Prefix depends on the resource, for example inv_, cus_, or ent_.
Body parameters
Invoice the tab was issued as.
import SpaceInvoices from '@spaceinvoices/js-sdk/sdk';
const sdk = new SpaceInvoices('YOUR_API_KEY');
const response = await sdk.pointOfSale.closeTab('POINTOFSALE_ID', {
invoice_id: "inv_65f1a2b3c4d5e6f7a8b9c0d9"
}, {
entity_id: "YOUR_ENTITY_ID"
});
console.log(response);Returns
{
"id": "ptab_65f1a2b3c4d5e6f7a8b9c0d5",
"entity_id": "ent_65f1a2b3c4d5e6f7a8b9c0d2",
"table_id": "ptbl_65f1a2b3c4d5e6f7a8b9c0d1",
"status": "issued",
"items": [
{
"line_id": "line_7f3a",
"item_id": "item_65f1a2b3c4d5e6f7a8b9c0d3",
"name": "Glass of red wine",
"quantity": 2,
"unit_gross_price": 5.5,
"discount_percent": null,
"tax_rates": null,
"note": null,
"added_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"added_at": "2026-07-31T20:12:00.000Z"
}
],
"note": null,
"customer_id": null,
"opened_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"updated_by_user_id": "usr_65f1a2b3c4d5e6f7a8b9c0d4",
"claimed_by_device": null,
"claimed_at": null,
"invoice_id": "inv_65f1a2b3c4d5e6f7a8b9c0d9",
"version": 5,
"opened_at": "2026-07-31T20:05:00.000Z",
"issued_at": "2026-07-31T20:22:00.000Z",
"metadata": {},
"deleted_at": null,
"created_at": "2026-07-31T20:05:00.000Z",
"updated_at": "2026-07-31T20:12:00.000Z"
}