Skip to main content

Node SDK reference

@acruxcoreai/sdk is the Node/TypeScript SDK for AcruxCore. It renders stored prompts, calls the gateway (with streaming, tools, and structured output), runs the tool-calling loop, reports and reads traces, and manages the tool catalog. Every method below has a 1:1 Python counterpart in acruxcore — only the casing and a few option-object shapes differ.

npm install @acruxcoreai/sdk

Construct the client

import AcruxCore from '@acruxcoreai/sdk';

// Reads ACRUXCORE_API_KEY and ACRUXCORE_BASE_URL from the environment.
const hub = new AcruxCore();

Create one instance at process startup and reuse it. The render cache is a module-level singleton sized by the first constructor.

OptionTypeDefaultNotes
apiKeystringprocess.env.ACRUXCORE_API_KEYThrows MISSING_API_KEY if neither is set.
baseUrlstringprocess.env.ACRUXCORE_BASE_URLThrows MISSING_BASE_URL if neither is set.
cacheTtlnumber (ms)60000Render cache freshness window. 0 disables caching (and serve-stale).
maxCacheSizenumber500Max LRU entries. Set by the first constructor.
maxRetriesnumber1Retries on transient failure (2 total attempts).
retryIntervalnumber (ms)500Delay between retries.
providerProviderConfigClient-level BYO default; overridden by a per-call provider.

Errors throw an acruxcoreError with a machine-readable code — see Error codes.

prompts.render(name, alias, variables?)

Render a stored prompt by name + alias into templated messages, plus the tools attached to that version. Cached per (apiKey, name, alias, variables) with stale-while-revalidate: a fresh hit returns immediately, a stale hit returns immediately and refreshes in the background, a cold miss fetches.

const { messages, tools, model, versionId } = await hub.prompts.render(
'support-reply',
'production',
{ company: 'Acme', customer_message: 'Order #123 is late' },
);
ParameterTypeRequiredNotes
namestringyesPrompt slug (not id).
aliasstringyese.g. 'production', 'staging'.
variablesRecord<string, unknown>noTemplate variables. Defaults to {}.

Returns { messages, tools, model, versionId, versionNumber, variables }. model is the version's bound default (or null); variables echoes back what you called with. Pass versionId as promptVersionId and variables as variables to gateway.chat()/gateway.runToolLoop() — the pair is what gives a trace prompt lineage and lets feedback on it become an evaluation dataset example. gateway.runPromptWithTools(rendered) does both for you. Throws MISSING_VARIABLES if the template needs a variable you did not supply.

Prompt lifecycle (hub.prompts)

prompts.list(options?)

List prompts for the team, newest first.

const page = await hub.prompts.list({ search: 'support', limit: 10 });
ParameterTypeNotes
options.searchstringFree-text search on name.
options.pagenumber1-based page.
options.limitnumberPage size.

Returns { data: PromptSummary[], total, page, limit }.

prompts.get(id)

Fetch one prompt by id.

const prompt = await hub.prompts.get(promptId);

Returns PromptDetail ({ id, name, description, versionCount, createdAt, updatedAt }).

prompts.create(input)

Create a new prompt shell. Commit a version with commitVersion to give it content.

const prompt = await hub.prompts.create({ name: 'support-bot', description: 'Customer support' });
FieldTypeRequiredNotes
namestringyesUnique per team.
descriptionstringnoHuman-readable.

Returns PromptDetail.

prompts.update(id, input)

Update a prompt's name and/or description. Does not touch versions.

const updated = await hub.prompts.update(promptId, { description: 'v2 description' });

Returns PromptDetail.

prompts.delete(id)

Delete a prompt and every version/alias under it. Returns void.

await hub.prompts.delete(promptId);

prompts.commitVersion(promptId, input)

Commit a new immutable version for a prompt. The first commit auto-creates production and staging aliases.

const version = await hub.prompts.commitVersion(promptId, {
messages: [
{ role: 'system', content: 'You are a support agent.' },
{ role: 'user', content: '{{question}}' },
],
model: 'gpt-4o-mini',
});
FieldTypeRequiredNotes
messagesMessage[]yesChat messages (may contain {{variables}}).
modelstringnoDefault model for this version.
tools{ toolId: string }[]noAttach catalog tools (max 64).

