> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coffeechats.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Contact tools

> Tools that read your contacts, notes, tags and research, add or import contacts, edit one contact's notes and tags, and propose changes or research you approve.

These tools read your contacts, notes, tags and research, add or import contacts, edit one contact's notes and tags, and propose changes or research you approve. They need the permissions `contacts:read`, `contacts:write` and `research:run`, which you grant on the Authorize access page when you connect.

This page is generated from the connector's tool catalogue. It describes 16 tools. Every tool reads or writes only your own account.

## Permissions this page needs

* `contacts:read`: Read your contacts, network information and research results
* `contacts:write`: Add contacts, update the notes and tags on your contacts, and change their details and status after you approve each change
* `research:run`: Start research on your contacts that uses credits, after you approve each batch in the browser

The [permissions reference](/mcp/scopes.md) lists every permission you can grant.

## `contacts.list`

Your agent host shows this tool as "List contacts".

List every authenticated student contact, including DO\_NOT\_CONTACT records, with measured pagination.

Permissions: This tool needs `contacts:read`, which the Authorize access page describes as "Read your contacts, network information and research results".

Risk: `read`. This tool only reads your own data. It never changes anything and it never asks for your approval.

### Input

| Property | Type | Required | Bounds |
| - | - | - | - |
| `cursor` | string | no | 1 to 512 characters; pattern `^[A-Za-z0-9_-]+$` |
| `limit` | integer | no | from 1 to 50 |
| `origin` | string | no | one of `CAMPAIGN`, `MANUAL_ENTRY`, `LINKEDIN_IMPORT`, `CSV_IMPORT`, `BOOKING`, `RESERVATION` |
| `status` | string | no | one of `NEW`, `NEEDS_REVIEW`, `APPROVED`, `SENDING`, `CONTACTED`, `REPLIED`, `SCHEDULED`, `COMPLETED`, `DO_NOT_CONTACT` |

### Example call

```json contacts.list input theme={"system"}
{
  "limit": 10,
  "status": "APPROVED"
}
```

### Result

A result is never larger than 49,152 bytes.
If the stored value does not fit, the tool returns a measured fragment and a cursor for the next one.

This tool's result takes one of these 2 forms, and one call returns exactly one of them. The field named beside each form below says which one you are looking at. Open a form to read its top-level fields.

> **Shape 1**
>
> ```json Top-level fields theme={"system"}
> [
>   "contacts",
>   "nextCursor"
> ]
> ```

> **Shape 2**
>
> ```json Top-level fields theme={"system"}
> [
>   "contacts",
>   "fragment",
>   "nextCursor"
> ]
> ```

### Errors

A failed call returns one protocol error with no error code, and its text is `Tool call failed.` every time. This tool looks up no record by id, so it never sends a text that names a record. The [error codes](/mcp/errors.md) page gives every text a read failure can carry.

## `contacts.get`

Your agent host shows this tool as "Get contact".

Read one authenticated student contact by id or its owner-scoped generated slug.

Permissions: This tool needs `contacts:read`, which the Authorize access page describes as "Read your contacts, network information and research results".

Risk: `read`. This tool only reads your own data. It never changes anything and it never asks for your approval.

### Input

The agent sends one object, and the table below is that object exactly as the connector publishes it. The connector accepts the object in one of these 2 forms and refuses any mixture of them. A published schema cannot state that rule, which is why the Required column reads `no` on every property and why each form is written out under the table. Within a form, every property the form does not name is refused.

| Property | Type | Required | Bounds |
| - | - | - | - |
| `cursor` | string | no | 1 to 512 characters; pattern `^[A-Za-z0-9_-]+$` |
| `id` | string | no | pattern `^[a-f0-9]{24}$` |
| `slug` | string | no | none stated |

#### Form 1, sending `id`

Send `id`. It is required in this form. `cursor` may be sent beside it, and nothing else may be.

#### Form 2, sending `slug`

Send `slug`. It is required in this form. `cursor` may be sent beside it, and nothing else may be.

### Example calls

```json contacts.get input form 1 theme={"system"}
{
  "id": "c0ffeec0ffeec0ffee000001"
}
```

```json contacts.get input form 2 theme={"system"}
{
  "slug": "example-contact-0001"
}
```

### Result

A result is never larger than 49,152 bytes.
If the stored value does not fit, the tool returns a measured fragment and a cursor for the next one.

This tool's result takes one of these 2 forms, and one call returns exactly one of them. The field named beside each form below says which one you are looking at. Open a form to read its top-level fields.

> **Shape 1**
>
> ```json Top-level fields theme={"system"}
> [
>   "contact"
> ]
> ```

> **Shape 2**
>
> ```json Top-level fields theme={"system"}
> [
>   "contact",
>   "fragment",
>   "nextCursor"
> ]
> ```

### Errors

