HTTP API

Every endpoint of the platform API, drawn from the contract.

On this page

The platform API is what the app, pitch and the connector all call: one contract, checked on both sides, so none of them can do what the API does not allow. You can call it too, from a script or your own tool. This page says how to sign in, how requests and replies are shaped, how an upload works, and lists every endpoint.

Base URL

https://api.getroomi.com

Every endpoint is under /api. GET /healthz answers {"ok":true,"service":"platform"} without signing in, for a check that the platform is up.

Authentication

Every endpoint needs you signed in, except sign-in itself under /api/auth/ and the community's public pages. Without a session, an endpoint answers 401 with {"error":"sign in first"}.

There are two ways to be signed in:

  • A cookie, which the app uses. Signing in to the app sets it.
  • A bearer token, which pitch uses, sent as Authorization: Bearer <token>.

To call the API from a script, sign in with pitch login and read the header with pitch auth header:

curl -s https://api.getroomi.com/api/projects \
  -H "$(pitch auth header | jq -r '"Authorization: " + .Authorization')"

The token is your session: it is as good as your password until it expires or you run pitch logout, which ends it on the platform. Keep it out of files, logs and the repository. A token the connector was issued for Claude is refused here, and a token from pitch login is refused by the connector: each is good for one thing only.

Workspaces

Every endpoint acts on the active workspace of the session: the workspace the session has active, or, for a session that has none, the first workspace the person joined. There is no workspace in the path. A person who belongs to no workspace is treated as signed out.

An id from another workspace is a 404, exactly as an id that does not exist, so the API never says whether something exists elsewhere.

Requests and replies

  • Bodies are JSON. Send Content-Type: application/json. A body that does not fit the endpoint's schema is refused with 400, {"error":"invalid"}, and an issues list naming each field that does not fit. Fields an endpoint does not name are ignored.
  • Replies are JSON, checked against the contract before they leave, so a reply always has the shape listed below.
  • Status codes. A GET answers 200. A POST that creates something (createProject, createBuild, createLink) answers 201; other POSTs answer 200. The upload's PUT answers 204 with no body.
  • Ids are strings, UUIDs today. Store them as they are; do not rely on their format.
  • Times are ISO 8601 strings in UTC, such as 2026-09-28T14:03:11.000Z. A time that does not apply, such as the expiry of a link that never expires, is null.
  • Days in the analytics paths are UTC dates written YYYY-MM-DD, both ends included.
  • Lists are returned whole. There is no pagination.

Errors

A refusal is a status and a JSON body whose error says what, in words you can show a person, and usually a reason a program can act on:

{ "error": "refused: taken", "reason": "taken" }
Status Means
400 The body does not fit the schema (invalid, with issues).
401 Not signed in (sign in first).
402 A plan's limit. The body names the limit, the plan, and the plan that would allow more as upgrade.
404 Not found in your workspace (not found).
409 The name is taken (reason: taken).
411 An upload was sent without Content-Length.
422 Refused on its merits (refused: <reason>).
423 The project or workspace is paused (reason: paused).
429 Too many reports in a day (reason: rate).
500 The platform's own fault (internal error).
503 A feature that is not switched on (reason: unavailable).

Errors lists every reason and what to do about it.

Limits

The API has no request rate limit of its own. What limits you is your plan: the number of projects, deploys in any 24 hours, private links, viewer-hours and analytics, each refused with a 402 that says which. The largest upload the platform accepts is 512 MB. See Plans and limits.

Deploying through the API

A build is uploaded in three calls, then made live. This is what pitch deploy --commit does, and the deploy_static tool.

1. Start an upload. Declare the build's kind and its exact size in bytes:

POST /api/projects/{projectId}/uploads
Content-Type: application/json

{"kind":"static","sizeBytes":20480}
{ "uploadId": "e4b7c2a1-9f3d-4c68-a5e1-0d8b6f2c9a37", "putUrl": "/api/uploads/e4b7c2a1-9f3d-4c68-a5e1-0d8b6f2c9a37" }

2. Send the bytes to putUrl, on the API's own address, signed in the same way. Content-Length must be the size you declared; what is stored is measured again and refused if it differs. An upload takes its bytes once.

PUT /api/uploads/e4b7c2a1-9f3d-4c68-a5e1-0d8b6f2c9a37
Content-Type: application/gzip
Content-Length: 20480

It answers 204.

3. Register the build with the upload and its pitch.json:

POST /api/projects/{projectId}/builds
Content-Type: application/json

{"uploadId":"e4b7c2a1-9f3d-4c68-a5e1-0d8b6f2c9a37","manifest":{…},"note":"New onboarding flow"}

It answers 201 with the build, which is not live yet. A static build is unpacked here, and refused if its archive is not one the platform will serve. The deploy counts against your plan's deploys at this step.

4. Make it live:

POST /api/projects/{projectId}/builds/{buildId}/live

The same call makes an earlier build live again, which is how a rollback is done.

What the artefact is:

  • A static build: a gzipped tar of the built folder, with index.html at its root. At most 5,000 files, 32 MB a file and 256 MB unpacked; files and folders only, no links.
  • A container build: the bundle pitch deploy makes from your image. Its manifest must carry run, the start command pitch deploy reads from the image, so a container build is deployed with pitch deploy, not by hand.

Endpoints

Every endpoint, drawn from the contract the platform checks each request and reply against. Paths show parameters as :name.

The core of the contract: projects, uploads and builds, viewer links, and what viewers did and said. pitch and the connector call these.

listProjects

GET /api/projects

Every project in your workspace that has not been deleted, newest first, each with its room’s address. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object[]

Field Type
id string required · not empty
slug string required · 3–40 characters
name string required
visibility "private" | "public" required
liveBuildId string | null required
roomUrl string (uri) required
createdAt string (date-time) required

createProject

POST /api/projects

Creates a project. Its slug is the room’s address; its name is what the room shows. Refused with 409 when the slug is taken or was deleted less than 30 days ago, 422 when it is not one the platform allows (format, reserved, impersonation, banned), and 402 beyond the plan’s projects.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
slug string required · 3–40 characters
name string required · 1–80 characters

Reply: object

Field Type
id string required · not empty
slug string required · 3–40 characters
name string required
visibility "private" | "public" required
liveBuildId string | null required
roomUrl string (uri) required
createdAt string (date-time) required

getProject

GET /api/projects/:projectId

