beakr-sdk
The Python SDK for the Beakr public API: a typed client for projects, files, Knowledge, runs, interviews, conversations and integrations, sync and async.
pip install beakr-sdk # not on PyPI yet; see "Installing before the first release"
The distribution is beakr-sdk; the import name is beakr_sdk. The
Beakr CLI is built on this package, and
the TypeScript SDK (@beakr/sdk)
mirrors it.
Quickstart
from beakr_sdk import Beakr
beakr = Beakr() # api_key=..., else BEAKR_API_KEY, else your `beakr auth login`
project = beakr.projects.create("Research")
# One batch, folders kept. wait="knowledge" returns once its files are indexed
# and compiled into Knowledge pages; jobs.wait(batch) would stop at indexing.
beakr.files.upload_many("docs/", project_id=project["id"], wait="knowledge", timeout=1800)
hits = beakr.knowledge.sections("launch date", scope="Research")
print(hits["content"][0]["text"]) # ranked sections, each naming its page and section_id
# Knowledge titles the pages it compiles: find one by meaning, then read it.
page = beakr.knowledge.pages.search("launch plan", project_id=project["id"])["data"][0]
card = beakr.knowledge.cat(page["id"], scope="Research")
found = beakr.knowledge.documents_search("who covers losses from defects", project_id=project["id"])
document = found["results"][0] # best-matching uploaded document
beakr.files.download_reference(document["source_ref"], "downloads/")
facts = beakr.knowledge.rows(table="facts", entity=page["title"], current_only=True)
beakr.knowledge.add_finding(
page["title"],
date="2026-10-01",
kind="decision",
text="Stay on Stripe through 2027",
evidence=document["source_ref"], # cites the file you uploaded
rationale="Board decision",
scope="Research",
)
# A whole new page as a proposal: project_id (the project's id) is required here.
beakr.knowledge.proposals.create(
"create", project_id=project["id"], title="Launch Checklist", content="# Launch Checklist\n..."
)
ctx = beakr.context(project_id=project["id"], format="markdown")
# Private to you. To add to an imported session, send only the new messages with
# base_index = how many were sent before; without base_index the batch is read
# as the whole session from position 0. Message text is kept only by opt-in:
# compound=True imports and queues compounding in one request, holding the text
# until that capture reads it (hold_text=True holds it for a later
# beakr.compound without queuing; store_full_text=True keeps it).
imported = beakr.conversations.import_(
client="generic",
session_id="run-42",
base_index=0,
messages=[{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}],
compound=True,
)
tools = [tool["name"] for tool in beakr.integrations.tools(provider="slack")] # yours, if connected
if "slack_search_messages" in tools:
messages = beakr.integrations.call_tool("slack_search_messages", {"query": "launch date"})
link = beakr.interviews.create_link("Assay handover", questions=["Controls?"])
print(link["url"]) # shown once; send it
Tell Knowledge what a project is about
A project's Knowledge profile steers how Beakr compiles its files: what the project is for, the people, organizations and topics that matter, and the kinds of things to build pages for. Set it before or after uploading; Knowledge reads it when it next compiles files. Only the project's owners may set it.
beakr.projects.set_knowledge_profile(
project_id,
description="Batch records, deviations and CAPAs for the Basel site",
goals=["Track deviations to closure"],
entity_types=["batch", "deviation", "capa", "supplier"],
core_people=[{"name": "Priya Natarajan", "role": "Batch owner"}],
extraction_guidance="Record each deviation's root cause and its CAPA.",
)
beakr.projects.update_knowledge_profile(project_id, focus_areas=["pH excursions"])
print(beakr.projects.knowledge_profile(project_id)["entity_types"])
knowledge_profile raises NotFoundError (code == "knowledge_profile_not_found")
while the project has none.
Skills
A skill is a SKILL.md folder your agent loads on demand. skills.list covers
your own skills, your organization's, the built-in ones and public skills from
anywhere; each carries visibility, owner (me, org, system, anyone),
origin (created, mined, imported, system) and installed, and each
is also a filter.
mine = beakr.skills.list(owner="me")
builtin = beakr.skills.list(owner="system")
to_install = beakr.skills.list(visibility="public", installed=False)
skill = beakr.skills.create_from_folder("skills/plate-reader") # or a .zip
beakr.skills.download(skill["id"], "exported/") # exported/plate-reader/
beakr.skills.install(to_install[0]["id"])
for proposal in beakr.skills.proposals.list(): # drafted from your work
beakr.skills.proposals.accept(proposal["id"])
skills.get takes an id or a slug; a slug resolves to your own skill first,
then a built-in, then one you installed, then your organization's.
upload_bundle(skill_id, files, expected_sha256=...) raises ConflictError
(code == "skill_conflict") when the skill changed since you read it.
Know when Knowledge is ready
Uploaded files reach a project's Knowledge in the background after indexing. Wait for it before asking questions that depend on those files, and retry what failed:
from beakr_sdk.resources.knowledge_readiness import KnowledgeFailed
# The batch's own files: indexing, then Knowledge.
result = beakr.files.upload_many("docs/", project_id=project_id, wait="knowledge", timeout=1800)
print(result["batch"]["knowledge"]) # {"queued": 0, "running": 0, "created": 41, "failed": 1, ...}
# The whole project.
status = beakr.projects.wait_until_ready(project_id, timeout=900) # KnowledgeTimeout if not
for failed in status["failed_files"]: # newest first; knowledge_failures() lists them all
print(failed["path"], failed["error_code"], failed["reason"])
if status["failed"]:
beakr.projects.retry_failed(project_id) # or retry_failed(project_id, [file_id])
try:
beakr.projects.wait_until_ready(project_id, raise_on_failure=True)
except KnowledgeFailed as failed:
print(failed.status["failed_files"])
ready is true when nothing is queued or running. Failed files never end a wait
early; error_code is one of processing_failed, timed_out,
concurrent_update or compile_failed, and a retry is safe to repeat.
Embedded mode (a backend holding a service account's key). An org admin creates the service account and its key in the web app (Settings > Organization > Developer); everything after that is the SDK:
person = beakr.external_users.create("customer-42", name="Ada Lovelace")
beakr.labels.add_member(label_id, person["user_id"]) # labels give it projects
minted = beakr.end_user_tokens.mint(external_user_id=person["user_id"], scopes=["kb:read"])
user_beakr = Beakr(api_key=minted["token"]) # 15 minutes, no refresh
AsyncBeakr has the same methods (await beakr.projects.list()).
Web app only: creating API keys, creating or disabling service accounts and
minting their keys need a signed-in person, so an API key or token gets
SessionRequiredError (session_required) from keys.create,
org.service_accounts.create, mint_key and disable. While an
organization's authentication policy requires step-up (on by default) or
enterprise SSO, changing member roles and deleting a project are refused the
same way. Keys never mint keys; a backend mints short-lived end-user tokens.
- Auth chain: explicit
api_key,BEAKR_API_KEY, a local dev identity (BEAKR_DEV_IDENTITY+BEAKR_DEV_EMAIL, or the onebeakr auth devsaved, which is only used for the API it was saved for), the CLI's device-flow login (refreshed and re-saved automatically), the CLI's saved key, then the service account file. - Base URL: explicit
base_url,BEAKR_BASE_URL(BEAKR_API_URLalso works, read after it), the CLI config, thenhttps://api.thebeakr.com. - Headers:
X-Beakr-Client(sdk-python/<version>unlessclient_name=),X-Beakr-Agentwhenagent=is set,X-Beakr-Runwhenrun=is set,X-Beakr-Sessionwhensession=(default:BEAKR_SESSION) names the agent session behind the writes, and anIdempotency-Keyon create calls. - Retries: 429, 500, 502, 503, 504 and connection errors, with exponential
backoff and jitter, honoring
Retry-After(max_retries=to change). The header may be seconds or an HTTP-date (IMF-fixdate, or the obsolete RFC 850 and asctime forms); a date already past retries at once, one that is not a valid date is ignored in favor of the normal backoff, and a wait beyond 60 seconds is not taken: the error (RateLimitError.retry_afterin seconds) is raised instead. A 429 is always retried. A 5xx or connection error is retried only when resending cannot repeat an effect: reads, PUT/DELETE, routes that deduplicate themselves (starting an assistant run), and every public create, which the engine replays on itsIdempotency-Key-- the SDK resends the same key and body, so the retry returns the first result instead of a second resource (the list isbeakr_sdk._replay.REPLAYING_CREATES). A keyed create that meets an earlier attempt still holding the key (409idempotency_in_progress) waits and retries. Other POSTs (decisions such as accept, token exchanges, reads with a body) are never resent after an ambiguous failure. 501, 505 and other 4xx are never retried. - Errors:
BeakrErrorwithstatus,code,messageandrequest_id; subclassesAuthenticationError,PermissionDeniedError(required_scope; its subclassesPersonReviewRequiredErrorforperson_review_requiredandSessionRequiredErrorforsession_required),NotFoundError(and its subclassNotProvidedError, below),ConflictError,ValidationError,RateLimitError,ServerError,APIConnectionError,KnowledgeCommandErrorfor a knowledge command the door refused, andUnexpectedResponseErrorfor a success whose body is not JSON. A body that is not JSON is never the message: an HTML page (a base URL that is not a Beakr API, or a proxy in front of it) is named by its address and title, with afixsaying to check the base URL, and other text is cut to a short excerpt. - Secrets stay out of output:
repr()andstr()of the client, itscredential, a stored sign-in, a service-account file, a request and an error show an API key, access or refresh token orAuthorizationheader masked to its kind and last four characters (bk_live_...a1b2), so printing one in a test or a log does not leak it. Responses are returned as they come: thekeyofkeys.createororg.service_accounts.mint_keyand thetokenofend_user_tokens.mintare plain dict values, shown once. - Resources (the documented core only):
keys,me,meta,jobs,organizations,members,org(service_accounts),external_users,labels,end_user_tokens,projects,files(withuploads;upload_manyfor many files;approve_knowledgefor a file waiting for a person),bulk_uploads,folders,knowledge(withpages,proposalsandagent_review),runs,interviews,threads,conversations,engine(context,compound) andintegrations(withtoolsandcall_tool). - Documented routes only: this package sends nothing outside the documented
contract (
tests/test_shipped_surface.pybuilds the wheel and fails on any other route in it). The API still serves some routes it no longer documents, with no stability promise; the Beakr CLI keeps its own private client for the few it calls.
What this installation serves: beakr.meta.capabilities()
A self-hosted Beakr installation serves only part of the documented contract
(no organization administration or members, external users, end-user tokens,
service accounts, integrations or skills) and answers a bare 404 for the rest. Each server
lists what it serves in its own GET /v1/public/openapi.json;
capabilities() reads it once per base URL (cached for five minutes) so a
client can hide what is not there instead of failing on it:
caps = beakr.meta.capabilities()
if not caps.serves("GET", "/v1/integrations"):
print("This Beakr installation does not provide integrations")
serves takes a concrete path or a contract template. A server that publishes
no document is assumed to serve everything, and a route outside the documented
contract is always assumed served: the document cannot speak for it.
Calling one anyway raises NotProvidedError, a NotFoundError (so an existing
except NotFoundError still catches it), when the 404 is the router's bare
{"detail": "Not Found"}. Its message names the area, in the CLI's words
("This Beakr installation does not provide skills."), or the operation
when the path belongs to no known area; area, method and path hold the
same. A method that first looks up the active organization (members.list()
without org_id) names its own area when that lookup is the route missing. A missing record answers 404 with an error
envelope and stays a plain NotFoundError.
from beakr_sdk.errors import NotProvidedError
try:
beakr.skills.list()
except NotProvidedError as exc:
print(exc.message) # This Beakr installation does not provide skills.
The generated, typed layer: beakr.api
Every documented operation also has one generated, typed method, grouped by
area like the TypeScript SDK's beakr.api:
from beakr_sdk import AsyncBeakr, Beakr
from beakr_sdk._generated import models
beakr = Beakr()
project: models.ProjectOut = beakr.api.projects.create_project(body={"name": "Assays"})
for item in beakr.api.projects.list_projects_all(member_only=True): # follows the cursor
print(item["name"])
other = await AsyncBeakr().api.projects.get_project(project["id"]) # the async twin
beakr_sdk._generated.modelshas aTypedDictper contract schema (an enum or union schema is a type alias). Results stay plain dicts at runtime, soproject["id"]keeps working; the types are for your editor and type checker (the package shipspy.typed).beakr_sdk._generated.operationshas, per operation, a request builder (build_<area>_<method>(...)) and the typed sync and async methods. Path values are one percent-encoded segment each; a list operation also has<method>_all(), which followsnext_cursor.OPERATIONSmaps every<area>.<method>to its HTTP method and path.- The resources above are helpers on this layer: every request they send is
built by a generated builder (a test fails if a resource builds one by hand;
the only exceptions are the PUTs and GETs to presigned storage URLs). The 70 methods that send exactly one
operation are typed by it, for each client:
beakr.projects.get(...)is aProjectOut,await async_beakr.projects.get(...)too, andprojects.list()is alist[ProjectOut]. Multi-step helpers (uploads, flows) returnAny. - Path values are refused with
ValueErrorwhen empty,.or..(they would reach another route), everywhere (beakr_sdk._paths.segment). - Regenerate after a contract change with
uv run python scripts/generate_python_sdk.py. It reads the engine'sdocs/api/public-openapi.json(or a path /BEAKR_OPENAPI), the only contract this package is built from; the hand-written resources build their requests with this layer too.tests/test_generated_layer.pyfails when the committed layer is stale, checks that every operation, sync and async, sends its own request, and runs mypy over the layer (good and deliberately wrong usage).
Installing before the first release
Until beakr-sdk is on PyPI, install it from this repository, pinned to a tag
or commit:
pip install "beakr-sdk @ git+https://github.com/BeakrHub/beakr-python@<tag-or-sha>"
Development
uv sync
uv run pytest # the suite
uv run ruff check .
uv run mypy src/beakr_sdk
The generated layer is derived from the engine's documented contract. One command regenerates it after the contract changes:
uv run python scripts/generate_python_sdk.py # writes src/beakr_sdk/_generated
uv run python scripts/generate_python_sdk.py --check # exit 1 when stale (CI)
It reads docs/api/public-openapi.json from $BEAKR_ENGINE_PATH, else a sibling
beakr-engine checkout, else the committed
snapshot (BEAKR_OPENAPI names the file directly). The rendering is
beakr_sdk._codegen (private): the Beakr CLI renders its private client with the
same generator.
CI reads no other repository. The files this package is generated from and
compared with are committed under snapshots/: snapshots/beakr-engine (the
documented contract, the API changelog, the compatibility rules, and the
engine's replayed-create list as derived/replaying-creates.json) and
snapshots/beakr-typescript (its src/replay.ts, whose replayed-create list
must match this package's). snapshots/manifest.json pins the commit each came
from (with uncommitted_changes when taken from a working tree) and each
file's SHA-256. After an engine or TypeScript SDK change, refresh them from
checkouts beside this one (or named with --engine / --typescript), then
regenerate:
uv run python scripts/refresh_snapshots.py refresh # rewrite snapshots/ and the manifest
uv run python scripts/refresh_snapshots.py diff # exit 1 when stale against the checkouts
uv run python scripts/refresh_snapshots.py verify # offline: the files are the manifest's (CI)
uv run python scripts/generate_python_sdk.py snapshots/beakr-engine/docs/api/public-openapi.json
Tests that compare with another repository read BEAKR_ENGINE_PATH /
BEAKR_TYPESCRIPT_PATH, else sibling checkouts, else the snapshots, and fail
instead of skipping with BEAKR_REQUIRE_ENGINE=1 (both CIs set it). The
engine's CI checks this repository and beakr-typescript out on the branch named
like its own and runs this suite against the real repositories, so drift shows
up there before a refresh here.
Private modules
Modules whose names start with an underscore (beakr_sdk._codegen,
beakr_sdk._calls, ...) are private: no stability promise. The Beakr CLI builds
its private client on them, pinned to an exact commit of this package.
Release
Nothing publishes automatically yet. The release workflow
(.github/workflows/publish.yml) is prepared but runs only when a GitHub
release is published and the repository variable PYPI_PUBLISH_ENABLED is
true; until both, nothing is uploaded.
To enable it once:
- On PyPI, add a pending Trusted Publisher (Account settings, Publishing):
project
beakr-sdk, ownerBeakrHub, repositorybeakr-python, workflowpublish.yml, environmentpypi. - In this repository, create the
pypienvironment (Settings, Environments), ideally with required reviewers, and set the repository variablePYPI_PUBLISH_ENABLEDtotrue.
Versions and release notes come from the API contract
scripts/prepare_release.py (identical in every Beakr client repository;
release/config.json names this package and its version files) compares the
engine's contract at the last release (release/released.json) with the current
one, using the engine's compatibility rules (vendored in
scripts/contract_compat.py). A breaking change needs a major bump (below 1.0,
a minor one), an added or changed operation a minor bump, a wording change a
patch. The API changelog entries added since that release become the release's
section in CHANGELOG.md; the ### Client changes subsection is hand-written
and kept across reapplies.
uv run python scripts/prepare_release.py plan # what changed, which bump it needs
uv run python scripts/prepare_release.py apply # bump versions, write CHANGELOG.md
uv run python scripts/prepare_release.py check # what CI and publishing run
Releasing
uv run python scripts/prepare_release.py apply --date 2026-10-12
uv run python scripts/prepare_release.py check
uv build && uvx twine check dist/*
# Commit, tag v<version>, push, and publish a GitHub release for the tag.
# publish.yml runs ci.yml first, refuses a tag that does not match
# pyproject.toml, then uploads dist/* to PyPI.
# Afterwards, record what was released (the engine commit it was built from),
# then refresh the snapshot so it carries that commit's contract and changelog
# (snapshots/beakr-engine/release-base/<sha>), which the offline release check reads:
uv run python scripts/prepare_release.py record --engine-commit <engine sha>
uv run python scripts/refresh_snapshots.py refresh --engine <engine checkout>
After the first PyPI release, switch beakr-cli from its git pin to the PyPI
version (its README, "The Python SDK dependency").
Metadata
Release files for beakr-sdk 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| beakr_sdk-0.1.0.tar.gz | 216.0 kB | With provenance | Details for beakr_sdk-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| beakr_sdk-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for beakr_sdk-0.1.0-py3-none-any.whl |
Total release size: 424.3 kB
Release files / beakr_sdk-0.1.0.tar.gz
| Download URL | beakr_sdk-0.1.0.tar.gz |
|---|---|
| Size | 216.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e7131f254d4b7945a510937b61002d91b042e7209762bb5f681eda8631d04a28
|
|
BLAKE2b-256 checksum How to use checksums |
7a2e7d838677dbee05834db42a1cfa5801687d7fd212e480fb21b13149b1a5ec
|
| 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 Nov 2, 2026.
Transparency logRelease files / beakr_sdk-0.1.0-py3-none-any.whl
| Download URL | beakr_sdk-0.1.0-py3-none-any.whl |
|---|---|
| Size | 208.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
484898661fd0fadbb17b1fed5e6cc7bb3d1957d6bf1a261fadef753ad30d8261
|
|
BLAKE2b-256 checksum How to use checksums |
e29ca0e161b4935dff3625d099198d844af76264deba7ae6065a1856e4b27d5f
|
| 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 Nov 2, 2026.
Transparency logSecurity for beakr-sdk 0.1.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.
Attestation for beakr_sdk-0.1.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 Nov 2, 2026.
Transparency logA 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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.