Skip to main content

busabase — the Busabase Python SDK

The official Python client for the Busabase REST API: Bases and records, Docs, files, skills, search, and the Change Requests every write goes through. One client works against Busabase Cloud and any self-hosted Busabase server.

  • Typed models for every endpoint, generated from the Busabase OpenAPI document with openapi-python-client.
  • A small hand-written layer on top: configuration from the same environment variables as the Busabase CLI, bb.<group>.<operation>(...) calls, and a typed BusabaseError for every failed request instead of a silent None.
  • Sync and async. Python 3.11+.

Install

pip install busabase
# or
uv add busabase

Quick start

from busabase import Busabase

bb = Busabase(api_key="bb_...")  # or set BUSABASE_API_KEY
for base in bb.bases.list():
    print(base.slug, base.name)
record = bb.records.get(record_id="rec_...")
print(record.head_commit.payload.to_dict())  # the record's field values

Busabase Cloud or self-hosted

Busabase Cloud Self-hosted server
base_url / BUSABASE_BASE_URL https://busabase.com (the default) your server, e.g. http://localhost:15419
api_key / BUSABASE_API_KEY required — create one in the dashboard not needed for a local server
space / BUSABASE_SPACE_ID needed when your key belongs to more than one space usually not needed
bb = Busabase()  # everything from the environment
bb = Busabase(base_url="http://localhost:15419")  # a local `busabase server`
bb = Busabase(api_key="bb_...", space="spc_...")  # a specific Cloud space

base_url may include /api/v1 or not. These are the same variables the Busabase CLI and the TypeScript SDK (busabase-sdk) read.

Cloud-only endpoints. The SDK covers the full public API. A few groups exist only on Busabase Cloud and return 404 from a self-hosted server: system (health, meta), users.me, and agent_tasks.

Writing: Change Requests

Every write in Busabase is a Change Request. When your key has write access it merges in the same call and you get the materialized object back; otherwise it waits for a person to review it. Check which one you got:

from busabase.generated.models import MaterializedRecord, PendingChangeRequest

result = bb.bases.create_change_request(
    base_id="bas_...",
    body={"fields": {"company": "Acme", "score": 7}, "message": "Add Acme — inbound lead"},
)
if isinstance(result, MaterializedRecord):
    print("merged:", result.id)
elif isinstance(result, PendingChangeRequest):
    print("waiting for review:", result.id)

Don't pass autoMerge — let the server decide based on your permissions.

Calling operations

Operations are grouped the way the API is (bases, records, nodes, docs, file_trees, change_requests, search, grep, …), with snake_case names. dir(bb) and dir(bb.records) list them.

bb.bases.list()
bb.records.list(base_id="bas_...", limit=20)
bb.nodes.get("nod_...")  # path parameters can be positional
bb.docs.create(body={"slug": "notes", "name": "Notes", "body": "# Hi"})
bb.grep(body={"pattern": "acme", "sources": ["records", "nodes"]})
bb.nodes.share.list()  # nested groups
await bb.records.get.asyncio(record_id="rec_...")  # async

Each one calls the generated function of the same name in busabase.generated.api and returns its parsed model (from busabase.generated.models). A request body can be a plain dict or the generated model. For full static typing, call the generated module directly with bb.client:

from busabase.generated.api.records import records_list

page = records_list.sync(client=bb.client, base_id="bas_...")

Errors

Any HTTP status of 400 or above raises BusabaseError — from bb.… calls and from the generated functions alike — carrying the server's error envelope:

from busabase import BusabaseError

try:
    bb.records.get(record_id="rec_missing")
except BusabaseError as error:
    print(error.status, error.code, error.message)  # 404 NOT_FOUND Record not found: rec_missing

code is the server's semantic outcome (NOT_FOUND, FORBIDDEN, BAD_REQUEST, CONFLICT, …). For a validation failure message spells out each field (fields.0.slug: Invalid input: …) and data holds the raw issues.

Configuration

Busabase(
    api_key=None,  # BUSABASE_API_KEY
    base_url=None,  # BUSABASE_BASE_URL, then https://busabase.com
    space=None,  # BUSABASE_SPACE_ID
    timeout=30.0,  # seconds, or an httpx.Timeout
    headers=None,  # extra headers on every request
    httpx_args=None,  # passed to httpx.Client / httpx.AsyncClient (proxy, verify, transport…)
)

Use it as a context manager (with Busabase() as bb:) or call bb.close() to release connections.

Changes in 0.2.0

Request models that used to have positional, generated names now have real ones, so you can build a request from typed models instead of a dict (a dict still works everywhere). If you import any of the old names from busabase.generated.models, rename them — the wire format and every bb.<group>.<operation>(...) call are unchanged.