One project of your workspace, by id. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
id string required · not empty
slug string required · 3–40 characters
name string required
visibility "private" | "public" required
liveBuildId string | null required
roomUrl string (uri) required
createdAt string (date-time) required

startUpload

POST /api/projects/:projectId/uploads

Declares a build’s artefact before it is sent: its kind and its exact size in bytes, at most 512 MB. Returns the upload’s id and the address to PUT the bytes to, with Content-Length equal to the size declared; that PUT answers 204, and is refused if the size differs.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
kind "container" | "static" required
sizeBytes integer required

Reply: object

Field Type
uploadId string required · not empty
putUrl string required

createBuild

POST /api/projects/:projectId/builds

Registers a build from a finished upload on the same project, with its pitch.json. The build is not live until makeLive. A static build is unpacked here; a container build must carry run. Refused as unfinished, kind, used or archive, with 402 beyond the plan’s deploys and 423 while the platform has the project paused. rehearsal, optional, is what the deploy saw rehearsing each story step; the app shows a build without one as not reported, never as passed.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
uploadId string required · not empty
manifest object required
note string optional · at most 200 characters
rehearsal object optional

Reply: object

Field Type
id string required · not empty
projectId string required · not empty
kind "container" | "static" required
manifest object required
sizeBytes integer required
note string | null required
live boolean required
createdAt string (date-time) required

listBuilds

GET /api/projects/:projectId/builds

Every build of the project, newest first, each saying whether it is the live one. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object[]

Field Type
id string required · not empty
projectId string required · not empty
kind "container" | "static" required
manifest object required
sizeBytes integer required
note string | null required
live boolean required
createdAt string (date-time) required

makeLive

POST /api/projects/:projectId/builds/:buildId/live

Makes one of the project’s builds the live one, the build its room shows. The same call makes an earlier build live again, which is a rollback. Who did it and when is recorded.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId, buildId

Body: none

Reply: object

Field Type
id string required · not empty
projectId string required · not empty
kind "container" | "static" required
manifest object required
sizeBytes integer required
note string | null required
live boolean required
createdAt string (date-time) required

POST /api/projects/:projectId/links

Makes a private link to the room for one named viewer, with their email if you give it (nothing is sent to it) and an expiry of 1 to 365 days if you want one. The reply carries the link’s full address; no list does, and revealLink returns it again. Refused with 402 on a plan without private links, beyond the plan’s links per project, or once the month’s viewer-hours are used.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
viewerName string required · 1–80 characters
viewerEmail string (email) optional
expiresInDays integer optional · 1–365

Reply: object

Field Type
link object required
url string (uri) required

GET /api/projects/:projectId/links

Every link of the project, each with its stage in the pipeline and whether its address can be shown again (recoverable), never the address. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object[]

Field Type
id string required · not empty
viewerName string required
viewerEmail string (email) | null required
expiresAt string (date-time) | null required
revokedAt string (date-time) | null required
stage "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
recoverable boolean optional

POST /api/links/:linkId/revoke

Revokes a link: it stops opening the room at once, and cannot be opened again. What its viewer already did stays on record.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: linkId

Body: none

Reply: object

Field Type
id string required · not empty
viewerName string required
viewerEmail string (email) | null required
expiresAt string (date-time) | null required
revokedAt string (date-time) | null required
stage "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
recoverable boolean optional

POST /api/links/:linkId/reveal

A link’s full address again, to copy and resend when the viewer has lost it. Recorded in the project’s history with who asked. Refused with 422 as revoked or expired when the link no longer opens the room, and as unrecoverable for a link made before 1 October 2026, when only a hash of its key was kept: reissue that one. A connection that may only read is refused with 403, because the address opens the room. Sent with Cache-Control: no-store.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: linkId

Body: none

Reply: object

Field Type
link object required
url string (uri) required

POST /api/links/:linkId/reissue

A new address for the same link: the same viewer, with their visits, answers and comments, under a new key. The old address stops opening the room the moment it lands. For a link made before addresses were kept, or one that has gone astray; refused as revoked or expired like revealLink. Recorded in the project’s history.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: linkId

Body: none

Reply: object

Field Type
link object required
url string (uri) required

listVisits

GET /api/projects/:projectId/visits

Every visit to the project’s room, with the seconds spent on each app and page. A preview records none. Solo and up: on Free it is refused with 402, as each viewer’s journey is, and total views are on the analytics endpoints. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object[]

Field Type
id string required · not empty
linkId string | null required
viewerName string | null required
startedAt string (date-time) required
endedAt string (date-time) | null required
time { [key]: integer } required

listResponses

GET /api/projects/:projectId/responses

Every answer viewers gave to “What happens next”, with the detail they added. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object[]

Field Type
id string required · not empty
linkId string required · not empty
viewerName string required
step "meeting" | "yes" | "later" | "colleague" | "question" | "no" required
detail { [key]: any } | null required
createdAt string (date-time) required

listComments

GET /api/projects/:projectId/comments

Every comment viewers left in the project’s room, with the app and screen it was pinned to and whether it is resolved. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object[]

Field Type
id string required · not empty
linkId string | null required
viewerName string | null required
appId string | null required
screen string | null required
body string required
resolvedAt string (date-time) | null required
createdAt string (date-time) required
position object | null required
area object | null required
device "phone" | "tablet" | "laptop" | "screen" | "none" | null required
hasScreenshot boolean required

pipeline

GET /api/pipeline

Every viewer link across every project in the workspace, with its stage: sent, opened, engaged, yes, later or no. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object[]

Field Type
id string required · not empty
viewerName string required
viewerEmail string (email) | null required
expiresAt string (date-time) | null required
revokedAt string (date-time) | null required
stage "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
recoverable boolean optional
projectId string required · not empty
projectName string required

addPage

POST /api/projects/:projectId/pages

Adds a Markdown or HTML page to the project’s room, which every viewer sees at once. A page with the title of one the room already has replaces that page’s content.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
title string required · 1–80 characters
kind "markdown" | "html" required
content string required · at most 2000000 characters

Reply: object

Field Type
id string required · not empty
title string required
kind "markdown" | "html" required

listWorkspaces

GET /api/workspaces

Every workspace you belong to: your own first, then each team, with your role in each (owner or member), which one this session is working in (active), and the brand its plan shows (brand: its name and whether it has a logo, read from GET /api/workspaces/:workspaceId/logo; null for none). Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object[]

Field Type
id string required · not empty
name string required
kind "personal" | "team" required
role "owner" | "member" required
active boolean required
brand object | null optional

