SAP Business One Service Layer: login, $filter, $select and paging, with working examples

By Zain Abbas, Kodenova · Updated

Short answerThe Service Layer is the REST (OData) API for SAP Business One. You POST your company database, user name and password to /b1s/v1/Login or /b1s/v2/Login, then send the B1SESSION and ROUTEID cookies on every call. You read data with GET and OData options such as $select, $filter and $orderby, follow the nextLink for more pages, create with POST, update with PATCH and end the session with Logout.

What the Service Layer is

The Service Layer is SAP Business One’s web API. It exposes business objects such as business partners, items, orders and invoices over HTTPS, using the OData protocol. Any language that can send an HTTP request can use it: JavaScript, Python, C#, PHP, or a low-code tool.

It started on the HANA version of SAP Business One. Recent 10.0 feature packs also offer it for SQL Server. By default it listens on port 50000 over HTTPS:

https://your-b1-server:50000/b1s/v1/
https://your-b1-server:50000/b1s/v2/

v1 or v2

Both paths reach the same business objects.

  • /b1s/v1 uses OData version 3. It still works and most older code uses it.
  • /b1s/v2 uses OData version 4, which SAP now treats as the main protocol.

The difference you notice first is in responses. v1 returns odata.nextLink, v2 returns @odata.nextLink. For new projects, pick v2 and stick with it. The examples below use v1 paths because they are still the most common in the field. Swap v1 for v2 and they work the same way.

Step 1: Log in

Send a POST with your company database, user and password:

POST https://your-b1-server:50000/b1s/v1/Login
Content-Type: application/json
{
  "CompanyDB": "SBODEMOUS",
  "UserName": "manager",
  "Password": "your-password"
}

A successful response contains the session ID, the version and the timeout in minutes:

{
  "SessionId": "1c9e4b2a-....",
  "Version": "1000xxx",
  "SessionTimeout": 30
}

The server also sets two cookies: B1SESSION and ROUTEID.

  • B1SESSION identifies your session.
  • ROUTEID tells the Service Layer load balancer which node holds your session, so each request goes back to the same node.

A browser stores and sends these cookies for you. Outside a browser, you must add both to the Cookie header of every later call:

Cookie: B1SESSION=1c9e4b2a-....; ROUTEID=.node1

The session expires after 30 minutes of no activity by default. Every call resets the timer. Build your client so that a 401 response triggers one fresh login and a retry, not an endless loop.

Step 2: Read data with GET

A plain GET returns a page of records with all fields:

GET /b1s/v1/BusinessPartners

That is slow and heavy. Use the OData query options to ask only for what you need.

OptionWhat it doesExample
$selectReturn only these fields$select=CardCode,CardName,Balance
$filterReturn only matching records$filter=CardType eq 'cCustomer'
$orderbySort the results$orderby=DocDate desc
$topReturn at most n records$top=50
$skipSkip the first n records$skip=100

Put together:

GET /b1s/v1/Orders?$select=DocEntry,DocNum,CardCode,DocDate,DocTotal&$filter=DocDate ge '2026-10-01' and DocumentStatus eq 'bost_Open'&$orderby=DocDate desc&$top=50

A few things that trip people up:

  • Field names are the Service Layer names, not the SQL column names. It is DocumentStatus, not DocStatus. Enum values are names like bost_Open and cCustomer, not O and C.
  • Strings and dates go in single quotes. Numbers do not.
  • Filter operators are words: eq, ne, gt, ge, lt, le, joined with and and or.
  • Text search uses functions, for example startswith(CardName, 'Al').
  • URL-encode spaces and quotes when your HTTP library does not do it for you.

To read one record, put its key in brackets: GET /b1s/v1/Orders(125) for the order with DocEntry 125, or GET /b1s/v1/BusinessPartners('C20000') for a business partner.

Our Service Layer request builder puts these URLs together for you, with the right quoting.

Step 3: Handle paging

The Service Layer never returns everything at once. The default page size is 20. When there are more records, the response ends with a link to the next page:

{
  "value": [ ... 20 records ... ],
  "odata.nextLink": "Orders?$select=DocEntry,DocNum&$skip=20"
}

You have two ways to deal with it.

  1. Follow the nextLink. Call the URL it gives (it is relative to /b1s/v1/) until no nextLink comes back. This is the safest way to read a full list.
  2. Ask for bigger pages. Send a Prefer header:
