Skip to content

Contacts, companies and deals

Business Studio’s CRM is not a separate product. A contact, a company and a deal are three kinds of card on the same board as everything else — filed, tagged, searched and moved the way every other card is — with three things the other kinds do not have: they are shared with your whole organisation, a deal moves along a sales pipeline instead of the four-column board, and every one of them carries a timeline and linked records.

The design decision behind this (build on the board rather than adopt a standalone CRM) is recorded in the repository under tasks/spike-crm-build-vs-adopt.md.

Most cards are private to whoever created them. A contact, a company or a deal is visible to every member of the organisation: a colleague sees the person you filed, can log a call on them, and can link them to a deal of their own. Their plain tasks stay theirs, and yours stay yours — a colleague’s private card reads as “Card not found”, exactly like a missing one.

Identity is shared too. A person is keyed on their LinkedIn profile, then their email, then their phone; a company on its domain (read from the website you give) or its LinkedIn page. Two people in the organisation filing the same person or company land on one card, whichever of them got there first. An email is keyed on the mailbox it reaches: jane+news@acme.com is jane@acme.com on any domain, and at Gmail jane.doe@gmail.com is janedoe@gmail.com too. Dots count everywhere else, where j.doe and jdoe may be two people. The card keeps the address as it was written.

KindTitle isStructured fieldsEnters at
Contactthe person’s namefirst/last name, email, phone, company (a label), role, LinkedIn profile, location, summarycaptured, already handed to NEO, whose contact squad looks them up on LinkedIn, fills the empty fields and writes what it found to the timeline
Companythe organisation’s namedomain, website, industry, size, location, LinkedIn company page, summarycaptured (no lookup yet)
Dealthe opportunityamount, currency, expected close date, company (a label)new on the pipeline

A contact’s company text is a display label. The real relation — which company card a contact or a deal belongs to — is a link (below), so a company page can list its people and its deals, and a contact can move employer without editing anyone’s deal.

Create them from Sales & CRM in the sidebar (Contacts, Companies, Deals), from the + Create button, by converting a plain task on its card (a task that turns out to be a person, a company or an opportunity), or from the API and the Claude connector.

Open Deals and the board draws the pipeline as columns: New → Qualified → Proposal → Won, with Lost behind the Archived toggle. Drag a deal between columns, or pick the stage on its card; the stage is validated (a value the pipeline does not know is refused with the real options) and the card’s board column follows it — won files under Done, lost under Archived. Every stage move is written to the deal’s timeline as a stage change, with who moved it and from where.

Each column header shows the total amount of the deals in it when they share one currency. ?columns=spine on the board URL shows deals on the ordinary four-column board instead.

Every contact, company and deal has a timeline beneath its card: notes, calls, meetings and emails you log; stage changes; and the result of each LinkedIn lookup, with what was found, which fields it filled, and its confidence. To look someone up again, take the card back from NEO and hand it over again. Entries are append-only — an audit trail that can be edited is not one — and the newest is first.

Suggest next step asks the assistant for the one most useful next action on this record, grounded in the card and its last ten timeline entries. The answer lands on the timeline as a system entry a few seconds later. It needs a connected AI channel; without one the button says so instead of pretending.

Three typed relations, all from the card’s Linked records section:

  • Company of — a contact or a deal belongs to a company.
  • Contact on this deal — a person is party to a deal.
  • Contact of — any card is about a person (a support thread, a task).

Both directions show on both cards, and a link that does not make sense (a company as a deal’s contact, say) is refused before it is written. On a deal, Contact on this deal and Contact of both count as the deal’s contacts.

Create invoice on a deal drafts its invoice and opens it — the month’s activity report for a mission (a won deal with a daily rate; pick this month or last beside the button), the deal’s amount otherwise, or the draft it already has. An invoice can only be raised from a deal, and only once that deal is linked to exactly one company and at least one contact — the button says which is missing. The deal’s timeline then follows the money: an “Invoice … drafted” line when it is created, and an “Invoice … paid” line, once, when Stripe reports the payment.

A contact or company card shows, under its fields, what the rest of the platform knows about that person or organisation — over the last 90 days or the last year (?profileDays=90 keeps the choice in the link):

  • Invoices — number, status, paid date and amount, with the paid total per currency.
  • Products & subscriptions — the lines of every non-void invoice, and the recurring schedules the client holds.
  • Tickets — event ticket orders.
  • X comments — replies and mentions filed in your Inbox whose author is the contact’s X handle (the field accepts @jdoe, jdoe or an x.com URL and stores jdoe).

