Build guides

Build a headless CRM your agent runs

Companies, contacts, deals, and every call or email, linked in one Base76 base. Your agent does the logging; you get pipeline totals and contact pages.

Snippets checked against the live API October 22, 2026
  • Natural keys
  • Inverse relations
  • Aggregates
  • Timeline templates
  • Guests

Small-team CRMs usually die because nobody logs anything. Here the agent does the logging. Paste in an email thread or your call notes and it files the contact, the company, the deal, and the touchpoint, linked together. You never open a form.

The prompt

Set up a CRM base in Base76. Companies have a unique name, a domain, and notes. Contacts have a name, a unique email, a role, and a company. Deals have a title, a company, a stage (lead, qualified, proposal, won, lost), a value in USD, and a close date. Interactions have a summary, a contact, a kind (call, email, meeting), a date, and notes. When a contact names a company we don't have yet, create it.

The schema

Four collections, linked like this:

FromFieldToInverseOn delete
contactscompanycompaniescontactsset_null
dealscompanycompaniesdealsrestrict
interactionscontactcontactsinteractionscascade

companies.name and contacts.email are marked unique, which lets them stand in for record ids. The last sentence of the prompt becomes on_missing: "create" on the contact’s company field:

{ "key": "company", "type": "relation",
  "options": { "to": "companies", "inverse": "contacts",
               "on_delete": "set_null", "on_missing": "create" } }

The delete rules matter. Deleting a contact takes their interactions with them (cascade). Deleting a company that still has deals is refused (restrict), so a deal can’t lose its company by accident.

Logging without looking things up

Because of the unique fields, the agent links records by name or email and skips the lookup round trip:

curl -s https://api.base76.com/v1/bases/tim/crm/records/interactions \
  -H "Authorization: Bearer $B76_KEY" \
  -H 'content-type: application/json' \
  -d '{"record": {"summary": "Sent proposal for the ordering portal",
                  "contact": {"email": "maya@northwindbakery.example"},
                  "kind": "email", "date": "2026-10-14"}}'

A new contact with "company": {"name": "Northwind Bakery"} creates the company if it doesn’t exist yet. Deals have no on_missing, so a deal naming an unknown company fails with an error instead of inventing one.

Pipeline totals

“What’s in the pipeline?” becomes an aggregate call. Totals are computed in the base, so the agent doesn’t pull every deal into the chat to add them up:

{"collection": "deals",
 "where": {"stage": {"nin": ["won", "lost"]}},
 "group_by": ["stage"],
 "metrics": {"deals": {"count": true}, "pipeline": {"sum": "value"}}}
{"groups": [
  {"stage": "lead", "deals": 1, "pipeline": 2500},
  {"stage": "proposal", "deals": 1, "pipeline": 4800},
  {"stage": "qualified", "deals": 1, "pipeline": 6200}],
 "truncated": false}

A find with include walks the links in one call. This returns companies with a deal at the proposal stage, along with their people and all their deals:

{"collection": "companies",
 "where": {"deals": {"some": {"stage": "proposal"}}},
 "select": ["name"],
 "include": {"contacts": {"select": ["name", "email"]},
             "deals": {"select": ["title", "stage", "value"],
                       "sort": "-value"}}}

A contact page with the whole history

The contact template puts the person on top and their interactions underneath as a timeline, newest first:

[{"type": "title"},
 {"type": "meta", "fields": ["role", "company"]},
 {"type": "fields", "fields": ["email"]},
 {"type": "related", "field": "interactions", "style": "timeline",
  "sort": "-date", "heading": "History"}]

A company page can list its people and deals the same way, with two related blocks in table style. Your teammates see these pages in the app.

For a teammate who should work the pipeline with you, the agent calls add_guest with their email and a role: editor to change records, viewer to read. Guests need a Base76 account, and the Free plan allows five guests per base. After that, every write result reports the wider audience, so the agent knows it’s no longer writing to a private base.

Limits

  • No email or calendar sync. The agent logs what you give it.
  • Nothing runs on a schedule. Follow-up reminders come from your agent or a script that queries deals by close_date.
  • A guest sees the whole base. To show someone one deal or one contact, send a share link for that record instead.
Base76 is in closed beta.

Join the waitlist and you'll get an email when your account is approved. Beta accounts are free.