Skip to content

Getting Started

This guide walks through the most common workflows: connecting to the API, managing family trees, adding persons, recording relationships, and handling errors.

Prerequisites

Install pyclann:

pip install pyclann

You need a running Clann server and credentials for the ullav-user-management auth service.

Connecting and authenticating

from pyclann import ClannClient

client = ClannClient(
    api_url="http://localhost:8090",
    auth_url="http://localhost:8081",
)
info = client.login(email="you@example.com", password="secret")
print(info.username, info.roles)

If your Clann server and auth service share a hostname (e.g. behind a reverse proxy), you can omit auth_url:

client = ClannClient("https://clann.example.com")
client.login(email="you@example.com", password="secret")

Creating a family tree

trees.create() creates a new tree with a globally unique slug:

tree = client.trees.create(
    "walsh-family",        # slug — used in API paths and as the tree identifier
    "Walsh Family",        # human-readable display name
    owner="alice",
    is_primary=True,       # mark as the owner's primary tree
)
print(f"Created tree: {tree.display_name}  (id={tree.id})")

List trees and filter by owner or team:

my_trees = client.trees.list(owner="alice")
team_trees = client.trees.list(team_id="my-team-uuid")

Upload a tree avatar:

with open("family-crest.jpg", "rb") as f:
    client.trees.upload_image("walsh-family", f.read(), "image/jpeg")

# Download the image (public endpoint — no auth required)
image = client.trees.get_image("walsh-family")

Adding persons

persons.create() adds a person to one or more trees:

from pyclann import Sex

father = client.persons.create(
    family_name="Walsh",
    first_name="Patrick",
    sex=Sex.MALE,               # or the plain string "Male"
    trees=["walsh-family"],
    date_of_birth="1820-06-01",
    place_of_birth="Galway",
    created_by="alice",
)
print(f"Added: {father.first_name} {father.family_name}  id={father.id}")

Update a person (MERGE semantics — only supplied fields change):

client.persons.update(
    father.id,
    biography="Patrick emigrated to New York in 1847.",
    verified=True,
)

Upload a profile picture and life-story media:

with open("patrick.jpg", "rb") as f:
    client.persons.upload_image(father.id, f.read(), "image/jpeg")

# Life story media (plan-dependent: images for Individual/Family; also video/audio/PDF for Pro+)
with open("life-story.jpg", "rb") as f:
    client.persons.upload_life_image(father.id, f.read(), "image/jpeg")

# Download (public endpoints — no auth required)
photo = client.persons.get_image(father.id)
life  = client.persons.get_life_image(father.id)

Recording relationships

relationships.add() creates an edge between two persons:

from pyclann import RelationshipType, SiblingType

son = client.persons.create("Walsh", "Michael", "Male", trees=["walsh-family"])

# Father relationship
client.relationships.add(son.id, RelationshipType.FATHER, father.id)

# Sibling — sibling_type is required
client.relationships.add(
    son.id,
    RelationshipType.SIBLING,
    sibling.id,
    sibling_type=SiblingType.BROTHER,
)

# Spouse with dates
client.relationships.add(
    father.id,
    RelationshipType.SPOUSE,
    mother.id,
    spouse_from="1845-09-14",
    spouse_to=None,   # still married / unknown end date
)

Retrieve all relationships for a person:

rels = client.relationships.get(son.id)
print(rels.father)    # list[Person]
print(rels.mother)    # list[Person]
print(rels.siblings)  # list[Person]
print(rels.spouse)    # list[SpouseInfo]

Get the recursive family-tree view (2 generations of ancestors, plus children, spouses, siblings of the root):

node = client.relationships.get_family_tree(son.id)
print(node.first_name, node.family_name)
for parent in node.father:
    print("  Father:", parent.first_name, parent.family_name)

Remove a relationship:

client.relationships.remove(son.id, RelationshipType.FATHER, father.id)

Life events

from pyclann import EventType

event = client.life_events.create(
    father.id,
    "Born in Galway",
    EventType.BIRTH,
    date="1820-06-01",
    description="County Galway, Connacht province",
    verified=True,
    source_link="https://civil-records.ie/...",
    created_by="alice",
)

# List events for a person
events = client.life_events.list(father.id)

# Update an event
client.life_events.update(event.id, verified=True, story="Long narrative in Markdown...")

# Delete
client.life_events.delete(event.id)

Research notes

Research notes are Markdown documents linked to one or more trees:

note = client.notes.create(
    "Walsh Family Research",
    trees=["walsh-family"],
    description="Primary source research notes",
    body="## Findings\n\nFound baptism record in Galway archives.",
    is_shared=True,       # visible to all team members with tree access
    created_by="alice",
)

# Reply to a shared note
reply = client.notes.create_reply(
    note.id,
    "Also found a marriage record in Dublin.",
    created_by="bob",
)

# File a note in a folder
folder = client.folders.create("Walsh Sources", created_by="alice")
client.notes.set_folder(note.id, folder.id)

AI chat

session = client.chat.create_session(
    "Walsh Family History",
    created_by="alice",
    tree="walsh-family",    # scope to a specific tree
)

client.chat.append_message(session.id, "user", "Who were Patrick Walsh's children?")
# (AI response stored separately by the clann-server)

messages = client.chat.list_messages(session.id)
for msg in messages:
    print(f"[{msg.role}] {msg.content}")

Error handling reference

Exception Raised when
ClannAuthError login() not called; 401 or 403 response
ClannNotFoundError 404 — resource does not exist
ClannValidationError 400 — invalid input
ClannServerError 5xx — server-side fault
ClannError Base class; catches all of the above