Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Biocompile Python SDK

A small, dependable client for the Biocompile API (v1). It handles the token exchange, refresh, retries and error mapping, and returns the API's JSON as plain dicts. Python 3.10+, one dependency (httpx).

pip install --pre biocompile    # pre-release while the API is in alpha

Quickstart

Personal access token

Create a personal access token (bcp_...) in Biocompile under your account settings, choosing the scopes the script needs.

from biocompile import Client

with Client(
    base_url="https://app.biocompile.com",
    personal_access_token="bcp_...",
    organization_id="org_...",       # optional; required for Knowledge Hub file calls
    scopes=["projects:read", "runs:read"],  # optional; narrows the token further
) as client:
    for org in client.organizations.iter():
        print(org["id"], org["name"])

    for run in client.runs.iter("org_..."):
        print(run["slug"], run["status"])

Service account

A service account authenticates with its user id and a key (bcs_...). It is always bound to one organization.

client = Client(client_id="<service account id>", client_secret="bcs_...")
client.files.upload("results/table.tsv", absolute_path="/projects/alpha/table.tsv")

From the environment

client = Client.from_env()

reads BIOCOMPILE_BASE_URL (default https://app.biocompile.com), BIOCOMPILE_PAT, or BIOCOMPILE_CLIENT_ID + BIOCOMPILE_CLIENT_SECRET, and BIOCOMPILE_ORGANIZATION_ID. Keyword arguments override the environment.

How authentication works

The client never sends your long-lived credential to the API. On the first call it exchanges it at POST /api/v1/oauth/token for a bearer token that lives about ten minutes, caches that token, and exchanges again about a minute before it expires. If the API still answers 401 (say, the token was revoked server-side), the client exchanges once more and retries the call once; a credential_revoked answer is raised straight away. Pass token= instead to use an access token you obtained yourself; it is never refreshed.

Credentials are held in a wrapper that prints as ***, and neither the client's repr nor any error message contains them.

Resources

Organization-scoped methods take the organization id first; pass None to use the client's organization_id.

Area Methods Scope
client.organizations list, iter, get projects:read
client.projects list, iter, get projects:read
create(org, name, ...), update(org, project_id, ...), archive(org, project_id) projects:write
client.contributions list(org, project_id, status=...), iter, get entities:read
accept, withdraw, request_revision(org, project_id, contribution_id, instructions) contributions:review
client.entities list(org, entity_type, **filters), iter entities:read
client.observations, client.hypotheses list, iter, get(org, slug) entities:read
client.executables list, iter, catalog, get runs:read
invoke(org, executable_id, payload), create_plan(...) runs:execute
client.plans refresh(org, plan_id), invoke(org, plan_id, payload) runs:execute
approve_spending(org, plan_id, binding), and direct_user_intent=, network_access=, network_acknowledged= on the invoke calls runs:execute + spending:approve
client.runs list, iter, get, logs, iter_logs, download_artifact(org, run, artifact, dest) runs:read
cancel runs:execute
client.files list, iter, get, download(file_id, dest) files:read
upload(path, ...), and the steps init_upload, upload_chunk, complete_upload, upload_status, cancel_upload files:write
create_folder(name, path=...), move(file_id, new_path), rename(file_id, name), update(file_id, description=..., tags=...), delete(file_id) files:write
client.workbenches list_action_types, get_action_type, get, list_changes entities:read
open(org, project_slug), stage(org, workbench, intents, expected_revision_id=...), submit(...) entities:write
client.conversations create, list, iter, get (transcript), send, get_turn, send_and_wait agent:converse
create(..., full_auto=True) agent:converse + spending:approve
client.webhooks list, create, get, update, delete, rotate_secret, test, list_deliveries, iter_deliveries, redeliver webhooks:manage

iter* helpers page through a list for you, following pagination.next_cursor where the API offers a cursor and skip/limit (or page) otherwise. Filters are passed as keyword arguments named as in the API (status=, project_slug=...).

Knowledge Hub file calls act in the organization the token is pinned to: a service account always is, and a PAT is when the client has organization_id.

Uploads and downloads

status = client.files.upload(
    "reads.fastq",
    absolute_path="/projects/alpha/reads.fastq",
    on_conflict="rename",
    progress=lambda sent, total: print(f"{sent}/{total}"),
)
print(status["file_id"])

client.files.download(status["file_id"], "copy.fastq")
client.runs.download_artifact("org_...", "run_abc", "art_1", "outputs/")  # server filename

upload uses the resumable protocol: it sends each chunk with its SHA-256, skips chunks the server already holds, completes the upload and (unless wait=False) polls until processing finishes. An interrupted upload resumes with client.files.upload(path, upload_id="resumable_..."); a terminal failure raises UploadFailed with the server's status on .upload. Downloads stream to a .part file and are renamed into place only when complete; dest may be a path, a directory, or a binary file object.

Changing a project: workbenches

Writes are staged in a workbench and submitted as a contribution that a person reviews in Biocompile; nothing changes in the project until they accept it (see Propose changes through the API).

workbench = client.workbenches.open("org_...", "rp_a7x3", title="Nightly import")
staged = client.workbenches.stage(
    "org_...",
    workbench["workbench_id"],
    [{"operation": "create", "collection": "hypotheses",
      "payload": {"statement": "Protein X activates pathway Y."},
      "rationale": "Seen in three independent screens."}],
    expected_revision_id=workbench["revision_id"],
    idempotency_key="import-2026-10-27-batch-1",   # makes a retry safe
)
result = client.workbenches.submit(
    "org_...", workbench["workbench_id"],
    expected_revision_id=staged["revision_id"],
    title="Nightly import", summary="Three hypotheses from the screening run.",
)
print(result["review_url"])

Reviewing contributions and approving spending

A contribution waits for a person to review it. A personal access token (or an application acting for you) with contributions:review decides as you would in the app; service accounts can read contributions but not decide (see Review contributions through the API).

for contribution in client.contributions.iter("org_...", "rp_a7x3", status="awaiting_approval"):
    result = client.contributions.accept("org_...", "rp_a7x3", contribution["contribution_id"],
                                         reason="Checked against the screen.")
    if result["status"] == "conflicted":   # nothing was applied
        client.contributions.request_revision("org_...", "rp_a7x3", result["contribution_id"],
                                              "Rebase on the current project.")

With spending:approve a token approves the credit spending of its own plan, for the plan's current quote only:

plan = client.executables.create_plan("org_...", "exe_...", input={"samples": "/projects/alpha/samples.csv"})
approval = plan["approval"]
client.plans.approve_spending("org_...", plan["plan_id"], approval.get("spending_binding") or approval["binding"])
run = client.plans.invoke("org_...", plan["plan_id"], idempotency_key="screen-2026-10-30")

Grant spending:approve and contributions:review only to credentials that should make those decisions without anyone opening the app (see Scopes).

Conversations with the agent

conversation = client.conversations.create("org_...", title="Weekly scan", project_slug="rp_cardio01")
turn = client.conversations.send_and_wait(
    "org_...", conversation["conversation_id"],
    "Summarize this week's new papers on the target.",
    idempotency_key="weekly-scan-2026-10-27",
    timeout=900,
)
print(turn["status"])          # completed, blocked, failed or cancelled
for event in turn["events"]:
    for part in event["parts"]:
        if part["type"] == "text":
            print(event["author"], part["text"])

send_and_wait sends the message, then polls get_turn with after= and returns the final turn with all its events. A turn in requires_action waits for a person in the app, so polling continues through it unless return_on_requires_action=True. After timeout seconds it raises TurnTimeout (the last state is on .turn).

create(..., full_auto=True) (needs spending:approve, and an organization that allows Auto Mode) lets the agent answer its own checkpoints, spending included, under your Auto Mode policy; the conversation's autonomy_mode is then auto. Contributions, project creation and open questions still stop.

Webhooks

client.webhooks.create(org, url, events) returns the signing secret once; store it. Verify each delivery against the raw body before parsing it:

from biocompile import WebhookVerificationError
from biocompile.webhooks import verify_signature


def receive(headers, raw_body: bytes) -> int:
    try:
        event = verify_signature(SECRET, headers, raw_body)  # tolerance_seconds=300
    except WebhookVerificationError:
        return 400
    queue_for_processing(headers["webhook-id"], event)  # dedupe on webhook-id; answer fast
    return 204

It implements Standard Webhooks: an HMAC-SHA256 over webhook-id.webhook-timestamp.body with the base64 key after whsec_, compared in constant time against every v1, entry in webhook-signature, and refuses timestamps more than tolerance_seconds from now.

Errors

Every error derives from biocompile.BiocompileError. HTTP errors are ApiError subclasses carrying .status, .code (the API's stable problem code, e.g. insufficient_scope), .detail, .problem (the full RFC 9457 document) and .retry_after:

Class When
AuthenticationError 401 (credential_revoked, expired token), or the token endpoint rejected the credential (invalid_grant, invalid_client)
PermissionDenied 403: insufficient_scope (see .missing_scopes), organization_mismatch, not_public; also invalid_scope / invalid_target from the token endpoint
NotFound 404
RateLimited 429 after retries ran out (.retry_after)
ServiceUnavailable 503, e.g. revocation_unavailable, after retries ran out
ApiError any other non-2xx status
TransportError no HTTP response at all (DNS, connection, timeout)
TurnTimeout conversations.send_and_wait ran out of time (.turn)
WebhookVerificationError webhooks.verify_signature refused a delivery
from biocompile import PermissionDenied

try:
    client.runs.cancel("org_...", "run_abc")
except PermissionDenied as exc:
    if exc.code == "insufficient_scope":
        print("token is missing:", ", ".join(exc.missing_scopes))

Retries and rate limits

  • 429 and 503 are retried up to max_retries times (default 3), waiting for the server's Retry-After (or a short jittered backoff when it sends none). A Retry-After longer than max_retry_wait (default 60 s) is raised at once instead of blocking the script.
  • Non-idempotent POSTs are only retried when the server refused them before doing anything: any 429, or a 503 the API's admission step raised (revocation_unavailable, rate_limit_unavailable). A POST that carries an idempotency key the server deduplicates on (idempotency_key= on workbenches.stage and conversations.send, or a client_turn_id) is retried like a GET. Use the idempotency_key field on invocations if you add retries of your own.
  • Connection failures (nothing reached the server) are retried the same way.
  • client.rate_limit holds the RateLimit-Limit/-Remaining/-Reset values of the most recent response that carried them.

Documentation and license

Guides and the full API reference: https://www.biocompile.com/docs/api-overview/. Licensed under the Apache License 2.0 (see LICENSE).

Metadata

Release files for biocompile 0.1.0a1

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

Source distribution (sdist)

Source distribution for biocompile 0.1.0a1
File Size Uploaded Provenance Actions
biocompile-0.1.0a1.tar.gz 29.3 kB Without provenance Details for biocompile-0.1.0a1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for biocompile 0.1.0a1
File Interpreter ABI Platform Actions
biocompile-0.1.0a1-py3-none-any.whl Python 3 none any Details for biocompile-0.1.0a1-py3-none-any.whl

Total release size: 62.6 kB

Release files / biocompile-0.1.0a1.tar.gz

Download URL biocompile-0.1.0a1.tar.gz
Size 29.3 kB
Tags Source
SHA-256 checksum
How to use checksums
d79526fe53176e1e411614d83a18db64a60dc1180c8f3d5ed91a34ab241ebf6d
BLAKE2b-256 checksum
How to use checksums
68ef1677e3f0582ebad0eeccd00eff5192a6166aab9a0c400040790eb0e35288
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / biocompile-0.1.0a1-py3-none-any.whl

Download URL biocompile-0.1.0a1-py3-none-any.whl
Size 33.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c60afbdd2ff7eb6e9f090fa22884009f669ace6c1305b298aab1c6eea3f0747e
BLAKE2b-256 checksum
How to use checksums
b830c4290ac1d17bd1dd030c6d974f8229025d969c82c1717fced945d0da7c7b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for biocompile 0.1.0a1

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release
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