A company’s view is its own plus every contact linked to it. On a post card, the same section lists who commented on X and whether each one is already a contact.

How a billing customer finds its card: when a customer or a ticket order is created, its email is matched (case-insensitively) against the org’s contact cards, and the company comes from that contact’s company link, or from a company card whose domain is the email’s own (never a gmail-type address). Nothing is created for an email that matches no card — and a card filed after the customer was billed still sees its history, matched by email.

My card and My company in the account menu (/me, /me?card=company) open your own contact and company cards. They are created on first visit and are private: unlike every other contact or company, a colleague cannot see them, find them or edit them.

Some facts do not belong on a card everyone in the org reads — an IBAN, a VAT or registration number, a legal name, a registered or home address, a date of birth, a personal phone. The Facts section of a contact or company card holds them: what you add there is visible to you only unless you tick Share with my organisation, even on a shared card. An IBAN must pass its checksum. Only a person adds these — enrichment never looks them up. Remove one with its ✕; it is kept in the card’s history rather than deleted.

Beyond Works at (a contact’s company), a contact or company card can record who it is to another company — under Linked records on its Properties tab:

RelationshipFromTo
Shareholder ofa contact or a companya company
Adviser ofa contacta company
Member ofa contact or a companya company (an association, a board, a network)
Partner ofa companya company

The other end reads the other way round — Acme’s card lists its Shareholders. Two relations are never typed: the mailing audiences a person is on and the events they took part in come from audience membership and ticket orders, and appear in the card’s Across the platform section.

Messages sent in a contact’s or company’s Across the platform section lists what the platform sent that person: every email campaign they received (with the subject they saw — {{name}} filled in), the purchase receipt for an offer they bought, and each subscription invoice email. One row per recipient per send, logged once even if a send is retried. A person who gets a contact card after a campaign went out still sees it — messages are matched by email address. Emails you send from your own mailbox are not counted here.

The Documents section of a contact, company, deal, task or user story holds links to where a document lives — a Google Drive file, a Notion page, any web page. Nothing is copied: the card keeps the title and the link. Attaching a link does not create a card; make a Doc card instead only when the file needs its own owner, due date or place on the board. Paste a link, or pick a file from your connected Drive folder (type a folder path such as clients/acme) or a page from your connected Notion database. Like a private fact, a document is visible to you only unless you tick Share with my organisation; only the person who attached it can remove it. A task or user story is yours alone, so its documents are too — there is no share option there. ID papers, contracts and medical documents belong here, as links — never as facts.

Any link can be promoted with Make its own card on its row: it files a Doc card for that link, grouped under the card you are on (Part of; it lists under Sub-cards in Properties). The link stays where it is, and promoting the same link again opens the Doc card already there rather than making a second. A Doc card is visible to your organisation, so a link only you can see — a private one, or any link on a task or user story — asks first. Over the API this is POST /api/tasks/:id/documents/:documentId/card, with { "share": true } to confirm that case.

Review is the one place to clear what the knowledge layer is unsure about. It is computed live each time you open it, and nothing in it is applied or created until you click:

  • Conflicts — a card field where two claims of similar weight disagree (a held claim). You see the card’s current value and every held value side by side; accepting one supersedes the others.
  • Suggestions — a field with only proposed claims. Accept writes it onto the card, dismiss remembers the no.
  • New people — email addresses you billed, sold a ticket to or sent a message to in the last year that have no contact card yet. Add to CRM files the contact; nothing is filed for you.
  • Memories — something NEO, a squad, Claude (the remember tool) or an import proposed for NEO to remember about you, such as “LinkedIn posts in French”. Keep applies it — for you, or, if you are an org admin, for the whole organisation — and NEO reads it on every card you hand it from then on. Dismiss means it is never proposed again. Kept memories, a memory you type yourself and the Import from Claude or ChatGPT box are on Memories (/memories).

Claims you cannot read (another member’s private facts) and cards you cannot see never appear, and stated-only fields such as an IBAN are never reviewed. When anything is open, Needs your attention carries one entry, Daily review: 2 conflicts · 3 suggestions · 1 new person · 1 memory to keep, that opens the page. There is no schedule and no email: the review is a page you visit.

The board’s search box reaches the CRM fields, not only titles: type an email address, a phone number, a company name or a domain and the matching contact, deal or company comes up.

All routes are session- or API-key-authenticated and org-scoped.

