Skip to content

list & filter contacts

GET /v1/contacts

List contacts in your workspace, optionally filtered or searched. This is the endpoint behind “filter feedback by your top-mrr customers” — pair it with GET /v1/contacts/{contactId}/posts to see what a specific customer has filed.

paramtypedefaultnotes
qstring, 1-200substring search across email + name (case-insensitive ILIKE)
sortcreated | last_seen | mrrcreatednewest first for created; most-recent-activity first for last_seen; mrr desc with NULLS LAST
planstring (csv)exact-match filter; pass repeated or comma-separated (?plan=pro,enterprise)
cursorstringfrom the previous response’s nextCursor
limitinteger, 1-10025page size

The plan filter is case-sensitive; pass the same string you used in identify.

If you’re moving from another feedback tool’s customer identification, the field you need is externalUserId — your system’s stable user id, the same value you’d pass to spirby identify. The mapping:

coming fromtheir fieldbecomes spirby
CannyuserIDexternalUserId
FeaturebasecustomIdexternalUserId
SegmentuserIdexternalUserId
PostHogdistinct_idexternalUserId
Intercomuser_idexternalUserId
Hubspotcontact vid (or your CRM key)externalUserId

Once externalUserId is set on a contact via identify, every subsequent identify call with the same id upserts the same contact — making this the migration anchor across systems.

Terminal window
curl -s "$SPIRBY_BASE/v1/contacts?sort=mrr&plan=pro&limit=20" \
-H "Authorization: Bearer $SPIRBY_KEY" | jq

Sort mrr returns highest mrr first; contacts with mrrCents = null are placed at the end (NULLS LAST), so a paginated walk eventually reaches them.

Terminal window
curl -s --get "$SPIRBY_BASE/v1/contacts" \
--data-urlencode "q=acme" \
-H "Authorization: Bearer $SPIRBY_KEY" | jq

ILIKE substring match against email and name. “acme” finds “[email protected]” and “Acme Corp”.

Terminal window
curl -s "$SPIRBY_BASE/v1/contacts?sort=last_seen&limit=10" \
-H "Authorization: Bearer $SPIRBY_KEY" | jq

Useful for “what did our 10 most recently-seen customers ask for last week?” — pipe each result’s id into GET /v1/contacts/{contactId}/posts.

Terminal window
# page 1
curl -s "$SPIRBY_BASE/v1/contacts?sort=mrr&limit=25" \
-H "Authorization: Bearer $SPIRBY_KEY" | jq
# page 2 — pass the previous response's nextCursor
curl -s "$SPIRBY_BASE/v1/contacts?sort=mrr&limit=25&cursor=eyJpZCI6Im..." \
-H "Authorization: Bearer $SPIRBY_KEY" | jq

Cursors are stable across the sort dimension. Don’t try to decode them — the format is internal and may change.

{
"data": [
{
"id": "ctc_01H8...",
"externalUserId": "usr_42",
"email": "[email protected]",
"name": "Acme Corp",
"plan": "pro",
"mrrCents": 14900,
"currency": "USD",
"metadata": { "signupSource": "google-ads" },
"firstSeenAt": "2026-05-14T16:32:00.000Z",
"lastSeenAt": "2026-05-14T16:32:00.000Z",
"createdAt": "2026-05-14T16:32:00.000Z",
"updatedAt": "2026-05-14T16:32:00.000Z"
}
],
"nextCursor": "eyJpZCI6Im..."
}

nextCursor is null on the last page.

The companion endpoint:

GET /v1/contacts/{contactId}/posts

Returns every post attached to the contact across all boards, newest first. See the api reference for parameters.

Required api key scope: read.