Spavik

TypeScript SDK

Use the TypeScript SDK in JavaScript or TypeScript for evaluation, models, forecasting and outcomes.

npm install spavik
import { Spavik } from 'spavik';

const spavik = new Spavik();                                    // reads SPAVIK_API_KEY
const model  = await spavik.train('churn.csv', { target: 'churn' });
console.log(await model.predict({ plan: 'pro', seats: 12, tickets_90d: 3 }));

No production dependency. Node 22 or later, and edge runtimes that provide fetch and WebCrypto. ESM and CommonJS.

The operations

CallCost
spavik.evaluate(data, { target })free
spavik.train(data, { target })free
spavik.backtest(data, { target, horizon })free
spavik.forecast(data, { target, horizon })horizon × the engine's credits per call, all series included; 1 credit per step on the default engine
spavik.models()free
spavik.usage()free
model.predict(rows)the engine's credits per row, 1 on the default engine
model.submitOutcomes(outcomes)free
model.performance(days)free
model.refresh()free
model.archive(), spavik.archive(modelId)free
spavik.restore(modelId)free

models() is an async iterator; models({ status: 'archived' }) lists what was set aside. A model exposes its engine and features, the columns predict expects. The raw API fields are in model.data, for example model.data.target.

for await (const model of spavik.models()) console.log(model.id, model.name, model.features);

Engine selection

Training and evaluation accept engineFamily; omitting it lets the API choose its configured default family. Read GET /v1/engine-families for available families. The model's family cannot change. Predictions accept engineVersion to select an exact version or latest alias within that family. Forecast and backtest accept both options. Available versions and prices come from GET /v1/engines. Keep the exact engine from the prediction result with its model version.

Evaluate before training

const report = await spavik.evaluate('churn.csv', { target: 'churn' });
console.log(report.assessment.status);
console.log(report.assessment.metric, report.assessment.message);
console.log(report.hint);

const check = await spavik.backtest('sales.csv', {
  target: 'sales', horizon: 7, timestamp: 'date', itemId: 'store',
});
console.log(check.assessment);
console.log(check.backtest);

Both operations are free and preserve the API's assessment. See Read an evaluation for comparison statuses, random-holdout limits and forecasting protocol metadata. A high score does not establish reliability or prove leakage.

backtest() also accepts pointEstimate (mean or median) and quantiles. Use the same settings for backtesting and forecasting so the evaluation matches the forecast you intend to request.

Configure and forecast

const spavik = new Spavik({
  apiKey: process.env.SPAVIK_API_KEY,
});
const forecast = await spavik.forecast('sales.csv', {
  target: 'sales', horizon: 7, timestamp: 'date', itemId: 'store',
  pointEstimate: 'mean',
});
console.log(await forecast.points()); // asynchronous method in JavaScript
console.log(forecast.csv);
console.log(forecast.get('point_estimate'), forecast.predictionsRemaining);

forecast.predictionsRemaining copies the X-Spavik-Predictions-Remaining header, or is null when the API does not send it. forecast.cost is null: the API does not return the amount charged. See Costs and credits.

forecast() also accepts quantiles and pointEstimate: 'median'. Keep these settings consistent with the backtest you actually ran. Both methods use itemId in JavaScript; the REST field is item_id.

The key travels in X-Spavik-Api-Key. If apiKey is omitted, the client reads SPAVIK_API_KEY; an explicitly empty string disables that fallback. baseUrl and SPAVIK_BASE_URL are optional overrides. The SDK defaults to https://api.spavik.co. Set an override only when needed; baseUrl takes precedence.

Outcomes, refresh and archives

const submitted = await model.submitOutcomes(outcomes);
console.log(submitted.get('accepted'), submitted.get('refresh_triggered'));
const performance = await model.performance(30);
const observed = performance?.get('observed');
console.log(observed?.assessment, observed?.metrics, observed?.baselines);
console.log(observed?.metric_context, performance?.get('evaluation'));

Batch outcomes. When refresh_triggered is true, automatic integration has already started; do not request a duplicate refresh. A manual refresh() returns the integration details, including incomplete_outcomes, or null only for NO_PENDING_OUTCOMES. Other API failures are raised, including performance-service failures. observed: null means there are no client-reported predictions to compare yet. Scores, baselines, undefined reasons and observed.assessment come directly from the API; the SDK does not recalculate them.

