Workspace API
Signals
Manage workspace signal subscriptions, the Home queue, and triage state.
Signal endpoints cover two areas: the raw signal feed (/signals, /signals/{id})
and the workspace-specific queue, subscriptions, and triage system (/signals/queue,
/signals/queue/stats, POST /signals/triage, and the /signal-subscriptions family).
All endpoints require a Bearer token and scope every query to a workspace via
workspace_id.
GET /signals
List signals across all projects. Not city-limited by default; add ?city= to scope.
Query parameters
| Parameter | Type | Description |
|---|---|---|
workspace_id | uuid (required) | Workspace scope |
project_id | uuid | Filter to one project |
account_id | uuid | Filter to projects associated with this account |
signal_type | string | One of the signal type values (see below) |
filed_after | ISO 8601 datetime | Lower bound on filed_at (inclusive) |
filed_before | ISO 8601 datetime | Upper bound on filed_at (exclusive) |
discovered_after | ISO 8601 datetime | Lower bound on discovered_at (inclusive) |
q | string | Free-text fuzzy search on filing ID, source URL, project name |
city | string | Optional city filter |
limit | integer | Max results, default 50, max 200 |
offset | integer | Pagination offset, default 0 |
Response 200
{ "signals": [ { "id": "e3b0c442-98fc-1c14-9afb-f0e1e5c7b7d3", "project_id": "7f3c1a42-2b8d-4e9f-a1c5-0d6b2e3f4a5b", "project_name": "125 Summer Street", "project_primary_address": "125 Summer St, Boston, MA 02110", "signal_type": "permit_issued", "filed_at": "2024-03-15T00:00:00.000Z", "discovered_at": "2024-03-16T08:22:11.000Z", "source_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "source_slug": "boston-isd", "source_display_name": "Boston Inspectional Services", "source_base_url": "https://www.boston.gov/departments/inspectional-services", "source_logo_domain": "boston.gov", "source_filing_id": "ALT-2024-001234", "source_url": "https://aca.boston.gov/", "reported_cost_usd": 1500000, "reported_gross_floor_area_sf": 8400 } ], "pagination": { "limit": 50, "offset": 0, "total": null }}GET /signals/{id}
Get a single signal including raw_data (the original source payload).
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | uuid (required) | Signal ID |
Query parameters
| Parameter | Type | Description |
|---|---|---|
workspace_id | uuid (required) | Workspace scope |
Response 200
{ "signal": { "id": "e3b0c442-98fc-1c14-9afb-f0e1e5c7b7d3", "project_id": "7f3c1a42-2b8d-4e9f-a1c5-0d6b2e3f4a5b", "project_name": "125 Summer Street", "project_primary_address": "125 Summer St, Boston, MA 02110", "signal_type": "permit_issued", "filed_at": "2024-03-15T00:00:00.000Z", "discovered_at": "2024-03-16T08:22:11.000Z", "source_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "source_slug": "boston-isd", "source_display_name": "Boston Inspectional Services", "source_base_url": "https://www.boston.gov/departments/inspectional-services", "source_logo_domain": "boston.gov", "source_filing_id": "ALT-2024-001234", "source_url": "https://aca.boston.gov/", "reported_cost_usd": 1500000, "reported_gross_floor_area_sf": 8400, "raw_data": {} }}Signal type values
project_announcement · pre_application · site_acquisition · demolition_permit ·
site_prep · entitlement_application · environmental_review · public_review ·
entitlement_approval · entitlement_denial · withdrawal · expiration ·
permit_filed · permit_issued · construction_loan · team_update · completion
GET /signals/queue
The Home triage queue. Returns signals of subscribed types, discovered after each
subscription’s queue watermark and filed within the 90-day history window. Ranked
new-first, then by subscription priority (high → medium → low), then by
filed_at descending. Each row carries its triage state and derived priority.
pagination.total is the filtered count.
Query parameters
| Parameter | Type | Description |
|---|---|---|
workspace_id | uuid (required) | Workspace scope |
status | string (repeatable) | new or done; omit for both |
signal_type | string (repeatable) | Filter to one or more signal types |
priority | string (repeatable) | high, medium, or low |
city | string | Optional city filter |
q | string | Free-text fuzzy search |
limit | integer | Max results, default 50, max 200 |
offset | integer | Pagination offset, default 0 |
Response 200 — QueueSignal objects extend SignalSummary with triage fields:
{ "signals": [ { "id": "e3b0c442-98fc-1c14-9afb-f0e1e5c7b7d3", "project_id": "7f3c1a42-2b8d-4e9f-a1c5-0d6b2e3f4a5b", "project_name": "125 Summer Street", "project_primary_address": "125 Summer St, Boston, MA 02110", "signal_type": "permit_issued", "filed_at": "2024-03-15T00:00:00.000Z", "discovered_at": "2024-03-16T08:22:11.000Z", "source_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "source_slug": "boston-isd", "source_display_name": "Boston Inspectional Services", "source_base_url": "https://www.boston.gov/departments/inspectional-services", "source_logo_domain": "boston.gov", "source_filing_id": "ALT-2024-001234", "source_url": "https://aca.boston.gov/", "reported_cost_usd": 1500000, "reported_gross_floor_area_sf": 8400, "triage_status": "new", "priority": "high", "triaged_by": null, "triaged_at": null } ], "pagination": { "limit": 50, "offset": 0, "total": 12 }}GET /signals/queue/stats
Lightweight counts over the same queue rule as GET /signals/queue. Powers the
Home header, tab/chip counts, the Settings Signals count column, and the sidebar
badge. Poll this endpoint — it is cheap.
Query parameters
| Parameter | Type | Description |
|---|---|---|
workspace_id | uuid (required) | Workspace scope |
Response 200
{ "new_count": 12, "high_priority_new_count": 4, "done_count": 31, "type_counts": [ { "signal_type": "permit_issued", "new_count": 7 }, { "signal_type": "construction_loan", "new_count": 5 } ]}POST /signals/triage
Bulk-mark signals done or new in the workspace-shared queue. Pass up to 100 signal
IDs per call. status "done" records who acted and removes signals from the New
tab for the whole workspace; status "new" restores them. Idempotent.
Request body
{ "workspace_id": "uuid", "signal_ids": ["uuid", "uuid"], "status": "done"}| Field | Type | Description |
|---|---|---|
workspace_id | uuid (required) | Workspace scope |
signal_ids | uuid[] (required) | 1–100 signal IDs |
status | string (required) | done or new |
Response 200
{ "updated": 2 }GET /signal-subscriptions
List all signal type subscriptions for the workspace. Each subscription includes its priority, alert channels, recipients, and a live count of untriaged new signals currently in the Home queue for that type.
Query parameters
| Parameter | Type | Description |
|---|---|---|
workspace_id | uuid (required) | Workspace scope |
Response 200
{ "subscriptions": [ { "signal_type": "permit_issued", "priority": "high", "channels": ["email"], "notify_all_members": false, "recipient_user_ids": ["uuid"], "new_count": 7, "created_at": "2024-03-01T12:00:00.000Z", "updated_at": "2024-03-01T12:00:00.000Z" } ]}SubscriptionResource fields
| Field | Type | Description |
|---|---|---|
signal_type | string | One of the signal type values |
priority | string | high, medium, or low |
channels | string[] | Alert delivery channels; currently email only (teams, slack coming soon) |
notify_all_members | boolean | When true, emails all workspace members (including future members); takes precedence over recipient_user_ids |
recipient_user_ids | uuid[] | Explicit email recipients (must be workspace members; re-validated at send time) |
new_count | integer | Live count of untriaged signals of this type in the queue |
created_at | ISO 8601 | Subscription creation time |
updated_at | ISO 8601 | Last modification time |
PUT /signal-subscriptions/{signal_type}
Subscribe to a signal type or update an existing subscription. Idempotent upsert
keyed by signal_type. Creating a subscription starts queueing signals discovered
from now (with a 14-day lookback so the queue is not empty on first use). Updates
apply only the provided fields and never reset the queue watermark.
Path parameters
| Parameter | Type | Description |
|---|---|---|
signal_type | string (required) | One of the signal type values |
Request body
{ "workspace_id": "uuid", "priority": "high", "channels": ["email"], "notify_all_members": false, "recipient_user_ids": ["uuid"]}| Field | Type | Description |
|---|---|---|
workspace_id | uuid (required) | Workspace scope |
priority | string | high, medium, or low (default medium on create) |
channels | string[] | Alert delivery channels; email is the only deliverable channel today |
notify_all_members | boolean | Email all workspace members |
recipient_user_ids | uuid[] | Up to 100 explicit recipient user IDs; must be workspace members |
Response 200 — returns the full subscription list after the upsert (same shape
as GET /signal-subscriptions).
DELETE /signal-subscriptions/{signal_type}
Unsubscribe from a signal type. Signals of this type leave the Home queue immediately and no further alerts are sent. Re-subscribing later starts a fresh queue watermark.
Path parameters
| Parameter | Type | Description |
|---|---|---|
signal_type | string (required) | One of the signal type values |
Query parameters
| Parameter | Type | Description |
|---|---|---|
workspace_id | uuid (required) | Workspace scope |
Response 200 — returns remaining subscriptions (same shape as
GET /signal-subscriptions).
Response 404 — { "error": { "code": "not_found", "message": "Not subscribed" } }
POST /signal-subscriptions/bulk
Transactional upsert of several subscriptions at once. Used by workspace onboarding.
Created subscriptions get default settings (no email channel, medium priority) and
a fresh 14-day lookback queue watermark. Existing subscriptions update only their
priority; channels and recipients are never touched by this endpoint (use the
single PUT for those). Types not listed are left untouched.
Request body
{ "workspace_id": "uuid", "subscriptions": [ { "signal_type": "permit_issued", "priority": "high" }, { "signal_type": "construction_loan", "priority": "medium" } ]}| Field | Type | Description |
|---|---|---|
workspace_id | uuid (required) | Workspace scope |
subscriptions | array (required) | 1–14 entries (one per unique signal type); each has signal_type (required) and priority (default medium) |
Response 200 — returns the full subscription list after the upsert.