Integrations

Stackbone Connect is how your agents and workflows reach the outside world, in both directions. An operator connects a provider once in Studio, and the credential stays in the broker inside your box. Your code names the connector and an operation; the broker mints a short-lived token and makes the call, so no key ever enters your container. When something happens on the provider's side, a new email say, a trigger starts a durable workflow run with it.

Direction What happens Where you set it up
Outbound A tool or a workflow step calls a provider: send a message, read a thread. Connections in Studio, then one line in your code.
Inbound A provider event starts a workflow as a durable run. Triggers in Studio. No code, unless the mapping needs it.
your code ──names connector + operation──▶ the broker, inside your box
                                             ├─ holds the real credential
                                             ├─ mints a short-lived scoped token
                                             └─ makes the call, returns the output

Connect a provider

Open Connections under Integrations and click Register connector. The catalog lists the providers Stackbone knows, and a Custom integration for any other service that speaks OpenAPI.

The catalog. Custom integration covers any OpenAPI service you set up by hand.

What ships in the catalog

Nine connectors come with the platform. Their protocol, their authentication and their events are already declared, so you only add the credential.

Connector Authentication Starts a workflow when
Gmail OAuth A message arrives in the inbox
Outlook OAuth A message arrives in the Inbox
Outlook (App-Only) Client credentials A message arrives in a named mailbox
Google Drive OAuth A file is added to a folder
GitHub API key A commit is pushed, an issue is updated
Linear API key An issue is created, a comment is added
Supabase API key A row is inserted
Telegram API key Never. This one only sends
WhatsApp API key Never. This one only sends

Anything else is a Custom integration: you point it at the provider's OpenAPI document and it gets the same treatment, operations included. The list grows, so the dialog in Studio is the current answer, not this table.

Pick one and the technical fields come pre-filled from the catalog. You add the credential: the API key, or the OAuth client id and secret. It is write-only: the broker encrypts it and never reads it back to the screen. For a custom integration you also paste the provider's OpenAPI document (JSON or YAML), its base URL, the auth mode, the scopes and where the token travels. Registering the same integration again rotates its credential in place.

A key-based provider is ready as soon as you register it. An OAuth one waits for an account.

Each row says what still needs doing. A provider that authenticates with a key is Ready on registration. An OAuth provider shows No account until you click Connect account and finish the provider's sign-in; that grant is the account the broker acts as. Open Operations on a row to see what the provider exposes, read live from its OpenAPI document. Your code names those operation ids.

What the Gmail integration exposes. The id under each path is what your code calls.

Registering, editing and removing an integration takes the owner or admin role: a connection holds a customer credential, so it follows the same gate as secrets.

Call it from your code

After you connect a provider, your code reaches it by name. There is no client to build and nothing to import from the provider.

You are in Use Import from
An agent's tool list connectorTool({ connector, operation }) @stackbone/sdk/deep
A tool body or a step stackbone.connection(id).call(op, args) @stackbone/sdk
A workflow step, explicit callConnector(id, op, args) @stackbone/sdk/workflow

The shortest form gives an agent a provider operation as a tool. The model sees a tool named after the connector and the operation; the body runs through the broker:

deep-agents/support/index.ts
import { z } from 'zod';
import { defineDeepAgent, connectorTool } from '@stackbone/sdk/deep';

export default defineDeepAgent({
  name: 'support',
  model: 'openai/gpt-4o-mini',
  tools: [
    connectorTool({
      connector: 'telegram',
      operation: 'send-message',
      description: 'Send a Telegram message to a chat.',
      schema: z.object({ chat_id: z.string(), text: z.string() }),
    }),
  ],
});

The agent's own instruction ("reply to customers on Telegram") is a prompt in the catalogue. You write it in Studio, not in this file. See Prompts.

From a workflow step, call the operation directly:

workflows/notify.workflow.ts
import { callConnector } from '@stackbone/sdk/workflow';

async function notify(chatId: string, text: string) {
  'use step';
  await callConnector('telegram', 'send-message', { chat_id: chatId, text });
}

While stackbone dev runs, it reads each connector's document and writes typed operation maps into .stackbone/connect.d.ts, so stackbone.connection('gmail').call(...) autocompletes its operations and their arguments.

A call fails with a coded error rather than a token: for example connector_installation_required when the provider was never connected. The stackbone.connection page covers every call form, the principal a call is made as, and the error codes.

Receive events