A failed call returns one protocol error with no error code. Its text is `Tool call failed.` in every case but one: a call that finds no contact of yours says so and tells the agent to call the matching list tool. The [error codes](/mcp/errors.md) page quotes that text in full and lists the eight kinds of record it can name.

## `contacts.notes.get`

Your agent host shows this tool as "Get contact notes".

Read one authenticated student contact note in measured fragments.

Permissions: This tool needs `contacts:read`, which the Authorize access page describes as "Read your contacts, network information and research results".

Risk: `read`. This tool only reads your own data. It never changes anything and it never asks for your approval.

### Input

| Property | Type | Required | Bounds |
| - | - | - | - |
| `cursor` | string | no | 1 to 512 characters; pattern `^[A-Za-z0-9_-]+$` |
| `id` | string | yes | pattern `^[a-f0-9]{24}$` |

### Example call

```json contacts.notes.get input theme={"system"}
{
  "id": "c0ffeec0ffeec0ffee000001"
}
```

### Result

A result is never larger than 49,152 bytes.
If the stored value does not fit, the tool returns a measured fragment and a cursor for the next one.

A result carries these top-level fields:

```json Top-level fields theme={"system"}
[
  "fragment",
  "nextCursor"
]
```

### Errors

A failed call returns one protocol error with no error code. Its text is `Tool call failed.` in every case but one: a call that finds no contact of yours says so and tells the agent to call the matching list tool. The [error codes](/mcp/errors.md) page quotes that text in full and lists the eight kinds of record it can name.

## `contacts.tags.get`

Your agent host shows this tool as "Get contact tags".

Read every authenticated student contact tag in measured fragments.

Permissions: This tool needs `contacts:read`, which the Authorize access page describes as "Read your contacts, network information and research results".

Risk: `read`. This tool only reads your own data. It never changes anything and it never asks for your approval.

### Input

| Property | Type | Required | Bounds |
| - | - | - | - |
| `cursor` | string | no | 1 to 512 characters; pattern `^[A-Za-z0-9_-]+$` |
| `id` | string | yes | pattern `^[a-f0-9]{24}$` |

### Example call

```json contacts.tags.get input theme={"system"}
{
  "id": "c0ffeec0ffeec0ffee000001"
}
```

### Result

A result is never larger than 49,152 bytes.
If the stored value does not fit, the tool returns a measured fragment and a cursor for the next one.

A result carries these top-level fields:

```json Top-level fields theme={"system"}
[
  "fragment",
  "nextCursor"
]
```

### Errors

A failed call returns one protocol error with no error code. Its text is `Tool call failed.` in every case but one: a call that finds no contact of yours says so and tells the agent to call the matching list tool. The [error codes](/mcp/errors.md) page quotes that text in full and lists the eight kinds of record it can name.

## `contacts.stats.get`

Your agent host shows this tool as "Get contact stats".

Count every authenticated student contact, including DO\_NOT\_CONTACT records, by status and due follow-up state.

Permissions: This tool needs `contacts:read`, which the Authorize access page describes as "Read your contacts, network information and research results".

Risk: `read`. This tool only reads your own data. It never changes anything and it never asks for your approval.

### Input

This tool takes no input.

### Example call

```json contacts.stats.get input theme={"system"}
{}
```

### Result

A result is never larger than 49,152 bytes.

A result carries these top-level fields:

```json Top-level fields theme={"system"}
[
  "byStatus",
  "followUpsDue",
  "total"
]
```

### Errors

A failed call returns one protocol error with no error code, and its text is `Tool call failed.` every time. This tool looks up no record by id, so it never sends a text that names a record. The [error codes](/mcp/errors.md) page gives every text a read failure can carry.

## `contacts.network.stats.get`

Your agent host shows this tool as "Get network stats".

Count the authenticated student network population after excluding DO\_NOT\_CONTACT records.

Permissions: This tool needs `contacts:read`, which the Authorize access page describes as "Read your contacts, network information and research results".

Risk: `read`. This tool only reads your own data. It never changes anything and it never asks for your approval.

### Input

This tool takes no input.

### Example call

```json contacts.network.stats.get input theme={"system"}
{}
```

### Result

A result is never larger than 49,152 bytes.

A result carries these top-level fields:

```json Top-level fields theme={"system"}
[
  "contacted",
  "new",
  "replied",
  "scheduled",
  "total",
  "unspecified"
]
```

### Errors

A failed call returns one protocol error with no error code, and its text is `Tool call failed.` every time. This tool looks up no record by id, so it never sends a text that names a record. The [error codes](/mcp/errors.md) page gives every text a read failure can carry.

## `contacts.notes.update`

Your agent host shows this tool as "Update contact notes".

What this changes: Replace the private notes on one of the authenticated student's own contacts, with durable idempotency.

The whole note is replaced rather than appended to, at most 8000 characters, and the write fails without changing anything if anybody edited that contact's notes first.

Permissions: This tool needs `contacts:write`, which the Authorize access page describes as "Add contacts, update the notes and tags on your contacts, and change their details and status after you approve each change".

