Docs navigation
Agent

Integrations from your code

Canary only
Standalone integrations are enabled only for selected internal canary agents and remain off for other agents. CLI 0.54.0 includes the context commands, and the Python SDK is available by Git commit as shown below. Installing either does not enable integrations for your agent. The examples describe the canary path, not general availability.

Code you run yourself, such as a program built with the Kafka SDK or any plain MCP client, can use an agent's connected apps through the same brainbase-apps MCP server that hosted tasks use. It sees the same accounts and follows the same Allow, Ask and Deny rules. Connect apps and set rules on the agent's Tools page first; see Integrations.

How it works

  1. A person with management rights on the agent creates a context for it. A context is one program's access to that agent's apps, for at most 30 days. Creating it returns a renewal handle.
  2. Your code trades the renewal handle for a short-lived access grant, valid for about 15 minutes. Each trade also returns a new renewal handle, which replaces the old one.
  3. Your code calls the MCP server with the access grant as its bearer token.
  4. When an action needs approval, the call returns an approval link. A person approves it in the browser, and your code checks back with get_operation.
Keep both tokens secret
The renewal handle (bbir_…) and the access grant (bbig_…) let your code run the agent's app actions. Store them like any other secret. Never put them in a prompt, a log or source control.

Create a context

With CLI 0.54.0 or later, create the context with the command below, signed in as yourself with brainbase login and management rights on an enabled agent. It prints the renewal handle once. Put it in your program's secret store.

bash
brainbase integrations context create --agent "$AGENT_ID" --application nightly-report
# --max-lifetime 30d   how long the context may live: 60s to 30d (default 7d)
# --json               print {context_id, renewal_handle, renewal_expires_at, max_expires_at}
  • --agent defaults to the agent the current folder is linked to.
  • --application names your program: up to 120 characters of a-z, 0-9, ., _, :, / and -, starting with a letter or digit.
  • The CLI requires your own signed-in session. It refuses BRAINBASE_TOKEN and does not use a stored personal access token. Task keys and integration grants cannot create contexts.

For the enabled canary, the API also accepts a signed-in person's session token with management rights on the agent. Keep that session with the person creating the context; give your program only the renewal handle. max_lifetime_s is required, from 60 to 2592000 seconds.

bash
curl --request POST \
  --url "https://api.brainbaselabs.com/v2/integration-contexts" \
  --header "Authorization: Bearer $BRAINBASE_SESSION_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "agent_id": "'"$AGENT_ID"'",
    "application": "nightly-report",
    "max_lifetime_s": 2592000
  }'
json
{
  "context_id": "4c1d9a0e-7f3b-4e2a-9d55-1b8f0c6a2e77",
  "renewal_handle": "bbir_…",
  "renewal_expires_at": "2026-11-26T10:00:00Z",
  "max_expires_at": "2026-11-26T10:00:00Z"
}

A recognised credential that is not a person's session can be refused with 403 human_credential_required: "Sign in with your Brainbase session to manage integration contexts." Missing or invalid credentials can instead return 401. A 404 can mean the feature is off, the agent is outside the canary rollout, or you cannot manage that agent.

Call apps with the Python SDK

Install the reviewed SDK commit from GitHub using an identity with access to the repository. The SDK is distributed by Git commit; the package version alone does not identify its contents.

bash
python -m pip install 'kafka-sdk @ git+https://github.com/BrainbaseHQ/kafka-sdk.git@61e786f833b8be9f887c4c3bb9c86022b711c63b'

kafka_sdk.standalone_integrations renews access, retries lost responses and waits for approval. Give it a renewal handle and a function that saves each replacement before the new grant is used. In this example, create the handle file with mode 0600 first, and replace the example feed URL with your own RSS feed.

nightly_report.pypython
import asyncio
from pathlib import Path
from kafka_sdk.standalone_integrations import StandaloneIntegrations

HANDLE = Path("~/.config/brainbase/renewal-handle").expanduser()  # mode 0600, outside source control

async def main():
    async with StandaloneIntegrations(
        HANDLE.read_text().strip(),
        on_renewal_handle=lambda handle: HANDLE.write_text(handle + "\n"),
    ) as apps:
        result = await apps.run(
            "run_action",
            {"action_key": "rss-merge-rss-feeds",
             "configured_props": {"urls": ["https://example.com/feed.xml"]}},
            timeout_s=600,
        )
        print(result["status"], result.get("data") or result.get("error"))