createTeam

POST /api/workspaces

Makes a team with the name given, with you as its owner, and answers it. A new team has no plan, so it holds no projects and nobody else until it has Studio or Agency; you may have one such team at a time (422, reason: unlicensed). Your own workspace is unchanged.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
name string required · 1–80 characters

Reply: object

Field Type
id string required · not empty
name string required
kind "personal" | "team" required
role "owner" | "member" required
active boolean required
brand object | null optional

listMembers

GET /api/workspaces/:workspaceId/members

Everyone in a workspace you belong to, owners first, each with their name, email, picture, role and when they joined; you marks you. Your own workspace has only you. Changes nothing; another workspace is a 404.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: workspaceId

Body: none

Reply: object[]

Field Type
userId string required · not empty
displayName string required
email string required
image string | null required
role "owner" | "member" required
joinedAt string (date-time) required
you boolean required

setMemberRole

POST /api/workspaces/:workspaceId/members/:userId/role

Makes someone in a team an owner or a member. An owner’s to do (403, reason: permission, for a member), and refused (422, reason: team) when it would leave the team with no owner. Answers the team’s people.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: workspaceId, userId

Body: object

Field Type
role "owner" | "member" required

Reply: object[]

Field Type
userId string required · not empty
displayName string required
email string required
image string | null required
role "owner" | "member" required
joinedAt string (date-time) required
you boolean required

removeMember

POST /api/workspaces/:workspaceId/members/:userId/remove

Takes someone out of a team at once: from their next request they reach none of it. An owner’s to do; never yourself, which is leaveTeam. Answers the team’s people.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: workspaceId, userId

Body: none

Reply: object[]

Field Type
userId string required · not empty
displayName string required
email string required
image string | null required
role "owner" | "member" required
joinedAt string (date-time) required
you boolean required

leaveTeam

POST /api/workspaces/:workspaceId/leave

Takes you out of a team. An owner leaves only while another owner stays (422, reason: team), and your own workspace is never left.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: workspaceId

Body: none

Reply: object

Field Type
left true required

renameWorkspace

POST /api/workspaces/:workspaceId/name

Renames a team, or your own workspace. An owner’s to do. Your own workspace’s name is what a room shows its viewers for a link made before display names.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: workspaceId

Body: object

Field Type
name string required · 1–80 characters

Reply: object

Field Type
id string required · not empty
name string required
kind "personal" | "team" required
role "owner" | "member" required
active boolean required
brand object | null optional

listInvitations

GET /api/workspaces/:workspaceId/invitations

A team’s invitations still waiting and not expired, each with its email, who made it and when it expires. An owner’s to see. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: workspaceId

Body: none

Reply: object[]

Field Type
id string required · not empty
email string required
role "owner" | "member" required
invitedBy string required
createdAt string (date-time) required
expiresAt string (date-time) required

createInvitation

POST /api/workspaces/:workspaceId/invitations

Invites one email into a team and answers the invitation with url, the link to send: nothing is emailed. Only someone signed in with that email, verified as theirs, can use the link, once, within 7 days. An owner’s to do; a waiting invitation holds a seat, so a team at its plan’s seats is refused (422, reason: team), and so is someone in the team already or invited already.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: workspaceId

Body: object

Field Type
email string (email) required · at most 254 characters

Reply: object

Field Type
invitation object required
url string (uri) required

revokeInvitation

POST /api/workspaces/:workspaceId/invitations/:invitationId/revoke

Revokes an invitation still waiting: its link stops working at once. An owner’s to do. Answers the invitations still waiting.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: workspaceId, invitationId

Body: none

Reply: object[]

Field Type
id string required · not empty
email string required
role "owner" | "member" required
invitedBy string required
createdAt string (date-time) required
expiresAt string (date-time) required

viewInvitation

GET /api/invitations/:invitationId

An invitation, told only to the person signed in with the email it is for: the team’s name and who invited them. A used, revoked or expired invitation, or anyone else’s, is a 404. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: invitationId

Body: none

Reply: object

Field Type
id string required · not empty
workspaceId string required · not empty
workspaceName string required
invitedBy string required
expiresAt string (date-time) required

acceptInvitation

POST /api/invitations/:invitationId/accept

Joins the team the invitation is for, as a member, and answers the team. Only the invited person, once, while a seat is free (422, reason: team, when none is). The joining is written in the team’s record.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: invitationId

Body: none

Reply: object

Field Type
id string required · not empty
name string required
kind "personal" | "team" required
role "owner" | "member" required
active boolean required
brand object | null optional

deleteTeam

POST /api/workspaces/:workspaceId/delete

Deletes a team, with its name typed again as confirm: its name, brand, people and invitations. An owner’s to do, and only once it holds no live project and its plan is cancelled (422, reason: team). Everyone in it keeps their own workspace.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: workspaceId

Body: object

Field Type
confirm string required

Reply: object

Field Type
deleted true required

projectHistory

GET /api/projects/:projectId/history

Every pause, change of note and resume on the project, and every link copied again (link.copy) or reissued (link.reissue) with whose it was, newest first, with who did it while they are a member. Append-only. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object[]

Field Type
id string required · not empty
kind "room.pause" | "room.message" | "room.resume" | "link.copy" | "link.reissue" | "room.busy" required
by string | null required
message string | null required
viewer string | null required
until string (date-time) | null required
at string (date-time) required

Changing a project

Renaming and deleting a project, the room’s order and its pages, and answering feedback.

renameProject

POST /api/projects/:projectId/name

Changes the project’s name, as the room and the app show it. The slug, and so the room’s address, stays as it is.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
name string required · 1–80 characters

Reply: object

Field Type
id string required · not empty
slug string required · 3–40 characters
name string required
visibility "private" | "public" required
liveBuildId string | null required
roomUrl string (uri) required
createdAt string (date-time) required

deleteProject

POST /api/projects/:projectId/delete

Deletes the project. confirm must be the project’s slug, or it is refused as confirm. Its links stop opening the room, and its slug is held for 30 days.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
confirm string required

Reply: object

Field Type
deleted true required

getRoomSetup

GET /api/projects/:projectId/room

The room’s order: the apps as you ordered them (null for pitch.json’s order), and the pages as the rail lists them. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
appOrder string[] | null required
pages object[] required

orderApps

POST /api/projects/:projectId/room/apps

Sets the order the room’s switcher lists the apps in. The apps it names come first, in its order, then any it leaves out, in pitch.json’s; an id the live build does not have is ignored.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
ids string[] required · at most 100 items

