How to Call the SAP Business One Service Layer API
A hands-on walkthrough with cURL: log in and hold a session, look up an Item, and create an A/R Invoice — the core moves behind almost every SAP B1 integration.
SAP Business One's Service Layer is the REST/OData API that sits in front of the SAP B1 database, and it's the standard way modern integrations — e-commerce platforms, POS systems, WMS, marketplaces — read and write B1 data without touching the DI API directly. Unlike a stateless REST API, Service Layer is session-based: you log in once, hold a cookie, and reuse it across calls. That one detail trips up more integrations than anything else, so this guide starts there before working through two of the most common calls: fetching an Item and creating an A/R Invoice.
1. Endpoint and prerequisites
Service Layer runs on the SAP B1 server itself, typically on port 50000:
https://{your-sap-server}:50000/b1s/v2/
/b1s/v2/ is the current OData v4 interface; the older /b1s/v1/ (OData v3) is still reachable on many installs but is deprecated — use v2 for anything new. You'll also need:
- A Company DB name (the SAP B1 database you're connecting to).
- A B1 user with the module authorizations for whatever you're calling (Sales – A/R for invoices, Inventory for items) and, importantly, a free named/professional license seat — each open Service Layer session holds one.
- A way to handle the server's TLS certificate. Most on-prem B1 servers ship with a self-signed certificate; the examples below use curl's
-kflag to skip verification for local testing only — install a proper certificate (or pin the self-signed one) before this runs in production.
2. Log in and hold a session
Every Service Layer call after this one rides on the session this creates. Log in once with POST /Login, and curl's cookie jar takes care of the rest:
curl -k -c cookies.txt -X POST "https://your-sap-server:50000/b1s/v2/Login" \
-H "Content-Type: application/json" \
-d '{
"CompanyDB": "SBO_PRODDB",
"UserName": "api_integration",
"Password": "your-password"
}'
A successful login returns a B1SESSION cookie (and, on a load-balanced Service Layer cluster, a ROUTEID cookie that pins you to the same node) plus a SessionTimeout in the body, usually 30 minutes of inactivity by default. The -c cookies.txt flag writes both cookies to a file; every request after this one uses -b cookies.txt to send them back:
3. Example: get an Item
Items are keyed by ItemCode. To fetch one directly, pass the code in parentheses:
curl -k -b cookies.txt -X GET \
"https://your-sap-server:50000/b1s/v2/Items('A00001')" \
-H "Content-Type: application/json"
The response includes fields like ItemName, ItemsGroupCode, QuantityOnStock, and nested collections such as ItemPrices and ItemWarehouseInfoCollection for per-warehouse stock.
To search instead — useful when a connected system only knows part of the item name or a filter, not the exact code — use OData query options against the Items collection. curl's -G with --data-urlencode keeps the query string readable and correctly escaped:
curl -k -b cookies.txt -G \
"https://your-sap-server:50000/b1s/v2/Items" \
--data-urlencode '$select=ItemCode,ItemName,QuantityOnStock' \
--data-urlencode '$filter=ItemsGroupCode eq 102 and Valid eq 'tYES'' \
--data-urlencode '$top=20'
$select limits the payload to the fields you actually need (cheaper and faster than pulling the full Item object), $filter narrows the result set with OData syntax, and $top caps how many rows come back per call — combine it with $skip to page through large catalogs.
4. Example: create an A/R Invoice
An A/R Invoice is created against the Invoices collection. At minimum you need the customer's CardCode and a DocumentLines array with the items being billed:
curl -k -b cookies.txt -X POST \
"https://your-sap-server:50000/b1s/v2/Invoices" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"CardCode": "C20000",
"DocDate": "2026-09-09",
"DocDueDate": "2026-09-09",
"Comments": "Created via AllSync integration",
"DocumentLines": [
{
"ItemCode": "A00001",
"Quantity": 10,
"UnitPrice": 25.00,
"WarehouseCode": "01"
},
{
"ItemCode": "A00002",
"Quantity": 3,
"UnitPrice": 12.50,
"WarehouseCode": "01"
}
]
}
EOF
On success, SAP returns the full created document as JSON — including the DocEntry (the internal key) and DocNum (the number your users see) you'll want to store against the source order in whatever system triggered this call. If you'd rather not receive the full document body back (common when you're firing many of these and only need the key), add a Prefer: return-no-content header: the response becomes a bare 204, and the new document's URL — DocEntry included — comes back in the Location response header instead.
CardCode and ItemCode. Only send UnitPrice explicitly when you intend to override B1's own pricing — otherwise leave it out and let the document price itself.
5. Reading errors
Service Layer returns errors as JSON with an HTTP status in the 4xx/5xx range, in a consistent shape:
{
"error": {
"code": -5002,
"message": {
"lang": "en-us",
"value": "Session expired or invalid"
}
}
}
Two error codes are worth handling explicitly in any long-running integration: a session-expired error (log in again and retry the call once), and validation errors from the business layer itself — an invalid ItemCode, insufficient stock on a warehouse that enforces it, a blocked customer, a closed posting period. Those come back with a descriptive message.value and generally mean the request itself needs correcting, not retrying as-is.
6. Log out when you're done
Because each session holds a license seat, always close it explicitly when a batch of work is finished rather than letting it expire on its own:
curl -k -b cookies.txt -X POST \
"https://your-sap-server:50000/b1s/v2/Logout"
7. A few production notes
A script that runs these calls once behaves very differently from a service that runs them continuously against a live SAP B1 instance. A few things are worth building in from day one: hold and reuse one session per worker instead of logging in per request, and re-authenticate proactively a little before the session timeout rather than waiting for a failure; watch your open session count against your license pool, since a leaked session (one never logged out) sits there consuming a seat until it times out; batch related writes with Service Layer's $batch endpoint when you're creating several related documents in one business transaction, so they commit together; and treat DocumentLines validation errors as data problems to surface back to whatever system originated the request, not as something to silently retry.
Connecting SAP Business One to your other systems?
AllSync keeps SAP Business One in sync with Shopify, POS platforms, WMS, and marketplaces like Lazada, Shopee, and TikTok Shop for retail, F&B, hospitality, and e-commerce businesses across Southeast Asia and APAC — orders, items, and invoices flowing automatically, with no custom Service Layer code to maintain on your end.
Talk to the AllSync team