Two doors into the same B1.

Every program that reads or posts something in SAP Business One without a person typing it uses one of two interfaces. The Service Layer is a web service: it runs on the server, speaks HTTPS and OData, and answers with JSON. You can call it from any language that can send a web request, and B1's mobile service depends on it. The DI API (Data Interface API) is the older route: a COM library (a DLL) for Windows, written for C/C++, Visual Basic and .NET, which a program loads on the machine where it runs.

Underneath they are the same. SAP's own Service Layer guide describes the DI Core inside the Service Layer as "the same one that is used by SAP Business One DI API", and the business logic (tax, posting, stock) runs in the same component as in the B1 client. That is why an order posted through either door goes through the same checks as one typed on screen: if B1 would refuse it in the window, it refuses it from your program too. The object and field names are also largely the same: CardCode is CardCode in both.

Which one to use when.

  • Service Layer for anything new: a scanner app, a web shop link, a planning board, an integration with another system. It runs on SUSE Linux and on Windows, the load balancer usually listens on port 50000, and your program needs nothing installed next to B1. It scales with several nodes behind the load balancer.
  • DI API when you maintain an existing add-on built on it, or when the code runs inside the B1 client on Windows anyway, next to the UI API. SAP's SDK help notes that the DI API does not support multithreading, and it must be installed on every machine that runs the program.
  • What only the DI API has: the guide lists it plainly. The Service Layer has no Recordset object for direct SQL, no import and export to XML, and no transaction across requests: there is no StartTransaction and EndTransaction as in the DI API. Each request, including a batch, is its own transaction.

In the DI API a program creates a Company object, fills Server, CompanyDB, UserName, Password and DbServerType, and calls Connect, which returns 0 on success. An order is then GetBusinessObject(BoObjectTypes.oOrders), filled line by line and saved with Add; when a call fails, GetLastError gives the code and the text. The rest of this guide is about the Service Layer, because that is where new work goes.

Logging in, and how long a session lives.

The Service Layer lives at https://<server>:50000/b1s/v2/. /b1s/v2 speaks OData version 4, /b1s/v1 the older version 3; pick v2 for new work. Nothing works before a login:

  • POST /b1s/v2/Login with the body {"CompanyDB": "SBODEMOUS", "UserName": "manager", "Password": "1234"}. An optional Language sets the language of the messages.
  • The answer is 200 OK with a SessionId and a SessionTimeout, and two cookies: B1SESSION and ROUTEID. Every following request must send B1SESSION; ROUTEID is optional and keeps you on the same node behind the load balancer. A browser sends both by itself; a desktop or server program has to set them.
  • The session ends after a period without requests: 30 minutes by default, set as SessionTimeout in b1s.conf on the server. After that the answer is 401 Unauthorized with code 301, "Invalid session or session already timeout." Log in again and repeat the request.
  • POST /b1s/v2/Logout with the cookie ends the session; the answer is 204 No Content. Log out when you are done, and do not log in for every single request: a login is the heaviest call there is.

Reading, creating and changing data.

Reading is a GET on an entity, with OData query options to keep the answer small. GET /b1s/v2/BusinessPartners?$select=CardCode,CardName&$filter=CardType eq 'cCustomer'&$top=5 returns five customers with only their code and name. $filter knows eq, ne, gt, ge, lt, le, and, or, not, and functions such as startswith and contains; $orderby sorts and $skip pages. One record is read by its key: GET /b1s/v2/BusinessPartners('C20000'), with quotes because the key is text, and GET /b1s/v2/Orders(22) without, because DocEntry is a number. The lines of an order come with the order, in DocumentLines.

A list never comes all at once. By default the Service Layer returns 20 records per page and adds an @odata.nextLink to the next page; the header Prefer: odata.maxpagesize=50 asks for bigger pages. Code that reads only the first page and stops is the most common bug in a first integration.

Creating is a POST with JSON. POST /b1s/v2/Orders with {"CardCode": "C20000", "DocDueDate": "2026-11-15", "DocumentLines": [{"ItemCode": "A00001", "Quantity": 10}]} adds a sales order and answers 201 Created with the whole document, including the new DocEntry and DocNum. Changing is a PATCH on the key with only the fields that change, for example PATCH /b1s/v2/BusinessPartners('C20000') with {"Phone1": "0201234567"}; the answer is 204 No Content. Use PATCH, not PUT: PUT resets every field you leave out. Steps that are not a plain change are actions, such as POST /b1s/v2/Orders(22)/Close. A DELETE on an order is refused, just as you cannot delete one in the B1 client.

