Declare your own source
Define a source of your own — its event types, fields and identifiers — and test it with a dry run before saving.
Shape your deployment before, or while, data flows: data models for your sources, attributes for your own fields, identity link types for your own ids. Everything here is a plain API call, and everything predefined already works with zero setup.
1. Think before you define
One question decides almost everything: does it happen, or is it true?
| It is an event if | It is an attribute if |
|---|---|
| It happened at a moment ("viewed /pricing at 9:14") | It holds until someone changes it ("tier: strategic") |
| You will count, window, or sequence it over time | You will filter or group by its current value |
| The source keeps producing more of them | A human or a workflow declares it once |
Then three habits keep a deployment clean:
- Extend before you create. If a predefined data model already carries the concept, add a field or an event type to it instead of minting a parallel source.
- Promote only what you query. Promoted fields become columns and scoring inputs; everything else stays in the payload, retrievable but out of the way.
- Scope and name for the reader.
companyfor account facts,personfor what survives job changes,contactfor one affiliation; bare specific event types (person_visited, neveractivity); descriptions written for the AI agent that will read them.
2. Discover data models
Fresh deployments carry the four person-first data models (job, education, web, signup) plus ackDB's own read-only system model; earlier deployments may also carry previously shipped ones like slack or stripe. List everything yours knows:
Drill into one to get its contract, which is what a workflow builder maps against:
GET /schemas/sources/stripe: the definition, its identity link types, its traits.GET /schemas/sources/stripe/events: every event type with typed fields.GET /schemas/sources/stripe/events/invoice.paid: one event in full. Each field carries acomputeHint(where it comes from in the source API) and each event aningestExample, a complete request body to start from.
3. Create a data model
A custom source lands in the same tables as the predefined ones, so ingest, discovery, and validation treat it identically:
In this definition:
identityLinkTypesmust includedomain, so items can resolve to or mint companies.eventTypeFieldnames where the bare event type rides in metadata;eventTypePrefixis how it is stored (hiring_job.posted).eventScope: "entity"fits here, the company is the actor; person-scope events instead carry acontactMappingnaming which fields hold the acting person, so people resolve automatically.promotedMetadataFieldsbecome filterableemf__columns and scoring inputs, so "hiring for GTM right now" is a segment away.?dryRun=truevalidates the whole payload without persisting anything, andjob_urldoubles as a natural idempotency key when you push.
Add more event types later with POST /schemas/sources/{sourceType}/events; that works on predefined models too. Edits are guarded: additions free, destructive changes refused unless explicitly forced, and GET /schemas/sources/{sourceType}/editability tells you what is currently allowed.
4. Create attributes
Attributes are your own fields on records: set directly, never written by ingest. Define one:
scope is company, person (survives job changes), or contact (one affiliation only). type is string, number, boolean, date, or string_array; description is read by AI agents. Keys are snake_case and cannot collide with a source's <source>_ prefix.
Then set values, by id or by domain, all-or-nothing:
null clears a value. Every real change emits a trait.updated event on the timeline, so edits are history too. Contacts and persons have the same route.
5. Create identity link types
Registered link types are the ids ackDB will accept and match on. Register a universal one:
canMint: true makes the type a minting anchor: it may create a company, which is how domainless companies get records. Everything else is match-only, so a typo can never mint a phantom.
6. Link records
Attach ids to existing records directly, without an ingest:
PUT /entities/{ref}/links/{type}sets all values of one type declaratively.DELETE /entities/{ref}/links/{type}/{value}removes one./contacts/{contactId}/linksis the same surface for people's ids.- The
domainlink is managed by resolution and record creation, read-only here.
Linking emits identity.linked and identity.unlinked events, so the identity map has history like everything else.
7. Create records directly
When there is no event to ingest, mint the record itself:
Idempotent: a fresh domain returns 201 with created: true; an existing one returns its record with created: false, safe for workflow re-runs. Domainless companies use anchorType and anchorValue with a mintable link type instead.