The Data Model: Rows, Columns, Actions
Outcome: a paper sketch of your workflow — one row grain, a named column per step, and each column marked free, cheap or expensive — before you build anything.
- Surface
- App and MCP server
- Level
- Beginner
- Uses
- Tables (no enrichment run yet)
- Credits
- 0
- Prerequisite
- Lesson 01's four-question table
Everything is a table
Sourcing tools, enrichment vendors and sequencers all describe themselves differently, but underneath there is one structure and it has three parts.
| Part | What it is | Example |
|---|---|---|
| Row | One thing you are working. The grain of the dataset. | One company. One person. One job post. |
| Column | One fact about that thing, or one step that produces a fact. | domain, employee_count, work_email, score |
| Action | The thing that fills a column. Runs per row, bills per row. | enrich_organization, find_work_email, an AI prompt |
Once you see a workflow this way, most build questions answer themselves. “Where do I put the tech stack check?” is a column question. “Why did this cost so much?” is an actions-times-rows question. “Why are there duplicates?” is a grain question.
Tables is the reference for the structure; Actions is the reference for what can fill a column.
Choosing the grain
The single most consequential decision in a build is what one row represents. Get it wrong and every later stage inherits the mistake.
Company-grain
One row per company. Use when the decision you are making is about the account: is this in market, does it fit, is it worth working.
Person columns do not belong here. The moment you want three contacts per company, a company-grain table forces you into contact_1_email, contact_2_email — the shape that breaks every export.
Person-grain
One row per human. Use when the output is outreach. Company facts get carried along as columns, repeated across the people at that company. That repetition is fine and normal.
Event-grain
One row per occurrence — a job post, a funding round, a LinkedIn post, a form fill. Use when the same company can legitimately appear many times and each occurrence is separately actionable.
The standard mistake is starting person-grain because outreach is the goal. It means you pay to find people at companies you would have disqualified, which is exactly the ordering lesson 06 tells you to avoid.
Most real pipelines are two tables, not one: a company-grain table that qualifies, and a person-grain table built only from the survivors. Splitting them is what makes the qualification stage cheap.
Columns have a price
A column is not a field in a form. It is a per-row cost, and the total is trivially predictable once you write it down.
| Column type | Cost per row | Examples |
|---|---|---|
| Free | 0 | Formulas, text you typed, filters, conditional logic |
| Cheap | Fractions of a credit to ~1 | Company enrichment, tech stack, headcount, job listings |
| Expensive | ~1 and up, often several | Person-level contact data, mobile numbers, AI research over a page |
Write your run cost as rows × sum(column costs) before you build. A 2,000-row table with six one-credit columns is a 12,000-credit run whether or not you intended it. See Credits for exact per-action pricing.
Worked example
Continuing the developer-tool build from lesson 01, on paper:
Table A — company grain
| Column | Type | Cost |
|---|---|---|
company_name, domain | Source | cheap |
employee_count, country | Enrich | cheap |
ci_tool | Tech stack | cheap |
infra_roles_30d | Job listings | cheap |
fit_score | Formula | free |
qualified | Filter on fit_score | free |
Table B — person grain, built only from qualified = true
| Column | Type | Cost |
|---|---|---|
name, title, linkedin_url | Find people | cheap |
work_email | Waterfall | expensive |
email_status | Verify | cheap |
opening_line | AI over Table A’s evidence | expensive |
Two tables, ten columns, and the expensive four run against a fraction of the rows. That is the whole design, and it fits on an index card.
Do this now
- Take lesson 01’s four-question table. Decide the grain of your final output — almost always person or event.
- Write the columns for that output table, one line each, in the order they must run.
- Mark every column free, cheap or expensive.
- Draw a line above the first expensive column. Everything above the line is qualification; everything below is execution.
- If any cheap column sits below the line, move it up. If the qualification half has fewer than three columns, your gate is too weak — add a criterion.
- Split anything company-level above the line into its own table.
Check your work
- You can state in one sentence what a single row is
- No column is a list crammed into a cell (
contact_1,contact_2) - Every expensive column sits below the qualification line
- You have a written
rows × costestimate for one run
Where this breaks
Mixed grain is the failure that survives longest before it hurts. A table where some rows are companies and some are people looks fine in the app, exports as garbage, dedupes incorrectly, and makes every count you report wrong. If you cannot answer “what is one row?” without saying “it depends,” stop and split the table before adding another column.
Further automation
A table designed this way re-runs cleanly: the qualification half is cheap enough to refresh weekly, and the execution half only runs for rows that newly crossed the line. Lesson 07 covers the dedupe and cadence rules that keep that re-run from re-billing you for rows you already paid for.
Next lesson
03 — Find → Research → Enrich → Outreach, which names the four stages those columns fall into and gives you a way to locate any failure to one of them.
Reference for this lesson: Tables, Actions, Credits, Import.