Workspace API
Upsert Contact
Add or update a workspace contact with POST /workspace/contacts/upsert.
POST https://api.parcelengineering.com/api/v1/workspace/contacts/upsertAdds a contact to your workspace or updates their custom fields if they are already saved. Accepts either an explicit contact UUID or an email-based match to identify the target contact.
Upsert costs 0 credits.
The Workspace API requires the Pro plan. A request from a non-Pro workspace or a non-member receives 403 forbidden.
Request body
Provide exactly one of contact_id or match to identify the contact. All annotation fields are optional.
| Field | Type | Required | Description |
|---|---|---|---|
contact_id | string (UUID) | One of contact_id or match | The UUID from a Data API contact record. |
match | object | One of contact_id or match | Email-based lookup. Provide { "email": "person@example.com" }. |
workspace_status | string | null | No | Your workflow state (any string). null clears the value. |
notes | string | null | No | Free-text notes. null clears the value. |
tags | string[] | No | Full-replace tag set. Sending [] clears all tags. |
Match resolution rules
When using match: { email }, Parcel looks up the email address against the canonical contact registry:
| Email matches | Result |
|---|---|
| Exactly 1 contact | Upsert proceeds |
| 0 contacts | 404 not_found |
| More than 1 contact | 409 ambiguous_match |
A 409 ambiguous_match means the email is associated with more than one canonical contact. Resolve the ambiguity by fetching the candidate contacts from the Data API and providing an explicit contact_id instead.
Partial update semantics
- Omitted fields are left unchanged. You can update
workspace_statuswithout touchingnotesortags. - Explicit
nullclears a field. Sending"notes": nullremoves the existing note. tagsis full-replace. Sending"tags": ["vip"]replaces the entire tag set, not merges it with the existing one.
Example
curl -X POST https://api.parcelengineering.com/api/v1/workspace/contacts/upsert \ -H "Authorization: Bearer pcl_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "match": { "email": "jrivera@acmedev.com" }, "workspace_status": "prospect", "notes": "Met at Boston Planning Forum 2026", "tags": ["vip", "developer"] }'const response = await fetch( 'https://api.parcelengineering.com/api/v1/workspace/contacts/upsert', { method: 'POST', headers: { 'Authorization': 'Bearer pcl_your_api_key', 'Content-Type': 'application/json', }, body: JSON.stringify({ match: { email: 'jrivera@acmedev.com' }, workspace_status: 'prospect', notes: 'Met at Boston Planning Forum 2026', tags: ['vip', 'developer'], }), });
const { data, metadata } = await response.json();Response
{ "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": { "credits": 0 }}Upsert costs 0 credits.