The OneEnterprise SAP Business One connector retrieves data from Business One on a timer basis. For most business objects, the connector uses the standard getList APIs of the SAP Business One Service Layer. Some objects — for example, the inventory transaction log (OIVL/OINM) or warehouse stock levels (OITW) — aren't exposed as Service Layer entities, so you can't fetch them through standard getList calls.
For these objects, the connector executes a registered SQL query through the Service Layer SQLQueries API. You register the query once in the Business One system, and the OneEnterprise timer process executes it repeatedly.
This guide describes the one-time setup steps that your IT administration team and consulting partner perform on the SAP Business One system before the connector can use this mechanism. It covers the generic procedure for any custom data object, along with a complete worked example for retrieving stock movements.

Setup task | Responsible |
Extend the Service Layer table allowlist (b1s_sqltable.conf) and restart the Service Layer | Customer IT administrator |
Prepare the SQL statement according to the rules in this guide | Consulting partner (based on OneEnterprise instructions) |
Register the SQL query via the Service Layer API | Consulting partner |
Verify the registered query | Consulting partner |
Configure the timer-triggered inbound process in OneEnterprise | Consulting partner |
The Service Layer doesn't accept ad hoc SQL. Instead, you register a query once as a named object (similar to user-defined queries in the Business One client), and the system executes it afterward using its code. OneEnterprise uses this pattern as follows:
Because the cursor is based on a monotonically increasing column (or a timestamp), the connector processes every record exactly once, and interrupted runs resume without data loss.

Performed by: customer IT administrator
The Service Layer executes SQL queries only against tables explicitly allowed by its configuration. You must add tables that aren't part of the default allowlist — such as OINM — once.
b1s_sqltable.conf in the conf Directory of the Service Layer installation.
Version note: As of SAP Business One 9.3, OINM is a database view; the physical data resides in the underlying inventory log tables (for example, OIVL). Depending on your Business One version, the allowlist entry must name the view, the underlying table, or both. Your OneEnterprise project documentation states the exact entries for your version.Performed by: consulting partner
Use the SQL statement exactly as provided in your OneEnterprise project documentation. If you adapt it (for example, to add columns), follow these rules:
:TransNum, not :par1 — the Service Layer resolves the parameter type through the column."TransNum"). SAP HANA requires this (case-sensitive identifiers), and Microsoft SQL Server accepts it as well, so a single statement serves both database types.Generic statement pattern:
SELECT "<CursorColumn>", <further columns>
FROM <Table>
WHERE "<CursorColumn>" >= :<CursorColumn>
ORDER BY "<CursorColumn>"
Note: The Service Layer validates the statement with its own SQL parser, which supports only a subset of the SQL language. If a statement that follows all the rules above is still rejected with Invalid SQL syntax … mismatched input, move the statement into a database view. See Fallback: database views for complex SQL.Performed by: consulting partner
Register the query once through the Service Layer API. Use any REST client, for example, Postman or curl.
POST https://<b1-server>:50000/b1s/v1/Login
Content-Type: application/json
{
"CompanyDB": "<company database>",
"UserName": "<connector user>",
"Password": "<password>"
}
POST https://<b1-server>:50000/b1s/v1/SQLQueries
Content-Type: application/json
{
"SqlCode": "<query code from project documentation>",
"SqlName": "<query name from project documentation>",
"SqlText": "<SQL statement from Step 2>"
}SqlText, escape every double quote as \".To update a registered statement later, send PATCH /b1s/v1/SQLQueries('<query code>') with the new SqlText.
Naming convention: OneEnterprise query codes always carry the prefix OE_ (for example OE_STOCKDELTA). This prevents collisions with queries that the customer or other add-ons register. Don't change the code — the OneEnterprise configuration references it.Performed by: consulting partner
Confirm that the query exists and executes correctly before you hand over to the OneEnterprise configuration.
GET https://<b1-server>:50000/b1s/v1/SQLQueries('<query code>')
POST https://<b1-server>:50000/b1s/v1/SQLQueries('<query code>')/List
Prefer: odata.maxpagesize=20
Content-Type: application/json
{ "ParamList": "<CursorColumn>=<value>" }
CreateDate='2026-07-01'); pass numeric values without quotes (TransNum=0).& inside the one ParamList string.value array. If more records match than the page size allows, the response also contains @odata.nextLink — this is expected; OneEnterprise reads block by block and doesn't follow the link within one run.If both calls succeed and the test run returns plausible data, the Business One side of the setup is complete.
Performed by: consulting partner
Configure the timer-triggered inbound method in OneEnterprise as described in the connector configuration guide. The configuration references:
Choose the initial cursor value deliberately: a value of 0 (or an early date) performs a full initial load of all existing records; the current maximum value starts the synchronization from now on and skips history.
A wholesale customer synchronizes the available stock of one warehouse from SAP Business One to their online shop. Stock changes with every delivery, goods receipt, and stock transfer — but neither the inventory transaction log (OIVL) nor the warehouse stock table (OITW) is available as a Service Layer entity.
The registered query combines both tables: OIVL identifies the items whose stock moved in the warehouse since the last processed transaction sequence, and for exactly those items, OITW supplies the current available quantity (OnHand minus IsCommitted). The transaction sequence number TransSeq Serves as the cursor: it increases with every movement, so each timer run picks up exactly the items that changed since the previous run.

