CRM & Contact Intelligence

Phase 7 · Auto-extraction Team & Enterprise

Paid tier required

The CRM API and contact auto-extraction are available on Team and Enterprise plans only. Requests from a Starter team return HTTP 403.

Overview

xbrain maintains a live contact directory for each team, populated automatically from two sources: meeting notes ingested via the Granola integration and any memory item whose content mentions people. No manual data entry required.

Contacts share the same tagging contract as memory items — every record carries team_scope, visibility, confidence, truth_level, and validation_status. This means contact data participates in the same truth-level promotion and scoped retrieval pipeline as the rest of the team's knowledge.

Automatic Contact Extraction

Every call to POST /v1/memory/upsert triggers a background hook, _extract_crm_contacts, that uses Claude to identify people mentioned in the memory content. Extracted contacts are upserted on the unique key (team_scope, email) — if a contact already exists, only new fields are added; existing data is never overwritten.

Fail-soft by design

If Claude extraction fails or returns no contacts, the memory upsert still succeeds. The background hook is fire-and-forget. Contact extraction never blocks the write path.

The same extraction runs on Granola meeting notes during ingest — participants from the participants field are directly upserted as contacts without requiring LLM extraction.

Contact Fields

Field Type Description
id UUID Auto-generated primary key.
team_scope string Team this contact belongs to. Hard isolation — Team A cannot see Team B's contacts.
email string Unique within a team. The deduplication key for upserts.
name string | null Display name extracted from meeting notes or memory content.
company string | null Company or organisation.
role string | null Job title or role.
notes string | null Free-form notes about the contact.
source string Origin: granola, manual, or a memory item source string.
visibility enum team (default), project, or private.
confidence float 0.0–1.0. Set by extraction pipeline; higher for direct meeting participants.
truth_level enum Epistemic status. Defaults to WORKING for extracted contacts.
validation_status enum pending, approved, or rejected.

API Reference

All endpoints require a valid JWT and the X-Team-Scope header. Team and Enterprise plans only — Starter teams receive 403.

List contacts

bashGET /v1/crm/contacts

curl "https://api.grooveos.app/v1/crm/contacts?limit=50&offset=0" \
  -H "Authorization: Bearer $JWT" \
  -H "X-Team-Scope: excalibur"

# Returns paginated list of contacts for the team

Create a contact

bashPOST /v1/crm/contacts

curl -X POST "https://api.grooveos.app/v1/crm/contacts" \
  -H "Authorization: Bearer $JWT" \
  -H "X-Team-Scope: excalibur" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "name": "Alice Martin",
    "company": "Acme Corp",
    "role": "CTO",
    "notes": "Met at SaaS Summit 2026",
    "visibility": "team",
    "confidence": 1.0,
    "truth_level": "WORKING",
    "validation_status": "pending"
  }'

Upsert a contact

Use PUT to create-or-update a contact by (team_scope, email). Existing fields are overwritten only if the new value is non-null.

bashPUT /v1/crm/contacts

curl -X PUT "https://api.grooveos.app/v1/crm/contacts" \
  -H "Authorization: Bearer $JWT" \
  -H "X-Team-Scope: excalibur" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "company": "New Corp"
  }'

Update a contact

bashPATCH /v1/crm/contacts/{contact_id}

curl -X PATCH "https://api.grooveos.app/v1/crm/contacts/c1a2b3c4..." \
  -H "Authorization: Bearer $JWT" \
  -H "X-Team-Scope: excalibur" \
  -H "Content-Type: application/json" \
  -d '{"role": "VP Engineering", "truth_level": "VALIDATED"}'

Delete a contact

bashDELETE /v1/crm/contacts/{contact_id}

curl -X DELETE "https://api.grooveos.app/v1/crm/contacts/c1a2b3c4..." \
  -H "Authorization: Bearer $JWT" \
  -H "X-Team-Scope: excalibur"

Audit Log

Every mutation (create, update, upsert, delete) writes an entry to the audit_log table with action, actor_id, team_scope, and a JSON diff of what changed. Query the audit trail via GET /v1/audit?resource=contact.

Error Reference

HTTP Status When Resolution
403 Team is on the Starter plan Upgrade to Team or Enterprise to access the CRM API.
409 Email already exists in this team (on POST, not PUT) Use PUT /v1/crm/contacts to upsert instead of POST.
422 Missing required fields (email) Email is the only required field on creation.
404 Contact ID not found or belongs to another team Verify the contact ID and X-Team-Scope header.