Risk: `safe_write`. This tool changes one of your own settings as soon as the agent calls it. It does not ask you to approve first, because the change affects only your own account and you can set it back yourself.

### Input

| Property | Type | Required | Bounds |
| - | - | - | - |
| `contactId` | string | yes | pattern `^[a-f0-9]{24}$` |
| `idempotencyKey` | string | yes | 16 to 128 characters |
| `notes` | string | yes | at most 8,000 code points |

### Example call

```json contacts.notes.update input theme={"system"}
{
  "contactId": "c0ffeec0ffeec0ffee000001",
  "idempotencyKey": "example-idempotency-key-0006",
  "notes": "Met at the spring fintech mixer. Follow up in April about the referral."
}
```

### Result

A result is never larger than 4,096 bytes.

This tool's result takes one of these 4 forms, and one call returns exactly one of them. The field named beside each form below says which one you are looking at. Open a form to read its top-level fields.

> **Shape 1, where tag is completed**
>
> ```json Top-level fields theme={"system"}
> [
>   "operationId",
>   "operationStatus",
>   "result",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 2, where tag is accepted**
>
> ```json Top-level fields theme={"system"}
> [
>   "operationId",
>   "operationStatus",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 3, where status is failed**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "scenario",
>   "status"
> ]
> ```

> **Shape 4, where status is uncertain**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "scenario",
>   "status"
> ]
> ```

### The result may carry a retry hint

A call can be accepted and still ask the agent to come back later. When that happens the result is an accepted result and not a failure, and it carries a hint beside it holding one of these codes and a delay in milliseconds. Nothing has gone wrong and nothing has been written yet.

| Code | What it means |
| - | - |
| `PERMIT_BACKPRESSURE` | Too much work is already running on this connection. The agent should wait for the stated delay and call the tool again. |
| `PERMIT_CONTENTION` | Another call was holding the same slot. The agent should wait for the stated delay and call the tool again. |

### Errors

A call that does not succeed carries one of these codes.

| Code | What it means |
| - | - |
| `ACTION_DENIED` | Coffee Chats would not let this connection make the change, so nothing was written. The connection may have been revoked, or it may no longer hold the permission this tool needs. |
| `ACTION_EXPIRED` | Coffee Chats did not finish the change inside the short window it allows itself, and stopped rather than write late. Nothing was written. Ask the agent to make the change again. |
| `NOT_APPLIED` | Coffee Chats stopped before writing anything. Your data is exactly as it was. |
| `OUTCOME_UNKNOWN` | Coffee Chats cannot prove whether the change was written and will not guess. Open your Coffee Chats page and look before you decide what to do. Do not ask the agent to retry with a new key, because that could apply the change twice. |
| `SERVICE_UNAVAILABLE` | Coffee Chats could not do the work just now and wrote nothing. Try again later. |
| `VERSION_CONFLICT` | Your stored preference changed after the agent read it, so nothing was written. Ask the agent to read it again and make the change from what is there now. |

## `contacts.tags.update`

Your agent host shows this tool as "Update contact tags".

What this changes: Replace the tags on one of the authenticated student's own contacts, with durable idempotency.

The whole list is replaced rather than merged, at most 25 tags of 60 characters each, and the write fails without changing anything if anybody edited that contact's tags first.

Permissions: This tool needs `contacts:write`, which the Authorize access page describes as "Add contacts, update the notes and tags on your contacts, and change their details and status after you approve each change".

Risk: `safe_write`. This tool changes one of your own settings as soon as the agent calls it. It does not ask you to approve first, because the change affects only your own account and you can set it back yourself.

### Input

| Property | Type | Required | Bounds |
| - | - | - | - |
| `contactId` | string | yes | pattern `^[a-f0-9]{24}$` |
| `idempotencyKey` | string | yes | 16 to 128 characters |
| `tags` | array | yes | at most 25 items; each item at most 60 code points |

### Example call

```json contacts.tags.update input theme={"system"}
{
  "contactId": "c0ffeec0ffeec0ffee000001",
  "idempotencyKey": "example-idempotency-key-0007",
  "tags": [
    "fintech",
    "warm"
  ]
}
```

### Result

A result is never larger than 4,096 bytes.

This tool's result takes one of these 4 forms, and one call returns exactly one of them. The field named beside each form below says which one you are looking at. Open a form to read its top-level fields.

> **Shape 1, where tag is completed**
>
> ```json Top-level fields theme={"system"}
> [
>   "operationId",
>   "operationStatus",
>   "result",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 2, where tag is accepted**
>
> ```json Top-level fields theme={"system"}
> [
>   "operationId",
>   "operationStatus",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 3, where status is failed**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "scenario",
>   "status"
> ]
> ```

> **Shape 4, where status is uncertain**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "scenario",
>   "status"
> ]
> ```

### The result may carry a retry hint