Returns VersionDetail ({ id, versionNumber, messages, model, tools?, aliases? }). aliases is present only on the first version.

prompts.listVersions(promptId, options?)

List a prompt's versions, newest first. Items omit messages — use getVersion for full content.

const versions = await hub.prompts.listVersions(promptId);

Returns { data: VersionSummary[], total, page, limit }.

prompts.getVersion(promptId, versionNumber)

Fetch one version with its full messages.

const v = await hub.prompts.getVersion(promptId, 1);

Returns VersionDetail.

prompts.diff(promptId, from, to)

Diff two versions.

const diff = await hub.prompts.diff(promptId, 1, 2);

Returns DiffResult ({ changes: ChangeEntry[] }).

prompts.promoteAlias(promptId, alias, versionNumber)

Point an alias (e.g. 'production') at a specific version. Creates the alias if it doesn't exist.

const alias = await hub.prompts.promoteAlias(promptId, 'production', 3);

Returns AliasDetail ({ alias, versionNumber, promptId }).

prompts.listAliases(promptId)

Read every alias on a prompt and the version each one points at — the read half of promoteAlias, for a deploy check or a CI guard asking "which version is production on right now?".

for (const a of await hub.prompts.listAliases(promptId)) {
console.log(a.alias, '->', `v${a.versionNumber}`);
}

Returns AliasDetail[]. Empty for a prompt with no committed version, since the first version is what mints the aliases. Added in 0.10.0.

prompts.exportVersion(promptId, versionNumber)

Export a version for portability (JSON blob).

const exported = await hub.prompts.exportVersion(promptId, 1);

Returns ExportedPromptVersion.

prompts.importPrompt(exportData)

Import an exported prompt as a new prompt with one version.

const imported = await hub.prompts.importPrompt(exported);

Returns ImportPromptResult ({ promptId, promptName, versionNumber }).

prompts.tracesForVersion(promptId, versionNumber, options?)

List traces that used a specific prompt version.

const traces = await hub.prompts.tracesForVersion(promptId, 1, { limit: 10 });

Returns { data: TraceSummary[], total, page, limit }.

gateway.chat(options)

One gateway completion at POST /gateway/chat/completions — no tool-dispatch loop. If the model returns tool_calls, they are handed back raw on result.message.tool_calls; use gateway.runToolLoop to dispatch them.

const { content, usage, gateway } = await hub.gateway.chat({
model: 'support-model',
messages,
temperature: 0.2,
});

For streaming, use gateway.stream() which returns an async iterable of chunks:

const stream = await hub.gateway.stream({ model: 'support-model', messages });
for await (const chunk of stream) process.stdout.write(chunk.delta.content ?? '');
ParameterTypeRequiredNotes
modelstringyesModel public name.
messagesMessage[]yesChat messages.
toolsToolDefinition[]noInline OpenAI-shaped tool definitions.
toolRefs{ name; alias? }[]noCatalog tool references (resolved server-side).
toolChoice'auto' | 'none' | 'required' | { type:'function'; function:{name} }noHow the model uses tools.
responseFormatResponseFormatnoStructured output. Mutually exclusive with tools/toolChoice.
temperaturenumbernoSampling temperature.
maxTokensnumbernoMax completion tokens.
streambooleannoReturn an async iterable of ChatChunk instead of ChatResult.
providerProviderConfignoPer-call BYO override.
promptVersionIdstringnoFrom prompts.render().versionId; stamped on the trace span.
variablesRecord<string, unknown>noFrom prompts.render().variables. Recorded on the span, never sent to a BYO provider. Send it whenever promptVersionId is set — without it the run cannot seed a dataset. With no promptVersionId, a gateway call renders {{ placeholders }} in your messages with these instead.
traceboolean | { traceId?; sessionId? }noDefault true on the BYO path, false on the gateway path.

Returns ChatResult ({ id, model, content, message, finishReason, usage?, gateway }) or, when streaming via gateway.stream(), an AsyncGenerator<ChatChunk>. gateway carries requestId, provider, model, costUsd, cache, traceId, and spanRef read from the gateway's x-gateway-* headers.

note

responseFormat and tools/toolChoice/toolRefs cannot ride the same gateway request — the gateway returns a 400. To get a typed answer that also calls tools, pass both to gateway.runToolLoop; the SDK gathers with tools, then makes one shaping call with the format.

gateway.runToolLoop(options)

