Getting started

The OnePageCRM data model

The seven core entities behind every OnePageCRM surface, how they connect, and how each one maps to the REST API, webhooks, OQL, and MCP.

Last updated Jul 17, 2026

OnePageCRM is action-focused. It is a queue of next steps, not a database of records. The data model exists to answer one question: who do I follow up with next, and why? Read this page once and you’ll understand the domain before you touch a single endpoint.

The map

Seven core entities. Contacts sit at the center β€” everything else hangs off a contact.

Company ◄──┐ (optional link)
           β”‚
        Contact ─── the hub of the model
           β”œβ”€β”€ Actions    what to do next (assigned to a user)
           β”œβ”€β”€ Deals      sales opportunities (status + pipeline stage)
           β”œβ”€β”€ Notes      the written record (can also reference a deal)
           β”œβ”€β”€ Calls      logged calls
           └── Meetings   logged meetings
EntityBelongs toKey references
ContactCompany (optional)company_id, owner_id
Companyβ€”rolls up status and tags (when sync is on)
DealContact (required)contact_id, owner_id, pipeline_id
ActionContact (required)contact_id, assignee_id
NoteContact (required)contact_id, author_id, optional deal link (deal_id in OQL, linked_deal_id in the REST API)
CallContact (required)contact_id, author_id
MeetingContact (required)contact_id, author_id

Two rules carry most of the model:

  • A deal cannot exist without a contact. Neither can an action, note, call, or meeting. Resolve the contact first; everything else follows.
  • A contact can exist without a company. The company link is optional β€” but a contact must have a name or a company name.

Contacts

A contact is a person in your CRM. The contact is the hub β€” every other record (except companies) points at exactly one contact.

  • A contact needs a first/last name or a company name. Both together is normal; a contact with neither is rejected.
  • The company display name is a plain string (company in OQL, company_name in the REST API); company_id is the optional link to a Company record. They are separate fields β€” see Companies for why.
  • Every contact has an owner (owner_id, a user) β€” the team member responsible for it.
  • status_id and lead_source_id classify the contact β€” see Statuses and lead sources.
  • Contacts carry tags, per-user starred, and custom fields.
  • A contact’s position in the Action Stream comes from its top action β€” see Actions.

Field-level reference: OQL contacts.

Companies

A company groups the contacts that belong to one organization. Companies are deliberately lightweight:

  • No owner. Ownership lives on contacts, not companies.
  • Auto-created. Save a contact with a company name and no company_id, and the company record is created (or reused) and linked. You never create a company directly.
  • Roll-ups, not source data. A company can sync a common status and common tags across all of its contacts when sync is enabled. Company size is the count of its non-deleted contacts.
  • Contacts can also be linked across companies β€” one person who matters to several organizations.
  • Companies support their own custom fields.

Field-level reference: OQL companies.

Deals

A deal is a sales opportunity attached to a contact.

  • One primary contact, required. A deal can additionally be linked to more contacts, but contact_id always points at the primary one.
  • Status is exactly one of pending, won, lost.
  • Stage is a per-pipeline integer, not sequential β€” a typical pipeline uses values like 10, 20, 40. Accounts can have multiple pipelines, each with its own stages. A pipeline is a sales or a delivery pipeline β€” deals in a delivery pipeline are always won (they track post-sales projects). Pending deals expose stage; won and lost deals expose last_stage.
  • Two close dates. expected_close_date is the forecast on a pending deal. close_date is the actual date, set when the deal becomes won or lost.
  • Money. amount is the deal value; recurring deals multiply it by months, and totals, margin, and commission are computed from these.
  • Line items. A deal can carry deal items (name, description, qty, price, cost, amount); has_deal_items flags their presence.
  • Own owner. A deal’s owner_id is independent of the contact’s owner β€” a teammate can own the deal on your contact.
  • Deals support their own custom fields, and notes can reference a deal.

Field-level reference: OQL deals.

Actions and the Action Stream

Actions are why the rest of the model exists. An action is a short task (max 140 characters) on a contact, assigned to a user, with one of five types. Every surface exposes the type in the status field.

TypeMeaningDateAction Stream position
asapDo nownoneTop
dateScheduled for a datedateSorted by date; overdue rises
date_timeScheduled with an exact timedate + exact_timeSorted by timestamp, in the assignee’s timezone
waitingWaiting on the contactwaiting_sinceBelow dated actions
queuedNo deadline yetnone, position onlyBottom

Completing an action stamps a completion time and removes it from the stream; you can reopen it later. Completed actions stay on the contact as history.

The one-next-action principle

Each contact has one top next action per assignee β€” the most urgent open, non-queued action. The system maintains this for you:

  • A contact can hold only one asap action per assignee. Create a second and the existing one is automatically demoted to a dated action for today.
  • When you complete the top action, the next most urgent action on that contact takes its place. No manual promotion needed.
  • Queued actions never become the top action on their own β€” they wait until you give them a date or make them ASAP.

