The TypeScript SDK and headless mode

Developer·5 minutes to read

Verified against docs/superpowers/specs/2026-09-15-p4-b-typescript-sdk-headless.md §1.1, §1.3, §4.3, §4.4, §4.5; re-verified against the code at close-out.

What this does for you

You send a bot a task from a script or a build step and get its answer back on standard output.

Before you start

  • An organisation API key of scope write or wider, created on the Developer page (Authentication and keys).
  • Node, and @botseon/sdk installed in the project or available to the command.
  • Two environment variables: BOTSEON_API_KEY, which is required, and BOTSEON_API_URL, which defaults to the loopback address a home install answers on. Neither value is ever printed, by the library or by the command.
  • The exact name, or the id, of the bot you are sending to. You can read both from Bots.

Steps

These steps run at a terminal and in a build log; the library has no screen of its own, so no screenshot is shown here. The one product screen a headless task touches is the conversation it appears in, unchanged: the task shows up as a message from the person the key belongs to, and the bot's answer follows as it would for anything typed in the app.

  1. Put the key in the environment, never on the command line. A key on a command line is visible to every process and every log, and the command refuses a --key flag for exactly that reason.

  2. Point the command at your install with BOTSEON_API_URL. It must be an absolute http or https URL, and plain http is admitted only for this machine.

  3. Send the task. The command's grammar is:

    botseon-task --bot <id|name> [--wait | --no-wait] [--timeout <duration>] [--json] [--quiet]
                  [--cancel-on-timeout] [--stop-on-approval] [--] <text> | -
    

    - in place of the text reads it from standard input.

  4. Read the streams apart. Progress goes to standard error, one line per state change; the answer goes to standard output. A build log therefore shows the progress and a pipe carries only the answer.

  5. Branch on the exit code rather than on the text: 0 the run completed, 1 it did not, 2 the wait timed out, 3 the command refused before sending, 4 the service could not be reached, 130 the command was interrupted (Ctrl-C or SIGTERM).

  6. For a machine reader, add --json. You then get exactly one line on standard output, whatever the outcome, carrying schemaVersion, the outcome, the run and conversation ids, the state, the text, the last result card, the spend in micro-cents, the steps, the elapsed milliseconds and the request id.

A worked example

Check that the key and the origin are right before a build step uses them:

curl -sS "$BOTSEON_API_ORIGIN/api/v1/bots?limit=5" \
  -H "authorization: Bearer $BOTSEON_API_KEY" \
  -H "accept: application/json"

The same work from the library, which is what the command is built on:

import { createClient } from '@botseon/sdk';

const client = createClient({
  baseUrl: process.env.BOTSEON_API_ORIGIN ?? '',
  apiKey: process.env.BOTSEON_API_KEY ?? '',
});

const result = await client.runTask({
  bot: { name: 'Inbox Triage' },
  text: 'Draft the release notes for v1.4.',
  timeoutMs: 900_000,
  onState: (run) => console.error(`run ${run.id} ${run.state}`),
});

if (result.outcome !== 'completed') process.exitCode = 1;
console.log(result.text);

runTask is three steps in one: it sends, it polls until the run settles, and it collects the bot's messages for that run into one piece of text. sendTask, waitForRun, cancelRun and resolveBot are on the same client when you want the steps apart.

What can go wrong

What you seeWhyWhat to do
BOTSEON_API_KEY is not set.The variable is unset or empty in the environment the command inherited.Export it in the same shell, or pass it into the build step's environment.
--key is not accepted: a key on the command line is visible to every process and every log. Set BOTSEON_API_KEY instead.A --key flag appeared anywhere in the arguments.Move the key into the environment.
BOTSEON_API_URL is not an absolute http or https URL.The variable is set to something that is not a URL, or to a bare host.Give it a scheme and a host.
The API key was not accepted. Check BOTSEON_API_KEY and whether the key has expired or been revoked.The key is unknown, revoked, expired, or its person is no longer an admin.Check the key's row on the Developer page.
No bot with that id or name is visible to this key.The name matches nothing this key's person can see, or the id is not a real one.List the bots first and copy the name exactly.
More than one bot has that name. Use the id instead.Two bots share the name you gave.Send to the id; the command prints the candidates.
The bot is still working on an earlier task. Try again when it has finished.The bot had a run in flight for the whole queue timeout.Raise --timeout, or wait and send again.
The wait ran out before the run settled.The run was still going when the timeout passed.Read the run id from the message and follow it with Runs, or add --cancel-on-timeout.

See also

Last verified against build c0f77aa.