November 12, 2025 · 5 min read
SAP Service Layer will tell you something broke. It won't say what.
Rant · SAP · Integration
I've been integrating against SAP Business One's Service Layer for a good while now, long enough that I've stopped being surprised by most of it. Not because it got easier. Because I've made my peace with the actual rule of working with SAP: when something breaks, you are not going to be told what broke. You're going to be told a number, and then it's your job to go figure out what that number has meant to someone else, somewhere, at some point.
The error looks modern. The error is not modern.
Service Layer wraps its errors in a perfectly normal-looking OData JSON envelope, error.code, error.message.value, the kind of shape you'd expect from any REST API written this decade. The first time you see one, it looks like progress. Then you actually read the message and it's a single line like "Invalid object" or "Wrong object state," and the code sitting next to it is one of the old DI API numeric codes that's been part of SAP Business One since long before Service Layer existed, just now dressed up in JSON instead of a COM error struct. The presentation is 2020s. The information underneath it is whatever it was in the SDK twenty years ago.
That's the part that gets me. It's not that the errors are undocumented, there's a list out there mapping codes to one-line descriptions. It's that the one-line description is frequently just as vague as the code itself. "Init error." "Invalid object." "Wrong object state." None of those tell you which object, which field, which state was expected versus what it got. You get a category of failure, not a cause.
Session handling is its own tax
Before you even get to business-logic errors, there's a whole layer of pain around just staying logged in. Login gets you a B1SESSION cookie, and that session times out after thirty minutes of inactivity by default. Miss that window and your next request comes back with a session-expired error code that, if you don't already know what it means, reads like any other opaque failure. Production integrations end up needing their own session pool that watches for that specific code and re-authenticates automatically, because logging in on every single call is its own performance tax, real, measurable seconds of overhead per request if you get lazy about it.
None of this is undoable. I've built the session pool, I've got the retry-on-expiry logic, it works. But it's a whole subsystem that exists purely to compensate for the fact that the platform won't tell you plainly "your session expired, log in again" without you first learning to recognize its error code for that condition on faith.
Field names are not what you think they are
Custom fields show up with a U_ prefix, which is fine once you know it, and mostly undocumented in the place you'd actually want it, next to the field itself when something rejects it. Metadata naming between the DI API and Service Layer doesn't always agree either, so a field or object name you're used to from one surface doesn't necessarily resolve on the other, and you find that out by trial and error, not by a validation message that says "you meant X." Custom schema setups can make a perfectly real field on a document line simply not resolve through Service Layer at all, no error pointing at why, just an "invalid property" response for a property that, from where you're standing, obviously exists.
The actual documentation is fine for the easy 80%
To be fair to SAP, the basic case is genuinely covered: create an entity, read an entity, filter a collection, the tutorial-shaped operations are all there and mostly correct. What's missing is everything past that. Deep inserts, batch requests, anything involving more than one object type in a single call, the official docs give you the shape of the request and very little about what happens when it partially fails, or which of the twenty things you changed was the one that got rejected. The actual knowledge for that lives in SAP Community threads, half of them years old, some contradicting each other, and in whatever your integration partner's consultants know and don't necessarily write down anywhere, because that knowledge is a chunk of why you're paying them.
What debugging actually looks like
In practice, a failed Service Layer call turns into an archaeology project. Strip the payload down to the smallest thing that still fails. Change one field at a time until the error changes or disappears. Cross-reference whatever numeric code you got against a list that may or may not cover your SAP version. Ask in a community thread and wait. None of that is a technical problem I can architect my way out of, it's a documentation and error-design problem that sits entirely on SAP's side of the wire, and it doesn't move no matter how good the integration code on my end gets.
I'm not expecting this to change. SAP is not going to rewrite twenty-year-old error codes into sentences because one integrator in Kampala is annoyed on a Wednesday. But I did want to write down, plainly, what the actual cost of that decision is for the people building on top of it: not that the platform is hard, plenty of platforms are hard, but that it's hard in a way that gives you almost nothing to go on when it fails, and expects you to have already read the forum thread that explains why.