asyncio.run(main())
  • For run_action, proxy_request and upload_file, run(tool, arguments) adds an operation_id if you leave it out and reuses it when retrying a lost response. Save and pass your own id if you need to resume after restarting your program.
  • When an action needs approval, run prints the approval link (or calls your on_pending), checks get_operation every 5 seconds or more, and runs the action once it is approved. It returns the pending result if nobody answers before the request expires or timeout_s passes.
  • call(tool, arguments) returns the envelope without waiting for approval. It can renew and retry after a 401. Both call and run renew the grant when two minutes or less remain.
  • Tool outcomes, including denied, error and outcome_unknown, are returned as envelopes. Connection and access failures can raise StandaloneNotEnabled, RenewalInvalid, ServiceRefused (with status, error and reason), or ServiceUnavailable; other renewal failures can raise IntegrationsError.

Get and renew access

Without the SDK, send the renewal handle, and nothing else, to the renew endpoint. The response holds an access grant and a new renewal handle. Save the new handle before you use the grant: the old one stops working.

bash
curl --request POST \
  --url "https://api.brainbaselabs.com/v2/integration-grants/renew" \
  --header "Authorization: Bearer $BRAINBASE_RENEWAL_HANDLE" \
  --header "Content-Type: application/json" \
  --data '{}'
json
{
  "grant": "bbig_…",
  "grant_expires_at": "2026-10-27T10:14:30Z",
  "renewal_handle": "bbir_…",
  "renewal_expires_at": "2026-11-26T10:00:00Z"
}
  • Renew a minute or two before grant_expires_at. A grant lasts about 15 minutes and never past the context's end. A call made with an expired grant is refused before anything runs.
  • If a renew response is lost, retry with the same handle within 60 seconds. The server can return the same replacement pair while that replacement handle is still current. After that recovery window, the old handle returns 401 renewal_invalid.
  • The renewal handle never outlives the context. Renewing never changes the agent, extends the context, or clears a Deny or a waiting approval.
  • 401 renewal_invalid means the handle is unknown, expired or already replaced, or its context has ended. Use the newest saved handle; if that is lost or the context has ended, a person must create a new context. 503 renewal_unavailable means Brainbase could not renew just now: try again with the same handle.

Call the MCP server

To use any MCP client instead, call the server over stateless Streamable HTTP at https://brainbase-pipedream-mcp.onrender.com/mcp. Send the current access grant as the bearer token on every request. The agent comes from the grant, so no tool takes an agent id. The tools, inputs and result shape are the ones described in What the agent sees and Results and errors. This example uses a grant you have just renewed and saved as BRAINBASE_ACCESS_GRANT; your application must renew it before expiry. Replace the example RSS feed URL with your own.

run_action.pypython
import asyncio, os, uuid
import httpx
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client

URL = "https://brainbase-pipedream-mcp.onrender.com/mcp"

async def call(tool: str, arguments: dict) -> dict:
    grant = os.environ["BRAINBASE_ACCESS_GRANT"]
    http = httpx.AsyncClient(headers={"Authorization": f"Bearer {grant}"}, timeout=300)
    async with http, streamable_http_client(URL, http_client=http) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            await session.list_tools()
            result = await session.call_tool(tool, arguments)
            return result.structuredContent

async def main():
    op = str(uuid.uuid4())  # keep it: retries and status checks reuse it
    envelope = await call("run_action", {
        "action_key": "rss-merge-rss-feeds",
        "configured_props": {"urls": ["https://example.com/feed.xml"]},
        "operation_id": op,
    })
    print(envelope["status"], envelope.get("data") or envelope.get("error"))

asyncio.run(main())
  • Send the current grant on every request. The example opens a new MCP session for each call; the server uses stateless HTTP.
  • Keep the same operation_id and input when retrying run_action, proxy_request or upload_file. A completed operation returns its stored receipt: success has data.replayed: true and a summary, not the full original result. An operation still running can return busy; do not replace its id to force another execution.
  • Uploads take source: {kind: "bytes_base64", …} or {kind: "public_url", url}, up to 25 MiB. Workspace paths are only for hosted tasks.
  • The standalone path does not write large results to workspace files or set result_ref. Use output.fields or output.filter on run_action to narrow the inline result; max_inline_bytes does not create a file here.

Approving an Ask

When an action has an Ask rule, the call returns status: "pending" and the requested action has not run. Your integration grant cannot approve it; a person answers in the browser. New execution operations in the same context wait behind that request. Discovery and get_operation remain available.

