SAP Business One API Integration Guide: Authentication, Endpoints, and OData Queries

A practical guide to integrating with the SAP Business One Service Layer API. Covers session-based authentication, OData v4 endpoints, querying, pagination, User-Defined Fields, error handling, and production deployment tips.

GJGJ

GJ

12 min read
SAP Business One API Integration Guide: Authentication, Endpoints, and OData Queries

SAP Business One runs the back offices of over 80,000 small and mid-sized companies worldwide. If you're building software that needs to read invoices, sync customers, or push journal entries into any of those systems, you'll eventually find yourself working with the Service Layer API. It's SAP's REST interface for Business One, and it's both more capable and more quirky than you might expect coming from modern SaaS APIs.

This is a practical walkthrough of what the integration actually looks like: authentication, core endpoints, querying patterns, pagination, and the gotchas that will cost you hours if nobody warns you about them first. If you're looking for SAP's enterprise product instead, see our SAP S/4HANA API integration guide.

What is the SAP Business One Service Layer?

The Service Layer is SAP Business One's OData-based REST API. It sits on top of an Apache HTTP server and talks to the same business object layer that the desktop client uses (the DI Core). That last part matters, because it means the API enforces the same validation rules and business logic as the application itself. You can't create an invoice with a missing required field through the API any more than an accountant could through the UI.

As of Feature Pack 2405, SAP has deprecated OData v3 and made OData v4 the primary protocol. The practical difference: your base URL path changes from /b1s/v1/ to /b1s/v2/, and the JSON response format follows OData v4 conventions (annotations use the @odata. prefix instead of odata.). If you're starting a new integration today, use v2.

Your Service Layer instance runs at a URL like:

https://your-sap-server:50000/b1s/v2/

The port and hostname depend on the customer's deployment. SAP Business One can run on either Microsoft SQL Server or SAP HANA, and the Service Layer works the same way on both. SAP publishes a complete API reference covering every entity and action, along with a detailed Service Layer user manual that covers installation, configuration, and query patterns.

It's worth knowing that the Service Layer is not SAP Business One's only integration path. The DI API is a .NET library for deep server-side access, mainly used by desktop add-ons running on the same machine as the SAP client. The Integration Framework (B1If) is a Java-based middleware layer that lets SAP partners build custom passive REST endpoints and event-driven workflows using XSLT transformations. B1If routes through the DI API on SQL Server deployments and through the Service Layer on HANA. Some partners, like Brazilian consultancy Ogeda & Nietsche, have used B1If to build domain-specific REST APIs (ecommerce, CRM, WMS) that wrap SAP Business One objects in a loosely coupled integration layer. The Service Layer is the right starting point for most external integrations: it's REST-native, requires no middleware installation, and works identically on both database backends.

How to authenticate with the SAP Business One API

This is where the Service Layer diverges from what you're used to with modern APIs. There are no API keys. No persistent bearer tokens. You authenticate by posting credentials and getting a session cookie back.

The login request looks like this:

POST https://your-sap-server:50000/b1s/v2/Login

{
  "CompanyDB": "SBODemoUS",
  "UserName": "manager",
  "Password": "your-password"
}

If the credentials are valid, you get back a 200 response with a SessionId in the body and a B1SESSION cookie in the response headers. Every subsequent request must include that cookie.

In Python, a basic connection using requests looks like this:

import requests

base_url = "https://your-sap-server:50000/b1s/v2"

session = requests.Session()
session.verify = False  # only for self-signed certs in dev

# Authenticate
login = session.post(f"{base_url}/Login", json={
    "CompanyDB": "SBODemoUS",
    "UserName": "manager",
    "Password": "your-password"
})
login.raise_for_status()

# Query customers
customers = session.get(f"{base_url}/BusinessPartners", params={
    "$filter": "CardType eq 'cCustomer'",
    "$select": "CardCode,CardName",
    "$top": 50
})

for bp in customers.json()["value"]:
    print(bp["CardCode"], bp["CardName"])

# Logout when done
session.post(f"{base_url}/Logout")

The key detail: use a requests.Session() object. It automatically persists the B1SESSION cookie across requests, so you authenticate once and every subsequent call reuses the same session. If you create a new requests.post() for each call without a shared session, you'll re-authenticate every time and hit the 5-second overhead on each request.

The session expires after 30 minutes of inactivity by default. You can extend this in the Service Layer configuration (the b1s.conf file) or in the SLD Control Center, but most integrations handle it by catching the 401 response and re-authenticating automatically.

One detail that catches people: the first authenticated request to the Service Layer can take around 5 seconds. Subsequent requests using the session cookie return in roughly 20 milliseconds. This is a well-known behavior in the SAP community, and it means you absolutely need to reuse sessions rather than authenticating per request. If you're running a web application that calls the Service Layer from a backend, implement a session cache that stores the B1SESSION cookie and only re-authenticates when the session expires.

