Skip to content

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

pip install yousleep-common     # or: uv add yousleep-common
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, and adjust_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(...) and decline_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, and get_storage_reconciliation() reads the last report. It returns None when 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, and client.organizations manages members, invitations and ownership.
  • A project is opened to every member or shared with named colleagues through its visibility and client.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_owner and forbidden.
  • 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, and client.user.list_audit() your own. Each takes AuditFilters (event types, a time window, a person or an item), and export_audit returns the same events as CSV, up to the export_limit each 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 with request_grant and for more time on one with request_extension; list_credit_requests shows 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 with blocks_to_events if 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.reports returns 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