Removing someone from a call so they can't rejoin
Kick drops the connection; it does not invalidate the token they already hold. The two-part fix, and the mint-side block that actually keeps them out.
POST /v1/rtc/participant/kick/{room} on Infrai removes a participant from a room by identity. What it doesn’t do is make their token stop working — and since a token is valid until it expires, a kicked participant with a live token simply reconnects.
The durable block lives in your own mint endpoint. Kick handles the moment; your authorisation logic handles the rest of the meeting.
Kick
curl -sS -X POST "https://api.infrai.cc/v1/rtc/participant/kick/support-4821" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"room": "support-4821", "identity": "au_usr_lMmXGySJeGVM1xicqHGJaJbB"}'
{
"ok": true,
"data": { "ok": true, "identity": "au_usr_lMmXGySJeGVM1xicqHGJaJbB" }
}
identity is the same value you passed when you minted their token, which is the reason to use a stable user id there rather than a display name. A kick needs to name the participant, and names aren’t identifiers.
Confirm who’s actually in the room
curl -sS "https://api.infrai.cc/v1/rtc/participant/list/support-4821" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
The response carries items, one per connected participant. Read it before and after — before, because you need the exact identity string, and after, because a kick that didn’t take is worth knowing about immediately rather than after the customer complains.
The part that keeps them out
Your mint endpoint is the gate. Record the ejection, then refuse to mint for that identity and room:
import os
import requests
API = "https://api.infrai.cc"
KEY = os.environ["INFRAI_API_KEY"]
SESSION = requests.Session()
SESSION.headers.update({"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
# In production: a table with a TTL, not a set. A restart that forgets who was
# ejected is a restart that lets them straight back in.
EJECTED: set[tuple[str, str]] = set()
def eject(room: str, identity: str, reason: str) -> dict:
"""Block first, then kick. In the other order there is a window — however
small — in which the client can re-mint a fresh token and walk back in."""
EJECTED.add((room, identity))
kicked = SESSION.post(f"{API}/v1/rtc/participant/kick/{room}",
json={"room": room, "identity": identity}, timeout=20)
ok = bool(kicked.ok and kicked.json().get("data", {}).get("ok"))
SESSION.post(
f"{API}/v1/errors/capture",
json={"title": "rtc participant ejected", "level": "warning", "user_id": identity,
"tags": {"room": room, "reason": reason}},
timeout=20,
)
return {"kicked": ok, "blocked": True}
def mint(room: str, identity: str, display_name: str) -> dict:
if (room, identity) in EJECTED:
raise PermissionError("participant was removed from this room")
resp = SESSION.post(
f"{API}/v1/rtc/token/issue",
json={"room": room, "identity": identity, "display_name": display_name,
"ttl_s": 900, "can_publish": True, "can_subscribe": True,
"can_publish_data": True, "is_admin": False},
timeout=25,
)
resp.raise_for_status()
return resp.json()["data"]
if __name__ == "__main__":
print(eject("support-4821", os.environ["IDENTITY"], "abusive language"))
Block before kick, not after. The window is small but it’s real, and the failure is a participant who reappears seconds later looking unstoppable.
Short TTLs make this work
The arithmetic: a kicked participant can reconnect for as long as their current token lives. With a fifteen-minute TTL that’s at most fifteen minutes and usually much less; with an eight-hour TTL it’s the rest of the working day.
So mint short and let the client re-mint through the endpoint that checks the block list. That single decision converts kick from a suggestion into an ejection.
| TTL | Worst-case rejoin window | When it’s appropriate |
|---|---|---|
| 300s | five minutes | consumer rooms, moderation matters |
| 900s | fifteen minutes | sensible default |
| 3600s | an hour | internal calls, trusted participants |
| 8h | the whole day | almost never |
When the room is the problem
If several participants are misbehaving, or the room name has leaked into a script, the blunt option is faster:
curl -sS -X DELETE "https://api.infrai.cc/v1/rtc/room/delete/support-4821" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
Everyone loses the call, including the people you wanted to keep. For a support call that’s usually acceptable — recreate under a fresh name and re-mint for the participants you want, which is a thirty-second recovery and leaves the old name useless to anyone holding a token for it.
Limitations
There’s no ban list in the platform: nothing remembers that an identity was ejected, so the block above is state you keep. If your service runs on several instances, that state has to be shared — a per-process set means an ejected user gets back in by hitting a different instance, which is the kind of bug that only appears in production.
There’s also no per-participant mute or permission downgrade mid-call: the flags are fixed at mint time, so “let them watch but stop them talking” means kicking and re-minting with can_publish: false. That’s a real limitation and a reason to look at LiveKit, whose server API can update a participant’s permissions in place, or Agora if you need fine-grained in-call moderation as a product feature.
What’s on the same credential is the rest of the response: the ejection record via POST /v1/errors/capture, the notification to the room owner via POST /v1/email/send, and the moderation metric via POST /v1/metrics/report — one key, one invoice, one GET /v1/account/usage. Kick and room management report billing_class: free in discovery while token issue is billed per call (verified 2026-09-21), and those rates drift downward as vendor contracts improve.