0.1.0 0.2.0
NodesCreateChangeRequestBodyOperationsItemType0 … Type4 CreateNodeOperation, RenameNodeOperation, DeleteNodeOperation, RestoreNodeOperation, MoveNodeOperation
FileTreesCreateChangeRequestBodyOperationsItemType0 … Type3 AssetFileOperation, TextFileOperation, DeleteFileOperation, FileTreeMetadataOperation
FileTreesCreateBodyFilesItemType0 / Type1 AssetFileInput / TextFileInput
ViewsChangeRequestBodyType0 … Type3 CreateViewOperation, UpdateViewOperation, DeleteViewOperation, RestoreViewOperation
NodesUpdateContentBodyContentType0 … Type3 DocNodeContent, WhiteboardNodeContent, WorkflowNodeContent, HtmlNodeContent
EmbedLinksCreateBodyFramePolicyType0 … Type2 EmbedFramePolicyAnywhere, EmbedFramePolicyOrigins, EmbedFramePolicyTopLevelOnly
WebhooksCreateBodyType0 … Type2, WebhooksCreateBodyType0Config HttpWebhookRuleInput, NotifyAgentWebhookRuleInput, RunFunctionWebhookRuleInput, WebhookHttpConfigInput
RecordsListFiltersItem (and the count / group_by / list_page copies), RecordsListSort RecordFilter, RecordSort
RecordsListValueFiltersItemType0 / Type1 (and copies) RecordValueComparison / RecordValueAnyOf
ViewConfigFiltersItem, ViewConfigSortsItem ViewFilter, ViewSort
FieldOptionsChoicesItem, FieldOptionsHelp, BasesFieldChangeRequestBodyPatchOptions* FieldChoice, FieldHelp, the shared FieldOptions* models
BaseFieldNameType1 (and copies) LocalizedFieldName
FormBindingsItem, FormPageTheme, FormsCreateBody* / FormsUpdateBody* copies FormFieldBinding, FormTheme, the shared FormPage / FormShare
NodeAgentPromptsAgentPromptsType0Item (and the update-body copy), …LabelType1 / …BodyType1 NodeAgentPrompt, LocalizedText
NodesResolveRouteStateResponse200Type0 … Type2 ActiveNodeRouteState, ArchivedNodeRouteState, UnavailableNodeRouteState
FileTreesPreparePreviewResponse200Type0 … Type2 BuiltinFilePreview, ReadyFilePreview, UnavailableFilePreview
ChangeRequestsReviewResponse200ResultsItemType0, ChangeRequestsMergeResponse200ResultsItemType0, …Type1 ChangeRequestReviewSuccess, ChangeRequestMergeSuccess, ChangeRequestBatchFailure
WorkflowDocumentNodesItemType0 … Type7, …Position TriggerWorkflowNode, WebhookWorkflowNode, FunctionWorkflowNode, ConditionWorkflowNode, WaitWorkflowNode, ApprovalWorkflowNode, ActionWorkflowNode, EndWorkflowNode, WorkflowNodePosition
AuthVerifyResponse200Space / …SpacesItem, MaterializedBaseReviewPolicy AuthSpace, BaseReviewPolicy
from busabase.generated.models import CreateNodeOperation, NodesCreateChangeRequestBody

bb.nodes.create_change_request(
    body=NodesCreateChangeRequestBody(
        operations=[CreateNodeOperation(kind="create", node_type="folder", slug="calls", name="Calls")],
        message="Add a folder for call notes",
    )
)

Fixed: object and array query parameters. filters, value_filters and sort on records.list / list_page / count / group_by (plus date_range, aggregates, and status on change_requests.list / list_page) are now sent the way the server reads them — filters[0][fieldSlug]=company&sort[direction]=desc. In 0.1.0 a filter list was rejected with a 400 and sort was silently ignored (an unsorted page). They accept the generated models or plain dicts, through bb.… and through busabase.generated.api alike:

from busabase.generated.models import RecordFilter, RecordSort, RecordValueComparison

page = bb.records.list(
    base_id="bas_...",
    filters=[RecordFilter(field_slug="company", field_type="text", operator="equals", value="Acme")],
    value_filters=[RecordValueComparison(field_slug="score", operator="gte", value=40)],
    sort=RecordSort(field_slug="score", field_type="number", direction="desc"),
)

View filters are a best-effort superset unless they carry a field_type; value_filters are always exact.

Development

uv sync                                    # Python 3.11+, installs the pinned generator too
uv run pytest                              # unit tests + generated-code drift check
BUSABASE_TEST_BASE_URL=http://127.0.0.1:15419 uv run pytest -m integration   # against a local server

src/busabase/generated/ is generated — never edit it by hand. It is produced from openapi.json by scripts/generate.py, and tests/test_generated.py fails if the two disagree:

uv run python scripts/generate.py          # regenerate after openapi.json changes
uv run python scripts/generate.py --check  # what the test runs

License

MIT

Metadata

Release files for busabase 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for busabase 0.2.0
File Size Uploaded Provenance
busabase-0.2.0.tar.gz 246.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for busabase 0.2.0
File Interpreter ABI Platform
busabase-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 894.0 kB

Release files / busabase-0.2.0.tar.gz

Download URL busabase-0.2.0.tar.gz
Size 246.1 kB
Tags Source
SHA-256 checksum
How to use checksums
7cf87621212630538420e8c916e7c7dcee2b9f24e1a8ff6a5654785db75412fc
BLAKE2b-256 checksum
How to use checksums
7342214dea0b27a65a29cb8c48418e374eb31fdc88fa64075ba1fda791975748
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Release files / busabase-0.2.0-py3-none-any.whl

Download URL busabase-0.2.0-py3-none-any.whl
Size 647.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cbb6919d387db1c0924472aeac0a367543d4ed89be49a43b6687283410c97dd1
BLAKE2b-256 checksum
How to use checksums
bd12bf4b9f7626436b9ba9ebd39e5246205e785749cd6e315823fe5867019255
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Security for busabase 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for busabase-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page