Skip to main content

familyos-client

An asynchronous Python client for the public API of FamilyOS, the self-hosted family organizer. It reads and changes the calendar, shopping lists, tasks, chores and the family board with an app key. It is the library the Home Assistant integration uses.

  • Python 3.12+, asyncio only.
  • aiohttp for HTTP (you pass in your session) and mashumaro for the typed models. Nothing else.
  • Fully typed (py.typed, mypy --strict).

Install

pip install familyos-client

Quick start

  1. In FamilyOS, as an admin, open Settings → You → Connected apps and press Add an app. Choose a name, a role and a mode, and copy the key when it appears. It is shown once.
  2. Use your FamilyOS address and the key:
import asyncio
import datetime as dt

import aiohttp

from familyos import Applied, Drafted, FamilyOS


async def main() -> None:
    async with aiohttp.ClientSession() as session:
        famos = FamilyOS("https://family.example.com", "famos_...", session=session)

        info = await famos.discover()
        print(info.family_os, info.time_zone, info.caller.app.name)

        today = dt.date.today()
        async for occurrence in famos.iter_occurrences(today, today + dt.timedelta(days=7)):
            print(occurrence.start, occurrence.title)

        answer = await famos.add_task("Pay the school fee", due_date=today + dt.timedelta(days=5))
        if isinstance(answer, Applied):
            print("added", answer.result.id)
        else:  # an app in "Changes wait for approval" mode
            print("waits for a person:", answer.draft_id)


asyncio.run(main())

The client never closes a session you give it. Without session= it makes one and closes it in await famos.close() (or leave an async with FamilyOS(...) block).

What you get back

  • Models are dataclasses. Instants are timezone-aware datetime, whole days are date, clock times are time. An all-day event's start/end are date. Fields the library does not know are ignored, so a newer FamilyOS does not break an older library.
  • Lists come as a Page (items, next, time_zone) from list_tasks(), list_chores(), list_notes(), list_occurrences(), list_shopping_lists() and list_shopping_items(). The iter_* twins (async for task in famos.iter_tasks()) follow the pages for you.
  • Changes return Applied[T] (result, etag, time_zone, replayed) or Drafted (draft_id) when the change waits for a person to approve it. Follow a draft with await famos.get_draft(draft_id).
  • Every operation of the API has a method of the same name in snake_case: create_event, patch_event, replace_event, delete_event, get_event, list_occurrences, add_shopping_items, add_shopping_items_default (the default list, no id needed), complete_shopping_item, uncheck_shopping_item, add_task, complete_task, reopen_task, complete_chore, undo_completion, create_note, discover, get_draft, changes and the rest.

Polling cheaply: ETag

Lists and single reads send If-None-Match for you. An unchanged answer is 304 and you get the cached page back with page.not_modified == True, which costs the server almost nothing:

page = await famos.list_tasks()
...
page = await famos.list_tasks()
if not page.not_modified:
    refresh(page.items)

What changed since I looked

cursor = (await famos.changes()).cursor  # where "now" is; read everything you need
...
try:
    result = await famos.changes(cursor)  # the things that changed since
except CursorExpired:  # older than 30 days: read everything again
    ...
cursor = result.cursor  # ask again at once while result.more is true

Writing safely

version = (await famos.get_task("task_...")).version
await famos.patch_task("task_...", notes="Bring cash", if_match=version)  # 412 if it moved on
  • if_match= takes the version you read (or an ETag text). A stale one raises PreconditionFailed with current_version. replace_* requires it.
  • Every write sends an Idempotency-Key (generated per call; pass idempotency_key= to choose, so a retry after a network error does not happen twice).
  • patch_* changes only the arguments you pass. None clears a field that may be empty.

Acting for a member

famos = FamilyOS(url, key, session=session, on_behalf_of="member_...")  # the default for every call
await famos.add_task("Homework", on_behalf_of="member_other")  # one call
await famos.add_task("Chore for nobody", on_behalf_of=None)  # none for this call

Errors

All errors derive from FamilyOSError and carry the API's code (ErrorCode), message and HTTP status.

Exception When
AuthenticationError 401, no valid key
ForbiddenError 403; reason says why (capability, approval, critical, on_behalf)
NotFoundError 404, also when the feature is switched off (code is feature_off)
PreconditionFailed 412; current_version
CursorExpired 410, from changes(since)
RateLimited 429; retry_after seconds
BadRequestError 400 (see fields) and 422
ConflictError 409
ConnectionError, TimeoutError FamilyOS could not be reached / did not answer in time (the library's own classes)

Versioning

The library follows FamilyOS's public API v1, which is expand-only: new operations and fields can appear, nothing is renamed or removed. The OpenAPI document the library is checked against is in familyos/openapi/v1.json; a test fails when the library and that document disagree. A breaking change to the API would be /api/public/v2 and a new major version of this library.

Development

uv sync
uv run ruff check . && uv run ruff format --check .
uv run mypy
uv run pytest                                  # unit and contract tests, 100% branch coverage
FAMOS_URL=http://localhost:3000 FAMOS_TOKEN=famos_... uv run pytest -m integration --no-cov

The integration tests change data in the family they run against (they create and delete their own tasks, events and items, and leave one note on the board). Point them at a scratch FamilyOS, with a key whose role may change the calendar, lists and chores and whose mode is "Changes happen at once".

License

AGPL-3.0-or-later, the same as FamilyOS.

Metadata

Release files for familyos-client 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 familyos-client 0.1.0
File Size Uploaded Provenance
familyos_client-0.1.0.tar.gz 55.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for familyos-client 0.1.0
File Interpreter ABI Platform
familyos_client-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 98.5 kB

Release files / familyos_client-0.1.0.tar.gz

Download URL familyos_client-0.1.0.tar.gz
Size 55.1 kB
Tags Source
SHA-256 checksum
How to use checksums
b37b8b996c43a74bc8e03ae58d820ebb83e54d2894b85d752c277ddbfbccba3e
BLAKE2b-256 checksum
How to use checksums
f8d3d16f6d52a9c34615ef718b421f2260e8d3b1bd5adf749d480fe221be4623
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

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

Download URL familyos_client-0.1.0-py3-none-any.whl
Size 43.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e7718817cfdc1bd10c6d82b6f79c2376d0d6ccf98b6831e85cc4a2d4e6b2f968
BLAKE2b-256 checksum
How to use checksums
872da7079229016d09ce7bc0355ed408729702fc25bcc799b9a64ebae393cc1c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Security for familyos-client 0.1.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for familyos_client-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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