Meeting link endpoints
Create, replace and turn off a channel's meeting link, remove guests from the room, and the public routes a guest uses to join without an account.
Updated
Paths are relative to https://api.fishcamp.space/api/v1. The channel routes need the X-Workspace-Id header and
a person: API keys get 403 forbidden on all of them, reading the link included. The /meet routes are public:
guests call them without an account. How meeting links work in the app is in
Meeting links for guests.
The meeting link object
{
"id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"code": "Hq7kT2mZpX4rNa",
"url": "https://fishcamp.space/m/Hq7kT2mZpX4rNa",
"channelId": "0b6f3c55-2a51-4e0a-a3d6-6a3f1f2e9d10",
"createdBy": { "id": "7a1c2e3f-4b5d-4e6f-8a9b-0c1d2e3f4a5b", "name": "Maya Lopes" },
"guestJoins": 3,
"lastUsedAt": "2026-11-05T15:04:02.000Z",
"createdAt": "2026-11-05T14:55:00.000Z"
}| Field | Description |
|---|---|
code | 14 letters and digits: the secret part of the link. |
url | The link to send to guests. |
createdBy | Who created it, or null. |
guestJoins | How many times guests joined with this link. |
lastUsedAt | When a guest last joined with it, or null. |
Get the link
GET /channels/:channelId/meeting-linkPeople who can see the channel. The channel's active link, or null when it has none.
Create or copy the link
POST /channels/:channelId/meeting-linkPeople who can see the channel. Returns 200 with the channel's active link, and creates it the first time. A
channel has one link at a time, so calling this again returns the same link.
curl -X POST "https://api.fishcamp.space/api/v1/channels/0b6f3c55-2a51-4e0a-a3d6-6a3f1f2e9d10/meeting-link" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN" \
-H "X-Workspace-Id: YOUR_WORKSPACE_ID"| Error | When |
|---|---|
409 voice_disabled | The channel's voice is off. |
422 validation_error | It's a direct message. |
404 not_found | You can't see the channel, or it was archived. |
Replace the link
POST /channels/:channelId/meeting-link/resetPeople. Turns off the current link and returns a new one (200). The old code stops working at once, and the
guests in the room are taken out of it. The channel's voice must be on (409 voice_disabled otherwise).
Turn off the link
DELETE /channels/:channelId/meeting-linkPeople. Returns 204, or 404 when the channel has no active link. The guests in the room are taken out of it,
and guests who open the old link get 410 meeting_link_revoked.
Remove a guest from the room
POST /channels/:channelId/call/guests/:identity/removePeople who can see the channel. Takes a guest out of the audio room and returns 204. A guest's identity
starts with guest_: find it in GET /channels/:id/call.
| Error | When |
|---|---|
422 validation_error | The identity isn't a guest's. People from the workspace can't be removed this way. |
404 not_found | That guest isn't in the room. |
503 media_unavailable | The audio rooms didn't answer. Try again in a moment. |
The link keeps working. To keep the guest out, replace it.
Public routes for guests
The guest page at https://fishcamp.space/m/<code> calls these routes for you. Use them only to build your own guest
client.
Preview a meeting
GET /meet/:codeNo token. Limit: 60 per minute.
{
"workspaceName": "Northstar Studio",
"channelName": "design-crit",
"status": "active",
"hostPresent": true,
"participantCount": 2,
"cameraEnabled": true,
"screenShareEnabled": true,
"transcriptionEnabled": true
}| Field | Description |
|---|---|
status | active; revoked when the link was turned off or replaced; unavailable when the channel's voice is off or the channel was archived. |
hostPresent | Whether someone from the workspace is in the room. Guests can only join while it's true. |
participantCount | Everyone in the room now, guests included. |
cameraEnabled, screenShareEnabled | What guests may use, following the channel's switches. |
transcriptionEnabled | Whether what guests say is transcribed. Tell them before they join. |
An unknown code answers 404.
Join
POST /meet/:code/joinNo token. Limit: 10 per minute.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | How the team sees the guest. Control characters are removed, spaces are collapsed and the name is cut to 40 characters. |
curl -X POST "https://api.fishcamp.space/api/v1/meet/Hq7kT2mZpX4rNa/join" \
-H "Content-Type: application/json" \
-d '{"name": "Ana Souza"}'{
"url": "wss://…",
"token": "eyJhbGciOi…",
"roomName": "ch_0b6f3c55-2a51-4e0a-a3d6-6a3f1f2e9d10",
"identity": "guest_q7Rk2mTzX9pLw4Hn",
"name": "Ana Souza",
"guestToken": "eyJhbGciOi…",
"expiresAt": "2026-11-05T21:04:02.000Z",
"cameraEnabled": true,
"screenShareEnabled": true,
"transcriptionEnabled": true
}- Connect to the room with any LiveKit client SDK, using
urlandtoken, within 10 minutes. The token only allows what the channel's switches allow, and never data messages. - Fishcamp checks the guest again when they connect: if nobody from the workspace is in the room, the link was turned off, or the room already has 20 guests, the guest is taken out.
- When the last person from the workspace leaves, the room closes for the guests too.
guestTokensends the guest's audio to transcription (below). It lasts 6 hours, untilexpiresAt, and opens nothing else: the rest of the API answers401to it.
| Error | When |
|---|---|
409 waiting_for_host | Nobody from the workspace is in the room yet. Check GET /meet/:code every few seconds and try again when hostPresent is true. |
409 meeting_full | The room already has 20 guests. |
409 meeting_unavailable | The channel's voice is off, or the channel was archived. |
410 meeting_link_revoked | The link was turned off or replaced. |
422 validation_error | The name is missing or empty. |
503 media_unavailable | Audio rooms aren't available on this server. |
Send audio for transcription
POST /meet/:code/transcript?startedAt=1793891052000&durationMs=15000Sends a chunk of the guest's own microphone while they're in the room and the channel's transcription is on. Fishcamp turns it into text and discards the audio. The last chunk still counts if it arrives up to 18 seconds after the guest left. Limit: 12 per minute.
- Header:
Authorization: Bearer <guestToken>. - Body: the raw audio, from 200 bytes to 1 MB, with a
Content-Typeofaudio/webm,audio/ogg,audio/mp4,audio/mpegoraudio/wav. - Query:
startedAt, when the chunk started, in Unix milliseconds (within 24 hours of now), anddurationMs, from 500 to 120,000.
The answer is the new transcript line, with the guest's name marked, or { "skipped": true } when the chunk had no
speech:
{
"id": "b7a6c5d4-e3f2-4a1b-9c8d-7e6f5a4b3c2d",
"mediaSessionId": "e4d3c2b1-a0f9-4e8d-b7c6-5a4b3c2d1e0f",
"channelId": "0b6f3c55-2a51-4e0a-a3d6-6a3f1f2e9d10",
"profileId": null,
"speakerName": "Ana Souza (guest)",
"startMs": 240000,
"endMs": 255000,
"text": "Send me the preview link when it's ready."
}| Error | When |
|---|---|
401 unauthorized | The guest token is missing, invalid or expired. |
410 meeting_link_revoked | The link was turned off or replaced after the guest joined. |
403 forbidden | The guest isn't in the room, and didn't leave it in the last 18 seconds. |
409 transcription_off | The channel's transcription is off. |
409 no_audio_room | No audio room is open in the channel. |
409 transcript_closed | This room's transcript is already closed. |
422 validation_error | The body, its type, startedAt or durationMs isn't valid. |
502 transcription_failed, 503 transcription_unavailable | Speech-to-text failed for this chunk, or isn't available right now. |