Using the APIs, the functions of open bi server can be consumed in own applications or configuration settings can be changed.
The easiest way to use the open bi API from .NET is the open bi REST client, the BiExcellence.OpenBi.Api.Client NuGet package. It supports netstandard2.0 and can also be used in WebAssembly.
The older BiExcellence.OpenBi.Api packages use the legacy command API and are still available via https://www.nuget.org/profiles/biexcellence. Their documentation stays at openbi.info/docs/openbinetapi.
Overview
Every endpoint of the open bi server, by purpose. All of them are available on every port of the server unless noted otherwise.
| Purpose | Endpoint | Details |
|---|---|---|
| REST API | /openbi/api/... |
REST API |
| OpenAPI document | /openbi/openapi/openbi.json |
REST API |
| OAuth token, authorization, revocation, user info | /openbi/api/oauth/... |
Authentication |
| OAuth discovery | /.well-known/... |
Authentication |
| Health | /health/live, /health/ready, /openbi/api/system/health |
Health Checks |
| Run a batch job | /openbi/api/batchjobs/{id}/execute, /openbi/api/batchjobevents/execute |
Batch Jobs |
| Server status | /openbi/xmlprovider/status.biex (config port only) |
Status |
| Configurator | /configurator (served by the Configurator plugin) |
Configurator |
| Legacy command API | /openbi/xmlprovider/mobile.biex |
Legacy Endpoints |
REST API
The open bi server offers a REST API. All resources are located under the base path /openbi/api, for example /openbi/api/users.
The API is described by an OpenAPI document which is served at /openbi/openapi/openbi.json. It always matches the running server version and can be used with any OpenAPI compatible tool (Swagger UI, Scalar, Postman, ...) or to generate a client for any language. It lists every route with its parameters, its response and the permission it needs.
| Area | Resources (under /openbi/api) |
|---|---|
| Users and permissions | users, usergroups, roles, permissions, organisations, authobjects, authvalues |
| Integration | apikeys, oauthapps, oauth |
| Data | connectiontemplates, dataprovidertypes, dataproviders, chartdefinitions, datastoreobjects, masterdataobjects, mapdimensions, baseentities, tableattributes, etltasks |
| CMS and content | rolecontent, rolecontentelements, rolecontenthistory, htmlitems, hostheaders, webfiles, webfilecontainers, webfilehistory, languagetexts, languagetextvalues, contentbooks, contentarticles, downloads, gridstylegroups, files |
| Batch jobs | batchjobs, batchjobhandlers, batchjoblogs, batchjobevents |
| Other | inquiries, inquirers, transporttasks, objectlocks |
| System | system/errors, system/mail, system/licenseinformation, system/configuration, system/sharedconfiguration (these two on the config port only), system/health |
List routes support $select, $filter, $search, $orderby, $top, $skip and $count.
Authentication
Authentication uses OAuth2 bearer tokens, sent as Authorization: Bearer <token>. A token is requested at /openbi/api/oauth/token, either with the password grant (user name and password) or with the client_credentials grant (API key). Most routes also require a permission, which the OpenAPI document names for each route.
The other OAuth routes are /openbi/api/oauth/authorize (authorization code flow), /openbi/api/oauth/revoke, /openbi/api/oauth/introspect and /openbi/api/oauth/userinfo.
Clients can discover these through the standard documents at the server root:
/.well-known/oauth-authorization-serverand/.well-known/openid-configuration: the endpoints and supported grants/.well-known/jwks.json: the keys the tokens are signed with/.well-known/oauth-protected-resource: describes the REST API as a protected resource
API keys
An API key is how a machine signs in: an ETL tool, a script, an SAP job or another server that calls the API without a person at the keyboard. It consists of an id and a secret, which are the OAuth2 client_id and client_secret for the client_credentials grant.
A key acts as a user. Every key belongs to one user, and everything done with it is done and logged as that user. A token from a key never gets a permission its user does not hold at the moment the token is issued. If the user is deactivated or loses a permission, the key loses it too. For an integration, create a dedicated technical user with only the permissions it needs, rather than using a personal account.
Creating a key
In the Configurator, open Integration → API Keys and create a new key:
| Field | Meaning |
|---|---|
| Name | A description, for example SAP BW process chain ETL_DONE. |
| User | The user the key acts as. Only a user administrator (permission openbi.user.write) can create a key for someone else; everyone else's keys are always their own. |
| Scope | The permissions a token from this key may use, separated by spaces, for example openbi.job.execute. Leave it empty only on purpose: an empty scope means unrestricted, so a token gets all permissions of the key's user. |
When the key is saved, the Configurator shows its secret. Copy it right away: it is shown only this once. The server stores only a hash, so nobody can read it later, an administrator included. If the secret is lost, click New secret on the key to replace it.
The same works through the REST API with POST /openbi/api/apikeys. The response with status 201 contains the generated id and the secret. A secret sent in the request body is ignored; the server always generates it.
Using a key
Request a token with the key id and secret, either as Basic authentication or as the form fields client_id and client_secret:
curl -u "$API_KEY_ID:$API_KEY_SECRET" -d "grant_type=client_credentials&scope=openbi.job.execute" https://<server>/openbi/api/oauth/token
scopeis optional. Left out, the token gets the key's scopes. Given, it narrows the token to some of them. A requested scope that the key does not list, or that its user does not hold, is refused withinvalid_scope. This also applies to an unrestricted key: it lists no scopes, so a request to it must not sendscopeat all.- The response contains
access_tokenandexpires_in. A token is valid for one hour. - No refresh token is issued. When the token has expired, request a new one the same way.
The token is then sent with every call as Authorization: Bearer <access_token>. For complete examples, see Example: triggering an event with curl and the ABAP example below it.
Who may manage keys
| Who | What |
|---|---|
| Every user | Create, change, renew and delete their own keys. |
A holder of openbi.user.write |
See and manage every user's keys, and create keys for other users. |
| A token with a scope | Manage keys only if its scope names openbi.apikey.read, openbi.apikey.write or openbi.apikey.delete. This stops a narrowly scoped key from creating an unrestricted one. |
Someone else's key looks to a user like a key that does not exist (404).
Renewing and revoking
| Action | Effect |
|---|---|
New secret (POST /openbi/api/apikeys/{id}/secret) |
The old secret stops working at once. Tokens already issued with it keep working until they expire, at most one hour. |
Delete the key (DELETE /openbi/api/apikeys/{id}) |
The key and every token issued for it stop working at once. |
| Change the scope | Applies to tokens requested afterwards. |
The key list shows when each key was created and when a token was last issued for it. A key that has not been used for a long time is a good candidate for deletion.
Good practice
- One key per system and purpose, so that one can be revoked without touching the others.
- A scope that names only what the integration needs, such as
openbi.job.executefor raising batch job events. - Keep the secret in the calling system's credential store (an SM59 destination, a key vault, a CI secret), not in source code, scripts or tickets.
- Renew the secret whenever someone who knew it leaves, and when it may have been exposed.
- A key cannot be moved to another system with a transport. Create it again in each landscape.
Batch Jobs
A batch job can be run on demand, in addition to its schedule:
POST /openbi/api/batchjobs/{id}/executeruns one batch job.POST /openbi/api/batchjobevents/executeruns every active batch job registered for an event, with the body{ "eventName": "...", "eventValue": "..." }. This is the hook an external system calls when its data is ready.
Both need the openbi.job.execute permission. An optional parameters object in the body is passed to the job for this run only, on top of its stored parameters. The response streams the log until the run ends; with ?detached the server answers 202 right away and the run continues in the background. Its log can then be read through /openbi/api/batchjoblogs/{id}/entries, and a running job is stopped with POST /openbi/api/batchjoblogs/{id}/cancel.
Example: triggering an event with curl
An external system such as an ETL tool or a script needs three things:
- A batch job that listens for the event. In the Configurator, the job is set to active, its type is
EVENT(orSYSTEM), and it has an event name and an event value, for exampleETL_DONEandSALES. An event runs only the active jobs whose event name and event value both match. - An API key with the scope
openbi.job.execute. The secret is shown only once, when the key is created. The runs are logged under the user the key belongs to. Anyone holding this key can raise any event, so use a dedicated key for this purpose. - Two calls: one that gets a token with the
client_credentialsgrant, and one that raises the event.
Get a token (the key id and secret are sent as Basic authentication):
TOKEN=$(curl -s -u "$API_KEY_ID:$API_KEY_SECRET" -d "grant_type=client_credentials&scope=openbi.job.execute" https://<server>/openbi/api/oauth/token | jq -r .access_token)
The response is JSON with the token in access_token. Without jq, copy the token from the response by hand. No refresh token is issued: when the token expires, request a new one the same way.
Raise the event:
curl -X POST "https://<server>/openbi/api/batchjobevents/execute?detached" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"eventName":"ETL_DONE","eventValue":"SALES","parameters":{"LOADDATE":"2026-10-28"}}'
| Request | Response |
|---|---|
with ?detached |
202 Accepted right away; the jobs run in the background |
without ?detached |
waits until all triggered jobs have finished, then returns their log entries as a JSON array |
with -H "Accept: text/event-stream" |
streams the log entries while the jobs run |
without eventName or eventValue |
400 Bad Request |
parameters is optional. It overrides the job's stored parameters for this run only. Afterwards, the runs can be found under /openbi/api/batchjoblogs.
On Windows, call curl.exe from PowerShell, because curl there is an alias for Invoke-WebRequest. Also put the JSON body in a file and pass it with -d "@body.json", because PowerShell mangles the inner quotes.
Example: triggering an event from SAP (ABAP)
The same two calls from an ABAP report, for example as the last step of a process chain or a background job that runs after the load. It needs SAP NetWeaver 7.40 or later and the class /UI2/CL_JSON (SAP Note 2526405 for older support packages).
Before the first run:
- The server's certificate (or its CA) must be in the SSL client PSE
ANONYMin transactionSTRUST, otherwise the HTTPS call fails. - If the SAP system reaches the server only through a proxy, enter it in transaction
SICFunder Client → Proxy Settings. - Do not keep the API key secret in the source code in production. Store it in an SM59 destination or the secure store, and limit who can read it.
REPORT z_openbi_raise_event.
CONSTANTS:
gc_server TYPE string VALUE `https://yourserver`,
gc_api_key_id TYPE string VALUE `YOUR_API_KEY_ID`,
gc_api_key_secret TYPE string VALUE `YOUR_API_KEY_SECRET`.
CLASS lcx_openbi DEFINITION INHERITING FROM cx_static_check FINAL.
PUBLIC SECTION.
METHODS constructor IMPORTING message TYPE string.
METHODS if_message~get_text REDEFINITION.
PRIVATE SECTION.
DATA message TYPE string.
ENDCLASS.
CLASS lcx_openbi IMPLEMENTATION.
METHOD constructor.
super->constructor( ).
me->message = message.
ENDMETHOD.
METHOD if_message~get_text.
result = message.
ENDMETHOD.
ENDCLASS.
CLASS lcl_openbi DEFINITION FINAL.
PUBLIC SECTION.
"! Requests a token with the client_credentials grant
METHODS get_token
RETURNING VALUE(result) TYPE string
RAISING lcx_openbi.
"! Runs every active batch job registered for the event
METHODS raise_event
IMPORTING token TYPE string
body TYPE string
RAISING lcx_openbi.
PRIVATE SECTION.
METHODS create_client
IMPORTING path TYPE string
RETURNING VALUE(result) TYPE REF TO if_http_client
RAISING lcx_openbi.
METHODS execute
IMPORTING client TYPE REF TO if_http_client
expected_status TYPE i
RETURNING VALUE(result) TYPE string
RAISING lcx_openbi.
ENDCLASS.
CLASS lcl_openbi IMPLEMENTATION.
METHOD get_token.
TYPES: BEGIN OF ty_token,
access_token TYPE string,
expires_in TYPE i,
END OF ty_token.
DATA token TYPE ty_token.
DATA(client) = create_client( `/openbi/api/oauth/token` ).
client->request->set_method( if_http_request=>co_request_method_post ).
" The API key id and secret are sent as Basic authentication
client->authenticate( username = gc_api_key_id
password = gc_api_key_secret ).
client->request->set_header_field( name = 'Content-Type'
value = 'application/x-www-form-urlencoded' ).
client->request->set_cdata( `grant_type=client_credentials&scope=openbi.job.execute` ).
DATA(response) = execute( client = client expected_status = 200 ).
/ui2/cl_json=>deserialize( EXPORTING json = response
CHANGING data = token ).
IF token-access_token IS INITIAL.
RAISE EXCEPTION TYPE lcx_openbi
EXPORTING message = |No access token in the response: { response }|.
ENDIF.
result = token-access_token.
ENDMETHOD.
METHOD raise_event.
" With ?detached the server answers 202 right away and runs the jobs in the background
DATA(client) = create_client( `/openbi/api/batchjobevents/execute?detached` ).
client->request->set_method( if_http_request=>co_request_method_post ).
client->request->set_header_field( name = 'Authorization'
value = |Bearer { token }| ).
client->request->set_header_field( name = 'Content-Type'
value = 'application/json' ).
client->request->set_cdata( body ).
execute( client = client expected_status = 202 ).
ENDMETHOD.
METHOD create_client.
cl_http_client=>create_by_url( EXPORTING url = gc_server && path
IMPORTING client = result
EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE lcx_openbi
EXPORTING message = |Cannot create an HTTP client for { gc_server }{ path }|.
ENDIF.
" A background job must never wait for a logon popup
result->propertytype_logon_popup = if_http_client=>co_disabled.
ENDMETHOD.
METHOD execute.
client->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc = 0.
client->receive( EXCEPTIONS OTHERS = 1 ).
ENDIF.
IF sy-subrc <> 0.
client->get_last_error( IMPORTING message = DATA(error) ).
client->close( EXCEPTIONS OTHERS = 1 ).
RAISE EXCEPTION TYPE lcx_openbi EXPORTING message = error.
ENDIF.
client->response->get_status( IMPORTING code = DATA(status) ).
result = client->response->get_cdata( ).
client->close( EXCEPTIONS OTHERS = 1 ).
IF status <> expected_status.
RAISE EXCEPTION TYPE lcx_openbi
EXPORTING message = |HTTP { status }: { result }|.
ENDIF.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA(openbi) = NEW lcl_openbi( ).
TRY.
DATA(token) = openbi->get_token( ).
openbi->raise_event(
token = token
body = |\{"eventName":"ETL_DONE","eventValue":"SALES",| &&
|"parameters":\{"LOADDATE":"{ sy-datum DATE = ISO }"\}\}| ).
WRITE / 'Event raised, the batch jobs run in the background.'.
CATCH lcx_openbi INTO DATA(error).
" Type E cancels the background job, so a process chain sees the failure
MESSAGE error TYPE 'E'.
ENDTRY.
Notes on the code:
- Every call is checked.
sendandreceivereport failures through classic exceptions, and an HTTP error such as401or403is not an exception at all. The report checks both and stops with the server's answer instead of sending an empty token on. - No logon popup. Without
co_disabled, a401makes the HTTP client wait for a popup that a background job can never answer. - The token response is read into a declared structure.
/UI2/CL_JSONneeds a target with a known type, andget_statusreturns the code throughIMPORTING, not as a return value. - The body is a string template. Literal braces are escaped as
\{and\}; an expression between unescaped braces is inserted, here today's date in ISO format such as2026-10-29. Values that may contain"or\must be escaped for JSON, for example with/UI2/CL_JSON=>SERIALIZE. - Failures cancel the job.
MESSAGE ... TYPE 'E'marks the background job as cancelled, so a process chain or job monitor notices.
Troubleshooting:
- An "Enter Logon Data" popup when the report runs in SE38 comes from the SAP HTTP client, not from openbi. SAP shows it whenever the server answers
401. The Resource field is the URL that was called. openbi answers401only when the Bearer token is missing or invalid. A wrong API key id or secret returns400 invalid_clientfrom the token call instead. Setpropertytype_logon_popuptoco_disabledas above to see the real status and response. - An SM59 destination is not required.
cl_http_client=>create_by_urlworks without one. A type G destination is still a good place for the proxy, the SSL settings and the secret. 403means the key's user, or the key's scope, lacksopenbi.job.execute.
The legacy URLs still work and need a logged-in user as well:
/openbi/jobs/batchjob.biex?id=XXXruns one batch job./openbi/eventhandler/batchjob.biex?event=XXX&eventvalue=YYYruns every batch job registered for the event.
Any other query parameter is passed to the job as a parameter.
See Batch Jobs for the system batch jobs.
Health Checks
The open bi server reports its own health over HTTP, for load balancers, container orchestrators (Docker, Kubernetes) and monitoring tools. This is separate from the status endpoint status.biex, which is a detailed report for administrators on the config port.
| Endpoint | Authentication | Answers |
|---|---|---|
/health/live |
none | 200 Healthy as long as the server process answers HTTP. It runs no checks. |
/health/ready |
none | 200 Healthy, 200 Degraded or 503 Unhealthy, from the checks below. |
/openbi/api/system/health |
OAuth token with the openbi.system.read permission |
The result of every check as JSON. |
- The two probes are available on every port of the server: the HTTP, the HTTPS and the config port.
- The response body of a probe is only the status word. Which check failed and why is only shown by
/openbi/api/system/health, because the details (certificate thumbprints, drives, cluster nodes) are not meant for everyone who can reach the server. - Responses are never cached.
Which one to use
Liveness (/health/live) answers the question "would a restart help?". It does not check the database, so an outage of the database does not make every server restart at the same time.
Readiness (/health/ready) answers the question "should this server get requests right now?".
| Used by | Endpoint |
|---|---|
| Load balancer or reverse proxy (F5, HAProxy, nginx, Traefik, IIS ARR, ...) | /health/ready |
Docker HEALTHCHECK, Docker Swarm |
/health/live |
| Kubernetes liveness probe | /health/live |
| Kubernetes readiness probe | /health/ready |
| Monitoring (Zabbix, PRTG, Nagios, ...) | /health/ready, or /openbi/api/system/health to see which check failed |
Docker and Docker Swarm replace or restart a container that is reported unhealthy. Using /health/ready there would restart every open bi container during a database outage, so use /health/live. The server only starts to listen once its database migration, license check and configuration have finished, so a successful /health/live also means the startup is complete.
Kubernetes example:
livenessProbe:
httpGet:
path: /health/live
port: 9091
periodSeconds: 30
readinessProbe:
httpGet:
path: /health/ready
port: 9091
periodSeconds: 10
startupProbe:
httpGet:
path: /health/live
port: 9091
periodSeconds: 10
failureThreshold: 30 # the first start migrates the database
Checks
| Check | Unhealthy | Degraded |
|---|---|---|
database |
The repository database does not answer within 10 seconds. | - |
license |
- | The license has expired, or expires within 30 days. |
certificate |
- | The HTTPS certificate has expired, is not valid yet, or expires within 30 days. |
cluster |
- | A server listed in CLUSTER_NODES is not connected. |
diskspace |
- | Less than 1 GiB or 5 % is free on a drive the server writes to. |
Only the database can make a server unhealthy. Everything else is reported as degraded, which is still a 200 for /health/ready:
- A license or certificate is usually the same on every server. Failing readiness because of it would take all servers out of the load balancer at the same moment.
- The license is only checked when the server starts. A running server keeps working after the license expires, but it will not start again until the license is renewed, see License.
- The certificate is only checked when a port serves HTTPS, i.e. the
HttpsPortor the config port withCONFIG_USE_HTTPS. A renewed certificate is picked up within an hour without a restart, see HTTPS. - A server that lost the connection to the other cluster nodes still serves requests; it only no longer receives the changes made on the others. Right after a start the check is degraded for a few seconds until the connections are established.
- The disk check looks at the drives of the installation directory, the
CONTENT_DIRECTORYand the temp directory. Network shares are not checked.
Degraded is meant as an alert for whoever watches the monitoring.
Connections to data sources (SAP BW, SAP HANA, databases, ...) and the SMTP server are not checked on purpose: one unavailable source system would take every open bi server out of the load balancer, including for all the content that does not need that system.
Details
GET /openbi/api/system/health runs the same checks and returns each result. It needs a bearer token with the openbi.system.read permission (see Authentication) and always answers 200; the status is in the body.
{
"status": "Degraded",
"durationMs": 12.4,
"checks": [
{ "name": "database", "status": "Healthy", "description": "The repository database answers.", "durationMs": 11.9 },
{ "name": "license", "status": "Healthy", "description": "The license is valid until 2026-12-31.", "durationMs": 0.1 },
{ "name": "certificate", "status": "Degraded", "description": "The HTTPS certificate E256CC995CDFBDF15DDCAFB88F32936434A236E8 expires on 2026-11-02 14:09 UTC.", "durationMs": 0.1 },
{ "name": "cluster", "status": "Healthy", "description": "No cluster is configured.", "durationMs": 0.1 },
{ "name": "diskspace", "status": "Healthy", "description": "C:\\ 35.5 GiB of 463.9 GiB free.", "durationMs": 0.4 }
]
}
The open bi REST client provides GetSystemHealthAsync, GetHealthLiveAsync and GetHealthReadyAsync in the BiExcellence.OpenBi.Api.SystemInfo namespace.
Configurator
The overview page of the Configurator shows the health of the connected server. Users with the openbi.system.read permission see every check; other users see the overall status only.
Status
/openbi/xmlprovider/status.biex returns the current status of the open bi server: version, uptime, memory and CPU usage, license, certificate, open sessions, loaded modules, running batch jobs and the hardware. It is only available through the config port. It answers as JSON when requested with Accept: application/json, otherwise as HTML.
For load balancers and monitoring use the Health Checks instead.
Legacy Endpoints
The endpoint for the legacy command API is /openbi/xmlprovider/mobile.biex. New integrations should use the REST API.