The full agent loop: calls the model, runs the tools it asks for, appends the results, and repeats until the model stops calling tools or maxIterations is hit. Tools requested in one turn run concurrently.

import { acrux } from '@acruxcoreai/sdk';

const getWeather = acrux.tool(
{ name: 'get_weather', parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] } },
async ({ city }) => ({ tempC: 21 }),
);

const { content, traceId } = await hub.gateway.runToolLoop({
model: 'agent-model',
messages: [{ role: 'user', content: 'Weather in Lahore?' }],
tools: [getWeather],
});
ParameterTypeRequiredNotes
modelstringyesModel public name.
messagesMessage[]yesSeed messages.
toolsAcruxTool[]noTools from acrux.tool. Run locally; reconciled with the catalog.
toolDefsToolDefinition[]noRaw OpenAI definitions, sent inline; route to dispatch.
toolRefs{ name; alias? }[]noCatalog refs. http executor runs on the platform.
clientToolsRecord<string, (args) => unknown>noCatalog tool name → the function that runs it, for client executors. Writes nothing to the catalog; keeps the binding's alias or pin. Called with one arguments object.
dispatch(name, args) => unknown | Promise<unknown>noFallback runner. Required for toolDefs, and the fallback for a client ref with no matching tools or clientTools entry.
syncbooleannoReconcile tools with the catalog first. Default true.
maxIterationsnumbernoMax round-trips. Default 10.
temperaturenumbernoSampling temperature.
maxTokensnumbernoMax completion tokens.
responseFormatResponseFormatnoShapes the final answer; may be combined with tools (gather + shape).
traceboolean | { traceId?; name?; sessionId? }noDefault true.
providerProviderConfignoPer-call BYO override.
promptVersionIdstringnoStamped on every llm span this loop records.
variablesRecord<string, unknown>noStamped alongside it. Same field as on chat().

Returns RunToolLoopResult ({ content, messages, iterations, stoppedAtLimit, traceId? }). Throws MISSING_DISPATCH before the first model call if a tool has no runner — the message names the tool and, when clientTools was passed, lists the keys it held.

gateway.runPromptWithTools(rendered, options?)

The same loop, with everything it needs taken from a render result: the version's bound model, the rendered messages, the tools bound to this prompt alias, and the version id that stamps trace lineage. Your code adds the user's turn and the tools it runs itself.

const rendered = await hub.prompts.render('travel-planner', 'production', { today });
const messages = [...rendered.messages, { role: 'user', content: question }];

const result = await hub.gateway.runPromptWithTools(rendered, {
messages,
clientTools: { search_flights: searchFlights },
});
ParameterTypeRequiredNotes
renderedRenderResultyesFrom prompts.render. Positional.
modelstringnoOverrides the version's bound model.
messagesMessage[]noOverrides the rendered messages.
toolRefs{ name; alias? }[]noOverrides the prompt's bindings entirely. [] runs the prompt with no tools.
clientToolsRecord<string, (args) => unknown>noThe prompt's client-executor tools, keyed by name. Its http tools need no entry — the platform runs them.
streambooleannotrue returns the same event stream as runToolLoop.

Every other option of runToolLoop is accepted and passes straight through. A prompt with no tools bound still runs, as a plain completion.

Returns the same RunToolLoopResult, or an event stream when stream: true. Throws VALIDATION_ERROR when the version has no bound model and no model was passed.

traces.ingest(input, options?)

Report a trace (a group of spans) to AcruxCore. A single-trace convenience over the batch endpoint — omit traceId to mint a new trace, pass one to append.

const { traceId } = await hub.traces.ingest({
name: 'rag-pipeline',
spans: [
{ spanId: 'retrieval-1', name: 'vector-search', kind: 'retrieval', status: 'ok',
startTime: '2026-01-01T00:00:00Z', endTime: '2026-01-01T00:00:01Z',
input: { query: 'shipping policy' }, output: { hits: 4 } },
],
});
ParameterTypeRequiredNotes
traceIdstringnoOmit to mint a new trace; pass one to append.
sessionIdstringnoGroups traces into a session.
namestringnoTrace name.
capturePayloadsbooleannoForce payload capture on for this trace.
tagsstring[]noSet on creation; merged (union) on append.
metadataRecord<string, unknown>noSet on creation; shallow-merged on append.
spansIngestSpan[]yesThe spans to report.

