---
title: "Contacts"
description: "OQL field reference for contacts. The people in your CRM and their associated company, communication details, and metadata."
canonical_url: https://developer.onepagecrm.com/oql/entities/contacts/
source: Markdown mirror of https://developer.onepagecrm.com/oql/entities/contacts/
---

Contacts are the people in your CRM and their associated company,
communication details, and metadata.

**Default sort:** `weight` descending. When you omit `order_by`,
contacts return in [Action Stream priority
order](/getting-started/data-model/#how-the-stream-sorts) (the same
order the OnePageCRM app uses).

## Fields

Legend: **F** filterable, **S** sortable, **A** aggregatable, **G** groupable.

| Field | Type | F | S | A | G | Description |
|-------|------|:-:|:-:|:-:|:-:|-------------|
| `id` | ID | Y | — | — | — | Contact ID |
| `first_name` | string | Y | Y | — | — | First name |
| `last_name` | string | Y | Y | — | — | Last name |
| `company` | string | Y | Y | — | — | Company name (display text) |
| `company_id` | ID | Y | — | — | Y | Linked company record ID |
| `job_title` | string | Y | Y | — | — | Job title |
| `status` | string (output only) | — | — | — | — | Status display name. Filter and sort by `status_id` instead. |
| `status_id` | string | Y | Y | — | Y | Status system_id (e.g. `lead`, `prospect`, `customer`) |
| `owner_id` | ID | Y | — | — | Y | Owner user ID |
| `lead_source` | string (output only) | — | — | — | — | Lead source display name |
| `lead_source_id` | string | Y | Y | — | Y | Lead source system_id |
| `tags` | string[] | Y | — | — | Y | Tag names |
| `starred` | boolean (virtual) | Y | — | — | — | Starred by the current user |
| `background` | string | — | — | — | — | Background text (select only) |
| `address` | string | Y | — | — | — | Primary street address |
| `city` | string | Y | Y | — | Y | Primary address city |
| `state` | string | Y | Y | — | Y | Primary address state or region |
| `zip_code` | string | Y | — | — | — | Primary address postal code |
| `country_code` | string | Y | Y | — | Y | ISO 3166-1 alpha-2 country code |
| `address_type` | string | Y | — | — | Y | One of `work`, `home`, `billing`, `delivery`, `other` |
| `created_at` | time | Y | Y | — | Y | Record creation timestamp |
| `modified_at` | time | Y | Y | — | Y | Last modification timestamp |
| `last_activity_date` | time | Y | Y | — | Y | Most recent activity (note, call, meeting, deal) |
| `weight` | number (virtual) | — | Y | — | — | Action Stream sort weight. Default sort field. |
| `emails` | array | Y | — | — | — | `{address, type}` objects |
| `phones` | array | Y | — | — | — | `{number, type}` objects |
| `urls` | array | Y | — | — | — | `{url, type}` objects |

## Notes

- **Virtual fields** (`status`, `lead_source`) are display labels
  resolved from their underlying `_id` field at query time. You
  cannot select, filter, or sort by them directly; use the `_id`
  field instead.
- **`tags`** uses bare-string equality for single-tag membership
  (`{"tags": "VIP"}`) and `in` for multi-tag overlap
  (`{"tags": {"in": ["VIP", "Hot"]}}`). Grouping by `tags` gives a
  per-tag breakdown: each tag is its own bucket, so a contact tagged
  both `VIP` and `Hot` is counted under each. `count()` per tag is
  exact; summing an unrelated field double-counts multi-tag contacts.
- **`starred`** is per-user. The value reflects whether the calling
  user has starred the contact, not whether anyone has.
- **`emails`, `phones`, `urls`** are arrays of objects. Filter using
  dotted subfields:
  - `emails.address`, `emails.type` (`work`, `home`, `other`)
  - `phones.number`, `phones.type` (`work`, `mobile`, `home`, `direct`, `fax`, `other`)
  - `urls.url`, `urls.type` (`website`, `blog`, `twitter`, `linkedin`, `facebook`, `instagram`, `xing`, `other`)
- **Custom fields** are queryable by name as `custom_fields.<name>` in
  `select`, `where`, and `order_by`; select `custom_fields.*` (or `*`)
  to return all of them. Filterability and sortability depend on the
  field's type. Numeric custom fields can also be aggregated
  (`{ "sum": ["custom_fields.<name>"] }`), and `min`/`max` work on date
  custom fields. `group_by` and `distinct` on custom fields are not
  supported: group by a top-level field and filter on the custom field
  in `where` instead. Use [`describe`](/mcp/tools/describe/) to
  discover an account's custom-field names and types.

## Example queries

Open leads owned by me, top of stream:

```json
{
  "from": "contacts",
  "where": { "owner_id": "ME()", "status_id": "lead" },
  "limit": 25
}
```

Contacts I've starred:

```json
{
  "from": "contacts",
  "where": { "starred": true }
}
```

Contacts at an Irish phone number, with no activity in the last 30 days:

```json
{
  "from": "contacts",
  "where": {
    "phones.number": { "like": "+353%" },
    "last_activity_date": { "<": { "DAYS_AGO": [30] } }
  }
}
```

Contacts by country:

```json
{
  "from": "contacts",
  "select": ["country_code", "count()"],
  "where": { "country_code": { "is not": null } },
  "group_by": ["country_code"]
}
```