model.archive() frees an active-model slot without deleting artifacts. spavik.restore(modelId) restores by identifier. Permanent deletion is outside the SDK's surface. See the feedback loop.

Types

The SDK is written in TypeScript and ships compiled JavaScript for ESM and CommonJS, with generated type declarations. JavaScript applications need no TypeScript setup. Autocompletion covers operations, options and error classes. The npm package is named spavik. verify is exported from spavik, and also from spavik/webhooks for bundles that want only webhook verification: both paths give the same function.

The same refusals as Python

What the Python library refuses, this one refuses too, with the same message and at the same moment. A conformance suite replays one scenario across both, and across the MCP server.

await spavik.train(eightRows, { target: 'churn' });
// SpavikDataError: training needs at least 10 rows, 8 provided. Nothing was sent.

Errors

import { SpavikCreditsExhausted } from 'spavik';

try {
  await model.predict(rows);
} catch (exc) {
  if (!(exc instanceof SpavikCreditsExhausted)) throw exc;
  console.error(exc.code, exc.detail, exc.requestId);
  console.error((await spavik.usage()).predictions_remaining);
}

Every class extends SpavikError and carries code, detail, status and requestId: SpavikDataError, SpavikAuthError, SpavikCreditsExhausted, SpavikQuotaExceeded, SpavikValidationError, SpavikNotFound, SpavikConflict, SpavikRateLimited, SpavikUnavailable, SpavikServerError, SpavikTransportError and SpavikSignatureError. They match the Python classes, under the same names.

Webhooks

verify checks the Spavik-Signature header and returns the decoded event. It rejects with SpavikSignatureError when the signature, the timestamp or the header is invalid. Pass the raw body, never a parsed one. See Webhooks for the delivery contract.

Express:

import express from 'express';
import { SpavikSignatureError, verify } from 'spavik';

const app = express();

app.post('/webhooks/spavik', express.raw({ type: 'application/json' }), async (req, res) => {
  let event;
  try {
    event = await verify(req.body, req.headers, process.env.SPAVIK_WEBHOOK_SECRET);
  } catch (error) {
    if (error instanceof SpavikSignatureError) return res.sendStatus(400);
    throw error;
  }
  // Store event.id and the event durably, then acknowledge.
  res.sendStatus(204);
});

Hono:

import { Hono } from 'hono';
import { SpavikSignatureError, verify } from 'spavik';

const app = new Hono();

app.post('/webhooks/spavik', async (c) => {
  let event;
  try {
    event = await verify(await c.req.arrayBuffer(), c.req.raw.headers, c.env.SPAVIK_WEBHOOK_SECRET);
  } catch (error) {
    if (error instanceof SpavikSignatureError) return c.body(null, 400);
    throw error;
  }
  // Store event.id and the event durably, then acknowledge.
  return c.body(null, 204);
});

Retries

The SDK follows the API retry policy for network failures, eligible 429/5xx responses and REQUEST_IN_PROGRESS. It keeps the same idempotency key during internal attempts. Completed responses can be replayed within the API retention window, 24 hours by default from operation creation.

Keep error.idempotencyKey and error.requestId after an uncertain failure. A new SDK method call generates a new key and can repeat an operation that already succeeded. Do not retry blindly or switch keys to bypass REQUEST_INTERRUPTED or IDEMPOTENCY_RESPONSE_UNAVAILABLE.

maxRetries counts every attempt, the first included: the default is 3, and maxRetries: 1 disables retries. Without Retry-After, the default backoff is exponential with jitter, from 0.5 s up to 8 s. Each operation has its own timeout, from 15 s for reads to 120 s for training; timeout, in milliseconds, applies to calls without one.

new Spavik({ maxRetries: 1 });             // a single attempt, no retry
new Spavik({ backoff: (n) => n * 1000 });  // linear backoff in ms, n is the attempt number

Debugging

SPAVIK_LOG=debug node my-script.js

How is this guide?

Last updated on

On this page