REST API
Patchrooms has a small REST API for reading and updating the data behind a single project — projects, feedback reports, channels, and API keys. It is intentionally minimal: most teams pull feedback through the MCP endpoint or the dashboard, and use this API for custom integrations and for agent self-setup.
Base URL and auth
Section titled “Base URL and auth”https://room.patchrooms.com/api/v1Every route lives under a project and is authenticated with a secret API key
(prefix pr_sk_) created in the dashboard, sent as a Bearer token bound to that
project:
Authorization: Bearer pr_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxEach endpoint requires a specific scope (listed below). Scopes are
feedback:read|write, channel:read|write, project:read|write, and
apikey:write. A short-lived setup token carries * (satisfies any scope)
and may set a TTL — see Agent self-setup. A missing,
revoked, or expired key returns 401; a key without the required scope returns
403.
In the paths below, :projectId is the project’s internal id (a MongoDB
ObjectId), not the public pr_ project key.
Resolve the calling key
Section titled “Resolve the calling key”GET /api/v1/meResolves the project from the key itself — no :projectId needed. Lets an agent
bootstrap from the key alone (the same project the MCP endpoint resolves). Works
with any scope.
{ "keyId": "665a...", "projectId": "665e...", "name": "Acme App", "projectKey": "pr_xxx", "scopes": ["feedback:read", "feedback:write"]}Projects
Section titled “Projects”Get a project
Section titled “Get a project”GET /api/v1/projects/:projectIdScope: project:read. Returns the project’s name, public key, URL patterns,
default channel, and whether an identity secret is set.
{ "id": "665e...", "name": "Acme App", "projectKey": "pr_xxx", "urlPatterns": ["https://app.acme.com/*"], "defaultChannelKey": "bug", "identitySecretSet": false, "createdAt": "2026-05-01T12:00:00.000Z"}Update project config
Section titled “Update project config”PATCH /api/v1/projects/:projectIdScope: project:write. Updates any of name, urlPatterns,
defaultChannelKey, and widgetConfig. widgetConfig, when present, is a full
replacement (mascot, shape, anchor, offsetX, offsetY, size, triggerVariant).
Returns the updated project.
Feedback
Section titled “Feedback”List reports
Section titled “List reports”GET /api/v1/projects/:projectId/feedbackScope: feedback:read. Returns reports newest first with cursor pagination.
| Query param | Type | Description |
|---|---|---|
limit | number | Page size. Defaults to 20, capped at 100. |
channelKey | string | Filter by channel key. |
status | string | Filter by report status. |
before | string | Cursor — a report id; returns reports older than it. |
{ "items": [ { "id": "665f...", "channelKey": "bug", "status": "open", "blocks": [ /* Block[] */ ], "context": { /* ... */ }, "identity": null, "createdAt": "2026-06-03T09:14:22.000Z" } ], "nextCursor": "665e..."}Pass nextCursor back as the before query param to fetch the next page.
nextCursor is null on the last page.
Get a report
Section titled “Get a report”GET /api/v1/projects/:projectId/feedback/:reportIdScope: feedback:read. Returns one report with its full blocks, context,
and identity. Returns 404 if the report is not found in this project.
Update report status
Section titled “Update report status”PATCH /api/v1/projects/:projectId/feedback/:reportIdScope: feedback:write. Body: { "status": "<status>" }. An invalid status
returns 400.
{ "id": "665f...", "status": "resolved" }A room groups reports under one artifact (artifactId). Rooms exist implicitly
the moment a report carries an artifactId — these endpoints let you
formalize one (give it a title/goal, or archive it) with or without an
existing report. POST is an idempotent upsert keyed on artifactId, so an
agent can call it every time it starts a task without checking first.
List rooms
Section titled “List rooms”GET /api/v1/projects/:projectId/roomsScope: feedback:read. Returns Room docs unioned with the implicit rooms
derived from report artifactIds, sorted by last activity. A room with no
reports yet still appears (count: 0). Pass ?archived=true to include
archived rooms.
{ "items": [ { "artifactId": "checkout-redesign", "title": "Checkout redesign", "meta": { "tool": "lovable", "goal": "Ship the new checkout flow", "url": null, "source": null, "constraints": null }, "archived": false, "lastActivity": "2026-07-25T09:14:22.000Z", "count": 3 } ]}Create or resume a room
Section titled “Create or resume a room”POST /api/v1/projects/:projectId/roomsScope: feedback:write. Idempotent upsert by artifactId — safe to call
every time you begin work on an artifact, before any report exists. Returns
201 on the call that creates the room, 200 on a repeat call; either way
the body includes created so you can tell them apart.
| Body field | Type | Description |
|---|---|---|
artifactId | string | Required. Stable id for the artifact/task. |
title | string | Room title. Defaults to artifactId. |
meta | object | { tool?, source?, goal?, constraints?, url? }. |
{ "id": "665f...", "artifactId": "checkout-redesign", "title": "Checkout redesign", "meta": null, "archived": false, "created": true }Update a room
Section titled “Update a room”PATCH /api/v1/projects/:projectId/rooms/:artifactIdScope: feedback:write. Body: any of { title?, meta?, archived? }. Returns
404 if no room with that artifactId exists yet — this endpoint updates,
it doesn’t upsert (use POST for that).
Channels
Section titled “Channels”List channels
Section titled “List channels”GET /api/v1/projects/:projectId/channelsScope: channel:read. Returns the project’s channels. Pass ?archived=true to
include archived channels.
{ "items": [ { "id": "665d...", "key": "bug", "name": "Bug", "color": "#c0392b", "mascot": "fox", "baseWeight": 1, "archived": false } ]}Create a channel
Section titled “Create a channel”POST /api/v1/projects/:projectId/channelsScope: channel:write. Creates a channel from the request body. A duplicate
key in the same project returns 409; invalid input returns 400.
Update a channel
Section titled “Update a channel”PATCH /api/v1/projects/:projectId/channels/:channelKeyScope: channel:write. Updates the matching channel. Returns 404 if no
channel with that key exists in the project.
Add a channel hint
Section titled “Add a channel hint”POST /api/v1/projects/:projectId/hintsScope: channel:write. Records a short-lived hint that biases channel matching
toward a channel (it expires after 5 minutes).
| Body field | Type | Description |
|---|---|---|
channelKey | string | Channel to bias toward. Required. |
weight | 'suggest' | 'max' | number | Strength of the hint. Required. |
userId | string | Optional user this hint applies to. |
{ "ok": true }API keys
Section titled “API keys”Mint and revoke keys from a key that carries apikey:write — typically a
short-lived setup token during agent self-setup.
Mint a key
Section titled “Mint a key”POST /api/v1/projects/:projectId/keysScope: apikey:write. Body: { "name", "scopes": [...], "expiresInSeconds"? }.
The plaintext key is returned once and never again. Scopes must be a subset
of the project scopes — * and apikey:write cannot be minted via the API (no
self-replication or privilege escalation), which returns 400.
{ "id": "665a...", "key": "pr_sk_...", "name": "feedback agent", "scopes": ["feedback:read", "feedback:write"], "expiresAt": null}Revoke a key
Section titled “Revoke a key”DELETE /api/v1/projects/:projectId/keys/:keyIdScope: apikey:write. Revokes the key by id (a setup token can revoke itself at
the end of setup). Returns 404 if no active key with that id exists in the
project.
{ "id": "665a...", "revokedAt": "2026-06-29T13:49:31.334Z" }Errors
Section titled “Errors”Errors are returned as JSON with an error message and a code:
{ "error": "Not found", "code": "NOT_FOUND" }| HTTP | code | Meaning |
|---|---|---|
400 | BAD_REQUEST | Invalid input. |
401 | UNAUTHORIZED | Missing, invalid, or revoked API key. |
403 | FORBIDDEN | Key lacks the required scope. |
404 | NOT_FOUND | Resource not found in this project. |
409 | CONFLICT | Channel key already exists. |