Workspace API
Contacts
Save and annotate the global contacts you care about. Search returns only the ones you have saved to your workspace.
You save global Parcel contacts to your workspace and annotate them with your own data. Workspace contact search returns only the contacts you have saved, not the full global dataset. To start tracking a new contact, find them via the Data API and then upsert by id or email.
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 contact (for example, "prospect", "customer"). 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 ["vip"] replaces the entire tag set). |
Fields Parcel computes (read-only)
These fields are extracted from permit filings and enrichment data. You cannot set them directly.
| Field | Type | Notes |
|---|---|---|
name | string | null | Contact’s full name as extracted from source records. |
title | string | null | Job title or role (for example, "Project Manager", "Principal Architect"). |
email | string | null | Email address extracted from filings. |
account_id | string | null | UUID of the canonical account this contact is associated with. |
Filters
Default sort: updated_at desc
Top-level query performs fuzzy matching across contact name, title, email, phone, and associated account name.
| Filter key | Type | Notes |
|---|---|---|
workspace_status | field | Your workflow status string. Exact match. |
tags | field | Tag value. Array value = OR. |
name | field | Contact name. Supports fuzzy match. |
title | field | Job title. Supports fuzzy match. |
email | field | Email address. Exact match. |
account_id | field | UUID of the associated account. Returns contacts linked to that account. |
Sort keys: updated_at, name, created_at
Relational filters
Use relational filters to match contacts based on properties of their related records.
| Key | Inner filter fields |
|---|---|
at_account | account_type, name |
on_project | stage, primary_use, city, name |
account_type, 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.
Includes
Valid include keys for GET /workspace/contacts/{id}: account, projects.
Example: GET /workspace/contacts/<id>?include=account&include=projects
Search example
The query below finds your saved contacts with the title “Project Manager” at accounts linked to active construction projects.
curl -X POST https://api.parcelengineering.com/api/v1/workspace/contacts/search \ -H "Authorization: Bearer pcl_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "filters": { "title": { "value": "Project Manager" }, "on_project": { "stage": { "value": "under_construction" } } }, "sort": { "field": "name", "order": "asc" }, "limit": 25 }'const response = await fetch( 'https://api.parcelengineering.com/api/v1/workspace/contacts/search', { method: 'POST', headers: { 'Authorization': 'Bearer pcl_your_api_key', 'Content-Type': 'application/json', }, body: JSON.stringify({ filters: { title: { value: 'Project Manager' }, on_project: { stage: { value: 'under_construction' }, }, }, sort: { field: 'name', order: 'asc' }, limit: 25, }), });
const { data, metadata } = await response.json();{ "data": [ { "id": "3c7a9f2d-0001-4e1b-bc4a-000000000001", "name": "Jordan Rivera", "title": "Project Manager", "email": "jrivera@acmedev.com", "account_id": "8a1f3c2e-0001-4b7d-9e5a-000000000001", "workspace_status": "prospect", "notes": "Met at Boston Planning Forum 2026", "tags": ["vip", "developer"] } ], "metadata": { "total": 7, "total_is_capped": false, "credits": 7 }}The data array is trimmed to one record above. A page returning 7 contacts costs 7 credits (1 per row returned).