SAP also added OAuth 2.0 and OpenID Connect support starting in version 10.0 FP 2305. This requires configuring an identity provider (SAP IAS or another SAML2-compatible IDP) and is more complex to set up, but it's worth considering if your integration needs to support SSO or if you're building a multi-tenant application where managing per-company credentials gets unwieldy.

SAP Business One API endpoints for accounting data

The API exposes SAP Business One's business objects as OData entities. The ones you'll work with most often in an accounting software integration:

BusinessPartners for customers and vendors. Items for products and services. Orders for sales orders. Invoices for A/R invoices. PurchaseInvoices for A/P invoices. JournalEntries for general ledger postings. ChartOfAccounts for the GL account list.

All of these support standard HTTP methods. A GET to retrieve, POST to create, PATCH to update, DELETE to remove. Here's what creating an invoice looks like:

POST /b1s/v2/Invoices

{
  "CardCode": "C20000",
  "DocDate": "2026-03-15",
  "DocumentLines": [
    {
      "ItemCode": "A00001",
      "Quantity": 10,
      "UnitPrice": 25.00
    }
  ]
}

The CardCode references an existing BusinessPartner. The API validates that the partner exists, that the item code is valid, and that all required fields are present before writing anything to the database. If validation fails, you get a structured error response with the SAP error code and message.

OData query options: filtering, sorting, and joins

OData gives you a consistent query language across all entities. The basics:

GET /b1s/v2/BusinessPartners?$select=CardCode,CardName&$filter=CardType eq 'cCustomer'&$orderby=CardCode&$top=50

This returns the first 50 customers, selecting only the fields you need. The filter operators include eq, ne, gt, lt, ge, le, and, or, plus string functions like startswith() and contains().

You can navigate relationships using OData associations. If you have an order's DocEntry, you can retrieve the associated customer in a single request:

GET /b1s/v2/Orders(7)/BusinessPartner

Aggregation support was added for HANA deployments and uses the $apply query option. You can run sums, averages, counts, and group-by operations directly through the API, which can save you from pulling large datasets just to aggregate on the client side.

The biggest limitation: you cannot join across entities in a single query. Each API call returns data from one endpoint. If you need to correlate invoices with their associated payments, that's two separate requests and a client-side join. For complex reporting queries, SAP requires you to save the query in Query Manager first, then call it through the SQLQueries endpoint. You can't pass arbitrary SQL through the API.

Pagination and page size configuration

The default page size is 20 records. If you request /b1s/v2/Invoices, you'll get back 20 invoices and an @odata.nextLink annotation pointing to the next page.

You have two options for controlling this. Client-driven paging uses $top and $skip:

GET /b1s/v2/Invoices?$top=100&$skip=200

Server-driven paging uses the Prefer header to request a larger page size:

GET /b1s/v2/Invoices
Prefer: odata.maxpagesize=100

The server responds with a Preference-Applied header confirming the page size. You can also set PageSize globally in the b1s.conf configuration file, though this requires Service Layer restart and affects all clients.

Setting odata.maxpagesize=0 disables pagination entirely. This is useful for small reference tables like chart of accounts but dangerous for transactional entities with thousands of records.

Batch operations and atomic transactions

The Service Layer supports OData batch requests through the /$batch endpoint. This lets you bundle multiple operations into a single HTTP request, and operations within a changeset are atomic: if one fails, they all roll back.

This is useful for scenarios like creating an invoice and its payment in a single atomic operation, or bulk-updating inventory across multiple items. The request format uses multipart MIME boundaries, which is ugly to construct manually but well-supported by most OData client libraries.

User-Defined Fields and User-Defined Objects

Almost every SAP Business One customer has customized their instance with User-Defined Fields (UDFs) and User-Defined Tables (UDTs). If your integration ignores these, you'll lose data on every sync.

UDFs are accessible through the Service Layer on the parent entity. A custom field added to BusinessPartners named "CustomerTier" appears in the API response as U_CustomerTier (all UDFs carry the U_ prefix). You can read, write, and filter on UDFs the same way you would any standard property:

GET /b1s/v2/BusinessPartners?$filter=U_CustomerTier eq 'Gold'

User-Defined Tables work similarly but use the U_ prefix on the table name. You access them through a dedicated endpoint:

GET /b1s/v2/U_CUSTOMTABLE

User-Defined Objects (UDOs) go a step further, wrapping UDTs with business logic like find, add, update, and delete operations. The Service Layer exposes registered UDOs as first-class entities, so they behave like any built-in object.

