TypeScript/JavaScript API Client
@owox/api-client is a TypeScript/JavaScript package for calling the OWOX Data Marts API from custom scripts, internal tools, automation, and AI agent workflows.
Use owox-ctl for terminal commands. Use @owox/api-client for code-level integrations.
Building a plugin? The same API-client abstractions are available through
ctx.owox, supplied by@owox/plugin-sdk. A plugin does not install@owox/api-clientdirectly and does not create or receive an API key. See the plugin authoring guide.
Install for an external application or script
Section titled “Install for an external application or script”npm install @owox/api-clientAuthenticate an external application or script
Section titled “Authenticate an external application or script”Direct use of @owox/api-client outside a
plugin requires an API key. See
API Keys.
Basic usage
Section titled “Basic usage”Set credentials as environment variables:
export OWOX_API_KEY=owox_key_xxxThen create a client:
import { OWOXApiClient } from '@owox/api-client';
const client = new OWOXApiClient({ apiKey: process.env.OWOX_API_KEY!,});
const dataMarts = await client.dataMarts.list();
console.log(dataMarts);Use low-level API methods
Section titled “Use low-level API methods”Prefer typed resources such as dataMarts, project, and reports. Use the low-level
methods only as an escape hatch for an API-key-compatible endpoint that does not yet have
a typed abstraction:
| Method | Purpose |
|---|---|
getJson<T>(path, query?) | GET a JSON response. |
postJson<T>(path, body, accept?) | POST a JSON body, optionally with an Accept value. |
putJson<T>(path, body) | PUT a JSON body. |
patchJson<T>(path, body) | PATCH a JSON body. |
deleteJson<T = void>(path) | DELETE and read an optional JSON response. |
getStream(path, query?) | GET a streaming Response. |
The generic is caller-owned TypeScript typing only: a low-level call does not validate the response at runtime. Validate the returned data yourself before using it. Typed resources remain preferred because they model and validate their responses.
All paths must be root-relative /api/... paths, and each path argument is limited to
2,048 characters. The client refuses unsafe or redirecting paths, including absolute URLs
and paths that resolve away from the API origin. API-key clients exchange and attach
authentication internally; do not add credentials to a path, query, or body.
Custom transports built against the previous interface remain compatible and may add PATCH and
DELETE support independently. Existing resources continue to work without those methods; calling
an unsupported new method rejects with OWOXConfigError.
type Renamed = { id: string; title: string };
const renamed = await client.patchJson<Renamed>('/api/example-resource/item-123', { title: 'Updated title',});Use an explicit response type only when the endpoint returns JSON that you will validate:
type Deleted = { deleted: true };
const deleted = await client.deleteJson<Deleted>('/api/example-resource/item-123');For an endpoint with an empty or 204 No Content DELETE response, omit the generic; it
defaults to void:
await client.deleteJson('/api/example-resource/item-123');Get auth context
Section titled “Get auth context”Use auth.getContext() to validate the configured API key and return the project and member context
resolved from the exchanged access token.
const context = await client.auth.getContext();
console.log(context.project.id);console.log(context.project.title);console.log(context.member.email);Manage project settings
Section titled “Manage project settings”Use project.getSettings() to read the current project’s settings. Project members can read the
settings available to their role.
const settings = await client.project.getSettings();
console.log(settings.description);Project admins can update the project description used as project-specific business context. The
description can contain up to 10,000 characters. Pass null to clear it.
await client.project.updateDescription( 'Use net revenue after refunds for monthly performance reporting.');
await client.project.updateDescription(null);Check project setup progress
Section titled “Check project setup progress”Use project.getSetupProgress() to inspect the current project member’s merged project- and
user-scoped onboarding state. The response includes the API contract version, persisted steps
schema version, completion percentage, and per-step completion details.
const setupProgress = await client.project.getSetupProgress();
console.log(setupProgress.progress);
for (const [step, state] of Object.entries(setupProgress.steps)) { console.log(step, state.done, state.completedAt);}Read project run history
Section titled “Read project run history”Use runs.list() to inspect historical Data Mart executions visible to the current
project member. The API key must resolve to a member with viewer access. Administrators can see
runs for every non-deleted Data Mart in the project. Owners see their owned Data Marts. Editors
can also see shared Data Marts available for reporting or maintenance, subject to configured
context access; viewers can see shared Data Marts available for reporting, subject to the same
context-access filter.
Pass optional limit and offset values to page through the newest-first history. limit defaults
to 100, floors finite fractions, falls back to 100 for non-finite or non-positive values, and caps
at 100. offset defaults to 0, floors finite fractions, falls back to 0 for non-finite or
non-positive values, and caps at 100,000. The response has no total or next-page marker. Prefer a
limit from 1 through 100, increment offset by the number of returned runs, and stop when a page
contains fewer runs than the server-normalized effective limit or the next offset would exceed
100,000. Because new runs can shift newest-first offset pages while a consumer is paging, deduplicate
by run.id when walking multiple pages.
const history = await client.runs.list({ limit: 50, offset: 0 });
for (const run of history.runs) { const author = run.createdByUser?.fullName ?? run.createdByUser?.email ?? 'System or unavailable author';
console.log(run.dataMart.title, run.type, run.status, author, run.finishedAt);}createdByUser is the run author field. It is always present, but can be null when the run has no
creator ID or the corresponding user projection is unavailable. When an author is available,
createdByUser.userId is required; fullName, email, and avatar are optional and can also be
null.
definitionRun is always present but can be null when a historical definition snapshot is
unavailable.
qualitySummary is either the compact Data Quality summary or null. It is optional for
compatibility with older self-hosted deployments that did not return the field.
@owox/api-client validates the response shape, enum values, nested references and author data,
nullable fields, logs and errors, totals, and the backend’s RFC3339 timestamp profile: uppercase
T/Z, seconds from 00 through 59, optional fractional seconds, and valid numeric offsets. It
throws OWOXApiError when the endpoint returns an incompatible payload.
List project insight templates
Section titled “List project insight templates”Use insights.getTemplates() to discover reusable insight definitions across the Data Marts
visible to the current project member. Pass optional limit and offset values to page through
the project-wide list. The API defaults limit to 100, caps it at 100, and caps offset at
100,000; invalid or non-positive values fall back to the defaults.
const templates = await client.insights.getTemplates({ limit: 50, offset: 0 });
for (const template of templates.insights) { console.log(template.dataMart.title, template.title, template.canDelete);}Each result includes the template summary, its Data Mart ID and title, creator metadata when
available, and whether the current member can delete it. This makes the method suitable for
automation and agent workflows that need to find reusable insight definitions without scanning
Data Marts individually. Because the response has no total or next-page marker, choose a limit
from 1 through 100, increment offset by the number of returned templates, and stop when a page
contains fewer items than that limit or the next offset would exceed 100,000. The endpoint cannot
page beyond that maximum offset.
Search project entities
Section titled “Search project entities”Use search.query() to find Data Marts, data storages, and data destinations visible to the
current project member. The server trims surrounding query whitespace and enforces its configured
minimum and maximum query lengths. Pass an optional result limit from 1 through 50, restrict the
search to specific entity types, or exclude draft Data Marts. When omitted, the server’s result
limit is used, all supported entity types are searched, and draft Data Marts may be included. Pass
an empty entityTypes array to preserve an explicit no-types filter and return no matches.
const results = await client.search.query('monthly revenue', { limit: 25, entityTypes: ['DATA_MART', 'DATA_STORAGE'], excludeDrafts: true,});
for (const result of results) { console.log(result.entityType, result.title, result.description, result.finalScore);}Each result includes the entity type and ID, title, nullable description, combined relevance score, keyword score, and a vector score when semantic matching contributed. Search returns an empty array when no visible entity matches. When prompt embeddings are unavailable, Search falls back to keyword matching.
Convert Markdown to HTML
Section titled “Convert Markdown to HTML”Use markdown.parseToHtml() to render Markdown with the same pipeline and styling wrapper used by
the OWOX Data Marts web interface. The method returns the rendered HTML string directly.
const html = await client.markdown.parseToHtml({ markdown: '# Weekly revenue\n\n**Net revenue** after refunds.',});
console.log(html);The method requires viewer access to the API key’s project. Treat the returned HTML according to the same trust and embedding rules as Markdown rendered in the application.
List data marts
Section titled “List data marts”Use dataMarts.list() to read every Data Mart visible to the current project member. Viewer
access is required. The client follows each server nextOffset until it is null and returns one
flattened OWOXDataMart[]; individual HTTP pages contain at most 1,000 items.
const dataMarts = await client.dataMarts.list();
for (const dataMart of dataMarts) { console.log(dataMart.id, dataMart.title, dataMart.status, dataMart.storage.title);}Start from a non-negative integer offset or keep only Data Marts with or without business or technical owners:
const unownedDataMarts = await client.dataMarts.list({ offset: 0, ownerFilter: 'no_owners',});Use the exported OWOXDataMartListOptions and OWOXDataMartOwnerFilter types when options are
assembled outside the call.
Each OWOXDataMart contains:
id,title, andstatus(DRAFTorPUBLISHED);storage.typeandstorage.title;- required nullable
description, optional nullabledefinitionType, and optionalconnectorSourceName; - non-negative
triggersCountandreportsCount; - nullable
createdByUser, owner-user arrays, andcontextswithidandname; - RFC 3339
createdAtandmodifiedAtstrings; and availableForReportingandavailableForMaintenanceflags.
The package also exports the nested OWOXDataMartStatus, OWOXDataMartDefinitionType,
OWOXDataMartStorage, OWOXDataMartStorageType, OWOXDataMartUser, and OWOXDataMartContext
types.
The client validates the complete page and nested item shapes before returning data. It rejects an
unknown enum value, malformed user, storage, or context, invalid timestamp, missing required
field, or invalid pagination value with OWOXApiError. Invalid list options are rejected before
authentication or any network request.
Manage Data Mart runs
Section titled “Manage Data Mart runs”Create a Data-Mart-scoped run client with runs.forDataMart(dataMartId). Its start(options) method
starts a manual connector run and requires Technical User access to the Data Mart. Omit the options,
or set runType to INCREMENTAL without data, for an incremental run. To send connector-specific
backfill fields in data, set runType to MANUAL_BACKFILL; connectors without backfill fields can
omit data. The API client rejects data on implicit or explicit incremental runs before
authentication or any network request. The backend HTTP endpoint separately tolerates retained
object-valued data on incremental requests for compatibility with existing run forms. The method
returns the new run ID. The serialized options must not exceed 1 MB.
Data Mart and run IDs must not be blank, dot segments, or contain /, \, or %. The client
rejects invalid scoped IDs before authentication or any network request.
const dataMartRuns = client.runs.forDataMart('data-mart-id');
const { runId } = await dataMartRuns.start({ runType: 'MANUAL_BACKFILL', data: { StartDate: '2026-07-01', EndDate: '2026-07-31', },});Business Users can inspect runs for a Data Mart they can see. The scoped list() method returns a
newest-first page, while get(runId) returns one run and includes full Data Quality detail when the
run is a Data Quality run.
const history = await dataMartRuns.list({ limit: 50, offset: 0,});
const [latest] = history.runs;if (latest) { const run = await dataMartRuns.get(latest.id); console.log(run.status, run.qualitySummary, run.dataQuality);}The existing client.runs.list() method remains the project-wide run history and includes each
run’s Data Mart reference. The scoped dataMartRuns.list() method uses the Data-Mart-specific route
and omits that redundant reference.
The scoped list endpoint defaults to 100 runs when limit is omitted and does not silently cap a
valid caller-provided limit or offset. The client accepts positive safe integer limits and
non-negative safe integer offsets, and rejects invalid list options before authentication or any
network request. The response has no total or next-page marker. Increment offset by the number of
returned runs and stop when a page contains fewer runs than the requested limit. New runs can shift
offset pages, so deduplicate by run.id while paging.
Use the scoped cancel(runId) method to cancel an active connector, standard report, or Data Quality
run. Technical User access is required. The method resolves with no value after the API returns
204 No Content. A cancellable run that is already terminal returns a conflict error; a run type
that does not support cancellation returns a bad-request error.
await dataMartRuns.cancel(runId);The package exports OWOXDataMartRun, OWOXDataMartRunDetail,
OWOXDataMartRunListOptions, OWOXDataMartRunStartOptions, OWOXDataMartRunsResponse,
OWOXDataMartRunsScope, and the run and Data Quality enum and nested-object types. The client
validates response field presence, nullability, enums, RFC 3339 timestamps, totals, author metadata,
compact Data Quality summaries, and full Data Quality detail. An incompatible response throws
OWOXApiError. Scoped list and detail methods normalize Data Quality fields omitted by older
self-hosted deployments to null.
Read the Models canvas
Section titled “Read the Models canvas”Use models.getDataMarts() to read one page of the data marts visible to the current project
member in a storage. Pass the returned nextOffset to request the next page.
const firstPage = await client.models.getDataMarts('storage-id');
if (firstPage.nextOffset !== null) { const nextPage = await client.models.getDataMarts('storage-id', firstPage.nextOffset); console.log(nextPage.items);}Use models.getEdges() to read the visible relationships between those data marts.
const edges = await client.models.getEdges('storage-id');
for (const edge of edges) { console.log(edge.sourceDataMartId, edge.targetDataMartId, edge.joinConditions);}Stream Data Mart rows
Section titled “Stream Data Mart rows”Use dataMarts.traverseData() to stream rows from a published Data Mart without direct storage credentials.
The method returns stream metadata and exposes rowChunks() as the traversal primitive, so callers can append to a cache or write output without loading the full result into memory.
const data = await client.dataMarts.traverseData('dm_123', { column: ['Event Date (local)', 'Revenue: net = USD'], filter: [ { column: 'Event Date (local)', operator: 'gte', value: '2026-01-01' }, { column: 'Country', operator: 'in', value: ['Germany', 'Ukraine'] }, ], sort: [{ column: 'Event Date (local)', direction: 'asc' }], aggregation: [{ column: 'Revenue: net = USD', function: 'SUM' }], dateTrunc: [{ column: 'Event Date (local)', unit: 'MONTH' }], limit: 1000,});
console.log(data.runId);
for await (const rows of data.rowChunks()) { await cache.appendRows(rows);}Call await data.cancel() if you open a traversal and decide not to iterate rowChunks().
The client rejects a successful response whose media type is not application/x-ndjson instead of
attempting to interpret it as row data.
The exported TraverseDataOptions and traversal rule types provide compile-time shape validation
of these controls.
Column selection uses two separate fields:
columns: '*'selects all current Data Mart output columns.columns: '**'selects all columns available to Reports, including joined fields.column: ['Event Date (local)', 'Revenue: net = USD']selects exact column names.column: ['*', '**']selects literal columns named*and**.
Do not pass comma-separated column lists. Column names are opaque strings and may contain commas, equals signs, spaces, quotes, or other symbols.
Use filter and sort arrays with the same rule shapes as report output controls. For normal filters on the streamed output, omit placement or use placement: 'post-join'. Use placement: 'pre-join' only with a resolved blended column name; the rule’s column identifies the joined source to filter before it is joined into the result.
Each filter rule is { column, operator, value? }. The shape of value depends on the operator:
| Operators | value shape | Example |
|---|---|---|
eq, neq, contains, not_contains, starts_with, ends_with, gt, lt, gte, lte, regex, not_regex | Single string or number (boolean conditions use is_true / is_false) | { column: 'Country', operator: 'eq', value: 'Germany' } |
in, not_in | Array of values — match any of / none of the listed values | { column: 'Country', operator: 'in', value: ['Germany', 'Ukraine'] } |
between | { from, to } bounds of the same type | { column: 'Revenue', operator: 'between', value: { from: 100, to: 500 } } |
relative_date | A preset object, see below | { column: 'Date', operator: 'relative_date', value: { kind: 'last_n_days', n: 7 } } |
is_blank, is_not_blank, is_true, is_false | No value | { column: 'Country', operator: 'is_blank' } |
in and not_in take an array of 1 to 500 values, never a comma-separated string — value: 'Germany, Ukraine' is rejected, exactly like comma-separated column lists, because stored values are opaque and may themselves contain commas. All values in one list must be the same type (all strings or all numbers); booleans are not allowed — use is_true / is_false instead. The whole encoded filter parameter is also capped at 8192 base64url characters (about 6 KB of JSON) on the wire, so with long values — UUIDs, URLs — that length cap can bind well before the 500-value cap does; the rejection is then a parameter-length error, not a value-count one. Which operators a given column accepts depends on its field type; the API rejects a mismatched operator with a 400 validation error (code INVALID_OPERATOR_FOR_TYPE) naming the column, its field type, and the rejected operator.
relative_date presets: { kind } with today, yesterday, this_week, last_week, this_month, last_month, this_quarter, last_quarter, or this_year, and { kind, n } with last_n_days, last_n_months, or next_n_days (n is a positive integer, up to 3650). Weeks are ISO weeks (Monday start) on every storage, and last_n_days / next_n_days both include today.
is_blank / is_not_blank match by what a rendered cell shows: on string columns a value is blank when it is NULL, '', or whitespace-only; on every other type it is blank only when NULL. The pre-merge operators is_empty, is_not_empty, is_null, and is_not_null are still accepted for saved configs and older callers, but new rules should use the blank pair.
The negative operators neq, not_contains, not_regex, and not_in are NULL-inclusive: they keep rows where the column is NULL, treating a missing value as “not equal to”. Add a separate is_not_blank rule on the same column to also drop missing values.
To filter an aggregated value after grouping, set the filter rule’s function to the same aggregate function used for that column in aggregation. For example, { column: 'revenue', function: 'SUM', operator: 'gt', value: 1000 } applies a HAVING SUM(revenue) > 1000 condition. The (column, function) pair must match an aggregation rule, and a filter with function cannot use placement: 'pre-join'.
Use aggregation and dateTrunc arrays to group the streamed rows, with the same rule shapes as report output controls. aggregation takes { column, function } rules; function is any report aggregate function — SUM, AVG, MIN, MAX, COUNT, COUNT_DISTINCT, STRING_AGG, ANY_VALUE, or a percentile (P25, P50, P75, P95). dateTrunc takes { column, unit, timeZone? } rules that bucket a date/timestamp dimension by DAY, WEEK, MONTH, QUARTER, or YEAR. Any selected column without an aggregation rule becomes a grouping key. Aggregation and dateTrunc require an explicit column list — they cannot combine with columns: '*' or columns: '**' (a wildcard would group by every column). The streamed row keys are the resolved labels: an aggregated column becomes "<column> | <TOKEN>", where <TOKEN> is an uppercase spreadsheet-style token — most match the function name, but COUNT_DISTINCT becomes COUNTUNIQUE, P50 becomes MEDIAN, STRING_AGG becomes STRINGAGG, and ANY_VALUE becomes ANYVALUE.
List storages
Section titled “List storages”const storages = await client.storages.list();List destinations
Section titled “List destinations”const destinations = await client.destinations.list();Use in AI agents and scripts
Section titled “Use in AI agents and scripts”AI agents can run scripts that use @owox/api-client when they need structured access to OWOX Data Marts from TypeScript or JavaScript.
import { OWOXApiClient } from '@owox/api-client';
const client = new OWOXApiClient({ apiKey: process.env.OWOX_API_KEY!,});
const [dataMarts, storages, destinations] = await Promise.all([ client.dataMarts.list(), client.storages.list(), client.destinations.list(),]);
console.log( JSON.stringify( { dataMarts, storages, destinations, }, null, 2 ));Security notes:
- Do not hard-code API keys in source code.
- Use environment variables or a secret manager.
- Do not commit
.envfiles containing API keys. - Avoid putting API keys in AI agent instructions or prompts.
- Revoke keys that are no longer used.
Authentication behavior
Section titled “Authentication behavior”@owox/api-client parses the owox_key_... value and uses the API key ID and secret inside it to request a short-lived access token.
The access token is kept in memory for the current process and is not persisted.
Error handling
Section titled “Error handling”@owox/api-client exports typed errors for request and authentication failures.
import { OWOXApiError, OWOXAuthError } from '@owox/api-client';
try { const dataMarts = await client.dataMarts.list(); console.log(dataMarts);} catch (error) { if (error instanceof OWOXAuthError) { console.error('Authentication failed'); } else if (error instanceof OWOXApiError) { console.error(`OWOX API request failed: ${error.message}`); } else { throw error; }}Compatibility
Section titled “Compatibility”The same @owox/api-client and OWOX Data Marts server version is supported. Different versions are best effort.