How the stream sorts

Diagram: each contact's open actions sort by urgency β€” an ASAP action sits on top as the contact's top action, above dated, waiting, and queued actions; the Action Stream lists every contact ordered by its top action's urgency, and completing the top action promotes the next one automatically

Every action carries a computed weight. Higher means more urgent. The resulting order is: ASAP β†’ overdue β†’ today β†’ future dates β†’ waiting β†’ queued. Queued actions sort by their manual position.

The Action Stream is the list of contacts sorted by the weight of their top next action β€” your follow-up queue for the day. The REST API exposes it read-only, and in OQL, contacts and actions return in Action Stream order by default when you omit order_by β€” see OQL concepts.

Accounts can also define predefined actions and action groups β€” reusable templates (with date and type shortcuts) applied to a contact in one click.

Field-level reference: OQL actions.

Notes, calls, and meetings

Notes, calls, and meetings are the logged history of what happened with a contact.

  • All three require a contact. A note can additionally reference a deal (deal_id in OQL, linked_deal_id in the REST API), but it is always contact-scoped first.
  • Each records an author (author_id) β€” the user who logged it. author_id never changes. The author display name stored alongside it does on notes: editing a note restamps author with the editing user’s name. Calls and meetings keep the original name.
  • Logging any of the three updates the contact’s last_activity_date.

Field-level references: notes, calls, meetings.

Cross-cutting concepts

Ownership, assignment, authorship

Three different user references, three different meanings:

FieldLives onMeans
owner_idContactWho the contact belongs to. Drives visibility.
owner_idDealWho owns the deal. Independent of the contact’s owner.
assignee_idActionWho has to do it. Can be any teammate, not just the contact owner.
author_idNote, Call, MeetingWho logged it. Immutable (the author name on a note reflects the last editor).

Companies have none of these β€” they inherit everything from their contacts.

Statuses and lead sources

Both are account-defined, ordered lists, not global enums. Each entry has a system_id (e.g. lead, prospect, customer), a display name, and a position. The contact stores just the system id as a string. API surfaces expose the id as status_id / lead_source_id and resolve the display label into status / lead_source. The default status for a new contact is lead.

Fetch the account’s actual lists over the API β€” the quickstart shows the call.

Tags

Tags are free-form strings kept in one account-level list. They live on contacts only β€” deals, actions, notes, calls, and meetings have no tags. Adding a new tag to a contact adds it to the account list automatically. A company can mirror a common tag set across all of its contacts when tag sync is enabled.

Custom fields

A custom field is an account-defined, typed field available on contacts, companies, and deals. The literal type values are single and multi (single- and multi-line text), number, dropdown, date, checkbox, anniversary, and external_id. In OQL they’re queryable by name as custom_fields.<name> β€” in select, where, and order_by β€” and custom_fields.* returns them all (filterability depends on the field type).

Private contacts

A contact can be marked private. In the app, a private contact is visible only to its owner. The REST API goes further: it excludes private contacts entirely β€” even the owner cannot fetch one over the API. Records hanging off a private contact (deals, actions, notes, calls, meetings) inherit the restriction. Webhooks skip full-payload events for private contacts too; only deleted events still fire β€” see webhook events.

IDs and timestamps

Every entity ID is a 24-character hex string ("507f1f77bcf86cd799439011"), unique per entity type and stable for the life of the record. Every record carries created_at and modified_at; some entities add purpose fields like the contact’s last_activity_date.

Beyond the seven

The REST API exposes more than the core records. Supporting resources either configure the account β€” users, statuses, lead sources, pipelines, custom-field definitions, predefined actions β€” or read across it, like the Action Stream. Most are covered above as cross-cutting concepts; the full list is in the API reference.

One entity, every surface

The same seven core entities appear on every surface. This table is the crosswalk:

EntityREST rootWebhook typeOQL fromMCP
Contact/api/v3/contactscontactcontactsβœ“
Company/api/v3/companiescompanycompaniesβœ“
Deal/api/v3/dealsdealdealsβœ“
Action/api/v3/actionsactionactionsβœ“
Note/api/v3/notesnotenotesβœ“
Call/api/v3/callscallcallsβœ“
Meeting/api/v3/meetingsmeetingmeetingsβœ“
  • REST β€” every endpoint, parameter, and response shape in the API reference.
  • Webhooks β€” the full type Γ— reason event matrix in Events.
  • OQL β€” one JSON query language across all seven core entities; start with the overview.
  • MCP β€” the MCP server exposes all seven through its query tool (OQL), plus create and update for writes.

Where to go next

  • Quickstart β€” get credentials and make your first call in five minutes.
  • Your first API call β€” a guided walkthrough of the request/response cycle.
  • OQL concepts β€” the query language built on this model.
  • Webhooks overview β€” react to changes in these entities in real time.