Helium API
Create rooms and add or remove the people in them from your own code.
https://heliumrooms.com/api/v1Introduction
The API is part of every paid plan. Every request and response body is JSON. Rooms are identified by their id and people by their email address.
Rooms are created by copying a room you set up in Helium. Build a template room once in the editor, then copy it for each client. A typical onboarding flow does that with Create a room, then adds the client’s contacts with Add a person. Each of them gets the same invite email as when you add them in Helium.
Authentication
Create a key in Settings → API. The full key is shown once, so store it somewhere safe. Revoke a key in the same place and anything using it stops working right away.
Send the key as a bearer token on every request.
curl https://heliumrooms.com/api/v1/rooms \
-H "Authorization: Bearer hr_live_..."Responses
A successful response wraps its result in data. A failed one returns an error with a stable code to branch on and a message for people. Every code is listed under Errors.
Lists return their items in data, in a fixed order, with hasMore. Today every list fits in one response, so hasMore is always false.
{ "data": { "id": "6f1c...", "name": "Acme" } }{ "data": [{ "id": "6f1c..." }], "hasMore": false }{
"error": {
"code": "room_not_found",
"message": "No room with this id in your workspace."
}
}Changes to the API
We add to this API without breaking it. New fields, new error codes and new values for existing fields can appear at any time, so ignore what you don't recognise. Removing or renaming anything would only happen in a new version, announced in advance.
Idempotency
Send an Idempotency-Key header with any write, for example the id of the customer in your own system. If the request is retried with the same key, Helium returns the first response instead of creating a second room or sending a second invite.
Successful responses are kept for 24 hours. Errors are not kept, so once you fix the cause, a retry with the same key runs again. Reusing a key for a different request returns 422 idempotency_key_reused. A retry while the first request is still running returns 409 request_in_progress.
curl -X POST https://heliumrooms.com/api/v1/rooms \
-H "Authorization: Bearer hr_live_..." \
-H "Idempotency-Key: customer-4821" \
-H "Content-Type: application/json" \
-d '{"templateRoomId": "...", "name": "Acme", "linearCustomerId": "..."}'Rate limits
60 requests per minute per workspace. Past that, requests return 429 rate_limited with a Retry-After header.
The room object
{
"id": "6f1c2a9e-...",
"name": "Acme",
"access": "invite_only",
"url": "https://heliumrooms.com/room/xY12ab",
"createdAt": "2026-10-31T12:00:00.000Z"
}Fields
idRequired- The room’s id.
accessRequiredpublic(anyone with the link),invite_only(people you add sign in) orclosed.urlRequired- The link to send. Uses your custom domain once it is verified.
List rooms
GET/ rooms
Every room in your workspace, newest first.
curl https://heliumrooms.com/api/v1/rooms \
-H "Authorization: Bearer hr_live_..."{
"data": [
{
"id": "6f1c2a9e-...",
"name": "Acme",
"access": "invite_only",
"url": "https://heliumrooms.com/room/xY12ab",
"createdAt": "2026-10-31T12:00:00.000Z"
}
],
"hasMore": false
}Get a room
GET/ rooms/ {roomId}
One room by its id.
curl https://heliumrooms.com/api/v1/rooms/6f1c2a9e-... \
-H "Authorization: Bearer hr_live_..."{
"data": {
"id": "6f1c2a9e-...",
"name": "Acme",
"access": "invite_only",
"url": "https://heliumrooms.com/room/xY12ab",
"createdAt": "2026-10-31T12:00:00.000Z"
}
}Create a room from a template
POST/ rooms
Rooms are always created by copying an existing room, the template. Set it up once in Helium the way every new room should look, then call this for each new room. Issue Boards fill from Linear while the request runs; a very large board finishes within a few minutes.
Body
templateRoomIdRequired- The id of the room to copy: any room in your workspace that you set up the way new rooms should look. Find it with List rooms or at the end of the room editor's URL (
/app/room/{id}). The short code in a room's link is not its id. nameRequired- The new room’s name, up to 100 characters.
linearCustomerIdRequired if the template room has a customer- The Linear customer this room is for: its id, or the last part of the customer's URL in Linear (for example
acme-8dde29bc80d6). To copy the id in Linear, open the customer, press Cmd+K (Ctrl+K on Windows) and choose Copy model UUID. The new room's Issue Boards then show only this customer's issues, on top of their other filters; see the notes below for which boards. Required when a module in the template room has a customer. accessOptionalpublic,invite_onlyorclosed. Defaults to the template’s access, and toinvite_onlywhen the template is closed.
What gets copied
Copied: pages, their order and icons, and the Issue Board, Request Issue, Changelog and Embed Website modules with their settings. Not copied: people, issues (they load from Linear for the new room) and changelog releases.
Setting up the template room
Build the template room once in Helium, the way every client room should look. Set its access to closed: clients can't open it, and it doesn't count toward your room limit.
Teams, projects, labels and embedded URLs are copied exactly as they are; only the customer changes per room. So filter the template room by what every client may see, never by one client's project or label, or each new room will show that client's issues.
Setting up the customer
What a copy does with linearCustomerId depends on whether the module has a customer in the template room:
| Module | Has a customer | Has no customer |
|---|---|---|
| Issue Board | Replaced by linearCustomerId | Gets linearCustomerId, unless another module in the template room has a customer. Then it stays without one. |
| Request Issue | Replaced by linearCustomerId | Stays without one ("Each company its own list") |
So a customer in the template room is a placeholder: set one on every module that should follow the client, and each copy fills in the right one.
Checks and limits
Two checks keep a room from showing the wrong customer. A template room with a customer anywhere needs linearCustomerId (422 linear_customer_required). A template room with no Issue Board and no Request Issue with a customer refuses one (422 template_has_no_customer_modules).
Rooms created through the API count toward your plan’s room limit like any other.
curl -X POST https://heliumrooms.com/api/v1/rooms \
-H "Authorization: Bearer hr_live_..." \
-H "Idempotency-Key: customer-4821" \
-H "Content-Type: application/json" \
-d '{
"templateRoomId": "1d0b7c3e-...",
"name": "Acme",
"linearCustomerId": "b7e2c1d4-..."
}'{
"data": {
"id": "6f1c2a9e-...",
"name": "Acme",
"access": "invite_only",
"url": "https://heliumrooms.com/room/xY12ab",
"createdAt": "2026-10-31T12:00:00.000Z"
}
}The person object
{
"email": "jane@acme.com",
"name": "Jane Doe",
"hasAccess": true,
"invitedAt": "2026-10-31T12:00:00.000Z",
"firstViewedAt": null
}Fields
hasAccessRequired- Whether they can open the room today. Everyone on a public room; on an invite-only room, the people you added.
invitedAtRequired- When the invite email went out, or null if it never did.
firstViewedAtRequired- When they first opened the room, or null if they haven’t yet.
Your own team is never listed; they see every room already.
List people
GET/ rooms/ {roomId}/ people
Everyone in a room, most recently added first.
curl https://heliumrooms.com/api/v1/rooms/6f1c2a9e-.../people \
-H "Authorization: Bearer hr_live_..."{
"data": [
{
"email": "jane@acme.com",
"name": "Jane Doe",
"hasAccess": true,
"invitedAt": "2026-10-31T12:00:00.000Z",
"firstViewedAt": null
}
],
"hasMore": false
}Add a person
POST/ rooms/ {roomId}/ people
Adds a person and emails them the room invite, the same invite the People list in Helium sends. On an invite_only room this creates their account.
Adding someone who was already invited changes nothing, sends nothing, and returns 200 with "invited": false.
If the invite email could not be sent, the person is still added and you get 201 with "invited": false. Adding them again retries the email. If you send an Idempotency-Key, use a new one for the retry.
Body
emailRequired- Their address.
firstNameOptional- Used for new accounts on invite-only rooms.
lastNameOptional- Used for new accounts on invite-only rooms.
curl -X POST https://heliumrooms.com/api/v1/rooms/6f1c2a9e-.../people \
-H "Authorization: Bearer hr_live_..." \
-H "Content-Type: application/json" \
-d '{"email": "jane@acme.com", "firstName": "Jane", "lastName": "Doe"}'{
"data": {
"person": {
"email": "jane@acme.com",
"name": "Jane Doe",
"hasAccess": true,
"invitedAt": "2026-10-31T12:00:00.000Z",
"firstViewedAt": null
},
"invited": true
}
}Remove a person
DELETE/ rooms/ {roomId}/ people/ {email}
Removes their access to the room and stops its emails to them. URL-encode the address. Their account stays, so access to other rooms is untouched.
Removing someone who is not in the room returns 404 person_not_found. After a retry, that means they are already removed.
curl -X DELETE https://heliumrooms.com/api/v1/rooms/6f1c2a9e-.../people/jane%40acme.com \
-H "Authorization: Bearer hr_live_..."{ "data": { "removed": true } }Errors
Branch on code; the message may change.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A field is missing, invalid or unknown. |
| 400 | invalid_json | The body is not valid JSON. |
| 401 | missing_api_key | No bearer token was sent. |
| 401 | invalid_api_key | The key is unknown or revoked. |
| 403 | plan_required | The workspace is not on a paid plan. |
| 403 | room_limit_reached | The plan has no room left. Close a room or contact us. |
| 404 | room_not_found | No room with this id in your workspace. |
| 404 | template_not_found | No room with this templateRoomId in your workspace. |
| 404 | person_not_found | The address is not in the room. |
| 409 | room_closed | People cannot be added to a closed room. |
| 409 | email_unsubscribed | The address unsubscribed from this room. |
| 409 | email_has_other_account | The address has an account in another Helium workspace. |
| 409 | email_is_team_member | The address belongs to your own team. |
| 409 | linear_not_connected | Linear is not connected to the workspace. |
| 409 | linear_customers_disabled | Customer requests are off in your Linear workspace. |
| 409 | request_in_progress | A request with this Idempotency-Key is still running. |
| 422 | invalid_linear_customer | No Linear customer with this id. |
| 422 | linear_customer_required | The template room has customer modules but no linearCustomerId was sent. |
| 422 | template_has_no_customer_modules | linearCustomerId was sent but the template room has nothing to apply it to. |
| 422 | idempotency_key_reused | The Idempotency-Key was used for a different request. |
| 429 | rate_limited | Too many requests. |
| 429 | invite_limit_reached | Your workspace sent 100 invites to public rooms in the last 24 hours. |
| 500 | internal_error | Something went wrong on our side. |
Questions or missing something? Email support@heliumrooms.com.