RouteWhat it does
POST /api/tasks/contactsFile a person; returns the existing card (200) when the identity is already known
POST /api/tasks/companiesFile an organisation; same idempotency, keyed on domain or LinkedIn page
POST /api/tasks with destination: { scheme: "sales" }File a deal; optional subStatus (a pipeline stage, validated)
PATCH /api/tasks/:id with subStatusMove a deal; the board status is derived from the stage
PATCH /api/tasks/:id with payload: { contact | company | deal }Set the structured fields
GET / POST /api/tasks/:id/activitiesRead or log the timeline (kind: note, call, meeting, email)
GET / POST / DELETE /api/tasks/:id/linksRead, add or remove a typed link
POST /api/tasks/:id/next-actionAsk for the next step; 202 with the run id, 409 with the remedy when no AI channel is connected
POST /api/tasks/selfYour own private contact and company cards (created on first call)
POST /api/tasks/:id/factsState a fact (field, value, optional visibility: org|owner); a personal or banking field is private by default
DELETE /api/tasks/:id/facts/:factIdRemove a fact you stated
GET / POST / DELETE /api/tasks/:id/documents[/:docId]Read, attach (url, title?, visibility?) or remove a document link
GET /api/tasks/document-sources[/drive?path=|/notion]Which sources you can pick from, and their files / pages
GET /api/tasks/:id/profile?days=90|365A contact’s or company’s invoices, products, subscriptions, ticket orders and X comments; a post’s X commenters
GET /api/tasks/reviewThe review: conflicts, suggestions (each a card, field, current value and its open claims, highest score first), newPeople, memories (your open memory proposals) and their counts
GET /api/memoriesWhat NEO remembers for you (applied: yours and the org’s) and what waits for you (proposed)
POST /api/memories/import{ text, origin: "claude" | "chatgpt" | "other" } — a pasted memory export; each line becomes a proposal
POST /api/memories/:id/accept · /dismiss · DELETE /api/memories/:idKeep ({ scope: "personal" | "org" }), dismiss or forget — from a signed-in browser session only; an API key is refused
GET /api/tasks/documentsEvery document link you may read across the organisation, grouped by card
GET /api/tasks/:id/activities?since=<ISO date>The timeline from a date on (400 on an invalid date); still takes limit and beforeSeq
GET /api/tasks?search=…&entity=contactSearch; every CRM row carries lastActivityAt

The SDK mirrors them on platformClient.tasks: createDeal, updateDeal, createCompany, listActivities, logActivity, listLinks, link, unlink, suggestNextAction, getProfile, ensureSelfCards, stateFact, retractFact, listDocuments, attachDocument, detachDocument, documentSources, browseDocumentSource, getReview, listAllDocuments.

Through the Claude connector, Claude works the CRM the way you do: create_task files a contact or a company, search_contacts finds a person by email, phone, company or name, log_activity remembers a call or a meeting on a card, link_records puts a contact at a company or on a deal, update_task moves a deal along the pipeline or sets its amount, and get_task reads a CRM card with its timeline and links in one call — plus, for a contact or company, a summary of the last year’s invoices, purchases, subscriptions and X comments, and for a post, which of its X commenters are in your contacts.

The Contacts from Gmail workflow template (/workflows → templates) files everyone who writes to you as a contact card each morning: read_gmail → extract_contacts → tag. The extract_contacts node reads each message’s From header — or its To, or both — and creates one contact per address, titled with the sender’s display name, tagged from:gmail, keyed on the email like every other capture, so a person who wrote twenty times is one card and a person a colleague already filed is reused untouched. It skips your own account address, the addresses and domains you list in Never capture, and automated senders (no-reply@, notifications@, …). It uses no AI: the cards enter at captured, not handed to NEO. To look someone up, assign their card to NEO and the contact squad fills in company, role and LinkedIn profile.

  • A lookup run for companies (the card’s lifecycle already reserves the state for it).
  • Enriching every contact a Gmail extraction found in the same run: update_contact writes back one card, so the lookup is per card, through NEO.
  • Capturing a contact from a social comment — a commenter has no email or profile to key on.
  • Calendar sync, custom fields and saved views.
  • A daily digest proposing a contact card for a billing customer or ticket buyer no card matches.
  • Declaring a Drive folder or Notion database the source of truth for a kind of record, and syncing from it.
  • LinkedIn and Reddit comments: only X replies and mentions reach the Inbox.
  • Payments as records of their own: a refund or a partial payment does not reach the deal; only an invoice turning paid does.