Gary Club
Resource

Contacts

People you can reach. Upsert is idempotent on email or phone — calling it twice with the same identity returns the existing contact, no duplicates.

Tenancy
Every contact lives at the intersection of org_id and client_id. With a gc_live_ key, you're writing to the agency's own CRM unless you pass X-Gary-Client-Id. With cl_live_, you're always writing to that bound client's CRM.

List contacts#

List contacts

GET/v1/crm/contacts
Cursor-paginated list. Defaults to 50 per page, max 200.

Query parameters

limitintegerdefault: 50
Maximum items per page (1–200).
page_tokenstring
Opaque cursor returned in next_page_token from the prior page.
qstring
Substring match across full_name / first_name / last_name.
lifecyclestring
Filter by lifecycle_stage (e.g. lead, qualified).
owneruuid
Restrict to contacts owned by this user_id.
Request
curl "https://agency.gary.club/api/public/v1/crm/contacts?limit=10&q=jane" \
  -H "Authorization: Bearer gc_live_EXAMPLE_AbCdEfGhIj0123456789"

Create / upsert a contact#

Idempotent upsert by email or phone

POST/v1/crm/contacts
Either email or phone is required (one of). If a contact already exists with that identity, we merge in the new fields and return created: false instead of inserting a duplicate.

Body parameters

emailstring
Identity. Required if phone is not provided.
phonestring
E.164. Required if email is not provided.
first_namestring
Given name.
last_namestring
Family name.
full_namestring
Use this only when you can't split first/last.
titlestring
Job title.
company_iduuid
Link this contact to a company you've already created.
company_namestring
If you don't have a company_id, we'll resolve / create one by domain.
sourcestring
Free-form attribution (e.g. n8n_inbound_form).
Request
curl -X POST https://agency.gary.club/api/public/v1/crm/contacts \
  -H "Authorization: Bearer gc_live_EXAMPLE_AbCdEfGhIj0123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "first_name": "Jane",
    "last_name": "Doe",
    "title": "Head of Ops",
    "source": "n8n_inbound_form"
  }'

Successful creation returns:

Response
{
  "contact": {
    "id": "con_EXAMPLE_aaaa",
    "client_id": "00000000-0000-0000-0000-000000000000",
    "full_name": "Jane Doe",
    "first_name": "Jane",
    "last_name": "Doe",
    "title": "Head of Ops",
    "lifecycle_stage": "lead",
    "company_id": null,
    "owner_user_id": null,
    "tags": [],
    "created_at": "2026-04-27T12:34:56.000Z",
    "updated_at": "2026-04-27T12:34:56.000Z"
  },
  "created": true
}

Read one#

Read a single contact

GET/v1/crm/contacts/{id}
Full contact record with hydrated company link.

Path parameters

iduuidrequired
Contact id.
Request
curl https://agency.gary.club/api/public/v1/crm/contacts/con_EXAMPLE_aaaa \
  -H "Authorization: Bearer gc_live_EXAMPLE_AbCdEfGhIj0123456789"

Update#

Update mutable fields

PATCH/v1/crm/contacts/{id}
Pass any subset of mutable fields. Sending archived: true sets the soft-delete timestamp (the row stays for tenancy triggers).

Path parameters

iduuidrequired
Contact id.

Body parameters

first_namestring
last_namestring
titlestring
lifecycle_stagestring
e.g. lead, qualified, customer.
company_iduuid | null
Re-link or unlink the company.
owner_user_iduuid | null
Reassign ownership.
tagsstring[]
Replaces the entire tags array. Read-modify-write to add a tag.
archivedboolean
Soft-delete shortcut. true sets archived_at; false clears it.
Request
curl -X PATCH \
  https://agency.gary.club/api/public/v1/crm/contacts/con_EXAMPLE_aaaa \
  -H "Authorization: Bearer gc_live_EXAMPLE_AbCdEfGhIj0123456789" \
  -H "Content-Type: application/json" \
  -d '{ "lifecycle_stage": "qualified", "tags": ["warm","followup-q4"] }'

Archive#

Soft-archive

DELETE/v1/crm/contacts/{id}
Sets archived_at to now. The row is preserved for tenancy triggers and historical activity timelines; un-archive via PATCH archived=false.

Path parameters

iduuidrequired
Contact id.
Request
curl -X DELETE \
  https://agency.gary.club/api/public/v1/crm/contacts/con_EXAMPLE_aaaa \
  -H "Authorization: Bearer gc_live_EXAMPLE_AbCdEfGhIj0123456789"

Phones & emails (sidecar tables)#

A contact can have multiple phones and emails. They live in 1:N sidecar tables and have their own CRUD endpoints — use these to add a second phone, mark a number as DNC, set consent state, or change the primary email without touching the contact row.

List phones

GET/v1/crm/contacts/{id}/phones
All phones attached to the contact, ordered with the primary first.

Path parameters

iduuidrequired
Contact id.

Add phone

POST/v1/crm/contacts/{id}/phones
Idempotent on phone_e164 — re-posting the same number returns the existing row with created: false. The first phone added is auto-marked as primary; pass is_primary: true to re-anchor.

Path parameters

iduuidrequired

Body parameters

phonestringrequired
E.164 (+15551234567). Normalized server-side.
phone_typestring
mobile | work | home | other (defaults to mobile)
is_primaryboolean
If true, demotes any existing primary first.
consent_statusstring
unknown | opt_in | opt_out | do_not_call
consent_sourcestring
How consent was captured (e.g. signup_form_v2).
sourcestring
Free-form provenance label.

Read phone

GET/v1/crm/contacts/{id}/phones/{phoneId}

Path parameters

iduuidrequired
Contact id.
phoneIduuidrequired
Phone row id.

Update phone

PATCH/v1/crm/contacts/{id}/phones/{phoneId}
Patch any subset. Toggling is_primary auto-demotes the previous primary.

Path parameters

iduuidrequired
phoneIduuidrequired

Body parameters

phone_typestring
is_primaryboolean
statusstring
unverified | verified | invalid
consent_statusstring
consent_sourcestring
consented_atstring (ISO 8601)
dnc_scopestring
individual | company | global

Delete phone

DELETE/v1/crm/contacts/{id}/phones/{phoneId}
Hard-delete the phone row. Returns 204.

Path parameters

iduuidrequired
phoneIduuidrequired

The same five endpoints exist for emails at /v1/crm/contacts/{id}/emails and /v1/crm/contacts/{id}/emails/{emailId}. Body shape is the same with email instead of phone and phone_type replaced by status (unverified, verified, bounced).

Webhooks for contacts#

  • contact.created — first time we've seen this identity.
  • contact.updated — any subsequent change to mutable fields, OR a phone/email added/changed.
  • contact.archived — soft-archive via DELETE.

Subscribe via /v1/webhooks to react in n8n, Claude, or your own service.