A trigger link binds one provider event to one workflow. Open Triggers under Integrations and click New trigger: pick the connector (one whose catalog entry fires a trigger), the trigger, the workflow to start, and the credential to poll with, the shared account by default. A new link starts switched off, so nothing runs until you have mapped the event.

Some triggers need one more thing from you: a value that scopes the watch to a place. Google Drive's folder trigger asks for a Folder ID, the last segment of the folder's URL; Outlook (App-Only) asks for a mailbox. The dialog labels and explains whichever value the trigger declares, and the link cannot be armed without it.

Map the event onto the workflow's input. The event has its own fields (from, subject, threadId); the workflow declares its own input schema. The mapping editor puts them side by side. Click a field on the left, then the field it should fill, or drag between their dots. One event field can feed several inputs. Transforms (a regex, a template, a default, a date format, JSON parse, a free expression) sit between the two when a value needs shaping. Auto-map asks the workspace's model for a first draft, drawn dotted until you accept each line. Code shows the same mapping as one expression you can edit by hand.

Two lines drawn, one required field still to fill. The link stays off until it is complete.

The editor names any required input still unmapped. Test against the sample event runs the mapping over a representative event from the catalog and shows what the workflow would receive. Save mapping keeps the polling cursor, so the box does not replay events it already handled. Then Turn on.

From then on the box polls the provider and starts one durable run per new event. Every link polls on the same cadence, 30 seconds by default. Those runs sit in Runs with the others, and View runs on the link filters to them. The polling itself is one of the box's recurring jobs, so its cadence and last result are on that screen too.

The Triggers list: each link with its state, its mapping and its last poll.

What counts as a new event

A poll asks the provider what changed since last time, so what starts a run is whatever the provider's own dates say moved. Google Drive's File added to folder fires when a file arrives in the folder, and also when a file the link has never delivered is edited — a document that was already sitting there when you armed the link, for instance.

It fires once per file, though. Once a file id has been delivered, later edits to it start no further run.

The event carries both of Drive's dates, so the workflow can tell which of the two happened. Read the gap between them, not just their order:

What you see What happened
modifiedTime before createdTime Someone uploaded a document written earlier. Drive keeps the source file's own date.
The two within a few seconds The file was created in the folder. A workflow that files through this connector lands here: it writes the name and the parent first, then the bytes, so modifiedTime settles a moment after createdTime.
modifiedTime clearly later The file was edited where it already sat.

Delivery is at-least-once per item, keyed on the provider's own id — for Drive, the file id. Three consequences worth knowing before you design the workflow:

  • A document dropped in the folder starts one run, however old the document is. Drive keeps the source file's own modified date on upload, so a contract last edited in March arrives stamped March. The trigger asks on the arrival date as well, which is what makes it fire anyway.
  • Editing that same file again does not start a second run. The delivery is already claimed under that file id. A re-uploaded recording does start one, because a re-upload is a new file in Drive with a new id — not a new date on the old one.
  • A file your own workflow writes into the folder it watches is an arrival like any other, so it fires the trigger and the run writes again. Watch one folder and write to a different one, or filter your own output out by name or mime type.

A Google Doc in a folder people work in is chatty for the same reason: its modified date moves on every collaborator's keystroke. That costs listings, not runs — every one of those polls dedups to the same file id.

Resume a run instead of starting a new one

By default every event starts a new run. When a workflow emailed someone and paused for their reply, you want that reply to wake that run. Wire the event field that identifies the conversation, the threadId in the mapping above, to the correlation key, and have the paused workflow park its hook on that same value (see waiting for a reply). When an event arrives, a run parked on that key resumes with the state it built up, and the delivery says resumed; with no run parked, a new run starts and the delivery says started. If every delivery says started when you expected replies to continue a conversation, the two sides are not landing on the same value.

Try the path without a real account

Start the workflow the event would start, with your own input:

stackbone workflows start refund --input '{ "orderId": "A-1042", "amount": 89.9 }'

How it stays safe

  • The broker runs inside your box. It encrypts credentials there, and they never reach the control plane or your agent code.
  • The runtime signs every call your code makes, and mints a token for that one call and that one connector.
  • A connector call outside the runtime (stackbone dev or a deployed box) fails with an error saying so, because nothing outside it can sign a call.
  • The box refreshes the token an OAuth provider handed over, on its own timer, another of the recurring jobs, so a long-lived trigger keeps polling.
  • A call made while the runtime is running your code carries the id of the run that made it. You pass nothing, and it still traces back to that run. See stackbone.connection.

Read more

BUILT WITH ❤️ FROM CANADA AND SPAIN