Skip to main content

bayes-vault (Python)

The Python client for the Bayes Vault REST API provides the same two clients as the TypeScript SDK (@bayes-health/vault), with synchronous and asynchronous versions built on httpx and pydantic 2:

  • VaultClient: authenticates with your API key (the tenant key) and must run only on your backend. Every call identifies a user. It also creates agent sessions and connect links, and manages your webhook endpoint.
  • VaultUserClient: authenticates with the token from a session that your backend created for one user. It can run in your backend, an agent, or a native app. It cannot identify any other user.

All methods, models, and error classes are generated from Vault's OpenAPI document (docs/openapi.yaml). The developer platform serves this document at https://platform.getbayes.com/openapi/vault.json to a Bayes developer token. The transport, retries, pagination, streaming, polling, and webhook verification are handwritten to match the TypeScript SDK (docs/developer/sdk.md), including the same retry policy, cursor handling, stream protocol, and webhook verification.

Installation

The package is not yet available on PyPI and requires Python 3.10 or later. Install it from the repository:

pip install "bayes-vault @ git+https://github.com/awesomenuzzo/bayes_vault#subdirectory=clients/python"

Alternatively, build and install a wheel:

cd clients/python
uv build                     # creates dist/bayes_vault-0.1.0-py3-none-any.whl
pip install dist/bayes_vault-0.1.0-py3-none-any.whl

The runtime dependencies are httpx, pydantic 2 and anyio.

Backend quickstart

import os
from bayes_vault import VaultClient

vault = VaultClient(os.environ["BAYES_API_KEY"])      # base_url defaults to https://vault.getbayes.com

reads = vault.records("user_8412")                     # the id of your authenticated user
page = reads.query(vault_type="lab", code_group="hba1c", limit=50)
for record in page.data:
    print(record.record_id, record.handle)

for encounter in reads.iter_query(vault_type="encounter"):   # all pages, pinned to a single snapshot
    save(encounter)

window = reads.text(page.data[0].record_id)
print(vault.get_status("user_8412").model_dump())

