Skip to content

Lead capture and the interest list

Each form on the landing site, and the same form as an agent's tool, turns a visitor into a contact kept with its consent.

A contact, from a form to a Customer

Each step is a state of the contact, written as an Event. Filled: what the visitor sees.

A demo form

The sales tracker's demo form asks for three fields. Each confirmed contact is enriched, then written to the team's CRM.

definition.ts
26 lines
import { define } from 'apis.dev'

/** Deals for each Customer, with a demo form that writes each confirmed visitor to the team's CRM. */
export default define({
  name: 'pipeline', domain: 'pipeline.apis.dev',
  sources: {
    firms: { api: 'companyfacts.apis.dev', cost: { 'Company.get': 0.002 } },
    crm:   { system: 'crm', scope: 'you', sync: 'write' },
  },
  Company: { name: 'string', domain: 'url!#', deals: '<-Deal.company' },
  Deal: {
    title: 'string', amount: 'decimal(12,2)', company: '->Company.deals',
    stage: { open: [], qualify: 'opened', win: 'qualified', lose: ['opened', 'qualified'] },
  },
  offer: {
    Free: { month: 0, seats: 1, includes: { 'Deal.open': 25 } },
    Team: { month: 49, seats: 5, includes: { 'Deal.open': 1000 } },
    'Deal.open': 0.001,
  },
  leads: {
    forms:   { demo: ['email', 'company', 'useCase'] },
    confirm: true,                                           // one link before anything else
    enrich:  { api: 'companyfacts.apis.dev', cost: 0.002 },  // a lookup per contact
    to:      'crm',
  },
})

Your team reads a contact with Contact.list on the Operator plane; the CRM receives the same record.

Response
HTTP/1.1 200 OK
Content-Type: application/json
X-Cost: price=0, currency="USD"
X-Request-Id: req_8e2b47d0c3f91a65b7e4

{
  "id": "con_12",
  "email": "dana@acme.com",
  "form": "demo",
  "source": "forms",
  "state": "synced",
  "consent": { "broadcasts": true, "at": "2026-10-28T10:02:11Z",
               "notice": "sha256:6a1f0c33" },
  "answers": { "company": "Acme", "useCase": "Track enterprise deals" },
  "enriched": { "employees": 240, "sector": "Software", "cost": 0.002 },
  "customer": null
}

Where a visitor leaves an address

A contact joins the interest list, beside every unmet call, pre-order and sign-up, in the order each arrived.

A visitorMeetsThe contact's source
On the landing siteEach form in leads.forms, at its own path, with the fields it namesforms
An agent for a personThe joinInterestList tool over MCP, with the same fields as inputforms
A caller whose call missedThe 402's or 404's offer to hear when it servesinterest
A buyer of a pre-orderIts Checkoutpreorders

Each form meets the bar for generated pages, and the edge challenges bots before it accepts one. A refused submission costs nothing and writes no contact.

Enrichment, at its posted price

Enrichment reads another API product as a Source: one lookup, at its posted price, per contact.

That price is the whole cost, with no fee on top. A lookup that misses costs what the enriching product charges for a miss.

With confirm on, enrichment waits for the confirmed address. It never runs on a personal address, such as one at a webmail domain.

The form asks for consent to broadcasts in its own unticked box, and keeps the notice version beside the answer.

A contact who never ticked it still gets the confirmation and the reply it asked for, and nothing else. An unsubscribe joins the suppression list at once.

Contact.erase removes the contact from the product, the interest list and the CRM it synced to, and keeps only its suppression. Lead capture into a CRM, with enrichment, is a Team feature.