A call can be accepted and still ask the agent to come back later. When that happens the result is an accepted result and not a failure, and it carries a hint beside it holding one of these codes and a delay in milliseconds. Nothing has gone wrong and nothing has been written yet.

| Code | What it means |
| - | - |
| `PERMIT_BACKPRESSURE` | Too much work is already running on this connection. The agent should wait for the stated delay and call the tool again. |
| `PERMIT_CONTENTION` | Another call was holding the same slot. The agent should wait for the stated delay and call the tool again. |

### Errors

A call that does not succeed carries one of these codes.

| Code | What it means |
| - | - |
| `ACTION_DENIED` | Coffee Chats would not let this connection make the change, so nothing was written. The connection may have been revoked, or it may no longer hold the permission this tool needs. |
| `ACTION_EXPIRED` | Coffee Chats did not finish the change inside the short window it allows itself, and stopped rather than write late. Nothing was written. Ask the agent to make the change again. |
| `NOT_APPLIED` | Coffee Chats stopped before writing anything. Your data is exactly as it was. |
| `OUTCOME_UNKNOWN` | Coffee Chats cannot prove whether the change was written and will not guess. Open your Coffee Chats page and look before you decide what to do. Do not ask the agent to retry with a new key, because that could apply the change twice. |
| `SERVICE_UNAVAILABLE` | Coffee Chats could not do the work just now and wrote nothing. Try again later. |
| `VERSION_CONFLICT` | Your stored preference changed after the agent read it, so nothing was written. Ask the agent to read it again and make the change from what is there now. |

## `contacts.create`

Your agent host shows this tool as "Add contact".

What this changes: Add one new person to the authenticated student's contacts, optionally with a status and tags.

Each row reports created, duplicate or error. A row whose email or LinkedIn URL is already on one of the student's contacts, whose name matches a contact at the same company (or a saved contact with no company), or that repeats an earlier row is a duplicate and is not added, with the existing contact's id when known, so a repeated call adds nobody twice. The status is one of NEW, CONTACTED, REPLIED, SCHEDULED, COMPLETED, DO\_NOT\_CONTACT and defaults to NEW. At most 25 tags. Adding contacts uses no credits; the plan's contact limit applies to each row, and once the student has added 200 or more contacts in the last hour, every remaining row reports rate\_limited and is not added.

Permissions: This tool needs `contacts:write`, which the Authorize access page describes as "Add contacts, update the notes and tags on your contacts, and change their details and status after you approve each change".

Risk: `safe_write`. This tool changes one of your own settings as soon as the agent calls it. It does not ask you to approve first, because the change affects only your own account and you can set it back yourself.

### Input

| Property | Type | Required | Bounds |
| - | - | - | - |
| `company` | string | no | at most 120 characters |
| `email` | string | no | at most 254 characters |
| `fullName` | string | yes | 1 to 120 characters |
| `linkedinUrl` | string | no | at most 200 characters |
| `role` | string | no | at most 120 characters |
| `school` | string | no | at most 120 characters |
| `status` | string | no | one of `NEW`, `CONTACTED`, `REPLIED`, `SCHEDULED`, `COMPLETED`, `DO_NOT_CONTACT` |
| `tags` | array | no | at most 25 items |

### Example call

```json contacts.create input theme={"system"}
{
  "company": "Stripe",
  "email": "ada@example.com",
  "fullName": "Ada Park",
  "role": "Product Manager",
  "status": "CONTACTED",
  "tags": [
    "fintech"
  ]
}
```

### Result

A result is never larger than 32,768 bytes.

A result carries these top-level fields:

```json Top-level fields theme={"system"}
[
  "created",
  "duplicates",
  "errors",
  "results"
]
```

### Errors

A call that does not succeed carries one of these codes.

| Code | What it means |
| - | - |
| `SERVICE_UNAVAILABLE` | Coffee Chats could not do the work just now and wrote nothing. Try again later. |

## `contacts.bulk_create`

Your agent host shows this tool as "Add contacts".

What this changes: Add up to 25 new people to the authenticated student's contacts in one call.

The optional top-level status applies to rows without their own. Each row reports created, duplicate or error. A row whose email or LinkedIn URL is already on one of the student's contacts, whose name matches a contact at the same company (or a saved contact with no company), or that repeats an earlier row is a duplicate and is not added, with the existing contact's id when known, so a repeated call adds nobody twice. The status is one of NEW, CONTACTED, REPLIED, SCHEDULED, COMPLETED, DO\_NOT\_CONTACT and defaults to NEW. At most 25 tags. Adding contacts uses no credits; the plan's contact limit applies to each row, and once the student has added 200 or more contacts in the last hour, every remaining row reports rate\_limited and is not added.

Permissions: This tool needs `contacts:write`, which the Authorize access page describes as "Add contacts, update the notes and tags on your contacts, and change their details and status after you approve each change".

Risk: `safe_write`. This tool changes one of your own settings as soon as the agent calls it. It does not ask you to approve first, because the change affects only your own account and you can set it back yourself.

