Skip to main content
Personas can join your video meetings. You create an invite with a meeting URL and a persona, and the persona appears in the call like any other participant: it shows up in the roster under its display name, listens to the conversation, and speaks with its avatar on camera. Everyone in the meeting can talk to it. This works with Google Meet, Zoom, and Microsoft Teams. Use it for AI-led user interviews, training and roleplay calls, product demos, or a subject-matter expert your team can pull into a call and question. Meetings are driven by the Meetings API, which requires an API key with the meetings permission scope. You can also invite a published persona from its Build page in the Lab. The API adds scheduling and region control on top.
Meeting participants always see that the persona is an AI. Its display name carries an “(AI)” suffix, added server-side if the name you provide doesn’t already include one, and its video shows a persistent AI-disclosure treatment.

Invite a persona

Send a meeting URL and a persona to POST /v1/meetings/invites:
curl -X POST https://api.anam.ai/v1/meetings/invites \
  -H "Authorization: Bearer $ANAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "meetingUrl": "https://meet.google.com/abc-defg-hij",
    "displayName": "Cara",
    "personaId": "00000000-0000-0000-0000-000000000000"
  }'
const response = await fetch("https://api.anam.ai/v1/meetings/invites", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ANAM_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    meetingUrl: "https://meet.google.com/abc-defg-hij",
    displayName: "Cara",
    personaId: "00000000-0000-0000-0000-000000000000",
  }),
});

const invite = await response.json();
Provide exactly one of:
  • personaId — a saved persona from the Lab or the Personas API.
  • personaConfig — a runtime configuration, the same shape you use when creating a session token. It needs at least an avatarId, a voiceId, and an llmId. The configuration is used for the meeting session but not stored.
The response is the invite in pending status. Without a joinAt, the persona starts joining right away.
{
  "id": "00000000-0000-0000-0000-000000000000",
  "provider": "google_meet",
  "meetingUrl": "https://meet.google.com/abc-defg-hij",
  "displayName": "Cara (AI)",
  "status": "pending",
  "createdAt": "2026-07-10T10:00:00.000Z",
  "joinAt": null,
  "groupCall": true,
  "sessionId": null,
  "region": "eu",
  "joinState": null,
  "statusReason": null
}
If the meeting has a waiting room, someone in the call needs to admit the persona, which appears under its display name. While it waits, the invite’s joinState is waiting_room. If nobody lets it in, the invite ends as failed with a statusReason of not_admitted or join_denied.

Schedule the join

For a meeting you know about in advance, set joinAt instead of creating the invite at meeting time:
{
  "meetingUrl": "https://meet.google.com/abc-defg-hij",
  "displayName": "Cara",
  "personaId": "00000000-0000-0000-0000-000000000000",
  "joinAt": "2026-07-14T15:00:00Z"
}
Scheduling at least 10 minutes ahead reserves guaranteed capacity, so the persona’s spot on the call is held for it. Immediate joins, and joinAt times less than 10 minutes out, use on-demand capacity instead and can be rejected with a 503 at busy moments. joinAt is ISO 8601 and must include a timezone (Z or an offset such as +02:00). It must be in the future, at most 7 days ahead. The invite stays pending until the scheduled time, and you can cancel it at any point before then.
When a calendar integration or booking flow tells you the meeting time, create the invite as soon as the meeting is scheduled rather than when it starts. Guaranteed capacity is the most reliable way to get a persona into a planned meeting.

Group calls and 1:1 calls

By default the persona behaves as a well-mannered participant in a multi-person meeting: it joins silently and speaks only when addressed by its display name (“Cara, what do you think?”). This is the groupCall: true default. Set groupCall: false when the persona is the main counterpart on the call, such as an interview or a 1:1 demo. It then greets everyone when it joins and responds to everything it hears, the same way it would in a regular session.

Follow the invite

An invite moves through a small set of states. Poll GET /v1/meetings/invites/{id} to follow it, or filter the list endpoint to watch several at once:
curl "https://api.anam.ai/v1/meetings/invites?status=active" \
  -H "Authorization: Bearer $ANAM_API_KEY"
StatusMeaning
pendingCreated; waiting for a scheduled joinAt or in the process of joining.
activeThe persona is in the call and its session is running.
endedThe call finished normally: the meeting ended or the persona left.
cancelledYou cancelled the invite.
failedThe persona couldn’t join, or the call ended abnormally.
Two fields add detail:
  • joinState reports the persona’s progress into the call as it happens: joining, waiting_room, in_call, media_denied, media_granted, media_active, left, done, or error. New values may appear over time, so treat unrecognized ones as informational rather than as errors.
  • statusReason explains how a finished invite got there, for example not_admitted, removed_by_host, meeting_ended, meeting_link_invalid, or idle_timeout. It’s null while the invite is in progress, and may also gain new values over time.
Once the invite is active, its sessionId refers to an ordinary session. Everything in the Sessions API works with it, including fetching the transcript after the call.

Remove the persona

DELETE /v1/meetings/invites/{id} cancels a pending invite. If the persona has already joined, it leaves the call and its session ends immediately:
curl -X DELETE https://api.anam.ai/v1/meetings/invites/$INVITE_ID \
  -H "Authorization: Bearer $ANAM_API_KEY"
The call returns 204 and is idempotent for invites that already finished. You don’t have to clean up invites you leave alone: the persona leaves on its own when the meeting ends.

Regions

The persona joins the call from one of three regions: eu, us-east, or us-west. If you omit region, one is picked automatically based on where your request comes from. For the lowest latency, set the region closest to the meeting’s participants, which may not be where your server runs. regionPolicy controls what happens when your chosen region is out of capacity:
  • preferred (the default) may serve the invite from another region so the persona still joins.
  • strict never leaves the selected region and fails with a 503 instead. It requires an explicit region.
Use strict when data residency matters, for example to keep an EU customer’s call in the EU:
{
  "meetingUrl": "https://meet.google.com/abc-defg-hij",
  "personaId": "00000000-0000-0000-0000-000000000000",
  "region": "eu",
  "regionPolicy": "strict"
}
The region field on the invite is the region actually serving the call, which under preferred can differ from the one you asked for.

Limits and capacity

Invite creation is rate limited to 10 requests per minute per requester and 30 per hour per organization. Successful (201) and rate-limited (429) responses both carry RateLimit-* headers for the per-minute window and X-RateLimit-Org-Hour-* headers for the hourly one. A 503 on create means there’s no on-demand capacity right now. The response includes a Retry-After header; retry after that many seconds, or switch to a scheduled joinAt at least 10 minutes ahead so capacity is reserved instead. A 422 means the meeting service rejected the invite itself, which usually points to a problem with the meeting URL. The full request and response reference, including every error shape, is in the Meetings API reference.
Last modified on July 10, 2026