Skip to content

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-client directly 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”
Terminal window
npm install @owox/api-client

Authenticate 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.

Set credentials as environment variables:

Terminal window
export OWOX_API_KEY=owox_key_xxx

Then 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);

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:

MethodPurpose
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');

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);

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);

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);
}

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.

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.

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.

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.

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, and status (DRAFT or PUBLISHED);
  • storage.type and storage.title;
  • required nullable description, optional nullable definitionType, and optional connectorSourceName;
  • non-negative triggersCount and reportsCount;
  • nullable createdByUser, owner-user arrays, and contexts with id and name;
  • RFC 3339 createdAt and modifiedAt strings; and
  • availableForReporting and availableForMaintenance flags.

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.

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.

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);
}

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:

Operatorsvalue shapeExample
eq, neq, contains, not_contains, starts_with, ends_with, gt, lt, gte, lte, regex, not_regexSingle string or number (boolean conditions use is_true / is_false){ column: 'Country', operator: 'eq', value: 'Germany' }
in, not_inArray 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_dateA preset object, see below{ column: 'Date', operator: 'relative_date', value: { kind: 'last_n_days', n: 7 } }
is_blank, is_not_blank, is_true, is_falseNo 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.

const storages = await client.storages.list();
const destinations = await client.destinations.list();

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 .env files containing API keys.
  • Avoid putting API keys in AI agent instructions or prompts.
  • Revoke keys that are no longer used.

@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.

@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;
}
}

The same @owox/api-client and OWOX Data Marts server version is supported. Different versions are best effort.