### Input

| Property | Type | Required | Bounds |
| - | - | - | - |
| `contacts` | array | yes | 1 to 25 items |
| `status` | string | no | one of `NEW`, `CONTACTED`, `REPLIED`, `SCHEDULED`, `COMPLETED`, `DO_NOT_CONTACT` |

### Example call

```json contacts.bulk_create input theme={"system"}
{
  "contacts": [
    {
      "company": "Stripe",
      "fullName": "Ada Park"
    },
    {
      "company": "Figma",
      "fullName": "Ben Ode",
      "status": "COMPLETED"
    }
  ],
  "status": "NEW"
}
```

### Result

A result is never larger than 32,768 bytes.

A result carries these top-level fields:

```json Top-level fields theme={"system"}
[
  "created",
  "duplicates",
  "errors",
  "results"
]
```

### Errors

A call that does not succeed carries one of these codes.

| Code | What it means |
| - | - |
| `SERVICE_UNAVAILABLE` | Coffee Chats could not do the work just now and wrote nothing. Try again later. |

## `contacts.import.check`

Your agent host shows this tool as "Check a contacts spreadsheet".

Check a spreadsheet before contacts.import, writing nothing. Takes the same input. Send the sheet as CSV text or as headers and rows. Convert .xlsx files to CSV first. Send csv or headers with rows, not both; rows are arrays of cells in header order. The call must fit in 2 MB and hold at most 1000 rows; split a bigger sheet. Returns which column maps to which contact field, the columns that will be ignored, and the created, skipped and failed counts contacts.import would report against the contacts the student has now, with the first 10 row errors. When the sheet cannot be imported as sent (no header it recognizes, no rows, more than 1000 rows), problem says what to fix and the counts are zero.

Permissions: This tool needs `contacts:read`, which the Authorize access page describes as "Read your contacts, network information and research results".

Risk: `read`. This tool only reads your own data. It never changes anything and it never asks for your approval.

### Input

The agent sends one object, and the table below is that object exactly as the connector publishes it. The connector accepts the object in one of these 2 forms and refuses any mixture of them. A published schema cannot state that rule, which is why the Required column reads `no` on every property and why each form is written out under the table. Within a form, every property the form does not name is refused.

| Property | Type | Required | Bounds |
| - | - | - | - |
| `csv` | string | no | 1 to 1500000 characters |
| `headers` | array | no | 1 to 50 items |
| `rows` | array | no | at least 1 items |

#### Form 1, sending `csv`

Send `csv`. It is required in this form. No other property is accepted with it.

#### Form 2, sending `headers`, `rows`

Send `headers` and `rows`. They are required in this form. No other property is accepted with them.

### Example calls

```json contacts.import.check input form 1 theme={"system"}
{
  "csv": "Name,Email,Company,Title\nAda Park,ada@example.com,Example Labs,Product Manager"
}
```

```json contacts.import.check input form 2 theme={"system"}
{
  "headers": [
    "Name",
    "Email",
    "Company"
  ],
  "rows": [
    [
      "Ada Park",
      "ada@example.com",
      "Example Labs"
    ]
  ]
}
```

### Result

A result is never larger than 32,768 bytes.

A result carries these top-level fields:

```json Top-level fields theme={"system"}
[
  "columns",
  "created",
  "errors",
  "failed",
  "problem",
  "skipped",
  "total",
  "unmappedColumns",
  "updated"
]
```

### Errors

A failed call returns one protocol error with no error code, and its text is `Tool call failed.` every time. This tool looks up no record by id, so it never sends a text that names a record. The [error codes](/mcp/errors.md) page gives every text a read failure can carry.

## `contacts.import`

Your agent host shows this tool as "Import contacts from a spreadsheet".

What this changes: Import a spreadsheet of people into the student's contacts.

Send the sheet as CSV text or as headers and rows. Convert .xlsx files to CSV first. Send csv or headers with rows, not both; rows are arrays of cells in header order. The call must fit in 2 MB and hold at most 1000 rows; split a bigger sheet. Columns such as Name, First name, Last name, Email, Company, Title, School, LinkedIn and Phone are found by name; other columns are ignored. A row needs a name. A row matching one of the student's contacts by email, LinkedIn URL, or name at the same company, or an earlier row, is skipped, so importing the same sheet twice adds nobody. Rows past the plan's contact limit, or past 200 contacts added in the last hour, fail. The details stay private to the student, as with contacts.create. Returns counts (updated is always 0) and the first 10 row errors (row 1 is the first row under the header). Nothing is sent and no research starts.

Permissions: This tool needs `contacts:write`, which the Authorize access page describes as "Add contacts, update the notes and tags on your contacts, and change their details and status after you approve each change".

Risk: `safe_write`. This tool adds people to your contacts as soon as the agent calls it. It never changes a contact you already have, and what it adds stays private to your account. It does not ask you to approve first, because it changes only your own contacts, sends nothing, and you can delete what it added yourself.

