Free Intro Class
    Botspot Logo

    BotSpot · developer docs · apps

    Building Apps

    An App is a static frontend with an agent as its backend. You build the page, bind it to one or more of your agents, and call them with a single function, botspot.run. This page covers what to send, what comes back, who pays, and what every error means.

    Page version
    1.1
    Updated
    2 Nov 2026

    Before you start

    How an App works

    The page runs in the visitor's browser and is hosted as static files on your own address. It has no server of its own. Anything that needs a backend (reading data, calling a model, calling another service) is done by an agent, and the page reaches the agent through botspot.run(role, input).

    A role is a name your page uses, such as assistant or scientist. In Workbench you bind each role to one of your agents. The page never holds an API key: the server resolves the role to the agent, runs it, and bills the right wallet.

    So server routes, server actions and secrets inside the page are outside the model. If the page needs a secret or a third-party call, put it in the agent and let the agent do it.

    Getting going

    Bind a role and call it

    1. In Workbench, open Apps and create an app, or open an existing one.
    2. Bind a role to an agent in the app's workflow binding. Use a short, lowercase role name.
    3. Check what the agent expects as input (see The input contract). This is the step people skip.
    4. Call botspot.run from the page with exactly those inputs.
    import { botspot } from './botspot.js';
    
    // The agent bound to the role "scientist" has a Big Text input named "scientist"
    // and a Big Text input named "question". Send them by name:
    const res = await botspot.run('scientist', {
      scientist: 'Marie Curie',
      question: 'What did you learn from radium?',
    });
    
    res.output;        // the reply, always a string
    res.data;          // the reply parsed as JSON, only when it is JSON
    res.bics_charged;  // what this run cost

    If you use the App builder, it is told each bound agent's inputs on every request, so it writes the call with the right keys. You can still read the contract yourself when you write the call by hand.

    Reference

    botspot.run

    botspot.run(role, input) returns a promise for { ok, role, output, data?, bics_charged }.

    FieldMeaning
    outputThe agent’s reply, always a string. Agents write markdown, so render it as markdown rather than as plain text.
    dataPresent only when the reply is JSON (for example a workflow that ends in a data node). The SDK parses it once so your page does not have to guess keys.
    bics_chargedWhat this run cost, when the platform could read it back.

    What you can pass as input.

    • An object, such as { scientist: "Marie Curie" }. Each key fills the input of that name. This is what you want for almost every agent.
    • A string. It is wrapped as { text: "…" }, and only a Chat Input reads that key. A Big Text input named scientist will not see it.
    // A plain string reaches ONLY a Chat Input.
    await botspot.run('assistant', 'hello');
    // is sent as { text: 'hello' }

    If a visitor is not signed in, botspot.run sends them to the hosted login and the page navigates away. It does not resolve in that case.

    Reference

    The input contract

    Every agent has a list of inputs it reads. A required input that the page does not send makes the run fail inside the workflow, which the page otherwise sees only as a failed request. The contract tells you the names up front.

    GET /agent/v1/workflow/agents/{agent_id}/input-contract
    
    {
      "agent_id": "78f30519-…",
      "inputs": [
        {
          "key": "scientist",
          "node_id": "n_3",
          "type": "input.bigtext",
          "kind": "text",
          "required": true,
          "label": "Scientist",
          "also_accepts_text": false
        }
      ]
    }
    FieldMeaning
    keyThe name to send. It is the input node’s name, or its id if it has no name.
    type, kindThe input node type, and a simple kind: text, files, image, audio, video or value.
    requiredBig Text inputs are required by default. Other inputs are optional unless the node says otherwise.
    also_accepts_textChat inputs only: a plain string, or a top-level text key, also fills it.

    The contract is read from the same release that a run would use, so it cannot drift from what the agent actually does. It is limited to your own workspace's agents and needs an authenticated workspace call; a visitor's page cannot read it.

    How the agent finds a value. Each input looks in this order: a nested entry under its node id, a nested entry under its name, then a top-level key by id, then by name. Sending by name is the simplest.

    The contract describes input nodes only. An agent started by a trigger payload, or any other entry point, has a contract that is incomplete.

    When it fails

    Errors the page can get

    A failed call throws an Error whose message is the cause. Show it.

    try {
      const { output } = await botspot.run('scientist', input);
      show(output);
    } catch (err) {
      // err.message is the real cause. Show it. Do not replace it with sample text.
      showError(err.message);
    }
    Do not catch the error and show sample text instead. A page that answers "in a live environment I would…" when the call failed looks fine and hides a broken backend. Several people have lost hours to exactly that.
    What you seeWhat it meansWhat to do
    Insufficient BICS balance…Status 402. The payer has no BICS left, or a preview link reached its spending limit.Top up the wallet (published apps), or open the app from Workbench for a fresh preview (dev).
    No agent is bound to role…Status 404. The role name the page used has no binding.Bind the role in Workbench, or fix the spelling in the page.
    The agent needs … but the page didn’t send itThe agent’s workflow rejected the run because a required input was missing. Status 422 inside, shown as a 502 to the page.Send the missing key. See the input contract.
    Agent run failed (…)The agent failed for another reason, or you are on a published app (see below).Test the agent on its own in Workbench.
    Sign-in redirectStatus 401. The session lapsed.Nothing. The visitor signs in and returns.

    Development and published apps differ on purpose. On a development app the workspace is the one looking at the page, so the error carries the readable cause above. On a published app the person looking is a customer, so they get a plain status instead of your agent's internals.

    In Workbench

    Failures in the builder

    When a botspot.run call from a development app fails, Workbench records it and shows a banner above the preview: what went wrong, what the page sent, and what was missing. Only key names are kept, never the values a user typed.

    • The banner lives in the Workbench builder. The standalone preview address does not show it.
    • It shows the newest failure from the last 15 minutes and refreshes every few seconds.
    • Fix with AI hands the cause to the builder. It is a button, not automatic, because the fault can be in the agent rather than the page, and an unattended loop would spend your BICS.
    • Published apps are not recorded this way.

    Who pays

    Billing and the wallet

    App stateWho paysWho can run it
    Development (unpublished)The owning workspace’s organisation BICS pool.Members of that workspace only.
    PublishedThe signed-in visitor’s own prepaid wallet.Any visitor with BICS. A zero balance is refused before the run starts.

    Published apps show a small BICS balance pill with a top-up popup, added for you. It is not shown on development hosts, because the balance there would be the wrong number.

    Reference

    The rest of the SDK

    CallStatus
    botspot.run(role, input)Live. Described above.
    botspot.auth.login / logout / isAuthenticated / tokenLive. Login uses the hosted sign-in page.
    botspot.wallet.balance / topup / openLive. Used by the wallet pill.
    botspot.files.uploadPlaceholder. It returns a stub and does not store a file yet.
    botspot.data.collectionPlaceholder. It returns an empty list and does not store documents yet.

    Until the last two ship, keep files and records in an agent: a workflow with a data node reads and writes your connected database, and the page gets the rows back in data.

    An app gets the current SDK when it is rebuilt. Until you rebuild, the deployed page keeps the SDK it was last built with. If something described here is missing in an older app, rebuild its release.

    Where it runs

    Hosting

    • Upload a zip, or connect a public git repository. BotSpot builds it and hosts the files at your-slug.botspot.app.
    • Development apps run on a separate development address, open only to workspace members.
    • The build supplies the app id and API address to the SDK. No key reaches the browser.
    • Rebuilding is manual for now: push your change, then rebuild the release.

    Troubleshooting

    Common questions

    I gave it a value and it says the variable is missing.

    You probably sent a string. A string becomes { text }, which only a Chat Input reads. Send an object keyed by the input's name.

    My page shows an answer, but the agent never ran.

    Look for a catch that fills in sample text. Remove it and show err.message.

    One click made several requests.

    That is a retry loop in the page code. Retries multiply the cost of a failing agent, so stop retrying after the first failure.

    Where are the logs for an agent that failed?

    In Workbench, open the agent's runs. For a development app the banner already names the missing input.

    The builder chat changed my files and now they're gone, or a change got overwritten.

    Every save keeps a snapshot — open the ⋯ menu above the preview and use Version history to restore an earlier one. See Version history in the App Builder guide.

    Do other surfaces know an agent's inputs?

    Not yet. Today the App builder reads the contract. Other surfaces will follow.