API

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-server and /.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
  • scope is 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 with invalid_scope. This also applies to an unrestricted key: it lists no scopes, so a request to it must not send scope at all.
  • The response contains access_token and expires_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.execute for 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}/execute runs one batch job.
  • POST /openbi/api/batchjobevents/execute runs 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:

  1. A batch job that listens for the event. In the Configurator, the job is set to active, its type is EVENT (or SYSTEM), and it has an event name and an event value, for example ETL_DONE and SALES. An event runs only the active jobs whose event name and event value both match.
  2. 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.
  3. Two calls: one that gets a token with the client_credentials grant, 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 ANONYM in transaction STRUST, otherwise the HTTPS call fails.
  • If the SAP system reaches the server only through a proxy, enter it in transaction SICF under 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. send and receive report failures through classic exceptions, and an HTTP error such as 401 or 403 is 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, a 401 makes 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_JSON needs a target with a known type, and get_status returns the code through IMPORTING, 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 as 2026-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 answers 401 only when the Bearer token is missing or invalid. A wrong API key id or secret returns 400 invalid_client from the token call instead. Set propertytype_logon_popup to co_disabled as above to see the real status and response.
  • An SM59 destination is not required. cl_http_client=>create_by_url works without one. A type G destination is still a good place for the proxy, the SSL settings and the secret.
  • 403 means the key's user, or the key's scope, lacks openbi.job.execute.

The legacy URLs still work and need a logged-in user as well:

  • /openbi/jobs/batchjob.biex?id=XXX runs one batch job.
  • /openbi/eventhandler/batchjob.biex?event=XXX&eventvalue=YYY runs 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 HttpsPort or the config port with CONFIG_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_DIRECTORY and 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.