Skip to main content
Staffify Docs
Updated

Contacts

A contact is a person in your CRM. All contact endpoints require the crm.contacts.read or crm.contacts.write scope.

Endpoints

GET/api/workspace/v1/contacts
GET/api/workspace/v1/contacts/{id}
POST/api/workspace/v1/contacts
PATCH/api/workspace/v1/contacts/{id}

The contact object

FieldTypeDescription
first_namestringGiven name
last_namestringFamily name
emailstringPrimary email (normalised to lowercase)
phonestringPrimary phone (E.164 recommended, we normalise)
job_titlestringFree-form job title
crm_company_idintegerLink to an existing CRM company
lifecycle_stageenumOne of lead | customer | opportunity | other (default: lead)
timezonestringIANA zone e.g. Europe/Amsterdam
preferred_languagestring(2)ISO 639-1 code, e.g. nl or en
countrystring(2)ISO 3166-1 alpha-2, e.g. NL
notesstringFree-form internal notes (max 5000 chars)
custom_fieldsobjectMerged with existing custom fields on update

On create, at least one of email, phone, or (first_name + last_name) is required.

List contacts

Supports cursor pagination (limit, starting_after) and filters: email, phone, search, updated_after, created_after, archived.

curl "https://app.staffifyai.com/api/workspace/v1/contacts?limit=50&updated_after=2026-09-01T00:00:00Z" \
  -H "Authorization: Bearer sfy_wsp_live_YOUR_KEY"

Retrieve a contact

curl "https://app.staffifyai.com/api/workspace/v1/contacts/1893" \
  -H "Authorization: Bearer sfy_wsp_live_YOUR_KEY"

Create a contact

Idempotent when you pass an Idempotency-Key. Returns 201 with the new contact.

curl "https://app.staffifyai.com/api/workspace/v1/contacts" \
  -X POST \
  -H "Authorization: Bearer sfy_wsp_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 1c1a-4a8f-9b2e" \
  -d '{
    "first_name": "Jan",
    "last_name": "de Vries",
    "email": "[email protected]",
    "lifecycle_stage": "lead"
  }'

Update a contact

Only fields present in the body are updated. Pass is_archived: true to soft-delete (there is no hard DELETE endpoint).

curl "https://app.staffifyai.com/api/workspace/v1/contacts/1893" \
  -X PATCH \
  -H "Authorization: Bearer sfy_wsp_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "job_title": "CTO",
    "custom_fields": { "source": "website" }
  }'

Delete a contact

Soft-delete places the contact in a 30-day recycle bin. It disappears from default list queries and returns 404 on retrieve. Use ?deleted=true on list to see recycled contacts, or ?deleted=any for both live + deleted. Requires the crm.contacts.delete scope (separate from write).

curl -X DELETE "https://app.staffifyai.com/api/workspace/v1/contacts/1893" \
  -H "Authorization: Bearer sfy_wsp_live_YOUR_KEY"

Restore a contact

Within the 30-day window you can restore a soft-deleted contact. After the window a nightly purge cron hard-deletes the row for good — restore returns 404 then.

curl -X POST "https://app.staffifyai.com/api/workspace/v1/contacts/1893/restore" \
  -H "Authorization: Bearer sfy_wsp_live_YOUR_KEY"

Bulk-delete contacts

Soft-delete up to 500 contacts in one call. Response reports how many rows matched (a missing id is silently skipped, not an error). Idempotent — re-sending the same ids keeps the same deleted_at.

curl -X POST "https://app.staffifyai.com/api/workspace/v1/contacts/bulk-delete" \
  -H "Authorization: Bearer sfy_wsp_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ids": [1893, 1894, 1895] }'

Batch operations

POST/api/workspace/v1/contacts/batch

Execute up to 100 operations in one call (create / update / upsert). Response is 207-style — each item has its own status. One failure doesn't block the rest. Perfect for CSV migrations and nightly syncs. Pattern mirrors HubSpot's batch API.

Idempotency: pair with Idempotency-Key at the batch level — retrying the same batch within 24h returns the cached response.

curl -X POST "https://app.staffifyai.com/api/workspace/v1/contacts/batch" \
  -H "Authorization: Bearer sfy_wsp_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: migration-batch-1" \
  -d '{
    "operations": [
      { "method": "create", "data": { "email": "[email protected]", "first_name": "A" } },
      { "method": "update", "id": 1234, "data": { "phone": "+31611111111" } },
      { "method": "upsert", "match_on": "email", "match_value": "[email protected]", "data": { "first_name": "B" } }
    ]
  }'

Available on all 5 resources: /contacts/batch, /companies/batch, /deals/batch, /invoices/batch, /workorders/batch.

Upsert a contact

POST/api/workspace/v1/contacts/upsert

Match an existing contact on email, phone, or external_id. If found: update with overwrite semantics. If not found: create. Returns 201 on create and 200 on match. The response includes a _upserted object with matched (boolean) and matched_on fields.

Use this for form-submit flows (Zapier, Make, custom website forms) to avoid duplicate contacts when the same person submits twice. Pair with Idempotency-Key for exactly-once semantics under retries.

Difference from POST /contacts: plain create never dedups — it always inserts a new row. Difference from PATCH /contacts/:id: requires an id, no create fallback.

curl -X POST "https://app.staffifyai.com/api/workspace/v1/contacts/upsert" \
  -H "Authorization: Bearer sfy_wsp_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: form-submit-$(date +%s)" \
  -d '{
    "match_on": "email",
    "match_value": "[email protected]",
    "data": {
      "first_name": "Jan",
      "last_name": "de Vries",
      "phone": "+31612345678",
      "external_id": "cust_abc123"
    }
  }'
Contacts - Workspace API - Staffify