Skip to content

Contacts API

A Contact is the identified person behind one or more sessions. Personal data lives on the contact; a session carries only a contact_id, set once and never changed.

MethodPathScope
POST/v2/property/contacts/identifycontacts:write
GET/v2/property/contactscontacts:read
GET/v2/property/contacts/{contact}contacts:read
GET/v2/property/contacts/{contact}/sessionscontacts:read
POST/v2/property/contacts/{contact}contacts:write
DELETE/v2/property/contacts/{contact}contacts:write

Every {contact} lookup is scoped to the token’s property. An id from another property returns 404 with contact not found in this property.

Every endpoint that returns a contact uses this shape:

FieldTypeNotes
idstring
external_idstring, nullYour own user id
emailstring, null
first_name, last_name, full_namestring, nullfull_name is derived
phone, company, job_title, website, country, citystring, null
lifecycle_stagestringFree text. SnipForm’s own pickers offer lead, subscriber, customer, user. Defaults to user
stateactive, unsubscribed, deleteddeleted means redacted and is only reachable through DELETE
state_label, state_colorstringDisplay helpers
metaarray of {key, value}Your custom fields
first_seen_ts, identified_ts, last_seen_tsintegerUnix seconds
session_countinteger

POST /v2/property/contacts/identify

Finds or creates the contact, merges the traits you send, and links it to a session if one is supplied and not already linked. This is the server-side twin of signals.identify() in the tracker.

FieldRules
session_idstring, optional. Also read from the X-Snipform-Session-Id header or the snip_session_id field, in that order
external_idstring, max 255
emailvalid email, max 255
traits.first_name, traits.last_namestring, max 120
traits.phonestring, max 64
traits.company, traits.job_title, traits.websitestring, max 255
traits.country, traits.citystring, max 120
traits.lifecycle_stagestring, max 64
traits.metaarray of {key, value}; key required, string, max 120

At least one of external_id or email is required. Otherwise the response is 422 with identify requires at least one of external_id or email.

Matching: if external_id is present the contact is looked up by it; otherwise by email (the most recently seen contact with that address). An existing contact has its traits merged; a missing one is created with state: active.

A session is optional. Without one the contact is still created or updated, session_id comes back null and linked_to_session is false. With one, the link is set once: a session that already belongs to a contact is not re-linked, and linked_to_session tells you whether this call did the linking.

Terminal window
curl -X POST https://api.snipform.io/v2/property/contacts/identify \
-H "Authorization: Bearer $SNIPFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"session_id": "9f1c2c1e-7b1a-4f0e-9f2b-1d2c3b4a5e6f",
"external_id": "user_8812",
"email": "[email protected]",
"traits": {
"first_name": "Jane",
"company": "Acme",
"lifecycle_stage": "customer",
"meta": [{ "key": "plan", "value": "pro" }]
}
}'

GET /v2/property/contacts

QueryRules
searchstring, max 255
stateactive, unsubscribed, deleted
lifecycle_stagestring, max 64
pageinteger, min 1
per_pageinteger, 1 to 100, default 25
Terminal window
curl "https://api.snipform.io/v2/property/contacts?lifecycle_stage=customer&per_page=50" \
-H "Authorization: Bearer $SNIPFORM_TOKEN"

The response data is a Laravel paginator spread at the root: data (array of contacts), current_page, last_page, per_page, total, from, to, first_page_url, last_page_url, next_page_url, prev_page_url, path, links.

{
"code": 200,
"status": "OK",
"data": {
"current_page": 1,
"data": [{ "id": "66c4d2f1a9b3c8e4f0a1b2c3", "email": "[email protected]", "...": "..." }],
"per_page": 50,
"total": 1,
"last_page": 1,
"next_page_url": null,
"prev_page_url": null
}
}

GET /v2/property/contacts/{contact} returns { "contact": {...} }.

GET /v2/property/contacts/{contact}/sessions

Cursor paginated, newest first, by entry_ts.

QueryRules
per_pageinteger, 1 to 100, default 25
cursorinteger. Pass the previous page’s next_cursor; returns sessions with entry_ts strictly before it
{
"code": 200,
"status": "OK",
"data": {
"sessions": [
{
"id": "9f1c2c1e-7b1a-4f0e-9f2b-1d2c3b4a5e6f",
"sid": "3a0c...",
"entry_ts": 1755780000,
"last_ts": 1755780420,
"entry_url": "https://acme.com/pricing",
"exit_url": "https://acme.com/signup",
"bounced": false,
"views": 3,
"channel": { "category": "paid_search", "name": "Google Ads", "source": "google.com" },
"geo": { "country": "South Africa", "city": "Cape Town" },
"device": "desktop"
}
],
"paging": { "per_page": 25, "has_more": false, "next_cursor": null }
}
}

POST /v2/property/contacts/{contact}

Every field is optional; send only what changes. Sending null clears a nullable field.

FieldRules
external_idstring, max 255
emailvalid email, max 255
first_name, last_namestring, max 120
phonestring, max 64
company, job_title, websitestring, max 255
country, citystring, max 120
lifecycle_stagestring, max 64
stateactive or unsubscribed only. deleted cannot be assigned
metaarray of {key, value}; key required, string, max 120
Terminal window
curl -X POST https://api.snipform.io/v2/property/contacts/66c4d2f1a9b3c8e4f0a1b2c3 \
-H "Authorization: Bearer $SNIPFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "state": "unsubscribed", "lifecycle_stage": "churned" }'

Returns { "contact": {...} }.

Statuserror_codeWhen
404not_foundNot in this property
410errorcontact was deleted - a redacted contact cannot be edited
422validation_failedRule failure, including state: deleted

DELETE /v2/property/contacts/{contact}

Deletion is redaction in place so that sessions and events linked to the contact keep their shape. The row survives with:

  • external_id, names, phone, company, job title, website, country, city set to null
  • meta emptied
  • email replaced with deleted_{random}@redacted.invalid
  • state set to deleted
  • identify events on its sessions scrubbed of the email and external id
Terminal window
curl -X DELETE https://api.snipform.io/v2/property/contacts/66c4d2f1a9b3c8e4f0a1b2c3 \
-H "Authorization: Bearer $SNIPFORM_TOKEN"
{ "code": 200, "status": "OK", "data": { "deleted": true } }

Deleting an already deleted contact returns the same 200. This is the endpoint to call for an erasure request; see Privacy & Compliance.