Use base_url only when targeting another Vault origin, such as a local server (http://localhost:3000). The value must be an origin without a path because Vault serves its API only from the root of its own origin.

The client strips whitespace around the key or token, including a trailing newline from a secret file. If the credential contains internal whitespace or control characters, the client raises ValueError without including the value in the error.

Agent sessions

Your backend creates a session for each agent conversation and grants only the permissions that the agent needs. Provide an MCP client with the session's mcp.url and mcp.headers alongside the MCP servers for your other tools. mcp.headers is a plain dict containing the Authorization header with the token, while mcp.model_dump() returns the complete entry as a dict:

session = vault.sessions.create("user_8412", permissions=["read", "connect"], toolset="default")
session.mcp.url; session.mcp.headers; session.token     # the MCP entry, and the token for VaultUserClient

For example, with the OpenAI Agents SDK, pass MCPServerStreamableHttp(params={"url": session.mcp.url, "headers": session.mcp.headers}). The Claude Agent SDK supplies its MCP servers to the Claude Code process through the command line, where other local users can read the token. Therefore, when using it, use the header {"Authorization": "Bearer ${BAYES_VAULT_SESSION_TOKEN}"} and pass the token in ClaudeAgentOptions(env=...).

Available permissions are read, read_full (includes read), attachments, export, refresh, annotate (with read or read_full) and connect. By default, sessions receive read and connect. Use expires_in (60 to 31,536,000 seconds) to end a session sooner. A user can have up to 100 live sessions; creating an additional session ends the least recently used one. vault.sessions.get(id) returns a session with its current token, list(user) returns a user's live sessions, list_all() and iter_sessions() return the tenant's sessions, and delete_all() ends all of them. Store the session's id, not its token. At the end of the conversation, call vault.sessions.delete(session.id) to end the session immediately, including any connect links it created. Otherwise, the session ends after 30 days without use.

You can also use a session's token with VaultUserClient:

from bayes_vault import VaultUserClient

agent = VaultUserClient(session.token)

home = agent.chart.home(format="markdown")              # the patient summary (beta surface chart)
hits = agent.chart.search(pattern="metformin")
labs = agent.records.query(vault_type="lab", limit=20)
status = agent.get_status()

Treat clinical text returned in any response as untrusted source evidence, and do not follow instructions contained in it. This package does not wrap MCP.

A connect link opens the hosted page where a user can connect a health system, whose records are then stored in Vault. Send the link only to that user because, until it expires, anyone with the link can connect a health system to the user's chart:

link = vault.connect_links.create("user_8412", return_url="https://app.example.com/connected")   # expires_in: 3600 by default
link = agent.connect_links.create()            # the session needs the connect permission: 60 minutes, or until the session ends
vault.delete_connection("user_8412", "conn_8f2e")    # records already stored stay; delete_source erases them

A link created by a session must return to one of your application's origins registered on the Bayes platform.

Async

AsyncVaultClient and AsyncVaultUserClient expose the same methods. Await method calls and use async for with iterators and streams:

from bayes_vault import AsyncVaultClient

async with AsyncVaultClient(api_key) as vault:
    page = await vault.records("user_8412").query(vault_type="lab")
    async for point in vault.records("user_8412").iter_points(codes=["4548-4"]):
        ...

Methods

Methods use snake_case names and follow the TypeScript SDK's groups:

Group API key Session
Records layer vault.records(user) agent.records
Chart data vault.chart(user) agent.chart
Exports for one chart vault.exports(user) agent.exports
Tenant exports vault.tenant_exports() none
Chart management vault.get_subject(user), vault.get_status(user), vault.list_connections(user), vault.list_sources(user), vault.refresh_records(user, connection_id=...), vault.get_refresh_job(user, job_id), vault.reprocess_subject(user, scope=...) agent.get_status(), agent.list_connections(), agent.list_sources(), agent.refresh_records(connection_id=...), agent.get_refresh_job(job_id)
Data erasure vault.delete_subject(user), vault.delete_source(user, source_id), vault.get_job(job_id), vault.wait_for_job(job_id), vault.activate_subject(user), vault.authorize_source(user, source_id), vault.authorize_connection(user, connection_id) none
Tenant-level operations vault.get_settings(), vault.update_settings(...), vault.get_usage(), vault.list_subjects(), vault.get_changes(), vault.ack_changes(through_seq=...), vault.get_audit(), vault.list_endpoints() none
Sessions vault.sessions: create, get, list, list_all, iter_sessions, delete, delete_all none
Connect links and connections vault.connect_links.create(user, ...), vault.delete_connection(user, connection_id) agent.connect_links.create(...)
Webhooks vault.webhook: get, set, rotate_secret, delete; vault.deliveries: list, iter_deliveries, replay none
Comments from patients vault.comments(user): list, get, history, purge agent.comments: list, get, history, create, edit, retract
Patient-reported health records vault.patient_records(user): purge agent.patient_records: create, edit, retract
Medical code lookup none agent.codes: search, lookup

The records layer includes query, count, get, history, text, resource, original, references, referenced_by, attachment, imports, get_import, import_original, stream, changes, series and latest. The docstring for each method documents its required session permissions and beta surface.

Pass path parameters positionally (reads.text(record_id)). Pass all other parameters as keyword arguments using their wire names. Python reserved names are the exceptions: from becomes from_, and a member such as _vault becomes vault_ on a model. Keyword arguments set to None are not sent. Methods that accept a record id require the record id (vrec_…), not a readable handle. Methods that accept a source id require the source's public source_id (vsp_…), returned by list_sources and every record row. A path parameter equal to . or .. raises ValueError. Otherwise, URL parsing would remove the parameter and route the request elsewhere.

Annotations (beta surface annotations) provide patient comments, patient-reported records, and code lookup. Your application can use Annotations to work with the patient to add patient-reported records and comments to the chart. Annotations are currently in beta. Contact us to enable them for your tenant. Writes require a session with the read and annotate permissions. idempotency_key is optional; when omitted, the client generates a key. Repeating a write with the same key and body returns the first write's result. Therefore, after a retryable error, the client retries the write with the same key. Use dry_run=True to return the data that the write would store without storing it. See docs/developer/annotations.md and docs/developer/code-lookup.md.

refresh_records refreshes a single connection when you provide connection_id. If you omit connection_id, it refreshes every connection that stores records in Vault. The returned jobs contains one entry per connection, with either its job or its error.

get_audit and iter_audit list the tenant's read audit in newest-first order. Vault retains each row for six years after the request, including after the chart is erased. Rows for an erased chart retain the erased generation's public_id and do not include an external_user_id.

vault.request(method, path, query=..., json=...) sends an arbitrary request with the client's credential and retry policy. Use it for routes that have no method in this version. path must begin with /vault/; otherwise, the client raises ValueError, ensuring that the credential never leaves the Vault origin. In query, a list value repeats the parameter once for each item. For parameters that accept a comma-separated list, join the items yourself.

Models

Responses are pydantic models from bayes_vault.models. Because /v1 changes only by addition, the models preserve undeclared members in model_extra. For the same reason, string enums accept values unknown to this version. Every list page uses the same envelope: data, next_cursor, has_more (true exactly when there is a next_cursor) and count (the rows in data), along with the route's own members. If a response violates the published schema, the client raises VaultResponseValidationError, whose body contains the parsed JSON. The message and problems describe only where and how schema validation failed and never include any value from the body, making them safe to log.

Cursors and snapshot ids (vt1.…) are sealed tokens. Return them exactly as received; the client does not decode them.

Revisions and generations

Every read includes the chart's revision and generation. When passing back a revision—as revision, changed_since_revision, or a revision used as since or until on changes—provide its associated generation. generation is optional, and a revision provided without it is read in the chart's current generation. If it is provided and the chart has since been erased and activated again, or restored, Vault returns 410 chart_reset and includes the current generation in details["generation"]. Vault returns the same response for any cursor or snapshot from the previous generation. Treat generation as an opaque number and compare it only for equality.

When reading a record's full text in windows, include the first window's version_id with every subsequent window. This prevents an import between windows from combining two versions:

parts: list[str] = []
offset: int | None = 0
version_id: str | None = None
while offset is not None:
    window = reads.text(note_id, unit="chars", offset=offset, version_id=version_id)
    parts.append(window.text)
    offset, version_id = window.next_offset, window.version_id

Errors

Every failed request raises VaultApiError. Each code in Vault's error catalogue maps to its own subclass in bayes_vault.errors, such as RateLimitedError, CursorExpiredError or ChartResetError:

Attribute Description
code The catalogue code. Branch on this value or the class, never on the message.
status The HTTP status, or 0 if no response arrived.
message The server-provided message for people.
retryable The body's retryable value. If absent, it is true for 429 and 5xx.
retry_after The number of seconds to wait, taken from the body's retry_after or, if absent, the Retry-After header.
request_id The X-Request-Id to provide to support.
details The server's error.details, returned exactly as sent.

details contains the information required for each code. For rate_limited, details["bucket"] identifies the bucket, and details["limit"] identifies the limit that rejected the request: credential, chart (the tenant key's limit on one chart), tenant (the ceiling shared by every credential), or url (one capability URL's downloads, for the link_downloads bucket only). If a chart search pattern is rejected as regex_unsupported, details["feature"] identifies the reason. A repetition_size rejection also provides details["size"] and details["reach"]. For invalid_request, details["reason"] identifies the cause when Vault has one, such as page_size or invalid_revision. For chart_reset, details["generation"] contains the chart's current generation. For a stream error, details["cursor"] resumes the stream after the last record you received.

from bayes_vault import VaultApiError
from bayes_vault.errors import ChartResetError, CursorExpiredError

try:
    page = reads.query(cursor=saved_cursor)        # a cursor contains its filters, so send it by itself
except CursorExpiredError:
    page = reads.query(vault_type="lab")          # cursors expire after an hour: start again
except ChartResetError:
    resync_from_scratch()                          # the chart was erased and reactivated, or restored
except VaultApiError as error:
    log.error("vault %s %s %s", error.code, error.status, error.request_id)
    raise

If this version does not recognize a code, it raises the base VaultApiError. Handle the error using status and retryable. ConnectionCutError (connection_cut) is raised by the client and is never sent by Vault: either the connection dropped or a body ended early. Every exception is pickleable, so it can be returned from a worker process (multiprocessing, Celery, Ray).

Retries

The client retries a request only when repeating it is safe. This includes a GET or an operation marked x-idempotent in the OpenAPI document, such as POST …/query, POST …/chart/open, POST …/chart/search, DELETE …/subjects/{user}, DELETE …/sources/{sourceId}, POST …/activate or POST /vault/v1/erasures/ack. The TypeScript SDK repeats these requests, and is_idempotent(method, path) indicates whether a request is one of them. The six annotation writes are marked x-idempotent only because repeating a request with the same idempotency_key and body returns the result of the first write. Without idempotency_key, each request creates a new write, so the client includes a key with every write.

  • It retries errors whose retryable is true and connections that fail before any response, up to max_retries times (default 2).
  • When Vault provides retry_after, it waits for that duration. Otherwise, it uses exponential backoff with jitter, starting at 0.25 seconds, and caps each wait at 30 seconds.
  • If Vault requests a wait longer than 60 seconds, the error is raised directly to you.
  • It does not repeat any other request, including POST …/sessions, POST …/connect-links, POST /vault/v1/webhook/secret, POST …/replay, POST …/refresh, POST …/reprocess or POST …/exports, even after a 429, because Vault may already have processed it. If the connection is interrupted during one of these requests, the client raises ConnectionCutError with retryable=False; check the result before repeating the request.
  • It does not repeat vault.webhook.set, even though PUT /vault/v1/webhook is x-idempotent, because the secret is returned only by the call that creates the endpoint.
  • A session or connection that no longer exists returns not_found. If a retry of vault.sessions.delete or vault.delete_connection receives that response after an attempt that received no response or a 5xx, the earlier attempt may have deleted it, so the method returns deleted: True.

When the server is too busy to compile the pattern in time, a later page of a regex chart search returns 503 search_busy. Because chart searches are safe to repeat, the client waits for Retry-After and then resends the same cursor.

Set max_retries=0 to manage every retry yourself. on_response receives the ResponseMeta for each response: the status, the request id, rate_limit (limit, remaining and reset from the RateLimit-* headers, when all three are present), deprecation, sunset and etag.

timeout (seconds, or an httpx.Timeout) sets how long each request waits. By default, connections can take 10 seconds, and each read can take 60 seconds. If you pass timeout, it also applies when you provide your own http_client; otherwise, that client's own timeout is used.

Pagination

Each paged method has an iter_ variant that automatically follows next_cursor: iter_query, iter_history, iter_imports, iter_changes, iter_points, iter_latest, iter_references and iter_referenced_by on the records layer; iter_search on the chart layer; iter_exports, iter_files and iter_parts on the exports; iter_comments and iter_history for comments; iter_sessions and iter_deliveries; and iter_connections, iter_sources, iter_subjects, iter_changes (the tenant feed), iter_audit and iter_endpoints on the clients. Iteration ends when a page has no cursor or includes has_more: false.

A cursor contains the request that created it, including its filters, sort, shape and snapshot. Vault ignores any argument provided with a cursor except the route's page size (limit, or max_keys on latest), the tenant feed's wait, and chart search's generation. Therefore, each subsequent page includes only its cursor and those arguments. When you provide cursor= directly, the client sends only the cursor and those arguments, omitting your other arguments:

page = reads.query(vault_type="lab", limit=100)
while page.next_cursor:
    page = reads.query(cursor=page.next_cursor, limit=100)

Cursors expire one hour after the page that returned them (410 cursor_expired). To pin multiple reads to the same revision, pass a page's snapshot_id to another first request.

The tenant feed includes a cursor on every page, allowing you to continue following it. Store the last tenant_seq you applied, and acknowledge erasure markers only after applying them and storing their public_id to external_user_id mapping:

seq = last_processed_seq                         # the last tenant_seq applied and stored (0 to start)
page = vault.get_changes(after_seq=seq, limit=500)
while True:
    for entry in page.data:                      # entry.entry is one of "revision", "erasure" or "source_event"
        apply(entry)
        seq = entry.tenant_seq
    save_processed_seq(seq)
    if page.data:
        vault.ack_changes(through_seq=seq)
    if not page.has_more:
        break                                    # or continue following: vault.get_changes(cursor=page.next_cursor, wait=20)
    page = vault.get_changes(cursor=page.next_cursor, limit=500)

Streaming

reads.stream(...) returns a VaultStream over …/resources.ndjson, containing every matching record in one pinned snapshot. shape can be summary (the default), full or original:

with vault.records("user_8412").stream(resource_types=["Observation"], shape="full") as stream:
    for row in stream:                   # dicts; stream.lines() returns each line exactly as sent
        save(row)
end = stream.end                         # the final line: snapshot_id, revision, generation, public_id, records_sent
assert end is not None                   # set after the stream has been read to the end

# Later, retrieve only what has changed since then:
vault.records("user_8412").stream(resource_types=["Observation"], shape="full",
                                  changed_since_revision=end["revision"], generation=end["generation"]).to_file("delta.ndjson")

The stream follows continuation lines and yields records only after a checkpoint confirms them. If the connection drops, the stream resumes from the last checkpoint, ensuring that each record is delivered exactly once. Retryable error lines resume the same way, and both scenarios count toward max_retries. Every continuation and resume sends only the cursor because it contains the filters, the shape and the snapshot. If the stream cannot resume, it raises ConnectionCutError or the error line's class. If the error provides a resume point, details["cursor"] continues after the last record you received. To resume later, pass it as cursor= to stream(); the client sends it alone and ignores every other argument.

Vault ends each batch with a checkpoint. To bound memory usage, the stream holds no more than 10,000 records or about 32 MiB of lines while waiting for a checkpoint. After that limit is reached, it yields the response's records as they arrive, but no exact resume point is available until the next control line: stream.cursor is None, and a dropped connection raises ConnectionCutError without details["cursor"] instead of delivering records twice.

stream.cursor identifies the position immediately after the last batch delivered in full. It advances only after you receive the batch's final record, never before or partway through a batch. If you stop partway through a batch and later resume from stream.cursor, the entire batch is delivered again, so make your writes idempotent (for example, keyed on record_id). It stays None until a resume point is known; for example, when a stream starts without a cursor, it remains None until the first batch has been delivered in full. Leaving the with block, calling close(), or exiting a for loop over the stream early (with break or an exception) ends Vault's stream. With async for, the stream ends after the event loop cleans up the abandoned loop, so use async with to end it immediately. Each stream can be read only once.

Downloads and conditional reads

  • reads.attachment(record_id, n, range=byte_range(start), if_range=etag) returns BinaryContent containing content, status (206 for a range), etag and the served range. If the body ends before its Content-Length, it raises ConnectionCutError. The details["received"] value indicates where to resume.
  • reads.original(record_id) and reads.import_original(import_id) return the stored bytes.
  • vault.exports(user).download(export_id, name, to="records.ndjson.gz") downloads one file from a completed export to disk. Export files can be as large as 1 GiB, and file() holds the entire file in memory, so use download() for these files. It writes chunks as they arrive (pass on_chunk(chunk, offset) to process chunks yourself). If the body is cut short, it resumes using Range and If-Range. If the file changed in the meantime, the download starts over, and on_restart allows you to discard what you wrote. It returns an ExportDownload containing the etag, size, resumes, and restarts. agent.exports and vault.tenant_exports() expose the same method.
  • download_export_link(url, to=...) (and adownload_export_link for async code) downloads a file from a capability URL generated by links(). Because the URL is the credential, no key or token is sent. The download resumes and restarts in the same way as download(). If every download slot for the key or URL is in use (429 rate_limited with details["bucket"] set to link_downloads), it requests the same bytes again after the Retry-After, up to slot_retries times (default 60), with a maximum of 5 minutes per request, because a slot can remain in use for that long after its client disconnects. Other retryable refusals are retried up to retries times (default 12), waiting no more than 60 seconds per request. Any other refusal is raised as its VaultApiError. This behavior matches the TypeScript SDK's downloadExportLink.
  • reads.resource(record_id, if_none_match=etag) returns a ResourceResult with not_modified=True when your copy is current.
  • File bytes are sent only when Bayes has enabled your tenant's attachments beta surface. While the surface is off, attachment raises SurfaceNotEnabledError. Other full reads (resource, original, import_original, changes with include="resource", and shape="full" or shape="original" queries and streams) return each resource without its file bytes and mark every removed element with the data-absent-reason extension (masked). In this case, resource uses the ETag W/"<version_id>.files-withheld". See docs/developer/records-api.md.

Erasure

delete_subject erases a chart and blocks imports until you activate it again. It returns a job_id; wait_for_job polls GET /vault/v1/jobs/{job_id} until the job is done, failed or cancelled:

erasing = vault.delete_subject("user_8412", block_future_imports=True)
job = vault.wait_for_job(erasing.job_id)          # job.phase is first "rows", then "bridge"; job.state ends as "done"
vault.activate_subject("user_8412")               # permits imports again; erased data is never restored

Use connections="keep" (the default) or "remove" to determine what happens to your tenant's connections for the user. keep leaves them connected but prevents them from storing data in Vault, while remove also deletes them. After activating a chart that was erased with keep, call authorize_connection(user, connection_id) for each connection that the user agrees to store again. delete_source(user, source_id) erases one source. After the user authorizes storage for that source again, authorize_source(user, source_id) allows its next import to reactivate it. Both DELETE requests can be repeated safely. See docs/developer/deletion.md.

Polling

  • vault.exports(user).wait(export_id) polls an export until it enters a final state (completed, failed, cancelled, expired or deleted). It honors the Retry-After value from each status (2 to 30 seconds) and from a 429 (up to 60 seconds; a 429 requesting a longer delay is raised to you). on_status receives every status. timeout limits all waiting, including retries: TimeoutError is raised if it expires while the export is still running, or immediately when a wait requested by Vault would exceed it.
  • vault.wait_for_refresh_job(user, job_id) and agent.wait_for_refresh_job(job_id) poll a job started by refresh_records until it succeeds or fails.
  • vault.wait_for_job(job_id) polls an erasure or reindex job until it is done, failed or cancelled.

Webhooks

Vault sends your tenant's events to a single webhook URL and signs them with the Standard Webhooks scheme. Set the URL once. The first call returns the signing secret in secret; Vault never displays it again:

hook = vault.webhook.set("https://app.example.com/webhooks/bayes")   # hook.secret only when this call created it
rotated = vault.webhook.rotate_secret()                              # rotated.secret: the old secret stops working at once

The client does not retry set. If the call fails without a response, or does not return a secret when one was expected, Vault may already have created the endpoint. Call rotate_secret() to obtain a new secret.

vault.deliveries.replay(delivery_id) immediately resends a delivery with the same webhook-id. A delivery cannot be replayed if its payload was removed 15 days after creation or redacted after an erasure (GoneError). Replays are limited to 60 at once, then 1 per second (RateLimitedError). To resend failed deliveries, skip deliveries that cannot be replayed and wait when Vault requests it:

import time
from bayes_vault.errors import RateLimitedError

for delivery in vault.deliveries.iter_deliveries(status="failed"):
    if delivery.payload is None or delivery.redacted:
        continue                                                     # removed after 15 days, or redacted after an erasure
    while True:
        try:
            vault.deliveries.replay(delivery.id)
            break
        except RateLimitedError as error:
            time.sleep(error.retry_after or 1)

Verify each delivery against the raw body. For example, with FastAPI:

from bayes_vault import VaultWebhookError, is_vault_event, parse_vault_event, verify_vault_event

@app.post("/webhooks/bayes")
async def bayes_webhook(request: Request) -> Response:
    body = await request.body()
    try:
        event = verify_vault_event(payload=body, headers=request.headers, secret=os.environ["BAYES_WEBHOOK_SECRET"])
    except VaultWebhookError:
        return Response(status_code=400)
    if is_vault_event(event):
        enqueue(parse_vault_event(event))   # VaultChartReadyEvent, VaultSubjectErasedEvent, ...
    return Response(status_code=204)

verify_vault_event validates webhook-signature; any of multiple v1, signatures may match, and comparison uses constant time. It also requires webhook-timestamp to be within tolerance_seconds (default 300) of the current time. It raises VaultWebhookError when tolerance_seconds is not a finite number greater than or equal to 0, such as float("nan") from an unset setting. secret is your tenant's webhook signing secret in base64, with or without a whsec_ prefix. It accepts and rejects exactly the same inputs as the TypeScript verifyVaultEvent, produces the same messages, and verifies deliveries signed by Vault's own signer. Deduplicate deliveries by the returned id, which remains unchanged across retries and replays.

For Vault events, parse_vault_event returns the typed model. Supported event types are vault.records.ready, vault.records.updated, vault.chart.ready, vault.subject.erasing, vault.subject.erased, vault.source.erased, vault.source.reauthorized, vault.export.completed, vault.export.failed, vault.connection.authorized, vault.connection.revoked, and vault.connection.reauth_required. VAULT_EVENT_KEYS lists the data keys associated with each event type. See docs/developer/webhooks.md for details about what each event means.

Development

cd clients/python
uv sync                                  # Python 3.12 with the locked development tools
uv run python scripts/generate.py        # regenerate src/bayes_vault/_generated from docs/openapi.yaml
uv run python scripts/generate.py --check
uv run mypy                              # --strict, for the package and the generator
uv run pytest

Regenerate after every change to docs/openapi.yaml. CI fails if the generated code is stale. Method names are defined by the METHODS table in scripts/generate.py. If a route is not named in the table, it still receives a method named after its operationId, and the generator reports this; if the table contains an entry for a route that is missing from the document, the generator stops. tests/test_coverage.py checks every operation recorded by the client and every route called by its handwritten code against the document's method and path. It also checks that the requests the client retries match the TypeScript SDK's table (packages/client/src/vault/idempotence.ts).

To generate tests/fixtures/webhook_vectors.json, run node clients/python/scripts/webhook_vectors.mjs from the repository root after running pnpm --filter @bayes-health/vault build. The script uses Vault's own signer (apps/api/src/lib/vault/webhooks/sign.ts), so the vectors include the TypeScript implementation's own outcomes for the data Vault sends, plus one delivery for every event type. CI regenerates the file and fails if it differs. Regenerate and commit the file whenever verifyVaultEvent, the signer or the event catalogue changes. To run the smoke tests against another document, with the package generated from that document, run BAYES_VAULT_SPEC=<document> uv run pytest tests/test_smoke.py.

The tests use httpx's mock transport and a fixture server generated from the OpenAPI document. Vault ignores all values sent alongside a cursor except the page size, but the server rejects them, allowing the tests to detect a client that sends more than the cursor requires. The tests use synthetic data only.

Metadata

Release files for bayes-vault 0.1.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 bayes-vault 0.1.0
File Size Uploaded
bayes_vault-0.1.0.tar.gz 157.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bayes-vault 0.1.0
File Interpreter ABI Platform
bayes_vault-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 316.3 kB

Release files / bayes_vault-0.1.0.tar.gz

Download URL bayes_vault-0.1.0.tar.gz
Size 157.6 kB
Tags Source
SHA-256 checksum
How to use checksums
1d3505ddc1199aebf86a0a0feba664f94ebfd0482c10485824ee182c58aafd9c
BLAKE2b-256 checksum
How to use checksums
4fa779968360eeb412e24f0cba92afa6fd56374b31ae6a961764363058796a7a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release files / bayes_vault-0.1.0-py3-none-any.whl

Download URL bayes_vault-0.1.0-py3-none-any.whl
Size 158.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6480629542807d27620f6b37a7be6358b2fa15ad23bb6b2e625b1a74d5fec6dd
BLAKE2b-256 checksum
How to use checksums
5dde26d38b0ee2708ba6defbb78a033066b65dc3c0a621fc45b7377ccb19b342
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.0 This release

2 release files

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