GET /b1s/v1/Items?$select=ItemCode,ItemName,QuantityOnStock
Prefer: odata.maxpagesize=200

The server confirms the size it used in the Preference-Applied header. odata.maxpagesize=0 turns paging off, but avoid that on large tables like invoices or journal entries. A huge response can time out or put load on the server.

You can also page yourself with $top and $skip. If you do, always add $orderby on a key field, or records can move between pages.

Step 4: Create a sales order with POST

POST /b1s/v1/Orders
Content-Type: application/json
Cookie: B1SESSION=...; ROUTEID=...
{
  "CardCode": "C20000",
  "DocDueDate": "2026-10-30",
  "Comments": "Created from web portal",
  "DocumentLines": [
    { "ItemCode": "A00001", "Quantity": 5, "WarehouseCode": "01" },
    { "ItemCode": "A00002", "Quantity": 2, "UnitPrice": 150.0 }
  ]
}

A successful POST returns 201 Created with the full new document, including DocEntry and DocNum. Save the DocEntry. You need it for every later update.

Step 5: Update with PATCH

PATCH changes only the fields you send. Everything else stays as it is:

PATCH /b1s/v1/Orders(125)
Content-Type: application/json
{
  "Comments": "Delivery moved to Friday",
  "DocDueDate": "2026-11-06"
}

A successful PATCH returns 204 No Content. Be careful when you PATCH document lines. Test on a copy of the company first, and read the document back afterwards to confirm the lines look the way you expect.

Step 6: Log out

POST /b1s/v1/Logout

Logging out frees the session on the server. For a nightly job, log in once, do the work and log out. For a web portal, keep one session per user or a small pool, rather than logging in on every request.

Common errors and what they mean

ErrorUsual causeFix
401 UnauthorizedSession expired, or cookies not sentLog in again; check both cookies are in the request
CORS error in browserOrigin not allowedEnable CORS in the Service Layer configuration (CorsEnable, CorsAllowedOrigins), or call from your backend
SSL or certificate errorDefault self-signed certificateInstall a proper certificate; trust the self-signed one only in test
Login fails with a licence messageUser has no suitable licenceAssign a licence to the user in License Administration
400 with an error messageMissing field, wrong value or business ruleRead error.message.value; it names the field

One more: if your logins work but calls fail at random behind a load balancer, check that ROUTEID is being sent. Without it, the request can reach a node that does not know your session.

DI API or Service Layer?

DI APIService Layer
TypeCOM libraryREST / OData over HTTPS
PlatformWindowsAny client platform
LanguagesMainly .NETAny language with HTTP
Best forClassic add-ons next to the B1 clientWeb portals, mobile apps, cloud integrations
ConcurrencyOne connection per processMany sessions, load balanced

For anything new that talks to SAP Business One from outside the client, we use the Service Layer. It is what we build on for business portals and mobile apps. The DI API still has a place in existing Windows add-ons.

To try these calls against your own system, start with the Service Layer request builder.

If you need a portal, app or integration built on the Service Layer, contact Kodenova with your SAP Business One version and what you want to connect.

Sources

Related services

FAQ

Frequently asked questions

What is the difference between /b1s/v1 and /b1s/v2?

The v1 path uses OData version 3 and v2 uses OData version 4. SAP now treats v4 as the main protocol. The main visible difference in responses is the annotation prefix: v2 returns @odata.nextLink where v1 returns odata.nextLink.

How long does a Service Layer session last?

The default timeout is 30 minutes of inactivity. The Login response returns the value in SessionTimeout. Each call resets the timer, so a busy integration keeps its session alive.

Why do I only get 20 records back?

The Service Layer pages results, and the default page size is 20. Follow the nextLink in the response, or send the header Prefer: odata.maxpagesize=100 to get larger pages.

Should I use the DI API or the Service Layer for a new project?

For new web, mobile and cloud integrations, use the Service Layer. It works over HTTPS from any language and platform. The DI API still makes sense for existing .NET add-ons that run on Windows next to the SAP Business One client.

Why does my browser app fail with a CORS error when Postman works?

Browsers block cross-origin calls unless the server allows them. CORS is switched off in the Service Layer by default. Enable it in the Service Layer configuration and list your app's origin, or call the Service Layer from your own backend instead.

Ready to get started?

Tell us what you need. You get a clear technical answer, a fixed scope and a price, usually within 24 hours.