Reply: object

Field Type
appOrder string[] | null required
pages object[] required

orderPages

POST /api/projects/:projectId/room/pages

Sets the order the rail lists the pages in. The list must name every page of the project exactly once, or it is refused as order.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
ids string[] required · at most 100 items

Reply: object

Field Type
appOrder string[] | null required
pages object[] required

removePage

POST /api/projects/:projectId/pages/:pageId/remove

Takes a page out of the room and deletes its content. The pages after it close ranks. It is uploaded again by the next deploy if pitch.json still lists it.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId, pageId

Body: none

Reply: object

Field Type
appOrder string[] | null required
pages object[] required

listFeedback

GET /api/projects/:projectId/feedback

Every comment on the project, with your reply to it and whether it is resolved. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object[]

Field Type
id string required · not empty
linkId string | null required
viewerName string | null required
appId string | null required
screen string | null required
body string required
resolvedAt string (date-time) | null required
createdAt string (date-time) required
position object | null required
area object | null required
device "phone" | "tablet" | "laptop" | "screen" | "none" | null required
hasScreenshot boolean required
reply string | null required
repliedAt string (date-time) | null required
from object | null required

resolveComment

POST /api/comments/:commentId/resolve

Marks a comment resolved, or, with false, not resolved again.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: commentId

Body: object

Field Type
resolved boolean required

Reply: object

Field Type
id string required · not empty
linkId string | null required
viewerName string | null required
appId string | null required
screen string | null required
body string required
resolvedAt string (date-time) | null required
createdAt string (date-time) required
position object | null required
area object | null required
device "phone" | "tablet" | "laptop" | "screen" | "none" | null required
hasScreenshot boolean required
reply string | null required
repliedAt string (date-time) | null required
from object | null required

replyToComment

POST /api/comments/:commentId/reply

Answers a viewer’s comment; the viewer sees the answer in the room. One answer per comment: answering again replaces it.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: commentId

Body: object

Field Type
body string required · 1–2000 characters

Reply: object

Field Type
id string required · not empty
linkId string | null required
viewerName string | null required
appId string | null required
screen string | null required
body string required
resolvedAt string (date-time) | null required
createdAt string (date-time) required
position object | null required
area object | null required
device "phone" | "tablet" | "laptop" | "screen" | "none" | null required
hasScreenshot boolean required
reply string | null required
repliedAt string (date-time) | null required
from object | null required

Pausing a room

Closing every link to a project’s room for now, with a note to viewers, opening it again, and the project’s history of both.

getRoomPause

GET /api/projects/:projectId/pause

Where the room stands: your pause (when, by whom, and the note to viewers), any pause of the platform’s (over-limit, suspended, takedown), and what a viewer with a working link meets now: room, paused or unavailable. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
owner object | null required
system "over-limit" | "suspended" | "takedown" | null required
viewers "room" | "paused" | "unavailable" required

pauseRoom

POST /api/projects/:projectId/pause

Pauses the room: every working link shows a page saying who paused it, with message under it (at most 280 characters, or null for none), and no demo starts. Links, builds and pages are untouched, and deploys and new links still work. Called on a paused room, it changes the note. Recorded in the project’s history.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
message string | null required

Reply: object

Field Type
owner object | null required
system "over-limit" | "suspended" | "takedown" | null required
viewers "room" | "paused" | "unavailable" required

resumeRoom

POST /api/projects/:projectId/resume

Lifts your pause, so the same links open the room again. A pause of the platform’s stays, and the reply says so. Refused as taken when the room is not paused. Recorded in the project’s history.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
owner object | null required
system "over-limit" | "suspended" | "takedown" | null required
viewers "room" | "paused" | "unavailable" required

projectHistory

GET /api/projects/:projectId/history

Every pause, change of note and resume on the project, and every link copied again (link.copy) or reissued (link.reissue) with whose it was, newest first, with who did it while they are a member. Append-only. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object[]

Field Type
id string required · not empty
kind "room.pause" | "room.message" | "room.resume" | "link.copy" | "link.reissue" | "room.busy" required
by string | null required
message string | null required
viewer string | null required
until string (date-time) | null required
at string (date-time) required

The tour

The live build’s story, step by step, beside what its deploy reported of rehearsing each step.

getTour

GET /api/projects/:projectId/tour

The live build’s story, step by step: each step’s app, its calls, and how its rehearsal went (passed, failed, nothing-to-run or not-reported), with what the demo last said. build is null when nothing is live. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
build object | null required
steps object[] required
reported boolean required

Analytics

Build history, each viewer’s journey through the room, and analytics over a range of days.

buildHistory

GET /api/projects/:projectId/builds/history

Every build of the project, newest first, with who deployed it and each time it was made live, so the app can show what was live when. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object[]

Field Type
id string required · not empty
projectId string required · not empty
kind "container" | "static" required
manifest object required
sizeBytes integer required
note string | null required
live boolean required
createdAt string (date-time) required
deployedBy object | null required
releases object[] required

journey

GET /api/links/:linkId/journey

One link’s visits in order: what the viewer opened, ran, said and chose, where they stopped, and how far they took the tour, with everyone who opened the link. Refused with 402 on a plan without analytics in full. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: linkId

Body: none

Reply: object

Field Type
link object required
people object[] required
sessions object[] required
outside object[] required
tour object | null required
windowStart string (date-time) | null optional

projectAnalytics

GET /api/projects/:projectId/analytics/:from/:to

One project’s analytics over a range of UTC days, written YYYY-MM-DD, both ends included, at most 366 days. In full on a plan with it; total views only on one without. A range that is not days, or ends before it starts, is refused as range.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId, from, to

Body: none

Reply: object

workspaceAnalytics

GET /api/analytics/:from/:to

The same as projectAnalytics, across every project in the workspace, with the split by project.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: from, to

Body: none

Reply: object

The pipeline

Each viewer as a card to work: placed in a stage, judged, followed up and noted. Solo and up: on Free every endpoint here is refused with 402.

board

GET /api/board

Every link on every project that has not been deleted, as a card: its stage, the stage the evidence suggests, the outcome, the follow-up, your notes and the signals behind it. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object[]

Field Type
id string required · not empty
viewerName string required
viewerEmail string (email) | null required
expiresAt string (date-time) | null required
revokedAt string (date-time) | null required
stage "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
recoverable boolean optional
projectId string required · not empty
projectName string required
suggested "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
placed boolean required
outcome "won" | "lost" | "later" | null required
followUp object required
notes integer required
signals object required
forwarded object[] required
claims integer required

