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.
Connect links and connections
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
retryableis true and connections that fail before any response, up tomax_retriestimes (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 …/reprocessorPOST …/exports, even after a429, because Vault may already have processed it. If the connection is interrupted during one of these requests, the client raisesConnectionCutErrorwithretryable=False; check the result before repeating the request. - It does not repeat
vault.webhook.set, even thoughPUT /vault/v1/webhookisx-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 ofvault.sessions.deleteorvault.delete_connectionreceives that response after an attempt that received no response or a 5xx, the earlier attempt may have deleted it, so the method returnsdeleted: 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)returnsBinaryContentcontainingcontent,status(206 for a range),etagand the servedrange. If the body ends before itsContent-Length, it raisesConnectionCutError. Thedetails["received"]value indicates where to resume.reads.original(record_id)andreads.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, andfile()holds the entire file in memory, so usedownload()for these files. It writes chunks as they arrive (passon_chunk(chunk, offset)to process chunks yourself). If the body is cut short, it resumes usingRangeandIf-Range. If the file changed in the meantime, the download starts over, andon_restartallows you to discard what you wrote. It returns anExportDownloadcontaining theetag,size,resumes, andrestarts.agent.exportsandvault.tenant_exports()expose the same method.download_export_link(url, to=...)(andadownload_export_linkfor async code) downloads a file from a capability URL generated bylinks(). Because the URL is the credential, no key or token is sent. The download resumes and restarts in the same way asdownload(). If every download slot for the key or URL is in use (429 rate_limitedwithdetails["bucket"]set tolink_downloads), it requests the same bytes again after theRetry-After, up toslot_retriestimes (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 toretriestimes (default 12), waiting no more than 60 seconds per request. Any other refusal is raised as itsVaultApiError. This behavior matches the TypeScript SDK'sdownloadExportLink.reads.resource(record_id, if_none_match=etag)returns aResourceResultwithnot_modified=Truewhen your copy is current.- File bytes are sent only when Bayes has enabled your tenant's
attachmentsbeta surface. While the surface is off,attachmentraisesSurfaceNotEnabledError. Other full reads (resource,original,import_original,changeswithinclude="resource", andshape="full"orshape="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,resourceuses the ETagW/"<version_id>.files-withheld". Seedocs/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 theRetry-Aftervalue 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_statusreceives every status.timeoutlimits all waiting, including retries:TimeoutErroris 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)andagent.wait_for_refresh_job(job_id)poll a job started byrefresh_recordsuntil 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)
| File | Size | Uploaded | |
|---|---|---|---|
| bayes_vault-0.1.0.tar.gz | 157.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|