---
title: "Invite To Care Team"
method: POST
path: "/care-team/invite"
tags: ["care-team-invite-canonical"]
---

# Invite To Care Team

`POST /care-team/invite`

## Request

- Base URL: `https://api.projectindi.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.projectindi.com/care-team/invite`
- Auth: none declared

Invite a person to a child's care team by email.

Caller must be a family member of the child OR a clinician with active
access. The server branches on the recipient's account type:

- active clinician → grant + clinician care team row (connected_clinician)
- non-clinician account → canonicalize + worker care team row + synthetic
  claimed invite code (connected_worker)
- no account → create care team row + active invite code, dispatch email
  (invite_sent)

Idempotent on (email, child_id): a re-invite returns ``already_on_team``
with the existing ids. When ``care_team_member_id`` is supplied (the
Connect surfaces always know it), the RPC matches the existing contact by
that id first - so adding or correcting a saved contact's email re-invites
the same record instead of forking a duplicate.

## Request body

- SrcApiCareTeamInviteCanonicalRoutesInviteRequest: Request body for POST /care-team/invite. Email validation is intentionally minimal - a syntactic ``@`` check via a custom ``field_validator``. We deliberately avoid pydantic's ``EmailStr`` because it rejects test / staging domains (``@test.local``, ``@example.invalid``) that are valid identity keys in the rest of this codebase. Stricter validation (DNS / disposable-domain checks) is not how identity is gated here. The RPC's ``invalid_email`` P0001 path is reserved for blanks-after-trim.
  - `child_id` string, required
  - `email` string, required
  - `first_name` string, nullable
  - `last_name` string, nullable
  - `role_value` string, nullable
  - `care_team_member_id` string, nullable
  - `surface` 'wizard_panel' | 'connect_sheet' | 'resend', nullable

## Response `200`

Successful Response

- SrcApiCareTeamInviteCanonicalRoutesInviteResponse: Echoes the RPC payload + a typed outcome enum. ``recipient_display_name`` and ``recipient_organisation_name`` are populated by the route AFTER the RPC succeeds - they are not part of the RPC contract. The route does a best-effort canonical-name lookup so the appweb hook can render the toast without re-querying. Field semantics by branch: - ``connected_clinician`` / clinician ``already_on_team`` - name comes from ``clinician_profiles.first_name + last_name``; org name comes from the invitee's ``clinician_profiles.organisation_id`` (i.e. the invitee's home org, not the caller's). - ``connected_worker`` / worker ``already_on_team`` - name comes from ``care_team_members.first_name + last_name``; org name is ``None`` (workers aren't tied to an organisation in our model). - ``invite_sent`` - name echoes the request body's first+last (joined), falling back to the request email if both halves are blank. Org name is ``None`` (no profile exists yet beyond the pending invite row). If the lookup itself errors (DB exception), both fields default to ``None`` - the invite already succeeded, display names are nice-to-have.
  - `outcome` 'connected_clinician' | 'connected_worker' | 'invite_sent' | 'already_on_team', required: Mirrors the RPC's jsonb ``outcome`` field verbatim. Stable strings - UI copy keys off them. Adding a new branch is a migration + enum extension in lockstep.
  - `child_care_team_id` string, nullable
  - `care_team_member_id` string, nullable
  - `clinician_access_id` string, nullable
  - `invite_code` string, nullable
  - `expires_at` string, nullable
  - `recipient_user_id` string, nullable
  - `email_status` 'not_applicable' | 'sent' | 'failed': Tri-state outcome for the B3 email dispatch. Replaces an earlier ``email_sent: bool`` that collapsed three real states into two: ``not_applicable`` (B1/B2/already_on_team, where no email is ever attempted), ``sent`` (B3 dispatch succeeded), and ``failed`` (B3 dispatch errored - the invite row still exists, so the caller can offer a re-send affordance without re-running the whole RPC). Keeps the contract explicit while the API is pre-consumer.
  - `recipient_display_name` string, nullable
  - `recipient_organisation_name` string, nullable
  - `converted` boolean

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/projectindi/apis/indi-agent-api-first-principles.md) - [All operations](https://skmtc.dev/projectindi/apis/indi-agent-api-first-principles/llms.txt) - [OpenAPI document](https://skmtc.dev/projectindi/apis/indi-agent-api-first-principles/revisions/25a7813e21fd?raw)
