Natzar Partner API Reference - v1.0.0
    Preparing search index...

    Interface Endpoints

    The machine-readable route table: every Partner API route mapped to its request and response types.

    Keys are METHOD /v1/path with {id} marking the path parameter (always our id of the addressed resource). For GET routes, request is the QUERY shape; for body-carrying methods it is the JSON body. undefined means the route takes no query/body.

    Useful for building typed clients:

    type Route = keyof Endpoints;
    async function call<R extends Route>(
    route: R,
    args: {params?: Record<string, string>; request: Endpoints[R]['request']},
    ): Promise<Endpoints[R]['response']> { …fetch with Authorization: Bearer pp_… }
    interface Endpoints {
        "POST /v1/patients": {
            request: {
                externalId: string;
                phone: string;
                email: string;
                givenName: string;
                familyName: string;
                birthdate: string;
                sex: "male" | "female";
                lang?: "en_US" | "es_US" | "de_CH" | "fr_CH" | "it_CH";
                state?: string;
                country?: string;
            };
            response: UpsertPatientResponse;
        };
        "GET /v1/patients": {
            request: { externalId?: string; cursor?: string; limit?: number };
            response: ListPatientsResponse;
        };
        "GET /v1/patients/{id}": {
            request: undefined;
            response: GetPatientResponse;
        };
        "PATCH /v1/patients/{id}": {
            request: {
                email?: string;
                givenName?: string;
                familyName?: string;
                birthdate?: string;
                sex?: "male"
                | "female";
                lang?: "en_US" | "es_US" | "de_CH" | "fr_CH" | "it_CH";
            };
            response: UpdatePatientResponse;
        };
        "GET /v1/patients/state": {
            request: { patientId?: string; externalPatientId?: string };
            response: GetPatientStateResponse;
        };
        "POST /v1/physicians": {
            request: {
                externalId: string;
                email: string;
                givenName?: string;
                familyName?: string;
                languages?: ("en" | "es" | "de" | "fr" | "it")[];
                sendPortalInvite?: boolean;
            };
            response: CreatePhysicianResponse;
        };
        "GET /v1/physicians": {
            request: { externalId?: string; cursor?: string; limit?: number };
            response: ListPhysiciansResponse;
        };
        "GET /v1/physicians/{id}": {
            request: undefined;
            response: GetPhysicianResponse;
        };
        "POST /v1/physicians/{id}/portal-invite": {
            request: undefined;
            response: SendPortalInviteResponse;
        };
        "POST /v1/physicians/{id}/availability": {
            request: { asyncAvailable: boolean };
            response: SetPhysicianAvailabilityResponse;
        };
        "POST /v1/physicians/{id}/session": {
            request: { ttlSeconds?: number }
            | undefined;
            response: CreatePhysicianSessionResponse;
        };
        "POST /v1/physicians/{id}/presence": {
            request: { ready: boolean };
            response: PresenceResponse;
        };
        "POST /v1/physicians/{id}/heartbeat": {
            request: Record<string, never>
            | undefined;
            response: PresenceResponse;
        };
        "GET /v1/physicians/{id}/workspace": {
            request: undefined;
            response: PhysicianWorkspace;
        };
        "GET /v1/physicians/{id}/agenda": {
            request: { from?: string; to?: string };
            response: GetPhysicianAgendaResponse;
        };
        "PUT /v1/physicians/{id}/specialties": {
            request: { specialties: string[]; acceptsAllSpecialties?: boolean };
            response: SetPhysicianSpecialtiesResponse;
        };
        "PUT /v1/physicians/{id}/languages": {
            request: { languages: ("en" | "es" | "de" | "fr" | "it")[] };
            response: SetPhysicianLanguagesResponse;
        };
        "GET /v1/specialties": {
            request: undefined;
            response: ListSpecialtiesResponse;
        };
        "POST /v1/agent/messages": {
            request: {
                patientId?: string;
                externalPatientId?: string;
                text?: string;
                idempotencyKey?: string;
                attachments?: {
                    stagingKey: string;
                    fileName?: string;
                    mimeType?: string;
                }[];
            };
            response: PostAgentMessageResponse;
        };
        "GET /v1/agent/messages": {
            request: {
                patientId?: string;
                externalPatientId?: string;
                cursor?: string;
                limit?: number;
            };
            response: TranscriptPage;
        };
        "POST /v1/agent/embed-session": {
            request: {
                patientId?: string;
                externalPatientId?: string;
                ttlSeconds?: number;
            };
            response: CreateEmbedSessionResponse;
        };
        "POST /v1/async-consults": {
            request: {
                patientId?: string;
                externalPatientId?: string;
                context?: string;
                consent: "collected"
                | "embed";
            };
            response: CreateAsyncConsultResponse;
        };
        "GET /v1/async-consults": {
            request: {
                patientId?: string;
                externalPatientId?: string;
                status?: "invited"
                | "queued"
                | "active"
                | "resolve_requested"
                | "closed";
                assignee?: string;
                origin?: "partner" | "platform" | "any";
                cursor?: string;
                limit?: number;
            };
            response: ListAsyncConsultsResponse;
        };
        "GET /v1/async-consults/{id}": {
            request: undefined;
            response: GetAsyncConsultResponse;
        };
        "GET /v1/async-consults/{id}/messages": {
            request: { cursor?: string; limit?: number };
            response: TranscriptPage;
        };
        "POST /v1/async-consults/{id}/messages": {
            request: {
                idempotencyKey?: string;
                text?: string;
                attachments?: {
                    stagingKey: string;
                    fileName?: string;
                    mimeType?: string;
                }[];
            };
            response: PostAsyncMessageResponse;
        };
        "POST /v1/async-consults/{id}/replies": {
            request: {
                idempotencyKey?: string;
                physicianId?: string;
                text: string;
                attachment?: { stagingKey: string; fileName?: string; mimeType?: string };
            };
            response: PostAsyncMessageResponse;
        };
        "POST /v1/async-consults/{id}/claim": {
            request: { physicianId?: string };
            response: ClaimAsyncConsultResponse;
        };
        "POST /v1/async-consults/{id}/takeover": {
            request: { physicianId?: string };
            response: TakeoverAsyncConsultResponse;
        };
        "POST /v1/async-consults/{id}/resolve": {
            request: { physicianId?: string; note?: string };
            response: ResolveAsyncConsultResponse;
        };
        "POST /v1/async-consults/{id}/close": {
            request: { reason?: "cancelled" | "patient_closed" }
            | undefined;
            response: CloseAsyncConsultResponse;
        };
        "POST /v1/async-consults/{id}/escalate": {
            request: Record<string, never>
            | undefined;
            response: EscalateAsyncConsultResponse;
        };
        "POST /v1/async-consults/{id}/consent": {
            request: { accept: boolean };
            response: RespondAsyncConsentResponse;
        };
        "POST /v1/async-consults/{id}/rate": {
            request: { stars: number; feedback?: string };
            response: RateAsyncConsultResponse;
        };
        "POST /v1/async-consults/{id}/embed-session": {
            request: { ttlSeconds?: number }
            | undefined;
            response: CreateEmbedSessionResponse;
        };
        "POST /v1/attachments/upload-urls": {
            request: {
                patientId?: string;
                externalPatientId?: string;
                files: { fileName: string; mimeType: string; sizeBytes?: number }[];
            };
            response: CreateUploadUrlsResponse;
        };
        "POST /v1/telehealth-consults": {
            request: {
                patientId?: string;
                externalPatientId?: string;
                context?: string;
                mode?: "scheduled"
                | "queue";
                specialty?: string;
            };
            response: CreateTelehealthConsultResponse;
        };
        "GET /v1/telehealth-consults": {
            request: {
                patientId?: string;
                externalPatientId?: string;
                status?: | "invited"
                | "cancelled"
                | "scheduled"
                | "waiting"
                | "ringing"
                | "in_progress"
                | "completed"
                | "no_show";
                practitionerId?: string;
                mode?: "scheduled"
                | "queue";
                origin?: "partner" | "platform" | "any";
                cursor?: string;
                limit?: number;
            };
            response: ListTelehealthConsultsResponse;
        };
        "GET /v1/telehealth-consults/{id}": {
            request: undefined;
            response: GetTelehealthConsultResponse;
        };
        "POST /v1/telehealth-consults/{id}/embed-session": {
            request: { ttlSeconds?: number }
            | undefined;
            response: CreateEmbedSessionResponse;
        };
        "POST /v1/telehealth-consults/{id}/cancel": {
            request: { reason?: string }
            | undefined;
            response: CancelTelehealthConsultResponse;
        };
        "POST /v1/telehealth-consults/{id}/room": {
            request: Record<string, never>
            | undefined;
            response: TelehealthRoomResponse;
        };
        "POST /v1/telehealth-consults/{id}/end": {
            request: { goOffline?: boolean }
            | undefined;
            response: EndTelehealthConsultResponse;
        };
        "POST /v1/telehealth-consults/{id}/ready": {
            request: { present?: boolean }
            | undefined;
            response: TelehealthReadyResponse;
        };
        "POST /v1/telehealth-consults/{id}/join": {
            request: Record<string, never>
            | undefined;
            response: JoinTelehealthConsultResponse;
        };
        "POST /v1/telehealth-consults/{id}/rate": {
            request: {
                communicationRating: number;
                overallPhysicianRating: number;
                feedback?: string;
            };
            response: RateTelehealthConsultResponse;
        };
        "GET /v1/telehealth-consults/{id}/slots": {
            request: { from?: string; to?: string; timezone?: string };
            response: ListTelehealthSlotsResponse;
        };
        "POST /v1/telehealth-consults/{id}/book": {
            request: {
                startsAt: string;
                practitionerId?: string;
                patientTimezone?: string;
            };
            response: BookTelehealthConsultResponse;
        };
        "PUT /v1/physicians/{id}/schedule": {
            request: {
                rules: {
                    weekday: number;
                    startMinute: number;
                    endMinute: number;
                    timezone?: string
                    | null;
                    effectiveFrom?: string | null;
                    effectiveUntil?: string | null;
                    intervalWeeks?: number | null;
                    specialty?: string | null;
                    active?: boolean | null;
                }[];
                exceptions?: {
                    date: string;
                    endDate?: string
                    | null;
                    kind: "block" | "open";
                    startMinute?: number | null;
                    endMinute?: number | null;
                    specialty?: string | null;
                    reason?: string | null;
                }[];
                timezone?: string;
            };
            response: GetPhysicianScheduleResponse;
        };
        "PATCH /v1/physicians/{id}/schedule": {
            request: {
                rules?: {
                    weekday: number;
                    startMinute: number;
                    endMinute: number;
                    timezone?: string
                    | null;
                    effectiveFrom?: string | null;
                    effectiveUntil?: string | null;
                    intervalWeeks?: number | null;
                    specialty?: string | null;
                    active?: boolean | null;
                    id?: string;
                }[];
                exceptions?: {
                    date: string;
                    endDate?: string
                    | null;
                    kind: "block" | "open";
                    startMinute?: number | null;
                    endMinute?: number | null;
                    specialty?: string | null;
                    reason?: string | null;
                    id?: string;
                }[];
                deletedRuleIds?: string[];
                deletedExceptionIds?: string[];
                timezone?: string
                | null;
            };
            response: GetPhysicianScheduleResponse;
        };
        "GET /v1/physicians/{id}/schedule": {
            request: { from?: string; to?: string };
            response: GetPhysicianScheduleResponse;
        };
        "PUT /v1/physicians/{id}/licenses": {
            request: {
                licenses: {
                    state: string;
                    country?: string;
                    licenseNumber?: string;
                    expiresAt?: string;
                    active?: boolean;
                }[];
            };
            response: GetPhysicianLicensesResponse;
        };
        "GET /v1/physicians/{id}/licenses": {
            request: undefined;
            response: GetPhysicianLicensesResponse;
        };
        "GET /v1/events": {
            request: { since?: string; cursor?: string; limit?: number };
            response: ListEventsResponse;
        };
    }
    Index
    POST /v1/patients GET /v1/patients GET /v1/patients/{id} PATCH /v1/patients/{id} GET /v1/patients/state POST /v1/physicians GET /v1/physicians GET /v1/physicians/{id} POST /v1/physicians/{id}/portal-invite POST /v1/physicians/{id}/availability POST /v1/physicians/{id}/session POST /v1/physicians/{id}/presence POST /v1/physicians/{id}/heartbeat GET /v1/physicians/{id}/workspace GET /v1/physicians/{id}/agenda PUT /v1/physicians/{id}/specialties PUT /v1/physicians/{id}/languages GET /v1/specialties POST /v1/agent/messages GET /v1/agent/messages POST /v1/agent/embed-session POST /v1/async-consults GET /v1/async-consults GET /v1/async-consults/{id} GET /v1/async-consults/{id}/messages POST /v1/async-consults/{id}/messages POST /v1/async-consults/{id}/replies POST /v1/async-consults/{id}/claim POST /v1/async-consults/{id}/takeover POST /v1/async-consults/{id}/resolve POST /v1/async-consults/{id}/close POST /v1/async-consults/{id}/escalate POST /v1/async-consults/{id}/consent POST /v1/async-consults/{id}/rate POST /v1/async-consults/{id}/embed-session POST /v1/attachments/upload-urls POST /v1/telehealth-consults GET /v1/telehealth-consults GET /v1/telehealth-consults/{id} POST /v1/telehealth-consults/{id}/embed-session POST /v1/telehealth-consults/{id}/cancel POST /v1/telehealth-consults/{id}/room POST /v1/telehealth-consults/{id}/end POST /v1/telehealth-consults/{id}/ready POST /v1/telehealth-consults/{id}/join POST /v1/telehealth-consults/{id}/rate GET /v1/telehealth-consults/{id}/slots POST /v1/telehealth-consults/{id}/book PUT /v1/physicians/{id}/schedule PATCH /v1/physicians/{id}/schedule GET /v1/physicians/{id}/schedule PUT /v1/physicians/{id}/licenses GET /v1/physicians/{id}/licenses GET /v1/events

    POST /v1/patients

    "POST /v1/patients": {
        request: {
            externalId: string;
            phone: string;
            email: string;
            givenName: string;
            familyName: string;
            birthdate: string;
            sex: "male" | "female";
            lang?: "en_US" | "es_US" | "de_CH" | "fr_CH" | "it_CH";
            state?: string;
            country?: string;
        };
        response: UpsertPatientResponse;
    }

    Upsert a patient by externalId. Idempotent.

    "GET /v1/patients": {
        request: { externalId?: string; cursor?: string; limit?: number };
        response: ListPatientsResponse;
    }

    List patients, or look one up by ?externalId=.

    "GET /v1/patients/{id}": { request: undefined; response: GetPatientResponse }

    Fetch one patient by our id.

    "PATCH /v1/patients/{id}": {
        request: {
            email?: string;
            givenName?: string;
            familyName?: string;
            birthdate?: string;
            sex?: "male" | "female";
            lang?: "en_US" | "es_US" | "de_CH" | "fr_CH" | "it_CH";
        };
        response: UpdatePatientResponse;
    }

    Update the editable subset (never phone/externalId).

    "GET /v1/patients/state": {
        request: { patientId?: string; externalPatientId?: string };
        response: GetPatientStateResponse;
    }

    Everything a patient-facing screen needs, in one call.

    POST /v1/physicians

    "POST /v1/physicians": {
        request: {
            externalId: string;
            email: string;
            givenName?: string;
            familyName?: string;
            languages?: ("en" | "es" | "de" | "fr" | "it")[];
            sendPortalInvite?: boolean;
        };
        response: CreatePhysicianResponse;
    }

    Create a physician + backing portal account. Identical repeat (same externalId + same email) → 200, idempotent; same externalId with a different email → 409 external_id_conflict; new externalId with an in-use email → 409 email_in_use.

    "GET /v1/physicians": {
        request: { externalId?: string; cursor?: string; limit?: number };
        response: ListPhysiciansResponse;
    }

    List physicians, or look one up by ?externalId=.

    "GET /v1/physicians/{id}": {
        request: undefined;
        response: GetPhysicianResponse;
    }

    Fetch one physician by our id, or me for the acting one. Physician session: me only.

    POST /v1/physicians/{id}/portal-invite

    "POST /v1/physicians/{id}/portal-invite": {
        request: undefined;
        response: SendPortalInviteResponse;
    }

    (Re-)send the portal invitation email.

    POST /v1/physicians/{id}/availability

    "POST /v1/physicians/{id}/availability": {
        request: { asyncAvailable: boolean };
        response: SetPhysicianAvailabilityResponse;
    }

    Toggle async auto-assignment eligibility. Self-only with a physician credential. Physician session.

    POST /v1/physicians/{id}/session

    "POST /v1/physicians/{id}/session": {
        request: { ttlSeconds?: number } | undefined;
        response: CreatePhysicianSessionResponse;
    }

    Mint a physician SESSION token for browser-direct calls (see "Acting as a physician"). Tenant key only — a session may not mint another (403 forbidden).

    POST /v1/physicians/{id}/presence

    "POST /v1/physicians/{id}/presence": {
        request: { ready: boolean };
        response: PresenceResponse;
    }

    Go on/off the live video queue; ready: true matches immediately. Self-only. Physician session.

    POST /v1/physicians/{id}/heartbeat

    "POST /v1/physicians/{id}/heartbeat": {
        request: Record<string, never> | undefined;
        response: PresenceResponse;
    }

    Keep a ready physician on the rota — every 15 s, TTL 45 s. Self-only. Physician session.

    "GET /v1/physicians/{id}/workspace": {
        request: undefined;
        response: PhysicianWorkspace;
    }

    The whole clinician screen in one read: presence, live queue, active call, upcoming appointments, inbox. The polling target. Physician session.

    "GET /v1/physicians/{id}/agenda": {
        request: { from?: string; to?: string };
        response: GetPhysicianAgendaResponse;
    }

    Booked appointments in a window (default now → +7 d, max 31 d). Physician session.

    "PUT /v1/physicians/{id}/specialties": {
        request: { specialties: string[]; acceptsAllSpecialties?: boolean };
        response: SetPhysicianSpecialtiesResponse;
    }

    Replace the specialties a physician covers (slugs from GET /v1/specialties; unknown ones are dropped). Self-only with a physician credential. Physician session.

    "PUT /v1/physicians/{id}/languages": {
        request: { languages: ("en" | "es" | "de" | "fr" | "it")[] };
        response: SetPhysicianLanguagesResponse;
    }

    Replace the languages a physician consults in (base codes from ./languages; a full locale such as fr_CH is 400 invalid_request). A ranked routing preference, never a filter — a physician with none recorded still receives consults, last. Self-only with a physician credential. Physician session.

    "GET /v1/specialties": { request: undefined; response: ListSpecialtiesResponse }

    The tenant's specialty catalogue. Physician session.

    POST /v1/agent/messages

    "POST /v1/agent/messages": {
        request: {
            patientId?: string;
            externalPatientId?: string;
            text?: string;
            idempotencyKey?: string;
            attachments?: { stagingKey: string; fileName?: string; mimeType?: string }[];
        };
        response: PostAgentMessageResponse;
    }

    Send a patient turn to the AI agent (202, FIFO-enqueued). Answered by the agent unless a human clinician already owns an open consult for this patient, in which case the turn is routed to them.

    "GET /v1/agent/messages": {
        request: {
            patientId?: string;
            externalPatientId?: string;
            cursor?: string;
            limit?: number;
        };
        response: TranscriptPage;
    }

    Read/poll the patient's agent conversation, oldest first.

    POST /v1/agent/embed-session

    "POST /v1/agent/embed-session": {
        request: {
            patientId?: string;
            externalPatientId?: string;
            ttlSeconds?: number;
        };
        response: CreateEmbedSessionResponse;
    }

    Mint a session token for the <natzar-agent> widget (patient-scoped).

    POST /v1/async-consults

    "POST /v1/async-consults": {
        request: {
            patientId?: string;
            externalPatientId?: string;
            context?: string;
            consent: "collected" | "embed";
        };
        response: CreateAsyncConsultResponse;
    }

    Start an async consult. 409 has_open_thread / feature_disabled.

    "GET /v1/async-consults": {
        request: {
            patientId?: string;
            externalPatientId?: string;
            status?: "invited" | "queued" | "active" | "resolve_requested" | "closed";
            assignee?: string;
            origin?: "partner" | "platform" | "any";
            cursor?: string;
            limit?: number;
        };
        response: ListAsyncConsultsResponse;
    }

    List async consults, most recently active first. assignee=me|unassigned|<id> and origin filter the physician's view. Physician session.

    "GET /v1/async-consults/{id}": {
        request: undefined;
        response: GetAsyncConsultResponse;
    }

    Fetch one async consult. Tenant-scoped with a physician credential. Physician session.

    "GET /v1/async-consults/{id}/messages": {
        request: { cursor?: string; limit?: number };
        response: TranscriptPage;
    }

    Page the consult transcript, oldest first. Physician session.

    POST /v1/async-consults/{id}/messages

    "POST /v1/async-consults/{id}/messages": {
        request: {
            idempotencyKey?: string;
            text?: string;
            attachments?: { stagingKey: string; fileName?: string; mimeType?: string }[];
        };
        response: PostAsyncMessageResponse;
    }

    Post a patient message (202, FIFO-enqueued). Tenant key only — not a physician-session route.

    POST /v1/async-consults/{id}/replies

    "POST /v1/async-consults/{id}/replies": {
        request: {
            idempotencyKey?: string;
            physicianId?: string;
            text: string;
            attachment?: { stagingKey: string; fileName?: string; mimeType?: string };
        };
        response: PostAsyncMessageResponse;
    }

    Post the assigned physician's reply (202, FIFO-enqueued). Physician credential required. Physician session.

    POST /v1/async-consults/{id}/claim

    "POST /v1/async-consults/{id}/claim": {
        request: { physicianId?: string };
        response: ClaimAsyncConsultResponse;
    }

    Assign a queued consult to the acting physician. Physician session.

    POST /v1/async-consults/{id}/takeover

    "POST /v1/async-consults/{id}/takeover": {
        request: { physicianId?: string };
        response: TakeoverAsyncConsultResponse;
    }

    Reassign an active consult after an SLA breach. 409 sla_not_overdue before. Physician session.

    POST /v1/async-consults/{id}/resolve

    "POST /v1/async-consults/{id}/resolve": {
        request: { physicianId?: string; note?: string };
        response: ResolveAsyncConsultResponse;
    }

    Mark the consult resolved (assignee only). Physician session.

    POST /v1/async-consults/{id}/close

    "POST /v1/async-consults/{id}/close": {
        request: { reason?: "cancelled" | "patient_closed" } | undefined;
        response: CloseAsyncConsultResponse;
    }

    Close an open consult — administratively, or on the patient's behalf. With a physician credential: assignee only, tenant-scoped. Physician session.

    POST /v1/async-consults/{id}/escalate

    "POST /v1/async-consults/{id}/escalate": {
        request: Record<string, never> | undefined;
        response: EscalateAsyncConsultResponse;
    }

    The assignee turns the thread into a live video consult: closes it as escalated, opens a telehealth consult for the patient, sends them the link. Physician session.

    POST /v1/async-consults/{id}/consent

    "POST /v1/async-consults/{id}/consent": {
        request: { accept: boolean };
        response: RespondAsyncConsentResponse;
    }

    Record the PATIENT's consent answer to an invite (accept or decline).

    POST /v1/async-consults/{id}/rate

    "POST /v1/async-consults/{id}/rate": {
        request: { stars: number; feedback?: string };
        response: RateAsyncConsultResponse;
    }

    Record the PATIENT's rating of a finished consult. Write-once.

    POST /v1/async-consults/{id}/embed-session

    "POST /v1/async-consults/{id}/embed-session": {
        request: { ttlSeconds?: number } | undefined;
        response: CreateEmbedSessionResponse;
    }

    Mint an embed session token for the async chat widget.

    POST /v1/attachments/upload-urls

    "POST /v1/attachments/upload-urls": {
        request: {
            patientId?: string;
            externalPatientId?: string;
            files: { fileName: string; mimeType: string; sizeBytes?: number }[];
        };
        response: CreateUploadUrlsResponse;
    }

    Presign attachment upload slots in the patient's staging area.

    POST /v1/telehealth-consults

    "POST /v1/telehealth-consults": {
        request: {
            patientId?: string;
            externalPatientId?: string;
            context?: string;
            mode?: "scheduled" | "queue";
            specialty?: string;
        };
        response: CreateTelehealthConsultResponse;
    }

    Start a live video consult. 409 feature_disabled when not enabled.

    "GET /v1/telehealth-consults": {
        request: {
            patientId?: string;
            externalPatientId?: string;
            status?:
                | "invited"
                | "cancelled"
                | "scheduled"
                | "waiting"
                | "ringing"
                | "in_progress"
                | "completed"
                | "no_show";
            practitionerId?: string;
            mode?: "scheduled"
            | "queue";
            origin?: "partner" | "platform" | "any";
            cursor?: string;
            limit?: number;
        };
        response: ListTelehealthConsultsResponse;
    }

    List telehealth consults, newest first. status, practitionerId (me), mode and origin filter the physician's view. Physician session.

    "GET /v1/telehealth-consults/{id}": {
        request: undefined;
        response: GetTelehealthConsultResponse;
    }

    Fetch one telehealth consult (fresh recordingUrl when available). Physician session.

    POST /v1/telehealth-consults/{id}/embed-session

    "POST /v1/telehealth-consults/{id}/embed-session": {
        request: { ttlSeconds?: number } | undefined;
        response: CreateEmbedSessionResponse;
    }

    Mint an embed session token for the video widget.

    POST /v1/telehealth-consults/{id}/cancel

    "POST /v1/telehealth-consults/{id}/cancel": {
        request: { reason?: string } | undefined;
        response: CancelTelehealthConsultResponse;
    }

    Cancel before the call starts (invited/scheduled/waiting only). With a physician credential on a booked consult: the clinician cancels their own appointment and the patient is told (reason relayed). Physician session.

    POST /v1/telehealth-consults/{id}/room

    "POST /v1/telehealth-consults/{id}/room": {
        request: Record<string, never> | undefined;
        response: TelehealthRoomResponse;
    }

    The PHYSICIAN's way into their call: the LiveKit grant once in_progress. Requires a physician credential naming the practitioner (409 not_assigned). Physician session.

    POST /v1/telehealth-consults/{id}/end

    "POST /v1/telehealth-consults/{id}/end": {
        request: { goOffline?: boolean } | undefined;
        response: EndTelehealthConsultResponse;
    }

    Hang up. The physician goes back to ready (or offline with goOffline) and the queue is re-matched at once — next is the patient now ringing. Idempotent once completed. Physician session.

    POST /v1/telehealth-consults/{id}/ready

    "POST /v1/telehealth-consults/{id}/ready": {
        request: { present?: boolean } | undefined;
        response: TelehealthReadyResponse;
    }

    The booked physician's waiting-room presence for a scheduled consult (the twin of the patient's /join); call every ~10 s. Starts the call when both sides are present in the window. Physician session.

    POST /v1/telehealth-consults/{id}/join

    "POST /v1/telehealth-consults/{id}/join": {
        request: Record<string, never> | undefined;
        response: JoinTelehealthConsultResponse;
    }

    The patient's waiting-room heartbeat AND read: enters/holds the queue and returns position, or the LiveKit credentials once the call starts. Call every ~10s while the patient is watching.

    POST /v1/telehealth-consults/{id}/rate

    "POST /v1/telehealth-consults/{id}/rate": {
        request: {
            communicationRating: number;
            overallPhysicianRating: number;
            feedback?: string;
        };
        response: RateTelehealthConsultResponse;
    }

    Record the PATIENT's post-call rating. Write-once.

    "GET /v1/telehealth-consults/{id}/slots": {
        request: { from?: string; to?: string; timezone?: string };
        response: ListTelehealthSlotsResponse;
    }

    The bookable times for a scheduled consult: every published availability window of every physician who covers its specialty, minus what is already taken, clipped to the tenant's lead time and booking horizon.

    Identical starts from different physicians collapse into ONE offer. That is deliberate: the patient picks a TIME, not a person, and book chooses the least-loaded eligible physician within the language tier — the patient's language first, then English, then anyone. practitionerIds is exposed so a surface that genuinely needs to pin one can, not so every surface should; each offer's languages and the response's patientLang let a grid badge the times a French speaker is preferred for.

    POST /v1/telehealth-consults/{id}/book

    "POST /v1/telehealth-consults/{id}/book": {
        request: {
            startsAt: string;
            practitionerId?: string;
            patientTimezone?: string;
        };
        response: BookTelehealthConsultResponse;
    }

    Take one of the offered slots. ATOMIC: the reservation is a conditional write, so two requests for the same start cannot both succeed — the loser gets 409 slot_taken with a fresh slots array attached, ready to render.

    Calling this on a consult that is ALREADY booked reschedules it: same consult, same id, same embed session, a different time.

    "PUT /v1/physicians/{id}/schedule": {
        request: {
            rules: {
                weekday: number;
                startMinute: number;
                endMinute: number;
                timezone?: string | null;
                effectiveFrom?: string | null;
                effectiveUntil?: string | null;
                intervalWeeks?: number | null;
                specialty?: string | null;
                active?: boolean | null;
            }[];
            exceptions?: {
                date: string;
                endDate?: string
                | null;
                kind: "block" | "open";
                startMinute?: number | null;
                endMinute?: number | null;
                specialty?: string | null;
                reason?: string | null;
            }[];
            timezone?: string;
        };
        response: GetPhysicianScheduleResponse;
    }

    Replace a physician's WHOLE schedule — every rule and every exception, whatever its date — with the one sent: the availability patients book against. A replace, not a merge, for a caller that owns the rota (an HR or rostering system): send the schedule you want to exist, and it becomes the schedule. Calendar-style edits use PATCH instead. Self-only with a physician credential. Physician session. Query: from/to shape the response range.

    "PATCH /v1/physicians/{id}/schedule": {
        request: {
            rules?: {
                weekday: number;
                startMinute: number;
                endMinute: number;
                timezone?: string | null;
                effectiveFrom?: string | null;
                effectiveUntil?: string | null;
                intervalWeeks?: number | null;
                specialty?: string | null;
                active?: boolean | null;
                id?: string;
            }[];
            exceptions?: {
                date: string;
                endDate?: string
                | null;
                kind: "block" | "open";
                startMinute?: number | null;
                endMinute?: number | null;
                specialty?: string | null;
                reason?: string | null;
                id?: string;
            }[];
            deletedRuleIds?: string[];
            deletedExceptionIds?: string[];
            timezone?: string
            | null;
        };
        response: GetPhysicianScheduleResponse;
    }

    Apply a CHANGE SET to a physician's schedule — create, update and delete individual rules and exceptions by id, leaving everything else exactly as it was however far ahead it lies. The way a calendar edits: "block the 14th", "end this pattern in March", "extra hours next Saturday" are each one small PATCH. diffSchedule in ./schedule produces the body from an edited document. Self-only with a physician credential. Physician session. Query: from/to shape the response range.

    "GET /v1/physicians/{id}/schedule": {
        request: { from?: string; to?: string };
        response: GetPhysicianScheduleResponse;
    }

    Read a physician's schedule: the whole rota, the exceptions and the resolved days over from..to (query; default today → the planning horizon), plus how far the tenant asks them to publish. Physician session.

    "PUT /v1/physicians/{id}/licenses": {
        request: {
            licenses: {
                state: string;
                country?: string;
                licenseNumber?: string;
                expiresAt?: string;
                active?: boolean;
            }[];
        };
        response: GetPhysicianLicensesResponse;
    }

    Replace a physician's STATE LICENCES — the jurisdictions they may practise in. A whole-list replace, like the schedule.

    Only consulted by tenants with licence enforcement switched on; for everyone else these are recorded and ignored. With it on the constraint is hard in both directions: no licence for the patient's state means the physician is not offered the consult, and no licences at all means they are offered nothing. Self-only with a physician credential. Physician session.

    "GET /v1/physicians/{id}/licenses": {
        request: undefined;
        response: GetPhysicianLicensesResponse;
    }

    Read a physician's licences, with the ones lapsing soon called out. Physician session.

    "GET /v1/events": {
        request: { since?: string; cursor?: string; limit?: number };
        response: ListEventsResponse;
    }

    Page the durable webhook replay log, oldest first.