SDK guide¶
This guide covers connecting to the API, the conventions every namespace of the client follows, reading results, and errors. The overview has the quick start and the API reference has every signature.
Connect¶
from yousleep_common.client import AsyncClient
from yousleep_common.models import UserAuthentication
client = await AsyncClient.from_credentials(
base_url="https://api.yousleep.ai",
credentials=UserAuthentication(email="me@example.com", password="…"),
)
async with client:
me = await client.user.get_current()
from_credentials signs in and keeps the session refreshed. Note that
client.auth.login() only returns a token pair; it does not authenticate the
client it was called on.
from yousleep_common.client import AsyncClient
client = AsyncClient.from_api_key(base_url="https://api.yousleep.ai", api_key="ysk_…")
A key is created on the account page by a user an administrator has enabled for
it. It carries only the permissions it was scoped to, expires on the date chosen
at creation, and is never refreshed: a 401 means unknown, revoked or expired.
Always use the client as a context manager, because it owns the HTTP connection pool.
Managing keys. Keys are managed from a signed-in session, never from a key:
from yousleep_common.models import ApiKeyCreate
from yousleep_common.types import Permission
async with await AsyncClient.from_credentials(base_url=..., credentials=...) as me:
created = await me.user.create_api_key(
ApiKeyCreate(
name="Nightly pipeline",
scopes=[Permission.RECORDING_CREATE, Permission.ANALYSIS_CREATE],
expires_in_days=90,
password="…", # the account password, entered again
)
)
created.secret # shown once; store it now
await me.user.list_api_keys() # name, prefix, scopes, expiry, last use
await me.user.revoke_api_key(created.id)
Versions. On its first request the client compares its own version with the
API's and raises ConnectionError on a mismatch, because the two are
paired. strict_version_check=False turns the error into
a warning and verify_connection=False skips the check. Use either only
briefly and for a known reason, since an incompatible client produces
validation errors far from their cause.
The shape of the client¶
Every area of the API is a namespace on the client:
admin · analyses · auth · batch · billing · legal ·
organizations · projects · recordings · report_exports · reports ·
status · studies · user · workflows
The admin namespace answers only for platform operators. Besides accounts,
organisations, analyses, the audit trail and the runtime controls, it holds
the operator tools:
update_organization(org_id, AdminOrganizationUpdate(...))sets or clears an organisation's limits (storage, seats, projects, studies per project).grant_credits(org_id, AdminGrantCreate(...))grants research credits directly, andadjust_organization_credits(org_id, ...)adds or deducts purchased credits. Limits and credits belong to the organisation, not to any one member.list_credit_requests(...),approve_credit_request(...)anddecline_credit_request(...)work through organisations' requests for research grants and for more time on them.get_health()returns the health of each dependency behind the public health check.delete_project(project_id)deletes any account's project, with everything in it, in the background.run_storage_reconciliation()starts a comparison of the storage buckets with the records that reference them, andget_storage_reconciliation()reads the last report. It returnsNonewhen no report is stored.
A few routes are left out of the client because they belong to the web app alone: the contact form and its triage, and accepting the legal documents at sign-up. Every other route the API publishes has a method.
Three conventions apply to every namespace.
Writes take a request model rather than keyword arguments.
from yousleep_common.models import ProjectCreate
project = await client.projects.create(ProjectCreate(name="Cohort 2026"))
Items take their own id; collections take their parent's. A study, recording
or analysis is addressed by its UUID alone. Listing or creating under a parent takes
that one parent id. A name in an id position returns 422; names are not
looked up.
studies = await client.studies.list(project.id)
recordings = await client.recordings.list(study.id)
analysis = await client.analyses.submit(recording.id, request)
events = await client.analyses.get_events(analysis.id)
A list method returns the whole list. Where the server serves a list a page
at a time, the client reads page after page, at the largest page the server
allows, until it has every item. A project of up to 10,000 studies, or one
night's events, is one request. Pass limit (with skip or offset) to read
one page instead, and use a count method to learn how many there are without
fetching them.
A summary answers without the list. client.studies.summary(project_id)
returns every tag and subject in a project with its count, the studies
carrying no tag, and the first studies of each set. Pass tags_any to also
count the studies a tag selection leaves out. The answer stays small at any
project size.
Everything belongs to your organisation. An account belongs to exactly one organisation, which is created with the account and becomes a team when a colleague is invited.
- Projects, storage and credits belong to the organisation.
client.user.get_organization()returns it with your role, andclient.organizationsmanages members, invitations and ownership. - A project is opened to every member or shared with named colleagues through
its
visibilityandclient.projects.share. - Inside a shared project everyone can add content. Each person can change what
they added, and the project's owner can change anything. The refusals are
not_resource_ownerandforbidden. - An API key reaches only the projects its owner owns.
- The audit trail records what happened, and each event names who may read it.
client.organizations.list_audit(org_id)is the organisation's trail for its owner and admins,client.projects.list_audit(project_id)a project's for its owner, andclient.user.list_audit()your own. Each takesAuditFilters(event types, a time window, a person or an item), andexport_auditreturns the same events as CSV, up to theexport_limiteach page states.client.status.audit_actions()lists every event type with its name and the readers who can see it. The trail is read with a signed-in session, not a key. - Research analyses are paid from research grants, commercial analyses and
storage above the free ceiling from purchased credits;
client.billing.credits()returns both balances.client.organizations.list_grants(org_id)lists the grants, each with what remains and when it expires.estimate_credits(org_id, ...)prices a plan of analyses (studies, their average length, the analyses and their runs per study) exactly as the runs would be priced. An owner or admin asks for a grant withrequest_grantand for more time on one withrequest_extension;list_credit_requestsshows each request and its decision.
flowchart LR
O[Organisation] --> P1[Project: private]
O --> P2[Project: open to members]
O --> P3[Project: shared with named colleagues]
P2 --> S[Studies, recordings, analyses<br/>each with an owner]
Reading results¶
Events are the analysis's output; statistics, tables and figures are computed from them on the server so that every renderer draws the same thing.
client.analyses.get_events(id)returns the flat list, which is suitable for iterating over one analysis.client.analyses.get_event_blocks(id)returns the same content as a block document: a handful of blocks for a night instead of tens of thousands of objects. Decode it withblocks_to_eventsif you need the flat form. The integration guide explains when to use each.client.analyses.get_biomarkers(id)returns the sleep statistics with their presentation table.client.reportsreturns the same over a study or a project.
Project report origins
A large project report deduplicates per-study provenance: identical origins
lists travel once on the envelope and are referenced per entry. Read them
through report.resolve_origins(entry), which returns the effective list for
either representation.
Errors¶
Every exception derives from YouSleepClientError, with one class per failure
mode. The overview lists them. Two deserve
attention:
from yousleep_common.client.exceptions import (
ClientTimeoutError, # NOT the builtin TimeoutError
ClientConnectionError,
)
analyses.wait_for and the workflows raise ClientTimeoutError when their
timeout elapses; except TimeoutError does not catch it.
Where to next¶
- Workflows upload, submit, poll and fetch results in one call.
- Bulk operations act on many recordings or analyses by selector.
- Study metadata in bulk sets ages, sexes, subjects and lights across a cohort.
- The integration guide covers a backend that submits on behalf of many subjects.