Returns { traceId }.

Reporting without waiting

wait: true (the default) awaits the POST: errors throw at the call site and traceId is the server's. { wait: false } buffers the trace and returns immediately, so instrumenting a retrieval or rerank step costs no round trip:

const { traceId } = await hub.traces.ingest({ name: 'retrieval', spans: [/* … */] }, { wait: false });

// Usable straight away — the id is generated client-side, and the API creates
// the trace under it. Hand it to the gateway so both land on one trace.
await hub.gateway.chat({ model: 'gpt-4o-mini', messages: [/* … */], trace: { traceId } });

await hub.traces.flush(); // before reading it back, or before the process exits

The trade: nothing is confirmed at the call site. A failed send warns once per error kind and drops the batch, the same way the gateway's own span reporting already behaves. traces.flush() waits for everything buffered — including the gateway's spans, which share the buffer — and gateway.close() flushes too.

traces.flush()

Wait for every trace buffered by ingest(…, { wait: false }) (and by the gateway's own span reporting) to be sent. Resolves once the queue is empty.

traces.submitFeedback(input) / traces.updateFeedback(input)

Attach feedback to a trace (or one span), then edit it in place. Only the original author may edit.

const fb = await hub.traces.submitFeedback({
traceId,
rating: 5,
label: 'helpful',
comment: 'Resolved my issue.',
});
await hub.traces.updateFeedback({ traceId, feedbackId: fb.id, rating: 1, label: 'unhelpful' });

submitFeedback input — at least one of rating/label/comment:

FieldTypeNotes
traceIdstringRequired.
spanIdstringScope to one span.
ratingnumber-1..5.
labelstringShort label.
commentstringFree text.
source'user' | 'developer' | 'end_user' | 'api'Origin.

updateFeedback input — { traceId, feedbackId, rating?, label?, comment? }. Pass null to clear a field; omit it to keep the existing value.

Returns FeedbackResult ({ id, traceId, spanId, rating, label, comment, source, createdBy, createdAt, updatedAt }).

traces.get(traceId) / traces.list(options?)

Read traces back. traces.get returns the header plus the full span tree; traces.list returns one page of summaries, newest first.

const { trace, spans } = await hub.traces.get(traceId);
const page = await hub.traces.list({ status: 'error', minLatencyMs: 2000, limit: 20 });

traces.list filters — all optional:

FieldTypeNotes
from / tostringISO date range.
statusSpanStatus'ok' | 'error' | 'unset'.
modelstringFilter by model.
sessionIdstringFilter by session.
promptVersionIdstringFilter by prompt version.
minLatencyMsnumberMinimum latency.
minCostUsdnumberMinimum cost.
minTokensnumberMinimum total tokens.
qstringFree-text search.
page / limitnumberPagination (1-based page).

traces.get returns { trace: TraceSummary, spans: TraceSpan[] }; traces.list returns { data: TraceSummary[], total, page, limit }.

Trace analytics (hub.traces)

traces.analytics(options?)

Aggregated trace metrics (latency, cost, tokens) grouped by a facet.

const analytics = await hub.traces.analytics({ group_by: 'model' });
ParameterTypeNotes
options.group_bystringFacet key to group by (e.g. 'model', 'status').
options.since / options.untilstringISO-8601 time bounds.

Returns AnalyticsResult ({ data: AnalyticsEntry[] }).

traces.listFacets()

Discover available facet keys for grouping.

const facets = await hub.traces.listFacets();

Returns TraceFacets — object whose keys are facet names.

traces.getFacetValues(key)

Get distinct values for a facet key.

const values = await hub.traces.getFacetValues('model');

Returns FacetValuesResult ({ values: string[] }).

traces.getSettings() / traces.updateSettings(capturePayloads)

Read or update the team's trace capture settings.

const settings = await hub.traces.getSettings();
await hub.traces.updateSettings(true); // enable payload capture

Returns TraceSettings ({ capturePayloads: boolean }).

traces.getFeedbackSummary(options?)

Aggregated feedback buckets (rating distribution).

const summary = await hub.traces.getFeedbackSummary();

Returns FeedbackSummaryResult ({ data: FeedbackBucket[] }).

traces.listFeedback(options?)

Paginated feedback list across all traces.

const feedback = await hub.traces.listFeedback({ limit: 10 });

Returns FeedbackListResult ({ data: FeedbackEntry[], total, page, limit }).

traces.getTraceFeedback(traceId)

All feedback for a specific trace.

const fb = await hub.traces.getTraceFeedback(traceId);

Returns TraceFeedbackResult ({ data: FeedbackEntry[] }).

gateway.flush() / gateway.close()

await hub.gateway.flush(); // wait for background trace writes to finish
await hub.gateway.close(); // flush, then release the exit hook

gateway.chat(), streaming, and gateway.runToolLoop() hand back their result without waiting for the trace write — call gateway.flush() before reading the traces API back. A script that returns from main() does not need either: the SDK flushes at process exit. gateway.close() is idempotent and also supports await using hub = new AcruxCore(...).

Tool catalog (hub.tools)

Catalog operations live on hub.tools.

tools.sync(tools, options?) / tools.syncOne(tool, options?)

Reconcile tools from acrux.tool against the catalog. Idempotent and cached per process on the spec hash — a second call with an unchanged tool makes no request.

await hub.tools.sync([getWeather]);
const one = await hub.tools.syncOne(getWeather, { onConflict: 'error' });
ParameterTypeNotes
tools / toolAcruxToolFrom acrux.tool.
options.onConflict'warn' | 'error'Default 'warn'. 'error' throws when a commit supersedes a dashboard-authored version.

Returns ToolSyncResult ({ toolId, versionNumber, committed, alias, supersededSource? }). committed is false on a cache hit.

tools.resolve(refs)

Resolve catalog refs to schemas plus executor types in one request.

const [resolved] = await hub.tools.resolve([{ name: 'get_weather', alias: 'production' }]);
// resolved.toolId, resolved.executorType ('client' | 'http'), resolved.function

Returns ResolvedTool[] ({ toolId, versionNumber, executorType, function }). Throws API_ERROR (404) when any ref does not resolve.

tools.execute(toolId, args, options?)

Run a tool's server-side http executor on the platform. The platform writes the tool span itself — do not report one for the same call.

const { result, latencyMs, toolVersionId } = await hub.tools.execute(toolId, { city: 'Lahore' }, {
alias: 'production',
traceId,
parentSpanId: llmSpanRef,
});
ParameterTypeNotes
toolIdstringFrom resolve(). Required.
argsRecord<string, unknown>The model's parsed arguments. Required.
options.aliasstringWhich alias to run.
options.versionNumbernumberPin an exact version.
options.traceIdstringAttach the span to this trace.
options.parentSpanIdstringNest under this span (normally the llm span).

Returns ToolExecuteResult ({ result, status, latencyMs, toolVersionId }).

tools.list(options?)

List tools for the team, newest first.

const page = await hub.tools.list({ search: 'weather', limit: 10 });
ParameterTypeNotes
options.searchstringFree-text search on name.
options.pagenumber1-based page.
options.limitnumberPage size.

Returns { data: ToolSummary[], total, page, limit }.

tools.get(id)

Fetch one tool's shell by id.

const tool = await hub.tools.get(toolId);

Returns ToolDetail ({ id, name, description, latestVersion?, createdAt, updatedAt }).

tools.create(input)

Create a new tool shell. Commit a version with commitVersion to give it a schema/executor.

const tool = await hub.tools.create({ name: 'get_weather', description: 'Weather lookup' });
FieldTypeRequiredNotes
namestringyesMust match ^[a-zA-Z0-9_-]{1,64}$, unique per team.
descriptionstringnoHuman-readable.

Returns ToolDetail.

tools.update(id, input)

Update a tool's name and/or description. Does not touch versions.

const updated = await hub.tools.update(toolId, { description: 'Updated description' });

Returns ToolDetail.

tools.delete(id)

Soft-delete a tool. Versions and aliases are preserved but unreachable. Returns void.

await hub.tools.delete(toolId);

tools.commitVersion(toolId, input)

Commit a new immutable version for a tool. The first commit auto-creates production and staging aliases.

const version = await hub.tools.commitVersion(toolId, {
description: 'Get weather for a city',
parametersSchema: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
executor: { type: 'client' },
});
FieldTypeRequiredNotes
descriptionstringnoModel-facing description.
parametersSchemaRecord<string, unknown>yesJSON Schema for the tool's parameters.
executor{ type: 'client' } | { type: 'http'; ... }yesHow the tool runs.
changelogstringnoRelease note for humans.

Returns ToolVersionDetail.

tools.listVersions(toolId, options?)

List a tool's versions, newest first. Items omit parametersSchema/executor.

const versions = await hub.tools.listVersions(toolId);

Returns { data: ToolVersionSummary[], total, page, limit }.

tools.getVersion(toolId, versionNumber)

Fetch one version with its full parametersSchema/executor.

const v = await hub.tools.getVersion(toolId, 1);

Returns ToolVersionDetail.

tools.promoteAlias(toolId, alias, versionNumber)

Promote an alias to point at a specific version. Creates the alias if it doesn't exist.

const alias = await hub.tools.promoteAlias(toolId, 'production', 2);

Returns ToolAliasDetail ({ alias, versionNumber, toolId }).

tools.analytics(options?)

Read aggregated call analytics (count, error rate, p50/p95 latency) per tool.

const analytics = await hub.tools.analytics({ since: '2026-01-01T00:00:00Z' });
ParameterTypeNotes
options.sincestringISO-8601 start bound.
options.untilstringISO-8601 end bound.

Returns ToolAnalyticsResult ({ data: ToolAnalyticsEntry[] }).

acrux.tool

Declare a tool whose interface and implementation live in one value. The model sees the name and parameters schema; your handler runs when it calls the tool. Works with a zod schema (typed args) or a plain JSON Schema object (untyped args).

import { acrux } from '@acruxcoreai/sdk';
import { z } from 'zod/v4';

const getWeather = acrux.tool(
{
name: 'get_weather',
description: 'Get the current weather for a city.',
parameters: z.object({ city: z.string() }),
alias: 'production',
},
async ({ city }) => ({ tempC: 21 }),
);
FieldTypeRequiredNotes
namestringyesMust match ^[a-zA-Z0-9_-]{1,64}$.
descriptionstringnoWhat the model reads. Omit to let the dashboard own it.
parametersz.ZodSchema | Record<string, unknown>yeszod v4 schema or a JSON Schema object.
aliasstringnoCatalog alias a sync moves. Default 'production'.
changelogstringnoRelease note for humans; never shown to the model.

The second argument is handler: (args) => unknown | Promise<unknown>. acrux.tool() itself throws TOOL_SCHEMA_ERROR for an invalid name. The zod schema is converted to JSON Schema lazily at sync time, so ZOD_NOT_AVAILABLE (zod given but not installed) and the TOOL_SCHEMA_ERROR for a classic zod v3 schema surface from tools.sync() / the first loop call — not from acrux.tool().

Structured output

responseFormat asks the model for a typed answer. Pass an OpenAI-shaped dict, or build one from a zod schema with the { zod, name, strict? } variant — the SDK converts it to JSON Schema at send time.

await hub.gateway.chat({
model: 'agent-model',
messages,
responseFormat: { zod: z.object({ sentiment: z.enum(['pos', 'neg', 'neutral']) }), name: 'sentiment' },
});
VariantShape
text{ type: 'text' }
json_object{ type: 'json_object' }
json_schema{ type: 'json_schema', json_schema: { name, schema?, strict? } }
zod{ zod: ZodSchema, name, strict? }

The gateway forwards the format to each provider's native structured-output mode and relies on the provider to honour it — it does not validate the returned content against the schema, so parse and validate on your side when conformance matters.

BYO provider

Route a call directly to your own OpenAI-compatible endpoint instead of the gateway — the hop and its latency are skipped, and apiKey is sent only to baseUrl, never to AcruxCore.

const hub = new AcruxCore({
apiKey: process.env.ACRUXCORE_API_KEY,
baseUrl: process.env.ACRUXCORE_BASE_URL,
provider: { baseUrl: 'https://api.groq.com/openai/v1', apiKey: process.env.GROQ_API_KEY },
});

Pass provider on the constructor (a default for every call) or per-call on gateway.chat()/gateway.runToolLoop(). There is no server-side catalog on this path, so every tool is sent inline as a full schema, and the SDK reports one llm span per round-trip. gateway.costUsd and gateway.cache are always null (the gateway never saw the call). Throws PROVIDER_ERROR for a non-2xx provider response.

Span shapes

IngestSpan (passed to traces.ingest()):

FieldTypeNotes
spanIdstringRequired; opaque, unique within the trace.
parentSpanIdstringLinks to another span's spanId.
namestringRequired.
kindSpanKind'llm' | 'tool' | 'retrieval' | 'embedding' | 'agent' | 'chain' | 'other'.
statusSpanStatus'ok' | 'error' | 'unset'.
startTimestringRequired; ISO-8601 with offset.
endTimestringISO-8601.
model, providerstringModel/provider metadata.
usage{ promptTokens?; completionTokens?; totalTokens? }Token usage.
costUsdnumberCost in USD.
promptVersionIdstringPrompt lineage.
variablesunknownThe prompt variables behind input. Read back when feedback becomes a dataset example.
input / outputunknownStored only with payload capture on.
attributesRecord<string, unknown>Free-form.
errorstringError message for failed spans.

Error codes

All failures throw an acruxcoreError with a code field — use it for programmatic handling rather than matching on the message.

CodeWhen it is thrown
MISSING_API_KEYNo apiKey in args or env; or a BYO provider.apiKey is empty.
MISSING_BASE_URLNo baseUrl in args or env; or a BYO provider.baseUrl is empty.
NETWORK_ERRORAll retries exhausted at the network level.
API_ERRORNon-retryable HTTP error from the gateway (4xx, or 5xx after retries). Inspect statusCode/body.
MISSING_VARIABLESTemplate requires variables you did not supply.
TOOL_SCHEMA_ERRORacrux.tool: invalid name, an unsupported parameters shape, or a classic zod v3 schema.
MISSING_DISPATCHrunToolLoop: a tool has no implementation (thrown before the first model call).
ZOD_NOT_AVAILABLEA zod schema was given but zod could not be imported.
PROVIDER_ERRORBYO: non-2xx response from your provider endpoint.

Sessions (hub.sessions)

sessions.list(options?)

List sessions with pagination.

const page = await hub.sessions.list({ limit: 10 });
ParameterTypeNotes
options.pagenumber1-based page.
options.limitnumberPage size.

Returns { data: SessionSummary[], total, page, limit }.

sessions.get(sessionId)

Get session detail with all traces.

const session = await hub.sessions.get(sessionId);

Returns { session: SessionSummary, traces: TraceSummary[] }.

Evaluations

Datasets (hub.datasets)

MethodDescription
datasets.create({ name, overallFeedback? })Create a dataset. Returns DatasetDto.
datasets.buildFromFeedback({ name, feedbackIds, overallFeedback? })Build a dataset from trace feedback. Returns BuildFromFeedbackResult.
datasets.list()List all datasets. Returns DatasetDto[].
datasets.get(id)Get dataset with examples. Returns DatasetWithExamples.
datasets.update(id, { name?, overallFeedback? })Update dataset metadata. Returns DatasetDto.
datasets.delete(id)Delete a dataset. Returns { success: boolean }.
datasets.addExample(datasetId, { input, criteria?, history? })Add an example to a dataset. Returns DatasetExampleDto.
datasets.removeExample(datasetId, exampleId)Remove an example. Returns { success: boolean }.

Experiments (hub.experiments)

MethodDescription
experiments.create({ datasetId, versionIds, models, promptId?, name?, alias? })Create an experiment. Returns ExperimentDto.
experiments.list()List all experiments. Returns ExperimentDto[].
experiments.get(id)Get an experiment. Returns ExperimentDto.
experiments.startRun(experimentId)Start a run for an experiment. Returns StartRunResult ({ runId, status }).

Runs (hub.runs)

MethodDescription
runs.list({ status?, datasetId?, promptId?, page?, limit? })List runs. Returns RunListResponse.
runs.get(id)Get run detail. Returns RunDetailDto.
runs.getReport(id)Get run report. Returns RunReport.
runs.getCell(id, cellKey)Get a specific cell. Returns RunCellDetailDto.
runs.getCandidate(id, candidateId)Get a candidate. Returns CandidateDetail.
runs.promoteCandidate(id, { promptCandidateId, alias? })Promote a candidate. Returns PromoteResult.

Optimize (hub.optimize)

MethodDescription
optimize.start(promptId, { datasetId, models, draftCount?, alias? })Start prompt optimization. Returns StartOptimizeResult ({ runId, status, promptMismatchWarning? }).

Where to next