The catch: UDFs, UDTs, and UDOs vary from customer to customer. Your integration needs to discover them at runtime using the /$metadata endpoint or the UserFieldsMD and UserTablesMD entities, rather than assuming a fixed schema. This is one of the more time-consuming parts of building a production SAP Business One integration, and it's a common reason teams choose a unified API approach that normalizes these differences across customers.

Error handling and debugging

The Service Layer returns errors as JSON objects with an error key containing a code and message. The codes come from SAP's DI API layer and carry more diagnostic value than the HTTP status alone. A 400 response might mean a missing required field, a referential integrity violation, or a business logic constraint like trying to post to a closed accounting period.

Some common error patterns worth handling explicitly:

Error code -5002 means the session has expired. Catch this and re-authenticate rather than failing the entire sync. Error code -2028 indicates a locked object, which happens when another user or process has the same record open. The right response is to retry with exponential backoff. Error code -10 is a generic validation failure; the message.value field will contain the specific field and constraint that failed.

Log the full error response body on every failure. SAP support and implementation partners will ask for the error code, the request payload, and the entity path. Without these, troubleshooting becomes guesswork.

For debugging during development, the Service Layer's /$metadata endpoint is indispensable. It returns the full OData schema for every entity, property, and association available in the API. Generate your client code from this schema rather than hard-coding property names, because SAP adds and occasionally renames properties across feature packs.

Production deployment considerations

Session management is the single most important thing to get right. Build a session pool that re-authenticates lazily when the 30-minute timeout hits, and never authenticate per-request. The 5-second authentication overhead will destroy your throughput otherwise.

Handle the CompanyDB parameter carefully. SAP Business One customers often run multiple company databases on the same server. Your session is scoped to a single company database, so if you need to sync data across companies, you need separate sessions for each.

The Service Layer enforces TLS 1.2 or 1.3 and requires a valid X.509 certificate. In practice, many on-premise deployments use self-signed certificates, which means your HTTP client needs to be configured to trust that specific certificate rather than skipping validation entirely.

Because SAP Business One is typically deployed on-premise, your integration also needs to account for network connectivity. Unlike cloud APIs where you can call an endpoint from anywhere, reaching a customer's Service Layer often requires VPN access, IP allowlisting, or a reverse proxy. Some SAP partners deploy a proxy layer in front of the Service Layer to handle session caching and certificate management for external clients. This on-premise reality is one of the main reasons ERP and accounting integrations are more complex than connecting to a cloud-native API like Xero or QuickBooks Online.

The Service Layer isn't the most elegant API you'll ever work with. The session-based auth, the OData conventions, and the on-premise deployment model all add friction compared to a typical cloud API. But it's well-documented, it's functionally complete, and it exposes the full depth of SAP Business One's business logic. For any SaaS company that needs to support SAP Business One customers, understanding the Service Layer is a prerequisite, not an option.

Frequently asked questions

What authentication methods does the SAP Business One Service Layer support?

The Service Layer supports two authentication schemes. Basic authentication uses a username, password, and CompanyDB posted to the /Login endpoint, returning a B1SESSION cookie valid for 30 minutes. OAuth 2.0 with OpenID Connect is available from version 10.0 FP 2305 onward and requires an identity provider like SAP IAS.

What is the default session timeout for SAP Business One Service Layer?

The default session timeout is 30 minutes of inactivity. You can extend this in the b1s.conf configuration file or through the SLD Control Center. Most production integrations handle expiry by catching the -5002 error code and re-authenticating automatically.

Can you use OData v3 and v4 with SAP Business One Service Layer?

As of Feature Pack 2405, OData v3 is deprecated and OData v4 is the primary protocol. OData v3 still works for backward compatibility (use the /b1s/v1/ path), but SAP recommends migrating to v4 (/b1s/v2/) for new integrations.

What is the default page size for SAP Business One API responses?

The default page size is 20 records. You can change this per-request using the Prefer: odata.maxpagesize=N header, or globally by setting PageSize in the b1s.conf file. Setting the page size to 0 disables pagination, which is useful for small lookup tables but risky for large transactional datasets.

How do you handle User-Defined Fields in the Service Layer?

UDFs appear on their parent entity with a U_ prefix. A custom field called "Region" on BusinessPartners becomes U_Region in the API. You can read, write, and filter on UDFs like any standard field. Discover available UDFs at runtime through the UserFieldsMD entity or the /$metadata endpoint.

Ready to get started?

Scale your integration strategy and deliver the integrations your customers need in record time.

Ready to get started?
Talk to an expert

Trusted by fast-moving product & engineering teams

JobNimbus
Blue Zinc
Drata
Octa
Nmbrs
Apideck Blog

Insights, guides, and updates from Apideck

Discover company news, API insights, and expert blog posts. Explore practical integration guides and tech articles to make the most of Apideck's platform.