The IT administrator adds OIVL and OITW to b1s_sqltable.conf and restarts the Service Layer.
SELECT T1."ItemCode",
(T1."OnHand" - T1."IsCommited") AS "Available",
MAX(T0."TransSeq") AS "LastTransSeq"
FROM OIVL T0
INNER JOIN OITW T1
ON T1."ItemCode" = T0."ItemCode"
AND T1."WhsCode" = T0."LocCode"
WHERE T0."LocCode" = :LocCode
AND T0."TransSeq" >= :TransSeq
GROUP BY T1."ItemCode", T1."OnHand", T1."IsCommited"
ORDER BY "LastTransSeq"
Note that both parameters are named after columns of OIVL (LocCode, TransSeq) — see Step 2, rule 2. The warehouse is a parameter rather than a literal, so the same registered query serves any warehouse.
Warning: Some Service Layer versions reject the arithmetic expression (T1."OnHand" - T1."IsCommited") in this statement with Invalid SQL syntax … mismatched input '.' expecting FROM. In that case, either select OnHand IsCommited as separate columns and let OneEnterprise calculate the difference, or move the statement into a database view. See Fallback: database views for complex SQL.POST https://b1srv.example.com:50000/b1s/v1/SQLQueries
Content-Type: application/json
{
"SqlCode": "OE_STOCKDELTA",
"SqlName": "OE Available stock delta",
"SqlText": "SELECT T1.\"ItemCode\", (T1.\"OnHand\" - T1.\"IsCommited\") AS \"Available\", MAX(T0.\"TransSeq\") AS \"LastTransSeq\" FROM OIVL T0 INNER JOIN OITW T1 ON T1.\"ItemCode\" = T0.\"ItemCode\" AND T1.\"WhsCode\" = T0.\"LocCode\" WHERE T0.\"LocCode\" = :LocCode AND T0.\"TransSeq\" >= :TransSeq GROUP BY T1.\"ItemCode\", T1.\"OnHand\", T1.\"IsCommited\" ORDER BY \"LastTransSeq\""
}
Expected response: HTTP 201 Created.
POST https://b1srv.example.com:50000/b1s/v1/SQLQueries('OE_STOCKDELTA')/List
Prefer: odata.maxpagesize=20
Content-Type: application/json
{ "ParamList": "LocCode='CD02'&TransSeq=0" }
The response returns one row per changed item — item code, current available quantity, and the item's highest transaction sequence — ordered by LastTransSeq. OneEnterprise stores the highest LastTransSeq of the processed rows plus 1 as the new cursor, so the >= comparison never returns an unchanged item twice.
Tip: The query returns absolute stock values, not movements. Processing an item twice simply rewrites the same quantity — every run is idempotent, and interrupted runs recover without special handling.The partner configures the inbound method with query code OE_STOCKDELTA, cursor column TransSeq, the warehouse code as the LocCode parameter value, initial cursor value 0 for a full initial load, and the block size agreed for the project.
The Service Layer doesn't pass a registered statement to the database unchecked — it validates the statement with its own SQL parser, which supports only a subset of the SQL language (a limited set of functions; the parser rejects arithmetic expressions in the SELECT list, among other restrictions). A statement that the database itself executes without complaint can therefore fail to register or execute with an error such as:
Invalid SQL syntax:, line 1, character position 38,
mismatched input '.' expecting FROM
Try to simplify the statement first — often it's enough to select the raw columns (for example , OnHand and IsCommited) and let OneEnterprise calculate derived values. If you can't simplify the statement, move it into a database view: inside a view, the database's full SQL dialect is available, and the Service Layer only reads the view's result.
Two things change compared to a registered SQL query: you create the view in the database rather than through the Service Layer API, and query parameters (ParamList) aren't available — OneEnterprise filters the view with OData query options ($filter, $orderby) instead. Every value that was a parameter before — such as the warehouse and the cursor — must therefore be an output column of the view.
Performed by: customer IT administrator or consulting partner with database access
dbo whose name ends with B1SLQuery. For the worked example:CREATE VIEW [dbo].[OE_StockAvailableB1SLQuery] AS
SELECT T0."LocCode",
T1."ItemCode",
(T1."OnHand" - T1."IsCommited") AS "Available",
MAX(T0."TransSeq") AS "LastTransSeq"
FROM OIVL T0
INNER JOIN OITW T1
ON T1."ItemCode" = T0."ItemCode"
AND T1."WhsCode" = T0."LocCode"
GROUP BY T0."LocCode", T1."ItemCode", T1."OnHand", T1."IsCommited"
POST https://<b1-server>:50000/b1s/v1/SQLViews('OE_StockAvailableB1SLQuery')/Expose
ParamList into $filter:GET https://<b1-server>:50000/b1s/v2/view.svc/OE_StockAvailableB1SLQuery
?$filter=LocCode eq 'CD02' and LastTransSeq ge 12345
&$orderby=LastTransSeq
Note: The result and the cursor handling are identical to the registered query: an item has moved since the last run exactly when its LastTransSeq reaches the cursor value. You control pagination through the page-size request header, as before.On SAP HANA, you can't expose plain SQL views to the Service Layer. The equivalent is a semantic layer calculation view, which requires the SAP Business One Analytics Service:
Query (for example OE_StockAvailableQuery). All Business One tables are available.Read the view through the semantic layer service with the same OData query options:
GET https://<b1-server>:50000/b1s/v2/sml.svc/OE_StockAvailableQuery
?$filter=LocCode eq 'CD02' and LastTransSeq ge 12345
Warning: The calculation view route requires considerably more setup than a SQL view on Microsoft SQL Server (Analytics Service, SAP HANA Studio, packaging, Service Layer restart). On SAP HANA installations, always check first whether you can simplify the statement so the SQLQueries parser accepts it.Symptom | Cause | Solution |
Registration or execution fails with “invalid table” or “table not allowed” | The table is missing from b1s_sqltable.conf, or the Service Layer was not restarted after the change | Complete Step 1; verify the entry and restart the Service Layer |
Login returns HTTP 401 | Wrong CompanyDB, user name, or password — or the user lacks authorization | Verify the credentials and the connector user's authorizations in Business One |
Registration fails: query code already exists | The query was registered before (for example, in an earlier attempt) | Use PATCH /SQLQueries('<code>') to update the existing query instead of POST |
Execution fails with a parameter error | The placeholder name does not match a column name in the query | Rename the placeholder to the column name (Step 2, rule 2) |
Registration or execution fails with Invalid SQL syntax … mismatched input, although the statement runs in the database | The Service Layer SQL parser supports only a subset of SQL — for example, arithmetic expressions in the SELECT list are rejected | Simplify the statement (select raw columns, calculate in OneEnterprise) or use a database view — see section “Fallback: database views for complex SQL” |
Execution returns no rows, although data exists | Column identifiers not quoted (HANA resolves unquoted names as uppercase), or the cursor value is higher than all existing records | Quote all column names; test with cursor value 0 |
Only 20 rows are returned | Server-side pagination — this is the expected block-wise behavior | No action needed; OneEnterprise continues with the next block on the next run. To change the block size, adjust the page-size header / OneEnterprise configuration |
# | Task | Responsible | Done |
1 | Table name(s) added to b1s_sqltable.conf | Customer IT | ☐ |
2 | Service Layer restarted | Customer IT | ☐ |
3 | SQL statement prepared according to the rules (or taken from project documentation) | Partner | ☐ |
4 | Query registered (POST /SQLQueries, HTTP 201) | Partner | ☐ |
5 | Query verified (GET + test execution with ParamList) | Partner | ☐ |
6 | OneEnterprise inbound method configured (query code, cursor, initial value, block size) | Partner | ☐ |