projectBoard

GET /api/projects/:projectId/board

One project’s links, as cards. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object[]

Field Type
id string required · not empty
viewerName string required
viewerEmail string (email) | null required
expiresAt string (date-time) | null required
revokedAt string (date-time) | null required
stage "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
recoverable boolean optional
projectId string required · not empty
projectName string required
suggested "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
placed boolean required
outcome "won" | "lost" | "later" | null required
followUp object required
notes integer required
signals object required
forwarded object[] required
claims integer required

POST /api/links/:linkId/stage

Places a viewer in a stage by hand, over what the evidence says; the evidence’s stage is still offered as a suggestion. null hands them back to the evidence.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: linkId

Body: object

Field Type
stage "sent" | "opened" | "engaged" | "yes" | "later" | "no" | null required

Reply: object

Field Type
id string required · not empty
viewerName string required
viewerEmail string (email) | null required
expiresAt string (date-time) | null required
revokedAt string (date-time) | null required
stage "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
recoverable boolean optional
projectId string required · not empty
projectName string required
suggested "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
placed boolean required
outcome "won" | "lost" | "later" | null required
followUp object required
notes integer required
signals object required
forwarded object[] required
claims integer required

POST /api/links/:linkId/outcome

Marks the deal won, lost or later. null clears it.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: linkId

Body: object

Field Type
outcome "won" | "lost" | "later" | null required

Reply: object

Field Type
id string required · not empty
viewerName string required
viewerEmail string (email) | null required
expiresAt string (date-time) | null required
revokedAt string (date-time) | null required
stage "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
recoverable boolean optional
projectId string required · not empty
projectName string required
suggested "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
placed boolean required
outcome "won" | "lost" | "later" | null required
followUp object required
notes integer required
signals object required
forwarded object[] required
claims integer required

followUp

POST /api/links/:linkId/follow-up

Sets the day to follow this viewer up, and a line on what about. Both null clears it. Nothing is sent to anyone.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: linkId

Body: object

Field Type
on string | null required
note string | null required

Reply: object

Field Type
id string required · not empty
viewerName string required
viewerEmail string (email) | null required
expiresAt string (date-time) | null required
revokedAt string (date-time) | null required
stage "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
recoverable boolean optional
projectId string required · not empty
projectName string required
suggested "sent" | "opened" | "engaged" | "yes" | "later" | "no" required
placed boolean required
outcome "won" | "lost" | "later" | null required
followUp object required
notes integer required
signals object required
forwarded object[] required
claims integer required

listNotes

GET /api/links/:linkId/notes

Your workspace’s notes on one viewer, newest first. They are never shown in the room. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: linkId

Body: none

Reply: object[]

Field Type
id string required · not empty
linkId string required · not empty
body string required
by string | null required
byImage string | null required
createdAt string (date-time) required

addNote

POST /api/links/:linkId/notes

Adds a note on one viewer, and returns all of their notes. Notes are never shown in the room.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: linkId

Body: object

Field Type
body string required · 1–2000 characters

Reply: object[]

Field Type
id string required · not empty
linkId string required · not empty
body string required
by string | null required
byImage string | null required
createdAt string (date-time) required

removeNote

POST /api/notes/:noteId/remove

Removes one note, and returns the link’s remaining notes.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: noteId

Body: none

Reply: object[]

Field Type
id string required · not empty
linkId string required · not empty
body string required
by string | null required
byImage string | null required
createdAt string (date-time) required

Feedback

One comment a viewer left, in full.

getFeedback

GET /api/comments/:commentId

One comment in full: where on the stage it was pinned, the app’s name, whether a screenshot came with it, your reply, and the viewer who left it with their stage. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: commentId

Body: none

Reply: object

Field Type
id string required · not empty
linkId string | null required
viewerName string | null required
appId string | null required
screen string | null required
body string required
resolvedAt string (date-time) | null required
createdAt string (date-time) required
position object | null required
area object | null required
device "phone" | "tablet" | "laptop" | "screen" | "none" | null required
hasScreenshot boolean required
reply string | null required
repliedAt string (date-time) | null required
from object | null required
projectId string required · not empty
appName string | null required
screenshot object | null required
screenshotNote "removed" | "no-answer" | "timeout" | "blocked" | "tainted" | "too-large" | "failed" | null required
viewer object | null required
thread object[] required

Room setup

The next steps a room offers, the workspace’s brand, and your own community profile.

getNextSteps

GET /api/projects/:projectId/next-steps

The next steps the project’s room offers at the end of a visit, as you set them, whether the plan lets you choose, and what the room offers now. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
steps object[] required · at most 6 items
removed object[] required
planAllows boolean required
offered "meeting" | "yes" | "later" | "colleague" | "question" | "no"[] required

setNextSteps

POST /api/projects/:projectId/next-steps

Sets which next steps the room offers, in what order and in what words. A step left out of steps is deleted: never offered, its words kept in removed; sending it again adds it back. On a plan that cannot choose, the setting is kept, and the room offers only a question.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
steps object[] required · at most 6 items

Reply: object

Field Type
steps object[] required · at most 6 items
removed object[] required
planAllows boolean required
offered "meeting" | "yes" | "later" | "colleague" | "question" | "no"[] required

getBrand

GET /api/brand

Your workspace’s brand: its name, accent colour and logo, and whether the plan shows it in the room. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
name string | null required
accent string | null required
logo object | null required
workspaceName string required
planAllows boolean required
problems object[] optional

setBrand

POST /api/brand

