Owctopus
Log in

Methods

These are the methods on the npm package @owctopus/sdk. Create a client, then call decide. getExecution and list read runs that the Owctopus API stored.

The package is TypeScript, ESM only, and has no runtime dependencies. It runs on Node 18 or newer. Import it from your backend. Do not ship the key in a browser.

npm install @owctopus/sdk

new Owctopus(options?)

Creates the client. One key is required: an Owctopus key, an OpenAI key, or both.

import { Owctopus } from "@owctopus/sdk";

const owctopus = new Owctopus({
  apiKey: process.env.OWCTOPUS_API_KEY,
  openaiApiKey: process.env.OPENAI_API_KEY,
  baseUrl: "http://localhost:3000",
});
OptionDefaultWhat it does
apiKeyOWCTOPUS_API_KEYBearer token for the Owctopus API. Live keys start with ow_live_. Test keys start with ow_test_.
baseUrlOWCTOPUS_BASE_URL, then https://api.owctopus.comOwctopus API root. Use http://localhost:3000 while you develop.
openaiApiKeyOPENAI_API_KEY only when apiKey is not passedThe customer’s OpenAI key. When this is set, decide() calls OpenAI and does not call the Owctopus API.
openaiBaseUrlhttps://api.openai.com/v1OpenAI-compatible root. The client posts to chat/completions.
modelsfast and balanced are gpt-4o-mini. careful is gpt-4o.Model ids for each tier. Pass only the tiers you want to change.
environmentnoneSent as the owctopus-environment header when a project has more than one environment.
timeoutMs30000Aborts the request after this many milliseconds.
fetchglobal fetchReplace the HTTP client, usually in tests.

If you pass apiKey, the client does not read OPENAI_API_KEY. Pass openaiApiKey as well when you want OpenAI. The constructor throws when neither key is set. Keys are not written into results or error messages.

decide(input)

Runs one decision. An Owctopus key sends POST /v1/executions. The API looks up a stored condition for this decision, context, and option list. A match returns that choice and does not call AI. No match calls AI, then stores the choice as the condition for the next time. Both count as one decision. OpenAI is called from this process only when the client has no Owctopus key, and that path does not store a condition.

const result = await owctopus.decide({
  decision: "shipping_method",
  context: { cartValue: 4500, customerType: "premium", urgency: "high" },
  options: [
    { id: "standard", nextAction: "ship_standard" },
    { id: "express", nextAction: "ship_express" },
    { id: "same_day", nextAction: "ship_same_day" },
  ],
});

if (result.status === "decided" && result.option) {
  // result.option is "standard", "express", or "same_day"
}
FieldRequiredWhat it does
decisionYesA name, such as shipping_method, meal_suggestion, or daily_workout.
contextYesAny object. Owctopus does not fix the fields. Your app sends the facts it has for this moment.
optionsRequired for OpenAIA string id, or { id, attributes?, nextAction? }. The chosen id comes back as result.option.
versionNoDecision version to run on the Owctopus API. Omitted uses the latest release in that environment.
constraintsNoSent to the Owctopus API. Not used on the OpenAI path.
idempotencyKeyNoSent as idempotency-key. The client generates sdk_ plus 32 hex characters when you omit it, and reuses that key on retries.
modelNoAn OpenAI model id. "auto" lets the client choose. Ignored on the Owctopus API path.

On the OpenAI path the client picks a model before it sends the request:

Option attributes are not sent to the model. The user message includes the decision name, the context, and the option ids. If the reply does not name one of those ids, status is needs_review and option is null.

An OpenAI result uses an executionId that starts with local_. result.learning.stored is false, and condition.updates is empty. Saving that record, and showing how a condition later changes, is not part of this call yet.

What decide() returns

Both paths return the same result shape. OpenAI fills openai and learning. The Owctopus API does not.

FieldMeaning
executionIdId of the stored run, or local_… when OpenAI answered and nothing was stored.
requestIdId for this request. On OpenAI this is the chat completion id.
decisionThe name you sent.
versionVersion that ran. 0 on an OpenAI call that did not pass version.
statusdecided, needs_review, rejected, failed, or timeout.
optionThe chosen option id, or null.
scoreScore for the chosen option, or null.
confidenceNumber from the model, or null.
risklow, medium, high, or unknown. OpenAI returns unknown.
reasonsShort strings explaining the choice.
alternativesOther options with a score. Each item is { option, score, value }.
nextActionnextAction from the chosen option, when you sent one.
objective{ type, metric, optionId, value }. OpenAI uses type select.
factsFacts the Owctopus API attached. Empty on the OpenAI path.
model{ provider, model, fallback }, or null.
traceStages that ran, each with stage, status, message, and elapsedMs.
openaiThe request messages and the reply content, id, model, and token counts. The API key is not included.
learningDecision, context, options, choice, confidence, model, and condition. stored is false until a later release saves it.

getExecution(executionId)

Reads one stored run with GET /v1/executions/:id. This needs an Owctopus API key. An id from an OpenAI-only decide() is not in the database, so this call will not find it.

const again = await owctopus.getExecution(result.executionId);

list(query?)

Reads recent runs with GET /v1/executions. This needs an Owctopus API key. The response is { data: DecisionResult[] }.

const recent = await owctopus.list({
  decision: "shipping_method",
  status: "decided",
  limit: 20,
});
FieldWhat it does
decisionOnly runs for this decision name.
statusOne of decided, needs_review, rejected, failed, timeout.
limitHow many runs to return.

selectModel(input, models?)

Returns the model decide() would use for this input. It does not call the network. The result is { tier, model, reason }. tier is fast, balanced, or careful.

import { selectModel } from "@owctopus/sdk";

const choice = selectModel({
  decision: "shipping_method",
  context: { cartValue: 4500 },
  options: ["standard", "express", "same_day"],
});
// choice.model is "gpt-4o-mini"

Errors

A failed call throws OwctopusError. Check error.code, error.status, and error.requestId. error.executionId is set when the Owctopus API stored the run before it failed. The client retries 408, 429, and 503 up to three times with the same idempotency key. A timeout is not retried.

import { AuthenticationError, OwctopusError, ValidationError } from "@owctopus/sdk";

try {
  await owctopus.decide({
    decision: "shipping_method",
    context: { cartValue: 4500 },
    options: ["standard", "express"],
  });
} catch (error) {
  if (error instanceof ValidationError) {
    // error.code is OPTIONS_REQUIRED when OpenAI was called with no options
  } else if (error instanceof AuthenticationError) {
    // the Owctopus key was rejected
  } else if (error instanceof OwctopusError) {
    // error.code, error.status, error.requestId
  }
}
ClassWhen
AuthenticationErrorOwctopus API returned 401.
AuthorizationErrorOwctopus API returned 403.
ValidationError400 or 422. OpenAI with no options uses code OPTIONS_REQUIRED.
DecisionNotFoundError404. The execution id is not in this environment.
IdempotencyConflictError409. The same idempotency key was sent with a different body.
RateLimitError429.
DecisionExecutionError502 from Owctopus, or an OpenAI HTTP error. A rejected OpenAI key uses code OPENAI_AUTH. Other OpenAI failures use OPENAI_ERROR.
TimeoutErrorThe request exceeded timeoutMs, or the API returned 504. Code MODEL_TIMEOUT.
OwctopusErrorAnything else, including a network failure. Code NETWORK when the host could not be reached.