# Contacts

> Capture the people behind your work without losing their context.

## A person, with context

Create a contact with a display name and an idempotency key. Add the details you have; an email address is optional.

```json
{
  "display_name": "Maya Chen",
  "given_name": "Maya",
  "family_name": "Chen",
  "emails": ["maya@example.com"],
  "description": "Collaborating on a research project.",
  "tags": ["collaborator", "research"],
  "external_references": ["Introduction at the autumn workshop"],
  "idempotency_key": "maya-introduction-001"
}
```

## Supported fields

| Field | Limit or behaviour |
| --- | --- |
| `display_name` | Required, up to 255 characters. |
| `given_name`, `family_name` | Optional, up to 255 characters each. |
| `emails` | Up to 10 unique addresses, normalised to lowercase. |
| `phone_numbers` | Up to 10 unique strings, 50 characters each. |
| `description` | Optional plain text, up to 10,000 characters. |
| `tags` | Up to 30 unique strings, 80 characters each. |
| `external_references` | Up to 20 unique strings, 2,048 characters each. |

References can be URLs, external identifiers, or descriptions. Orbit stores them without fetching their contents. Use `custom_fields` for values registered by an administrator. Discover their keys and types with `fields_list`; unknown keys are rejected. See [Custom fields](/docs/custom-fields).

## Search before creating

Use `contacts_search` with a name fragment, exact email address, or phone number. Names and emails are case-insensitive. Tags match a complete normalised label. Explicit `name`, `email`, `phone`, `tag`, `organisation_id`, `project_id`, and `custom_fields` filters can be combined; all supplied filters must match.

You do not need to remember IDs. Ask your agent to search, inspect the matching names/emails/phone numbers, then use the returned ID. Multiple matches require disambiguation; an email or shared phone number is not proof of a unique person.

```json
{
  "query": "Maya",
  "tag": "collaborator",
  "per_page": 20,
  "page": 1
}
```

Results are scoped to your workspace and ordered by display name, then ID. Follow `next_page` while `has_more` is true. Archived records are excluded unless `include_archived` is explicitly true.

## Search by phone

```json
{"phone": "0044 7700 900123", "name": "Maya"}
```

Search ignores spaces, parentheses, dots, and hyphens. International `00` and `+` prefixes are equivalent. `+44 (7700) 900-123` matches the example above. Local `07700900123` is not automatically treated as an international number: Orbit does not infer a country from the workspace timezone. Supply the country code when known. Extensions and fuzzy phone matching are not supported. Stored phone strings are preserved.

## Update a contact

Retrieve the current contact first, then use its `id` and `revision`:

```json
{
  "id": "01900000-0000-7000-8000-000000000001",
  "expected_revision": 1,
  "idempotency_key": "maya-update-001",
  "changes": {
    "description": "Working together on the autumn project.",
    "tags": ["collaborator", "research"]
  }
}
```

Call `contacts_update`. Omitted fields remain unchanged. Supplied arrays replace their old values; `[]` clears an array. Use `null` to clear optional name components or description. Display name remains required and cannot be cleared. Custom fields use key-level patches, described in [Custom fields](/docs/custom-fields).

A real change increments `revision` and records an audit event. An unchanged patch does neither. A stale `expected_revision` returns `revision_conflict`; retrieve the latest record and reconcile the intended edit before submitting a new operation. Updating identity details does not automatically merge records or enforce unique emails.

## Archive and restore

Call `contacts_archive` or `contacts_restore` with `id`, `expected_revision`, and `idempotency_key`. These operations retain the record and its relationships. Archived contacts are excluded from ordinary get/search calls and must be restored before editing. Already-archived or already-restored requests are no-ops when their expected revision is current. There is no permanent-deletion MCP tool.

## Resolve possible duplicates

An exact case-insensitive name or matching normalised email can produce an `ambiguous_match` error. The error includes a bounded list of candidate IDs.

Retrieve those records and decide whether one is the person you mean. If this is a distinct person, retry with `allow_duplicate: true`. Orbit never automatically merges contacts.

## Retry safely

Give each intended creation, update, archive, or restore operation a unique `idempotency_key`, using letters, digits, dots, underscores, colons, or hyphens (up to 128 characters).

Reuse that key with the same input when retrying an interrupted request. Orbit returns the original operation snapshot and sets `replayed: true`. Reusing it with different input returns `idempotency_conflict`.

Keys are scoped to the agent and operation and retained indefinitely in this milestone. Retrying under another identity is a new operation.

## Retrieve a contact brief

Call `contacts_brief` with `id` for the contact, directly linked activity with source references, outstanding tasks, and affiliations. Each section defaults to five entries (maximum 20 with `per_section`). Follow its `next_page` using `activity_page`, `task_page`, or `relationship_page`. Briefs contain structured data; Orbit does not generate prose or authorise external actions.

## Choose result order

Search accepts `sort`: `display_name`, `created_at`, or `updated_at`, with `direction`: `asc` (default) or `desc`. The first field is the default. ID ascending breaks ties; nullable dates sort last. Only these field names are accepted.