### Input

The agent sends one object, and the table below is that object exactly as the connector publishes it. The connector accepts the object in one of these 2 forms and refuses any mixture of them. A published schema cannot state that rule, which is why the Required column reads `no` on every property and why each form is written out under the table. Within a form, every property the form does not name is refused.

| Property | Type | Required | Bounds |
| - | - | - | - |
| `csv` | string | no | 1 to 1500000 characters |
| `headers` | array | no | 1 to 50 items |
| `rows` | array | no | at least 1 items |

#### Form 1, sending `csv`

Send `csv`. It is required in this form. No other property is accepted with it.

#### Form 2, sending `headers`, `rows`

Send `headers` and `rows`. They are required in this form. No other property is accepted with them.

### Example calls

```json contacts.import input form 1 theme={"system"}
{
  "csv": "Name,Email,Company,Title\nAda Park,ada@example.com,Example Labs,Product Manager"
}
```

```json contacts.import input form 2 theme={"system"}
{
  "headers": [
    "Name",
    "Email",
    "Company"
  ],
  "rows": [
    [
      "Ada Park",
      "ada@example.com",
      "Example Labs"
    ]
  ]
}
```

### Result

A result is never larger than 32,768 bytes.

A result carries these top-level fields:

```json Top-level fields theme={"system"}
[
  "created",
  "errors",
  "failed",
  "skipped",
  "total",
  "updated"
]
```

### Errors

A row that cannot be added is counted in `failed` with its reason, and is not a failure of the call. A call Coffee Chats refuses before importing anything, such as a sheet with more than 1000 rows or with no name column, writes nothing and returns an error whose text names what to fix. Otherwise a call that does not succeed carries this code.

| Code | What it means |
| - | - |
| `SERVICE_UNAVAILABLE` | Coffee Chats could not do the work just now and wrote nothing. Try again later. |

## `contacts.update`

Your agent host shows this tool as "Propose contact changes".

What this changes: Propose changes to the details, status, rating, tags or notes of 1 to 25 of the authenticated student's contacts, one item per contact.

Nothing changes until the student approves each change, in the approval card in Claude or ChatGPT or in a browser. A contact that changed after the student saw the changes is skipped, never overwritten. Read the outcome with contacts.update.get; calling this tool again with the same idempotencyKey returns the same proposal.

Permissions: This tool needs `contacts:read`, which the Authorize access page describes as "Read your contacts, network information and research results", and `contacts:write`, which the Authorize access page describes as "Add contacts, update the notes and tags on your contacts, and change their details and status after you approve each change".

Risk: `consequential`. This tool cannot change anything on its own. It proposes the changes, and nothing happens until you approve exactly what is shown, in the approval card in Claude or ChatGPT or on the Coffee Chats page it points to. A contact that changed after you saw the changes is left as it is.

### Input

| Property | Type | Required | Bounds |
| - | - | - | - |
| `idempotencyKey` | string | yes | 16 to 128 characters |
| `items` | array | yes | 1 to 25 items |

### Example call

```json contacts.update input theme={"system"}
{
  "idempotencyKey": "example-idempotency-key-0013",
  "items": [
    {
      "changes": {
        "notesAppend": "Met at the fintech career fair.",
        "rating": 4,
        "role": "Senior Product Manager",
        "status": "CONTACTED",
        "tagsAdd": [
          "fintech"
        ]
      },
      "contactId": "c0ffeec0ffeec0ffee000001"
    }
  ]
}
```

### Result

A result is never larger than 32,768 bytes.

This tool's result takes one of these 6 forms, and one call returns exactly one of them. The field named beside each form below says which one you are looking at. Open a form to read its top-level fields.

> **Shape 1, where tag is requires\_confirmation**
>
> ```json Top-level fields theme={"system"}
> [
>   "actionStatus",
>   "handoff",
>   "nextStep",
>   "operationId",
>   "preview",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 2, where tag is completed**
>
> ```json Top-level fields theme={"system"}
> [
>   "operationId",
>   "operationStatus",
>   "result",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 3, where tag is accepted**
>
> ```json Top-level fields theme={"system"}
> [
>   "operationId",
>   "operationStatus",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 4, where status is failed**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "result",
>   "scenario",
>   "status"
> ]
> ```

> **Shape 5, where status is cancelled**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "scenario",
>   "status"
> ]
> ```

> **Shape 6, where status is uncertain**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "scenario",
>   "status"
> ]
> ```

### The result may carry a retry hint

A call can be accepted and still ask the agent to come back later. When that happens the result is an accepted result and not a failure, and it carries a hint beside it holding one of these codes and a delay in milliseconds. Nothing has gone wrong and nothing has been written yet.

| Code | What it means |
| - | - |
| `PERMIT_BACKPRESSURE` | Too much work is already running on this connection. The agent should wait for the stated delay and call the tool again. |
| `PERMIT_CONTENTION` | Another call was holding the same slot. The agent should wait for the stated delay and call the tool again. |

