openobserve-api
Low-level REST client for the OpenObserve API, 100% generated
by openapi-generator (-g ruby-nextgen) from the instance's
OpenAPI spec. A generic gem with no business logic: multi-instance,
Faraday, Zeitwerk,
per-resource namespaced sub-clients.
- Verified target: OpenObserve 0.91.4 (OpenAPI 3.1.0, 150 paths, 261 schemas, 33 tags).
- Pinned and committed spec:
versions/openobserve-rest.v0.91.4.json, served by the instance itself on/api-doc/openapi.json(Swagger UI on/swagger, gated byZO_SWAGGER_ENABLED, which defaults totrue).
⚠️
lib/is entirely generated — never edit it by hand. Every change goes through the spec +mise run generate. The only hand-maintained files are listed in.openapi-generator-ignore(README, CLAUDE.md,mise.toml,versions/,.github/,.gitignore,spec/e2e/,docker-compose.e2e.yml).
Installation
# Gemfile
gem 'openobserve-api', git: 'https://github.com/jbox-web/openobserve-api.git'
bundle install
Configuration (environment variables)
Secrets are never hard-coded or committed — only supplied through the environment:
| Variable | Purpose |
|---|---|
OPENOBSERVE_BASE_URL |
e.g. https://openobserve.example.org |
OPENOBSERVE_USER |
account e-mail |
OPENOBSERVE_PASSWORD |
account password |
Usage
A multi-instance Client carries the base URL and the credential; sub-clients are namespaced
per resource:
require 'openobserve-api'
# pack('m0') is strict Base64 from core Ruby — no `base64` stdlib dependency.
credential = ["#{ENV.fetch('OPENOBSERVE_USER')}:#{ENV.fetch('OPENOBSERVE_PASSWORD')}"].pack('m0')
client = OpenObserve::Api::Client.new(
base_url: ENV.fetch('OPENOBSERVE_BASE_URL'),
api_key: "Basic #{credential}"
)
response = client.api.summary(org_id: 'default')
response.status # => 200
response.data # => Hash
Authentication — pass Basic through api_key:
The spec declares two security schemes, BasicAuth and Authorization, but no operation
references BasicAuth: all 210 authenticated operations declare Authorization only, an
apiKey sent as a raw Authorization header. The transport applies exactly the schemes an
operation declares, so Configuration#username / #password are never used against this
spec — they are dead options here.
The server does expect HTTP Basic. Build the header value yourself and hand it to api_key:,
as above. A long-lived token works the same way — it is the raw header value either way.
Sub-client shape — mind the api hop
Sub-clients follow the URL segments, not the OpenAPI tags. Every OpenObserve route lives
under /api/, so it lands on the api sub-client — one hop more than you may expect. Client
exposes six sub-clients, one per distinct first URL segment:
client.api # /api/... — 44 operations, the bulk of the API
client.healthz.list # GET /healthz
client.rum.v1 # /rum/v1/{org_id}/...
client.short.short # /short/{org_id}/short/{short_id}
client.service_streams # /{org_id}/service_streams
client._well_known # /.well-known/oauth-authorization-server
client.api.summary(org_id: 'default') # GET /api/{org_id}/summary
client.api.organizations # GET /api/organizations
client.api.clusters # GET /api/clusters
This is a deliberate trade-off: the pinned spec is committed verbatim, exactly as the server
publishes it. Rewriting paths to flatten the namespace would break the ten routes that live
outside /api, and would make the regenerate workflow compare against a patched artefact
instead of the real thing.
Reaching the deeper resources
Deeper URL segments generate their own classes (Alerts, Streams, Dashboards, Folders,
Functions, Kv, Pipelines, Reports, Roles, Savedviews, SearchJobs,
ServiceAccounts, Traces, Users, V1, V2, …) but the generator emits no accessor for
them on their parent — the same is true of dolibarr-api (Dolibarr::Api::Invoices::Lines).
Instantiate them with the client's connection:
alerts = OpenObserve::Api::Api::Alerts.new(client.connection)
alerts.templates(org_id: 'default') # GET /api/{org_id}/alerts/templates
streams = OpenObserve::Api::Api::Streams.new(client.connection)
streams.list(org_id: 'default') # GET /api/{org_id}/streams
v2 = OpenObserve::Api::Api::V2.new(client.connection)
v2.alerts(org_id: 'default') # GET /api/v2/{org_id}/alerts
Wrapping those in a friendlier facade is the job of a higher-level gem — this one stays a generated transport with no hand-written code.
Ingest, then search
client.api._json(
org_id: 'default',
stream_name: 'canary',
body: [{ level: 'info', message: 'hello' }]
)
client.api._search(
org_id: 'default',
search_sql_request: OpenObserve::Api::Models::SearchSQLRequest.new(
query: OpenObserve::Api::Models::SearchQuery.new(
sql: 'SELECT * FROM canary',
start_time: (Time.now.to_i - 3600) * 1_000_000, # microseconds
end_time: (Time.now.to_i + 60) * 1_000_000,
from: 0,
size: 10
)
)
)
Timestamps are microseconds since the epoch, and ingestion is not synchronous — a freshly ingested row needs a moment before it is searchable (see the e2e canary, which polls instead of sleeping).
Regeneration
mise run generate # purge lib/ + generate from the pinned spec (-g ruby-nextgen)
mise run build # generate + format
mise run dev:spec # spec suite
Generator.
ruby-nextgenis upstream in openapi-generator since 7.24.0. The version is pinned byOPENAPI_GENERATOR_VERSIONinmise.tomland cross-checked by thegeneratetask. Install it locally withbrew install openapi-generator; CI downloads the released jar — see.github/workflows/regenerate.yml.
Unlike a fully clean generation, this spec makes the generator emit one autocorrectable
RuboCop offense, so mise run format is not a no-op here: it runs bundle exec rubocop -A lib,
which is why bin/rubocop reports zero offenses. Dependencies must therefore be installed
before mise run build.
To bump the pinned OpenObserve version, fetch the spec from that release and regenerate:
docker run -d --name oo-spec -p 127.0.0.1:7005:5080 \
-e ZO_ROOT_USER_EMAIL=root@example.com -e ZO_ROOT_USER_PASSWORD='Complexpass#123' \
public.ecr.aws/zinclabs/openobserve:v<version>
curl -sf http://localhost:7005/api-doc/openapi.json -o versions/openobserve-rest.v<version>.json
docker rm -f oo-spec
# then bump OPENOBSERVE_VERSION in mise.toml and run `mise run build`
Tests
- Generated network-free unit specs (model round-trips, sub-client resolution).
- An e2e canary (
mise run e2e) that spins up a disposable OpenObserve, ingests a log line and searches it back, then tears everything down. Opt-in: the spec self-gates onOPENOBSERVE_E2E.
License
MIT — see LICENSE.