Workspace API
Projects
Save and annotate the global projects you care about. Search returns only the ones you have saved to your workspace.
You save global Parcel projects to your workspace and annotate them with your own data. Workspace project search returns only the projects you have saved, not the full global dataset. To start tracking a new project, find it via the Data API and then upsert it by id.
The Workspace API requires the Pro plan. Non-Pro requests receive a 403 forbidden response.
Fields you set
These fields are written by your team via the upsert endpoint.
| Field | Type | Notes |
|---|---|---|
workspace_status | string | null | Your workflow state for this project (for example, "watching", "bid_submitted"). Any string value or null. |
notes | string | null | Free-text notes for your team. Explicit null clears the value. |
tags | string[] | Arbitrary labels. Full-replace on every write (sending ["boston"] replaces the entire tag set). |
Fields Parcel computes (read-only)
These fields are computed by Parcel from permit records and market signals. You cannot set them directly.
| Field | Type | Notes |
|---|---|---|
stage | string | null | Current lifecycle stage of the project (for example, "filed", "permitted", "under_construction", "completed"). |
last_signal_at | string | null | ISO 8601 timestamp of the most recent signal on this project. |
primary_use | string | null | Primary building use type (for example, "residential", "commercial", "mixed_use"). |
residential_units | integer | null | Number of residential units, when applicable. |
gross_floor_area_sf | number | null | Total gross floor area in square feet. |
cost_of_construction_usd | number | null | Estimated construction cost in US dollars. |
stories | integer | null | Storeys a filing or the project’s own site states. Null means no source states it, not a one-storey building |
parking_spaces | integer | null | Parking spaces stated. 0 is a stated zero; null means no source states it |
gc_status | string | named (a general contractor is linked), not_yet_selected (a filing states none is chosen yet), or unknown (no source says). Derived when read |
Detail fields (GET only)
A single-project GET also returns these. Search results leave them out to keep rows small.
| Field | Type | Notes |
|---|---|---|
height_ft | integer | null | Building height in whole feet. Null means no source states it |
hotel_rooms | integer | null | Hotel rooms stated. 0 is a stated zero; null means no source states it |
land_area_sf | integer | null | Site area in square feet. Null means no source states it |
unit_mix | object | null | { studio_or_1br, two_br, three_br_plus }. Null when no bedroom count is known; a null member is a count no source states |
affordable_units | object | null | Income-restricted units by area-median-income band: { ami_0_30, ami_30_50, ami_50_80, ami_80_120, ami_unknown }. Null when no band is known; a null member is a band no source states |
use_areas | object[] | { use_type, floor_area_sf } for each non-residential use a source states, largest first. Empty means none is stated, not that there is none |
structure_types | string[] | Construction types a source names: wood_frame, podium, steel, concrete, mass_timber, masonry, modular. Empty means none is stated |
regulatory_flags | string[] | Review paths the project went through: chapter_40b, special_permit, site_plan_review, variance, pud, design_review, wetlands_notice_of_intent, large_project_review, small_project_review |
far | object | null | Floor area ratio, { value, derived_from: "gross_floor_area_sf/land_area_sf" }. Derived when read; no source states it. Null unless both areas are known |
updates_last_30_days | integer | Signals filed in the last 30 days, counted by filing date rather than when Parcel found them |
Filters
Default sort: updated_at desc
| Filter key | Type | Notes |
|---|---|---|
workspace_status | field | Your workflow status string. Exact match. |
tags | field | Tag value. Array value = OR. |
name | field | Project name. Supports fuzzy match. |
stage | field | Project stage value. Array value = OR. |
primary_use | field | Building use type. Array value = OR. |
city | field | City name. exact_match: false (default) = fuzzy. |
state | field | State code, for example "MA". |
gc_status | field | named, not_yet_selected or unknown. Array value = OR |
stories_known | field | known or unknown: whether any source states a storey count. REST only; the MCP search tools leave it out |
Sort keys: updated_at, last_signal_at, name, created_at
Range keys: stories, residential_units, gross_floor_area_sf, cost_of_construction_usd, last_signal_at
A range never matches a project whose value is unknown (null). To find projects with no stated storey count, filter stories_known to unknown.
stage and primary_use are enum-backed: a value outside the allowed set returns 400 (not zero rows), and they do not accept exact_match since a closed vocabulary is always exact. See Errors.
Relational filters
Use relational filters to match projects based on properties of their related records.
| Key | Inner filter fields |
|---|---|
has_account | role, name, account_type |
without_account | role, name, account_type |
has_signal | signal_type, filed_at (range) |
without_signal | signal_type, filed_at (range) |
has_contact | role, title |
without_contact | role, title |
Each without_* key takes the same inner fields as its has_* twin, but matches your saved projects with NO related record satisfying the inner criteria (NOT EXISTS) instead of at least one. See Filtering for the semantics.
Includes
Valid include keys for GET /workspace/projects/{id}: accounts, contacts, signals.
Example: GET /workspace/projects/<id>?include=accounts&include=signals
Search example
The query below finds your saved projects currently under construction in Massachusetts, sorted by most recent signal.
curl -X POST https://api.parcelengineering.com/api/v1/workspace/projects/search \ -H "Authorization: Bearer pcl_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "filters": { "stage": { "value": "under_construction" }, "state": { "value": "MA" } }, "sort": { "field": "last_signal_at", "order": "desc" }, "limit": 25 }'const response = await fetch( 'https://api.parcelengineering.com/api/v1/workspace/projects/search', { method: 'POST', headers: { 'Authorization': 'Bearer pcl_your_api_key', 'Content-Type': 'application/json', }, body: JSON.stringify({ filters: { stage: { value: 'under_construction' }, state: { value: 'MA' }, }, sort: { field: 'last_signal_at', order: 'desc' }, limit: 25, }), });
const { data, metadata } = await response.json();{ "data": [ { "id": "7b2e4d1f-0001-4c8e-af6b-000000000001", "name": "100 Seaport Boulevard", "city": "Boston", "state": "MA", "stage": "under_construction", "primary_use": "mixed_use", "residential_units": 120, "gross_floor_area_sf": 180000, "cost_of_construction_usd": 42000000, "stories": 9, "parking_spaces": 40, "gc_status": "unknown", "last_signal_at": "2026-06-01T00:00:00Z", "workspace_status": "watching", "notes": "GC bid due Q3", "tags": ["seaport", "mixed-use"] } ], "metadata": { "total": 11, "total_is_capped": false, "credits": 11 }}The data array is trimmed to one record above. A page returning 11 projects costs 11 credits (1 per row returned).