Entities are not tables.

The Service Layer talks in business objects, the database in tables. BusinessPartners is stored in OCRD; Orders is the sales order, with the header in ORDR and the lines in RDR1. Many marketing documents share one entity type, Document, so Orders, Invoices and PurchaseOrders have the same fields. The full list of entities, fields and actions is in GET /b1s/v2/$metadata, and it includes your own user-defined fields, so the metadata of one company database can differ from another's. One catch: a new user-defined field or table is only visible after the Service Layer is restarted.

Reading a table with SQL is fine. Writing into one is not. SAP's SDK rules do not allow changes to standard objects, and an INSERT or UPDATE straight into ORDR skips everything B1 does when a document is added: numbering, stock, the journal entry, authorisations, approval, the change log. You end up with data B1 never checked. Everything that changes data goes through the Service Layer or the DI API, without exception.

Errors, batches and saved queries.

  • Errors come back as an HTTP status (for example 400 for a refused request, 401 without a valid session, 403 without authorisation) and a JSON body with an error object holding a code and a message: the same text a user would see in the B1 client, such as a business partner code that is already taken. Log that text; it nearly always says what is wrong.
  • Batch requests go to POST /b1s/v2/$batch as one multipart message. Requests inside a change set are all-or-nothing: if one fails, the whole change set is rolled back. That is the Service Layer's answer to a transaction over several documents, as long as they fit in one batch.
  • Saved SQL queries (since 10.0 FP 2011): you store a SELECT as an entry in SQLQueries, with SqlCode, SqlName and SqlText, and run it with GET /b1s/v2/SQLQueries('sql01')/List. Only reading is allowed; UPDATE, INSERT and DELETE are refused, and only allowed tables and columns can be queried. A normal user needs authorisation for each query in General Authorizations. The details are in our guide to SQL queries.

Before you start: a checklist.

  • The Service Layer is installed and running on your B1 server, and your program can reach port 50000 over HTTPS. It is meant for internal calls; it does not need to be exposed to the internet.
  • A test company to try requests on, never the live database.
  • A B1 user of its own for the program, with only the authorisations it needs, and a licence for that user. Indirect Access is the type made for programs; see our guide to B1 licences and confirm it with your partner.
  • One session reused across requests, a fresh login on a 401, and a logout at the end.
  • Paging handled: follow @odata.nextLink until it is gone.
  • Field names taken from $metadata of your own company database, not guessed and not copied from another system.
  • The error text from every refused request written to a log, and nothing written into SAP tables directly.

How KoonLearn approaches it.

KoonLearn, our training simulation for SAP Business One, has a deep dive called Service Layer and DI API: sections on architecture and login, reading, writing, advanced, DI API, troubleshooting. You work in a request console in the browser: method, URL, headers and body, a Send button, and an answer with status and JSON from the training company's data. You log in and watch your session expire, filter and page through business partners and purchase orders, post and patch documents, run Close and Cancel, send a change set in a batch, and repair requests that come back with 400, 401 or 404. The DI API part is reading code and finding the fault: a connection that is not checked, a transaction without its error handling, a Recordset loop. The console answers like the Service Layer; it is not one, and nothing is installed or connected to a real B1.

Prices and options are in the customer portal. The deep dive ends with a quiz and a module exam. It goes beyond the exam: it is not preparation for C_TB120 and gives no certificate. If you want to see how KoonLearn works first, chapter 03 Purchasing is free without an account: start the free chapter on learn.koonsystems.com. KoonLearn is not affiliated with SAP SE.

Sources: SAP, Working with SAP Business One Service Layer (version 1.29, 27-07-2026: login and session, CRUD, query options, paging, batch, SQL Query, limitations versus the DI API); the Service Layer API reference (Login, Logout, BusinessPartners, Orders); SAP Business One SDK Help 10.0 (DI API, connecting, error handling, modifications to the standard); DI API reference 10.0 (Company, Documents); Database Tables Reference 10.0 (OCRD, ORDR, RDR1); the KoonLearn module overview. SAP and SAP Business One are trademarks of SAP SE.