json
{
  "status": "pending",
  "operation_id": "a479040e-55c5-4c86-ad3b-dc6aa04592b1",
  "pending": {
    "event_id": "fca861a6-ce5b-4f30-b37b-ef67c2a51c58",
    "capability": "rss.rss-merge-rss-feeds",
    "state_generation": 3,
    "mode": "standalone",
    "message": "rss.rss-merge-rss-feeds needs a person's approval. Open https://app.brainbaselabs.com/integrations/approvals/fca861a6-ce5b-4f30-b37b-ef67c2a51c58 to approve it, then call get_operation with this operation_id (no more often than every 5 s) and repeat the call once it is approved.",
    "approval_url": "https://app.brainbaselabs.com/integrations/approvals/fca861a6-ce5b-4f30-b37b-ef67c2a51c58",
    "poll_after_s": 5,
    "expires_at": "2026-10-27T10:10:00Z"
  }
}
  1. Show pending.approval_url to the person who should approve, or send it to them.
  2. They open it and sign in to Brainbase. The page shows the agent, your program's name, the action and when the request expires. They choose Allow once or Deny. Only people with access to the agent can see or answer it, and someone who is not an agent manager cannot allow what the agent's rules deny.
  3. Your code calls get_operation with the same operation_id, no more often than every poll_after_s seconds (at least 5). It returns pending until someone answers, denied after a Deny, and after Allow once it returns success with data.phase: "approved". That means approved, not run.
  4. Repeat the original call with the same operation_id and input within 10 minutes of the approval and before the context expires. This submits the approved operation for execution. The agent's rule stays Ask, so the next new action asks again.
  • A request expires 10 minutes after it is made. get_operation can keep returning pending after expires_at, so stop waiting at the expires_at of the first response. Call again with a new operation_id to ask again; the old one returns cancelled.
  • Allow once covers only that operation_id and input. To ask about different input, use a new operation_id; reusing the old one with different input returns invalid_input.
  • Deny in the browser blocks that action for the whole context, for every later operation_id and across renewals, until an agent manager selects Clear this Deny on the same page or the context ends. It does not change the agent's rules.
  • Saving a lasting rule for the agent is a separate step on its Tools page. The approval page never changes the agent's rules.

When the outcome is unknown

outcome_unknown means an action may have reached the app, but Brainbase cannot confirm its result. New execution operations in that context return busy; the SDK does not retry an outcome_unknown envelope. Ask an agent manager to check the app before continuing. Do not use a new operation id or context to repeat the uncertain action.

On the standalone approval page, an agent manager can review a stored unknown outcome from their own signed-in session. After checking the app, they choose It happened, It did not happen or I can't tell, then Acknowledge. This records the finding and releases the context for further work. It never resends the uncertain action or changes its receipt: polling the same operation still returns outcome_unknown. Keep that action paused unless the manager confirms that repeating it is safe. Ending or recreating a context does not establish whether it happened.

End a context

When the code no longer needs access, end the context. Its renewal handle and grants are revoked; this cannot undo an action already dispatched. Use the command below in CLI 0.54.0 or later.

bash
brainbase integrations context end "$CONTEXT_ID" --reason "job retired"

Or call the endpoint with your session token. reason is 1 to 200 characters.

bash
curl --request POST \
  --url "https://api.brainbaselabs.com/v2/integration-contexts/$CONTEXT_ID/end" \
  --header "Authorization: Bearer $BRAINBASE_SESSION_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{"reason": "job retired"}'

Errors

The service's authentication refusals (401 or 403) happen before dispatch. The SDK validates renewal-handle format and removes bearer values and chained transport tracebacks from its connection errors; see Standalone SDK connection errors. Tool outcomes, including a Deny or an upstream failure, use the result envelope's status. A lost response, timeout or generic server error does not prove that nothing ran: retain the operation id and check get_operation before deciding what to do next.

ResponseMeaning
401 unauthorizedNo credential, or one the server does not recognise.
401 grant_invalidThe grant has expired, was revoked, or its context has ended. Check the body's reason: renew an expired grant; an ended context needs a new context.
403 grant_requiredOnly list_apps, search_actions and get_action work without an access grant. Every other tool needs one.
403 flag_offIntegrations are off for this kind of access.
404 agent_requiredThe credential is not tied to an agent. Discovery clients can use /p/{agent_id}/mcp or /t/{task_id}/mcp with a credential that can access that agent or task; execution still requires a grant.
404The agent or task is not one your credential can reach.
405A GET request. The server takes POST only.
413The encoded request exceeds the server body limit. Files have a separate 25 MiB limit.
503 unavailableAn authentication or access check was unavailable. Preserve the operation id when retrying.
401 renewal_invalid (renew endpoint)The renewal handle is unknown, expired or replaced, or its context ended.