This release is a pre-release and may not be stable for production use.
ovok-sdk (Python)
Python SDK for the Ovok API. It follows the @ovok/core/next TypeScript contract: the same namespaces and
routes, with method names in snake_case (searchOne is search_one). It talks to the Ovok API directly and
uses only the Python standard library.
Install
pip install ovok-sdk
Or with uv:
uv add ovok-sdk
pip install ovok-sdk installs final releases only. Release candidates (0.1.0rc<n>) and
development builds (0.1.0.dev<N>) are pre-releases on PyPI:
pip install --pre ovok-sdk
pip install "ovok-sdk==0.1.0.dev<N>"
Python 3.10 or later.
Create a client
from ovok_sdk import Ovok
ovok = Ovok("https://api.sandbox.ovok.com", "your-tenant")
| Option | Default | Notes |
|---|---|---|
base_url |
Required. An absolute http(s) URL. |
|
tenant_code |
Required. Picks the tenant at sign-in and names the storage key. | |
fhir_path |
"fhir" |
The FHIR base under base_url, such as "fhir/R4". |
storage |
MemoryStorage() |
Anything with get_item, set_item and remove_item. |
client_id |
Your client application's ID. | |
client_secret |
Makes this a machine client. Requires client_id. |
|
transport |
UrllibTransport() |
A callable from OvokRequest to OvokResponse, for proxies or tests. |
timeout |
30.0 |
Seconds per request. |
max_network_retries |
2 |
For GET, HEAD, PUT, DELETE and conditional creates only. |
on_unauthenticated |
Called with an UnauthenticatedReason when a refresh token is refused. |
A bad option raises OvokConfigError from the constructor, not from the first request.
Sign users in
result = ovok.auth.login(profile_type="Practitioner", email="ada@example.com", password=password)
if result.status == "mfa-required":
result = ovok.auth.verify_mfa(result.challenge, code_from_authenticator_app)
session = ovok.auth.current()
ovok.auth.logout()
- Login flow:
loginruns the tenant login with PKCE and signs in to the configuredtenant_code.profile_typeis"Patient","Practitioner"or"RelatedPerson". - Session storage: the session is saved under
ovok:<tenant_code>:session, as JSON in the same shape the TypeScript SDK writes, so the SDKs can share one store. - Refresh: access tokens refresh a minute before they expire, and concurrent callers share one refresh.
- A refused refresh token (400, 401 or 403) signs the user out and calls
on_unauthenticated. - Any other failure keeps the session and raises, so a later call can retry.
- A refused refresh token (400, 401 or 403) signs the user out and calls
- Logout:
logoutalways clears the local session, even when the server cannot be reached. - Also on
ovok.auth:register,register_practitioner,social_login,me,change_password,reset_password,complete_password_reset,delete_account, andsessions.list/sessions.revoke.
from ovok_sdk import PatientRegistration
ovok.auth.register(PatientRegistration("ada@example.com", password, password, "Ada", "Lovelace"))
Machine clients
On a server, pass client_id and client_secret from a client application. The client fetches its own token
from /oauth2/token and renews it before it expires. A machine client cannot sign users in: those methods raise
OvokConfigError before sending anything.
import os
ovok = Ovok(
os.environ["OVOK_BASE_URL"],
os.environ["OVOK_TENANT_CODE"],
client_id=os.environ["OVOK_CLIENT_ID"],
client_secret=os.environ["OVOK_CLIENT_SECRET"],
)
FHIR
patient = ovok.fhir.read("Patient", patient_id)
page = ovok.fhir.search("Observation", {"patient": f"Patient/{patient_id}", "_count": 50})
for observation in page.auto_paging_iter():
...
ovok.fhir.create(observation, if_none_exist="identifier=urn:device|42")
ovok.fhir.update(patient)
ovok.fhir.patch("Patient", patient_id, [{"op": "replace", "path": "/active", "value": False}])
ovok.fhir.delete("Patient", patient_id)
ovok.fhir.batch(bundle)
first = ovok.fhir.search_one("Patient", {"identifier": "urn:mrn|123"})
- Search parameters: a list value repeats its parameter, which FHIR reads as AND, as in
{"date": ["ge2026-01-01", "le2026-02-01"]}. For OR, write one comma-separated string. - Resources are plain dicts in FHIR's own JSON shape.
Patient and measurements
ovok.patient provides FHIR-backed appointments, schedules, slots,
measurements, care_plans, questionnaires, questionnaire_responses, devices and
notifications. It also supports latest_observations() and dashboard(). List methods
return resource lists; dashboard() reads the signed-in profile and its latest observations,
measurement history and care plans.
ovok.measurements builds and saves observations for the measurement types in
MeasurementTypeKey. Measurement fields use snake_case, while generated FHIR resources
retain FHIR's camelCase JSON fields:
from ovok_sdk import MeasurementTypeKey
result = ovok.measurements.save({
"patient_id": "patient-id",
"measurement": {
"measurement_type_key": MeasurementTypeKey.heart_rate,
"heart_rate": 72,
},
})
The namespace also supports build, save_observations, latest, last_for_patient,
codes_for and offline_queue.flush. Saves use conditional FHIR entries, link related
observations, split large bundles, and queue retryable failures in the configured storage.
Pass identifier_systems and replace=True to update an observation already saved under
one of those reading identifiers.
Questionnaires and content
ovok.questionnaires.populate(questionnaire_id) populates a questionnaire.
ovok.questionnaire_responses supports submit, submit_with_observations and extract.
ovok.content provides search, retrieve, create, update, delete, map,
copy_from_parent and copy_all_from_parent for language-scoped CMS content.
Documents and integrations
ovok.documents supports list, upload creation and replacement, metadata updates,
delete, download_url and public_download_url. Download URLs are constructed locally;
pass query values such as width, height and fit when needed.
ovok.video_calls groups appointments, access and emails, and provides
notify_active. ovok.ai provides translate, fhir_search and chat sessions/messages.
ovok.wearables groups provider and connection operations. ovok.bots.execute(identifier, input) runs a bot.
Platform namespaces
ovok.projects.list()lists projects the signed-in user administers. Itsmembersnamespace supportslist,invite,updateandremove;featuressupportsretrieveandupdate;settingssupportsretrieve,updateandupdate_text.ovok.accounts.list()reads the signed-in user's accounts.ovok.locales.retrieve()andupdate()manage project locales.ovok.localizationssupportslist,retrieve,updateanddelete;localizations.i18nextreads and writes the translation document.ovok.email.templates.list()reads the available email templates.ovok.invitesexposesrecord_shares,care,practitionersandpatientsflows, each withcreateandaccept.invites.accept(body)selects the record-share or care flow from the signed-in profile.ovok.sharingexposesinvites.create/redeem,shares.list/revoke, andsettings.retrieve/update.
Slim and Signals
ovok.slim groups the Slim API resources. patients supports list, retrieve, create, update, delete,
everything and current_vitals, with patient thresholds.retrieve / update. devices supports CRUD,
assign_to_project, live, live_telemetry, telemetry, and effective or last observation ID reads. Concurrent
identical live reads share one request. notifications supports list and recent; its notes resource supports
list, create, update, patch and delete.
Other Slim resources are thresholds (retrieve, update), trends (graphs, realtime), sleep.statistics,
locations (CRUD and list), zones (list, retrieve, device_counts), exports.patient_pdf,
change_history.list, projects (retrieve, update, users, current_users, device_counts, patient_counts,
and children.list / create / delete), policies (CRUD and list), organizations (CRUD),
practitioners.invite, users.delete and health().
devices = ovok.slim.devices.live({"page": 0, "count": 20})
resident = ovok.slim.patients.retrieve("patient-id")
unreachable = ovok.signals.undeliverable.list()
ovok.signals also supports enable(patient_id) and gateway.request(method, path, query=..., body=...). Gateway
paths are lists of safe path segments. undeliverable.repair() repairs the residents returned by list();
repair({"patientIds": [...]}) limits the repair.
CMS
ovok.cms.legal_page(slug), translations(locale), documents.list(collection, params) / retrieve(collection, slug, params), and release_notes.list(params) read published content. CMS requests include the client's tenant_code;
collection queries support locale, page, limit and sort.
Health and consent
ovok.health.check()reads the healthcheck. It always answers 200, so readok.ovok.consent:legal.retrieve(),legal.set(accepted),medical.retrieve(),medical.set(accepted)andrequest_signature(consent_id).
Paging
A list call returns a Page with data, has_more and total (None when the server does not say).
auto_paging_iter() walks every item from that page on, fetching later pages as it goes, and next_page()
fetches one page.
Errors
Every failure is an OvokError with status, code, type, message, trace_id, request_id,
retry_after_ms and field_errors. Catch a subclass to handle one case:
| Class | When |
|---|---|
OvokValidationError |
400, 422, or a request the SDK refuses to send |
OvokAuthenticationError |
401 |
OvokPermissionError |
403: signed in, but not allowed |
OvokNotFoundError |
404 |
OvokConflictError |
409 |
OvokRateLimitError |
429 |
OvokServiceError |
5xx |
OvokConnectionError |
No answer: network failure or timeout |
OvokConfigError |
A bad option, or an action this client cannot take |
Quote trace_id when you report a problem. The x-trace-id, x-request-id and Retry-After headers win over
the same details in the body.
Documentation
See the Python SDK documentation for guides and the API reference, generated from the docstrings.
Development
uv run python -m unittest discover -s tests
uv run ruff check
uv run --group docs python scripts/generate_reference.py
ruff check fails on a missing or malformed docstring. generate_reference.py writes the API
reference pages to docs/reference/; see AGENTS.md for the docs rule.
Tests never reach a server: tests/fake_transport.py queues replies, and tests/route_harness.py turns a
namespace call into a line such as "GET v1/me/consent/legal" (request_line) or its JSON body
(request_body).
Releases
The release workflow publishes to PyPI on every push:
| Branch | Git tag | PyPI version |
|---|---|---|
development |
v0.1.0-dev.<N>+<sha7> |
0.1.0.dev<N> |
staging |
v0.1.0-rc.<n> |
0.1.0rc<n> |
main |
v0.1.0 |
0.1.0 |
N is the commit count of development. The base version is version in pyproject.toml. Bump it
after a final release; the workflow refuses to publish dev or rc builds of a version that is already
released.
License
Metadata
Release files for ovok-sdk 0.1.0.dev28
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 |
|---|---|---|---|---|
| ovok_sdk-0.1.0.dev28.tar.gz | 76.3 kB | Without provenance | Details for ovok_sdk-0.1.0.dev28.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ovok_sdk-0.1.0.dev28-py3-none-any.whl | Python 3 | none | any | Details for ovok_sdk-0.1.0.dev28-py3-none-any.whl |
Total release size: 167.5 kB
Release files / ovok_sdk-0.1.0.dev28.tar.gz
| Download URL | ovok_sdk-0.1.0.dev28.tar.gz |
|---|---|
| Size | 76.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
826ddbbaa4c510065f0b681dbe42c471afa6c04acd2cb4ce3d9e0bb333689daa
|
|
BLAKE2b-256 checksum How to use checksums |
4436679dc085553acdf0906c9694494b3fd594aae85cb6cc339bae54dd8ce5dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","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":true}
|
Release files / ovok_sdk-0.1.0.dev28-py3-none-any.whl
| Download URL | ovok_sdk-0.1.0.dev28-py3-none-any.whl |
|---|---|
| Size | 91.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c879ba1bc2e1446dd36fb995c2372ec6a44ac69960737b33e271b1ecdd3263e3
|
|
BLAKE2b-256 checksum How to use checksums |
f160d0e46b4332b43cf3b3486168b77daaf7da5f494959268fa391f4b8f3a610
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","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":true}
|
Security for ovok-sdk 0.1.0.dev28
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.