Sets the brand’s name and accent colour (#rrggbb); null clears either. On a plan without its own branding, rooms carry “Made with Roomi” instead. An accent a viewer could not read in the room is refused with 400, reason: contrast, and problems saying each pair that fails and a shade that would pass. The logo is uploaded with PUT /api/brand/logo: a PNG, JPEG or WebP image of up to 256 KB, from 16 × 16 to 2048 × 2048 pixels, and no more than 8 times as wide as it is tall.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
name string | null required
accent string | null required

Reply: object

Field Type
name string | null required
accent string | null required
logo object | null required
workspaceName string required
planAllows boolean required
problems object[] optional

POST /api/brand/logo/remove

Removes the brand’s logo.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
name string | null required
accent string | null required
logo object | null required
workspaceName string required
planAllows boolean required
problems object[] optional

getTheme

GET /api/brand/theme

Your workspace’s room theme: its tokens as saved (null for none), whether the plan shows it, the first plan that does, every colour and value the room draws for it with your accent, and anything in it a viewer could not read now. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
theme object | null required
planAllows boolean required
shownOn string | null required
effective { [key]: string } required
problems object[] required
fellBack string[] required

setTheme

POST /api/brand/theme

Saves the room theme whole: a token left out is the room’s own. Colours are #rrggbb; radius is square, soft or round; font is system, serif or rounded. A theme a viewer could not read is refused with 400, reason: contrast, and its problems. Kept whatever the plan, and shown on a plan that includes it.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
theme object required

Reply: object

Field Type
theme object | null required
planAllows boolean required
shownOn string | null required
effective { [key]: string } required
problems object[] required
fellBack string[] required

resetTheme

POST /api/brand/theme/reset

Removes the room theme: rooms are drawn in the room’s own look again.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
theme object | null required
planAllows boolean required
shownOn string | null required
effective { [key]: string } required
problems object[] required
fellBack string[] required

checkTheme

POST /api/brand/theme/check

Checks a room theme, with another accent if you give one, and says what the room would draw and what would be refused. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
theme object required
accent string | null optional

Reply: object

Field Type
problems object[] required
effective { [key]: string } required

getProfile

GET /api/me/profile

Your own community handle and bio. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
handle string | null required
bio string | null required

setProfile

POST /api/me/profile

Sets your community handle and bio. The handle follows a project address’s rules and is unique among people: refused as format, reserved, impersonation, banned or taken.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
handle string required · 3–40 characters
bio string | null required

Reply: object

Field Type
handle string | null required
bio string | null required

The community

A project’s community card, listing and unlisting it, the room’s access rule, and the one-time passes into a room.

getCard

GET /api/projects/:projectId/card

The project’s community card: its title, line and cover, where its listing stands (draft, listed, blocked with what a check found, pulled with Roomi's comment, or pending while it waits for review), whether a new cover is still held back, whether a review can be asked for, the public address it would have, and whether suggested wording is available. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
projectId string required · not empty
title string required
line string required
cover object | null required
status "draft" | "listed" | "blocked" | "pulled" | "pending" required
rejectReason string | null required
submittedAt string (date-time) | null required
reviewedAt string (date-time) | null required
warnings object[] required
blocked object[] required
coverPending boolean required
hidden boolean required
appealable boolean required
appeal object | null required
publicUrl string (uri) required
suggest boolean required
updatedAt string (date-time) | null required

saveCard

POST /api/projects/:projectId/card

Saves the card’s title and line. A listed card’s new words are checked first: it stays listed if they pass, and is blocked (off the community) if a check fails. At most 10 such edits a day; past that, 429. Any other card keeps its state. The cover is uploaded with PUT /api/projects/:projectId/card/cover: a PNG, JPEG or WebP image of up to 1 MB, once the card exists; a new cover is held back from the community until its own check passes.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
title string required · 1–60 characters
line string required · 1–140 characters

Reply: object

Field Type
projectId string required · not empty
title string required
line string required
cover object | null required
status "draft" | "listed" | "blocked" | "pulled" | "pending" required
rejectReason string | null required
submittedAt string (date-time) | null required
reviewedAt string (date-time) | null required
warnings object[] required
blocked object[] required
coverPending boolean required
hidden boolean required
appealable boolean required
appeal object | null required
publicUrl string (uri) required
suggest boolean required
updatedAt string (date-time) | null required

removeCover

POST /api/projects/:projectId/card/cover/remove

Removes the card’s cover.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
projectId string required · not empty
title string required
line string required
cover object | null required
status "draft" | "listed" | "blocked" | "pulled" | "pending" required
rejectReason string | null required
submittedAt string (date-time) | null required
reviewedAt string (date-time) | null required
warnings object[] required
blocked object[] required
coverPending boolean required
hidden boolean required
appealable boolean required
appeal object | null required
publicUrl string (uri) required
suggest boolean required
updatedAt string (date-time) | null required

checkBuildCover

POST /api/projects/:projectId/card/cover/build/check

Reads an image in the project’s live build, by its path from the build’s root, as the card’s cover would take it, and returns its type and size in bytes. The type is read from the file’s bytes, never its name: a PNG, JPEG or WebP image, at most 1 MB, or it is refused as file. A container build is never unpacked, so its files are refused too. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
path string required · 1–255 characters

Reply: object

Field Type
path string required
type "image/png" | "image/jpeg" | "image/webp" required
bytes integer required

coverFromBuild

POST /api/projects/:projectId/card/cover/build

Makes an image in the project’s live build the card’s cover, checked as checkBuildCover checks it, and copied on the platform: nothing is uploaded, and the cover is stored and served from the card’s row like an uploaded one. Needs a saved card. Held back from the community until its own check passes, as with an upload.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
path string required · 1–255 characters

Reply: object

Field Type
projectId string required · not empty
title string required
line string required
cover object | null required
status "draft" | "listed" | "blocked" | "pulled" | "pending" required
rejectReason string | null required
submittedAt string (date-time) | null required
reviewedAt string (date-time) | null required
warnings object[] required
blocked object[] required
coverPending boolean required
hidden boolean required
appealable boolean required
appeal object | null required
publicUrl string (uri) required
suggest boolean required
updatedAt string (date-time) | null required

listProject

POST /api/projects/:projectId/card/list

Lists the project on the community: runs the pre-flight scan, which warns and never blocks, and Roomi's checks. If they pass it is listed at once; if one fails it is blocked, with the part and the reason. After Roomi took it off before, it waits for review (pending). Refused as unfinished without a card or without a live build; at most 3 a day for a workspace, 429 past that.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
projectId string required · not empty
title string required
line string required
cover object | null required
status "draft" | "listed" | "blocked" | "pulled" | "pending" required
rejectReason string | null required
submittedAt string (date-time) | null required
reviewedAt string (date-time) | null required
warnings object[] required
blocked object[] required
coverPending boolean required
hidden boolean required
appealable boolean required
appeal object | null required
publicUrl string (uri) required
suggest boolean required
updatedAt string (date-time) | null required

rehearseListing

GET /api/projects/:projectId/card/listing

What asking to list would do now — listed, blocked with its findings, pending, already or refused — and why, with the pre-flight warnings. Runs the checks without the content classifier, and writes and counts nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
outcome "listed" | "blocked" | "pending" | "already" | "refused" required
findings object[] required
warnings object[] required
said string[] required

appealListing

POST /api/projects/:projectId/card/appeal

Asks Roomi to review a block or a decision to take the listing off, in your own words (10 to 1,000 characters). One open at a time, at most 3 a workspace in 30 days: 409 while one is open, 429 past the limit. If it is upheld, exactly this card and build are listed.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
message string required · 10–1000 characters

Reply: object

Field Type
projectId string required · not empty
title string required
line string required
cover object | null required
status "draft" | "listed" | "blocked" | "pulled" | "pending" required
rejectReason string | null required
submittedAt string (date-time) | null required
reviewedAt string (date-time) | null required
warnings object[] required
blocked object[] required
coverPending boolean required
hidden boolean required
appealable boolean required
appeal object | null required
publicUrl string (uri) required
suggest boolean required
updatedAt string (date-time) | null required

listNotices

GET /api/notices

Your own notices in this workspace, newest first, the last fifty: a listing taken off, a project taken down, a review decided. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object[]

Field Type
id string required · not empty
kind "listing.blocked" | "listing.pulled" | "listing.takedown" | "appeal.upheld" | "appeal.denied" required
title string required
body string required
href string | null required
createdAt string (date-time) required
readAt string (date-time) | null required

readNotices

POST /api/notices/read

Marks your own notices read: the ids given, or all of them. Says how many changed.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
ids string[] optional · at most 50 items

Reply: object

Field Type
read integer required

scanCard

POST /api/projects/:projectId/card/scan

Runs the pre-flight scan on the project with the title and line given, as asking to list would, and returns its warnings. Changes nothing: the card is not saved and nothing is listed. The Claude connector’s set_community_card and request_listing show it before you agree.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
title string required · 1–60 characters
line string required · 1–140 characters

Reply: object

Field Type
warnings object[] required

unlistProject

POST /api/projects/:projectId/card/unlist

Takes the project off the community at once; its public address stops answering. The card is kept as a draft.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
projectId string required · not empty
title string required
line string required
cover object | null required
status "draft" | "listed" | "blocked" | "pulled" | "pending" required
rejectReason string | null required
submittedAt string (date-time) | null required
reviewedAt string (date-time) | null required
warnings object[] required
blocked object[] required
coverPending boolean required
hidden boolean required
appealable boolean required
appeal object | null required
publicUrl string (uri) required
suggest boolean required
updatedAt string (date-time) | null required

suggestWording

POST /api/projects/:projectId/card/suggest

Drafts neutral wording for the card with Claude, from the card’s own words and nothing else. Not switched on yet: it answers 503 while no Anthropic key is configured.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
title string required · 1–60 characters
line string required · 1–140 characters

Reply: object

Field Type
title string required · 1–60 characters
line string required · 1–140 characters

previewPass

POST /api/projects/:projectId/preview

A one-time address that opens the room exactly as a viewer sees it, marked as a preview, recording nothing. For a member of the project’s workspace; it lives 120 seconds and is spent on arrival.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
url string (uri) required

getAccess

GET /api/projects/:projectId/access

Whether the project’s private links ask for an email before the room shows anything, and whether the plan has private links at all. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
requireEmail boolean required
privateLinks boolean required

setAccess

POST /api/projects/:projectId/access

Sets whether the project’s private links ask the viewer for an email before the room shows anything. Nothing is sent to the email, and it is not verified.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: object

Field Type
requireEmail boolean required

Reply: object

Field Type
requireEmail boolean required
privateLinks boolean required

communityPass

POST /api/community/pass

A one-time address back into a listed project’s room, signed in, to comment or react. Any signed-in person may ask, for any listed project; the room learns their public face and nothing else. A project that is not listed is 404.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
slug string required · 1–63 characters

Reply: object

Field Type
url string (uri) required

Arranging projects

Your workspace’s own order and groups of projects, as the app’s side bar and Projects page draw them, and where each project stands on the community. Cosmetic: nothing here changes who can see a project.

getArrangement

GET /api/arrangement

Your workspace’s groups in order, and every project once, in the order it is drawn: the ungrouped first, then each group’s. A project nobody has placed yet comes first, newest first. Each says where it stands on the community: unlisted, listed, blocked (a check kept it off), pulled (taken off) or pending (waiting for review). Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
groups object[] required
projects object[] required

arrangeProjects

POST /api/arrangement

Sets the order: the ungrouped projects, then each group with its projects, groups in the order given. What is left out keeps its place after what was given. Each id at most once (422 otherwise); a project or group not your workspace’s is 404, and nothing moves.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
ungrouped string[] required · at most 500 items
groups object[] required · at most 30 items

Reply: object

Field Type
groups object[] required
projects object[] required

createGroup

POST /api/groups

Adds an empty group, last. Its name is 1 to 40 characters; a workspace has at most 30 groups.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
name string required · 1–40 characters

Reply: object

Field Type
groups object[] required
projects object[] required

renameGroup

POST /api/groups/:groupId

Renames one group. Another workspace’s group is 404.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: groupId

Body: object

Field Type
name string required · 1–40 characters

Reply: object

Field Type
groups object[] required
projects object[] required

deleteGroup

POST /api/groups/:groupId/delete

Deletes one group and nothing else: its projects move to the end of the ungrouped. Another workspace’s group is 404.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: groupId

Body: none

Reply: object

Field Type
groups object[] required
projects object[] required

Public

Answers anyone, signed in or not, and shows only what is listed on the community.

profile

GET /api/community/people/:handle

A publisher’s public page, by handle: their name, avatar and bio, and their projects listed on the community. A handle nobody has is 404.

Open to anyone, signed in or not.

Path parameters: handle

Body: none

Reply: object

Field Type
handle string required
name string required
bio string | null required
image string (uri) | null required
projects object[] required

listings

GET /api/community/listings

Every project listed on the community, newest listing first: each card’s title and line, its public address, and whether it has a cover (at /api/community/projects/:slug/cover). Nothing about who built it: no name, handle, avatar or email. Cached for a minute; getroomi.com may read it from a browser.

Open to anyone, signed in or not.

Body: none

Reply: object[]

Field Type
slug string required
title string required
line string required
url string (uri) required
cover boolean required
listedAt string (date-time) required
credit string | null optional

Plan and usage

The workspace’s plan, and what it has used this month.

getBilling

GET /api/billing

Your workspace’s plan and its status, this month’s usage, the viewer-hours allowance and where it stands, and whether the workspace is suspended. Paid plans cannot be bought yet. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
plan "free" | "pro" | "team" | "organisation" | "solo" | "studio" | "agency" | "enterprise" required
kind "personal" | "team" optional
seats object optional
interval "month" | "year" | null required
status "active" | "past_due" | "canceled" required
currentPeriodEnd string (date-time) | null required
usage object required
allowance object required
history object optional
suspended boolean required
paidPlans "coming-soon" required

projectBounces

GET /api/projects/:projectId/bounces

How many viewers a full room turned away from the project this calendar month (UTC), counted once per session per hour. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: projectId

Body: none

Reply: object

Field Type
thisMonth integer required

You, and your workspace’s people

Your name and photo as the app draws you, how you sign in and where you are signed in, deleting your account, and your workspace’s people and container demos. Nothing here names anyone else to change.

whoami

GET /api/me/account

Who you are, as the app draws you: your name (or your email while you have set none), your picture — your uploaded photo, else your sign-in provider’s avatar, else none — and the workspace your session is in, with your role. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
userId string required · not empty
name string required
displayName string required
nameChosen boolean optional
suggestedName string optional
email string required
emailVerified boolean required
image string | null required
photo object | null required
providerImage boolean required
handle string | null required
workspace object required
createdAt string (date-time) required

setName

POST /api/me/name

Sets the name you are shown by, to you and to the people in your workspace: 1 to 80 characters.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
name string required · 1–80 characters

Reply: object

Field Type
userId string required · not empty
name string required
displayName string required
nameChosen boolean optional
suggestedName string optional
email string required
emailVerified boolean required
image string | null required
photo object | null required
providerImage boolean required
handle string | null required
workspace object required
createdAt string (date-time) required

removePhoto

POST /api/me/photo/remove

Removes your uploaded photo; you are drawn with your provider’s avatar, or your initials. A photo is uploaded with PUT /api/me/photo: a PNG, JPEG or WebP image of up to 2 MB and 4096 pixels a side, checked by its own bytes.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
userId string required · not empty
name string required
displayName string required
nameChosen boolean optional
suggestedName string optional
email string required
emailVerified boolean required
image string | null required
photo object | null required
providerImage boolean required
handle string | null required
workspace object required
createdAt string (date-time) required

deletable

GET /api/me/deletable

Whether your account can be deleted now, and what stands in the way: a workspace you own that still holds projects or other people. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
allowed boolean required
blockers object[] required

deleteAccount

POST /api/me/delete

Deletes your account: your sign-in methods, passkeys and sessions, your profile and photo, and every workspace you own. confirm must be your email. Refused with 422 while deletable names a workspace.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
confirm string required

Reply: object

Field Type
deleted true required

containerUsage

GET /api/usage/containers

Your workspace’s container demos this calendar month (UTC): runs, container-hours, starts that failed, viewers held back at the plan’s cap, and the median start. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
month string required
runs integer required
containerHours number required
failedStarts integer required
heldBack integer required
medianColdStartMs integer | null required

members

GET /api/workspace/people

The people in your workspace, each with the name and picture the app draws them with. Their photos are served at GET /api/people/:userId/photo to people in a workspace with them, and to nobody else. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object[]

Field Type
userId string required · not empty
displayName string required
image string | null required

signIns

GET /api/me/sign-in

How you can sign in: GitHub and Google, each set up on the platform or not and linked to you or not, how many passkeys you have, and whether emailed links are offered. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
providers object[] required
passkeys integer required
magicLinks boolean required

disconnect

POST /api/me/sign-in/:provider/disconnect

Unlinks GitHub or Google from your account. Refused with 422 while it is your only way in: no other provider, and no passkey.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: provider

Body: none

Reply: object

Field Type
providers object[] required
passkeys integer required
magicLinks boolean required

sessions

GET /api/me/sessions

Every live session of yours, newest first, with the one asking marked current. Each has an id to sign it out by, never the token a cookie carries. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object[]

Field Type
id string required · not empty
current boolean required
signInMethod string | null required
userAgent string | null required
device string required
createdAt string (date-time) required
expiresAt string (date-time) required

revokeSession

POST /api/me/sessions/:sessionId/revoke

Signs out one of your sessions. Another person’s session is a 404, and nothing changes.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Path parameters: sessionId

Body: none

Reply: object[]

Field Type
id string required · not empty
current boolean required
signInMethod string | null required
userAgent string | null required
device string required
createdAt string (date-time) required
expiresAt string (date-time) required

signOutEverywhere

POST /api/me/sessions/revoke-all

Signs out every session of yours, the one asking included.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
signedOut integer required

Your first days

The welcome letter, whether you have read it, and a note to the Roomi team from inside the app. Each answers only for the person signed in.

myPreferences

GET /api/me/preferences

Your own preferences: whether you have read the welcome letter and whether the feedback tab is folded. Someone with none yet gets the defaults. Changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
welcomeSeenAt string (date-time) | null required
feedbackTabFolded boolean required

welcomeSeen

POST /api/me/welcome/seen

Marks the welcome letter as read, the first time only; calling it again changes nothing.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: none

Reply: object

Field Type
welcomeSeenAt string (date-time) | null required
feedbackTabFolded boolean required

foldFeedbackTab

POST /api/me/feedback-tab

Folds the “Help us improve” button to a small tab, or opens it out again. Yours alone.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
folded boolean required

Reply: object

Field Type
welcomeSeenAt string (date-time) | null required
feedbackTabFolded boolean required

sendProductFeedback

POST /api/product-feedback

A note to the Roomi team, filed under you and the workspace you are signed in to, whatever the body says. Up to 2,000 characters, and a few an hour; past that, 429. Says back only that it arrived.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
body string required · 1–2000 characters
page string optional · at most 300 characters
appVersion string optional · at most 64 characters

Reply: object

Field Type
id string required · not empty
createdAt string (date-time) required

Reports

Reporting a project to Roomi.

reportProject

POST /api/reports

Reports a project, by its address, to Roomi, with a reason and any detail. A project that does not exist is 404; more than 20 reports from one person in 24 hours is 429.

Signed in: a session cookie (the app) or Authorization: Bearer <token> (the CLI and the connector).

Body: object

Field Type
slug string required · 1–63 characters
reason "phishing" | "impersonation" | "abuse" | "illegal" | "spam" | "other" required
detail string optional · at most 1000 characters

Reply: object

Field Type
id string required · not empty