### Errors

A call that does not succeed carries one of these codes. A call Coffee Chats refuses before proposing anything, such as a rating outside 0.5 to 5 or not in half steps, a batch too large to show for approval or a batch in which every contact would be left out, creates no review request and returns an error whose text names what to fix.

| Code | What it means |
| - | - |
| `ACTION_DENIED` | Either you declined this review request, or the connection was no longer allowed to make the change when Coffee Chats came to apply it. Nothing was written. |
| `ACTION_EXPIRED` | The review request ran out of time, either before you decided or before Coffee Chats could apply it. Nothing was written. |
| `NOT_APPLIED` | Coffee Chats stopped before writing anything. Your data is exactly as it was. |
| `OPERATION_CANCELLED` | The connection was revoked before the change started, so nothing was written. |
| `OUTCOME_UNKNOWN` | Coffee Chats cannot prove whether the change was written and will not guess. Open your Coffee Chats page and look before you decide what to do. Do not ask the agent to retry with a new key, because that could apply the change twice. |
| `SERVICE_UNAVAILABLE` | Coffee Chats could not do the work just now and wrote nothing. Try again later. |
| `VERSION_CONFLICT` | The idempotency key was already used for different changes, so nothing new was proposed. The `operationId` names the earlier review request. Send the new changes with a new key. |

## `contacts.update.get`

Your agent host shows this tool as "Get contact update status".

Read the state of a contact update proposed with contacts.update: waiting for approval, applying, or done with the contacts it updated and the ones it left unchanged and why. Polling here shows no approval card.

Permissions: This tool needs `contacts:write`, which the Authorize access page describes as "Add contacts, update the notes and tags on your contacts, and change their details and status after you approve each change".

Risk: `read`. This tool only reads your own data. It never changes anything and it never asks for your approval.

### Input

| Property | Type | Required | Bounds |
| - | - | - | - |
| `operationId` | string | yes | pattern `^[a-f0-9]{24}$` |

### Example call

```json contacts.update.get input theme={"system"}
{
  "operationId": "0123456789abcdef01234567"
}
```

### Result

A result is never larger than 8,192 bytes.

This tool's result takes one of these 6 forms, and one call returns exactly one of them. The field named beside each form below says which one you are looking at. Open a form to read its top-level fields.

> **Shape 1, where tag is requires\_confirmation**
>
> ```json Top-level fields theme={"system"}
> [
>   "actionStatus",
>   "handoff",
>   "nextStep",
>   "operationId",
>   "preview",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 2, where tag is completed**
>
> ```json Top-level fields theme={"system"}
> [
>   "operationId",
>   "operationStatus",
>   "result",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 3, where tag is accepted**
>
> ```json Top-level fields theme={"system"}
> [
>   "operationId",
>   "operationStatus",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 4, where status is failed**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "result",
>   "scenario",
>   "status"
> ]
> ```

> **Shape 5, where status is cancelled**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "scenario",
>   "status"
> ]
> ```

> **Shape 6, where status is uncertain**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "scenario",
>   "status"
> ]
> ```

### The result may carry a retry hint

A call can be accepted and still ask the agent to come back later. When that happens the result is an accepted result and not a failure, and it carries a hint beside it holding one of these codes and a delay in milliseconds. Nothing has gone wrong and nothing has been written yet.

| Code | What it means |
| - | - |
| `PERMIT_BACKPRESSURE` | Too much work is already running on this connection. The agent should wait for the stated delay and call the tool again. |
| `PERMIT_CONTENTION` | Another call was holding the same slot. The agent should wait for the stated delay and call the tool again. |

### Errors

A failed call returns one protocol error with no error code, and its text is `Tool call failed.` every time, including for an `operationId` that names no review request this connection made. The [error codes](/mcp/errors.md) page gives every text a read failure can carry.

## `contacts.research.start`

Your agent host shows this tool as "Propose contact research".

What this changes: Start research on 1 to 25 of the authenticated student's contacts (people only).

Research uses credits: a contact's first research costs 5 credits, and a contact that has a booked meeting or was already charged is free. This proposes one batch with its cost and the student's available balance; nothing starts until after the student approves the batch in the browser, which the approval card links to. Contacts with no company or LinkedIn, or already being researched, are left out. Read each contact's research with contacts.research.get; calling this tool again with the same idempotencyKey returns the same proposal. A connection made before research was available must reconnect to grant research:run before it can use this tool.

Permissions: This tool needs `contacts:read`, which the Authorize access page describes as "Read your contacts, network information and research results", and `research:run`, which the Authorize access page describes as "Start research on your contacts that uses credits, after you approve each batch in the browser".

Risk: `consequential`. This tool cannot start research on its own. It proposes one batch with its cost in credits and your available balance, and nothing starts until you approve the batch on the Coffee Chats page it points to. Research spends credits, so it is never approved in the approval card. When the batch runs it starts no more paid research than the cost you approved, or than your balance when it starts.

