How to Integrate
This guide ties together the POS Session, Payment Method, Pricelist, and Order endpoints into a single, practical walkthrough for building a POS integration against API v1, including a full end-to-end payload, how to query the resulting data, and how to handle errors.
Prerequisites
- A valid API key — see the Authentication Guide.
- All requests are made to
https://{account-name}.juleb.com/api/v1. - All examples below send
Authorization: ******— replace it with your real API key.
Choose your integration mode
POST /pos/order supports two modes, controlled by the autoCreateSession field:
| Mode | When to use it | What you send |
|---|---|---|
| Auto | You're integrating an external/RSD-style system that only needs to push completed sales into Juleb and should not have to manage POS sessions, configs, or cashiers itself. | company_id + autoCreateSession: true (+ optional autoSessionConfig) |
| Manual | You're building a client that manages the full ERP POS operation yourself — opening/closing sessions, picking a cashier config, choosing payment methods, etc. | An explicit pos_session_id + payment_method_id you already obtained |
Both modes create the same kind of order and support the same line formats described below — the only difference is who's responsible for resolving the session/config/payment method.
Manual flow: session → order → read back
Use this flow when your integration owns the full POS lifecycle.
1. Create a session
POST /pos/order
is preceded by opening a session for a known POS config:
POST /pos/session
{
"configId": 12
}
This returns the new pos_session_id. See Create a POS Session for
details.
2. (Optional) Look up payment methods and pricelists
GET /pos/payment-method?fields=id,name
GET /pos/pricelist?fields=id,name
Use these to resolve the payment_method_id / pricelist_id you want to use for the order if
your integration doesn't already have them cached. See
Get Many Payment Methods and
Get Many Pricelists.
3. Create the order
POST /pos/order
{
"pos_session_id": 1799,
"payment_method_id": 5,
"pricelist_id": 1,
"partner_id": 42,
"pos_reference": "EXT-ORDER-10293",
"lines": [
{
"product_id": 91,
"qty": 2,
"discount_by_percent": 10
}
]
}
See Post POS Order for the full field reference. The response contains
the new order's id and pos_reference.
4. Read the order back
GET /pos/order/{id}
GET /pos/order/{id}/line
GET /pos/order/{id}/payment
See Get One Order, Get Many Lines, and Get Many Payments.
5. Close the session
Once you're done processing orders for that session (e.g. end of shift):
POST /pos/session/{id}
See Close and POST Session. A session should normally stay open while you're actively sending orders to it — avoid closing and reopening a session per order.
Auto flow: let the API manage the session
Use this flow for RSD-style or other external integrations that just need to push a completed sale, without tracking sessions/configs themselves.
POST /pos/order
{
"company_id": 3,
"autoCreateSession": true,
"autoSessionConfig": {
"name": "rsd-integration",
"allowOutOfStock": true
},
"pos_reference": "EXT-ORDER-10293",
"lines": [
{
"product_id": 91,
"qty": 2,
"discount_by_percent": 10
}
]
}
What happens behind the scenes:
- The API looks for a POS config for
company_idnamedrsd-integration(fromautoSessionConfig), creating one if it doesn't exist yet. - It reuses an already opened session for that config, or opens a new one — you never need
to call
/pos/sessionyourself. payment_method_idandpricelist_idare resolved automatically for that company if you didn't provide them.
Because the session is reused across calls, repeated requests with the same company_id /
autoSessionConfig.name land on the same open session — there's no need (and no endpoint call
required) to close it between orders. Omit autoSessionConfig entirely to just use/create the
company's default config.
autoCreateSession: true without company_id returns a 400 Bad Request.
Building order lines
There are two independent ways to describe lines[], selected with selectProductByGtin:
A. Manual lines (default)
Provide product_id + qty yourself:
{
"product_id": 91,
"qty": 1,
"lot_name": "923190297493",
"discount_by_percent": 0,
"price_unit": 45
}
You can mix in a qr_code array on a manual line purely to record scanned serial numbers
for traceability — it has no effect on which product or quantity is used:
{
"product_id": 91,
"qty": 2,
"qr_code": [
"010628602300100021557558631092319029749317270915",
"010628602300100021557558631092319029749317270916"
]
}
B. GTIN/QR-resolved lines (selectProductByGtin: true)
Set selectProductByGtin: true at the top level of the request, and provide only
qr_code per line — no product_id/qty:
{
"selectProductByGtin": true,
"lines": [
{ "qr_code": ["010628602300100021557558631092319029749317270915"] },
{ "qr_code": ["010628602300100021557558631092319029749317270916"] }
]
}
Each code is parsed for its GTIN and batch/lot number; the matching product is looked
up automatically, and codes sharing the same batch + GTIN are grouped into a single line with
qty equal to the number of codes scanned for that batch.
When selectProductByGtin is true, every line must provide qr_code — a line with only
product_id/qty and no qr_code will fail with no Qr codes provided. If you need both
manually-priced lines and GTIN-scanned lines in the same order, send manual lines (mode A) and
attach qr_code to them for traceability instead of using selectProductByGtin.
Full end-to-end payload example
A combined example using the auto flow, a partner, a pricelist override, a per-line discount, and a scanned serial number for traceability:
- NodeJS
- curl
- python
const axios = require('axios');
const accountName = process.env.ACCOUNT_NAME;
const apiKey = process.env.API_KEY;
axios.post(`https://${accountName}.juleb.com/api/v1/pos/order`, {
company_id: 3,
autoCreateSession: true,
autoSessionConfig: {
name: 'rsd-integration',
allowOutOfStock: true
},
pricelist_id: 1,
partner_id: 42,
pos_reference: 'EXT-ORDER-10293',
lines: [
{
product_id: 91,
qty: 2,
lot_name: '923190297493',
discount_by_percent: 10,
qr_code: ['010628602300100021557558631092319029749317270915']
}
]
}, {
headers: {
'accept': '*/*',
'Authorization': `******`,
'Content-Type': 'application/json'
}
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error('Error:', error);
});
curl -X POST \
-H "accept: */*" \
-H "Authorization: ******" \
-H "Content-Type: application/json" \
-d '{
"company_id": 3,
"autoCreateSession": true,
"autoSessionConfig": {
"name": "rsd-integration",
"allowOutOfStock": true
},
"pricelist_id": 1,
"partner_id": 42,
"pos_reference": "EXT-ORDER-10293",
"lines": [
{
"product_id": 91,
"qty": 2,
"lot_name": "923190297493",
"discount_by_percent": 10,
"qr_code": ["010628602300100021557558631092319029749317270915"]
}
]
}' \
"https://$ACCOUNT_NAME.juleb.com/api/v1/pos/order"
import os
import requests
account_name = os.getenv('ACCOUNT_NAME')
api_key = os.getenv('API_KEY')
url = f'https://{account_name}.juleb.com/api/v1/pos/order'
headers = {
'accept': '*/*',
'Authorization': f'******',
'Content-Type': 'application/json'
}
data = {
'company_id': 3,
'autoCreateSession': True,
'autoSessionConfig': {
'name': 'rsd-integration',
'allowOutOfStock': True
},
'pricelist_id': 1,
'partner_id': 42,
'pos_reference': 'EXT-ORDER-10293',
'lines': [
{
'product_id': 91,
'qty': 2,
'lot_name': '923190297493',
'discount_by_percent': 10,
'qr_code': ['010628602300100021557558631092319029749317270915']
}
]
}
response = requests.post(url, headers=headers, json=data)
if response.status_code == 201:
print(response.json())
else:
print(f'Error: {response.status_code}', response.text)
Response
[
{
"id": 7155,
"pos_reference": "EXT-ORDER-10293"
}
]
Querying the data afterward
Every GET list endpoint (orders, lines, payments, sessions, configs, payment methods,
pricelists) supports the same fields / filter / page / limit query parameters — see
Mastering API Queries for the full syntax.
Common examples for orders created through this guide:
# Orders created today above a given total
GET /pos/order?fields=name,date_order,amount_total,session_id&filter=date_order>2024-01-01&page=1&limit=50
# Lines for a specific order, returns only, excluding sales
GET /pos/order/7155/line?fields=name,product_id,qty,price_subtotal_incl,is_return&filter=is_return=false
# Payments recorded for a specific order
GET /pos/order/7155/payment?fields=name,payment_method_id,amount
Use the pos_reference you sent on creation (or the returned id) as the correlation key
between your system and Juleb when querying orders back.
Error handling
| Status | Message | Cause |
|---|---|---|
| 400 | pos_session_id and payment_method_id are required if autoCreateSession is false | Manual mode was used without supplying both pos_session_id and payment_method_id (and no autoCreateSession/company_id). |
| 400 | company_id is required if autoCreateSession is true | Auto mode was requested without company_id. |
| 400 | Order lines cannot be empty | lines was missing or an empty array. |
| 400 | invalid pos_session_id: {id} | The pos_session_id provided does not exist or isn't linked to a valid POS config. |
| 400 | Batch/Lot {lot_name} not found | lot_name doesn't match an existing batch and the session's config doesn't allow selling out of stock. |
| 400 | no gtin found for product {qrCode} / no batch found for qr code {qrCode} / no product found for GTIN {gtin} | A qr_code couldn't be parsed or resolved to a known product while selectProductByGtin was true. |
| 400 | no Qr codes provided | selectProductByGtin was true but a line had no qr_code. |
| 401 | Unauthorized | Missing or invalid Authorization header. |
| 404 | Record not found | The id (order/line/payment) requested on a GET endpoint doesn't exist, or doesn't belong to the given parent order. |
Related endpoints
- POS Session — open/close sessions used in manual mode.
- POS Config — the config a session belongs to.
- POS Payment Method — available payment methods per company.
- POS Pricelist — pricelists and pricelist items.
- POS Order — orders, lines, and payments.