### Input

| Property | Type | Required | Bounds |
| - | - | - | - |
| `contactIds` | array | yes | 1 to 25 items |
| `idempotencyKey` | string | yes | 16 to 128 characters |

### Example call

```json contacts.research.start input theme={"system"}
{
  "contactIds": [
    "c0ffeec0ffeec0ffee000001"
  ],
  "idempotencyKey": "example-idempotency-key-0014"
}
```

### Result

A result is never larger than 32,768 bytes.

This tool's result takes one of these 6 forms, and one call returns exactly one of them. The field named beside each form below says which one you are looking at. Open a form to read its top-level fields.

> **Shape 1, where tag is requires\_confirmation**
>
> ```json Top-level fields theme={"system"}
> [
>   "actionStatus",
>   "handoff",
>   "nextStep",
>   "operationId",
>   "preview",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 2, where tag is completed**
>
> ```json Top-level fields theme={"system"}
> [
>   "operationId",
>   "operationStatus",
>   "result",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 3, where tag is accepted**
>
> ```json Top-level fields theme={"system"}
> [
>   "operationId",
>   "operationStatus",
>   "scenario",
>   "tag"
> ]
> ```

> **Shape 4, where status is failed**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "result",
>   "scenario",
>   "status"
> ]
> ```

> **Shape 5, where status is cancelled**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "scenario",
>   "status"
> ]
> ```

> **Shape 6, where status is uncertain**
>
> ```json Top-level fields theme={"system"}
> [
>   "error",
>   "operationId",
>   "scenario",
>   "status"
> ]
> ```

### The result may carry a retry hint

A call can be accepted and still ask the agent to come back later. When that happens the result is an accepted result and not a failure, and it carries a hint beside it holding one of these codes and a delay in milliseconds. Nothing has gone wrong and nothing has been written yet.

| Code | What it means |
| - | - |
| `PERMIT_BACKPRESSURE` | Too much work is already running on this connection. The agent should wait for the stated delay and call the tool again. |
| `PERMIT_CONTENTION` | Another call was holding the same slot. The agent should wait for the stated delay and call the tool again. |

### Errors

A call that does not succeed carries one of these codes. A call Coffee Chats refuses before proposing anything, such as a repeated contact, a batch too large to show for approval or a batch in which every contact would be left out, creates no review request and returns an error whose text names what to fix.

| Code | What it means |
| - | - |
| `ACTION_DENIED` | Either you declined this review request, or the connection was no longer allowed to make the change when Coffee Chats came to apply it. Nothing was written. |
| `ACTION_EXPIRED` | The review request ran out of time, either before you decided or before Coffee Chats could apply it. Nothing was written. |
| `NOT_APPLIED` | Coffee Chats stopped before writing anything. Your data is exactly as it was. |
| `OPERATION_CANCELLED` | The connection was revoked before the change started, so nothing was written. |
| `OUTCOME_UNKNOWN` | Coffee Chats cannot prove whether the change was written and will not guess. Open your Coffee Chats page and look before you decide what to do. Do not ask the agent to retry with a new key, because that could apply the change twice. |
| `SERVICE_UNAVAILABLE` | Coffee Chats could not do the work just now and wrote nothing. Try again later. |
| `VERSION_CONFLICT` | The idempotency key was already used for different contacts, so nothing new was proposed. The `operationId` names the earlier review request. Send the new contacts with a new key. |

## `contacts.research.get`

Your agent host shows this tool as "Get contact research".

Read the research status and research results for one of the authenticated student's contacts. running is false when nothing is researching the contact now: a pending or in-progress run whose lease went stale keeps its status but is not running. When a report exists, report summarizes the one-pager: the executive summary, bottom line, sections and sources, with its completedAt. truncated is true when text was clipped or parts were dropped to fit the page. Polling here shows no approval card.

Permissions: This tool needs `contacts:read`, which the Authorize access page describes as "Read your contacts, network information and research results".

Risk: `read`. This tool only reads your own data. It never changes anything and it never asks for your approval.

### Input

| Property | Type | Required | Bounds |
| - | - | - | - |
| `contactId` | string | yes | pattern `^[a-f0-9]{24}$` |

### Example call

```json contacts.research.get input theme={"system"}
{
  "contactId": "c0ffeec0ffeec0ffee000001"
}
```

### Result

A result is never larger than 16,384 bytes.

A result carries these top-level fields:

```json Top-level fields theme={"system"}
[
  "contactId",
  "report",
  "running",
  "status",
  "truncated"
]
```

### Errors

A failed call returns one protocol error with no error code. Its text is `Tool call failed.` in every case but one: a call that finds no contact of yours says so and tells the agent to call the matching list tool. The [error codes](/mcp/errors.md) page quotes that text in full and lists the eight kinds of record it can name.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.