Contacts API
List, get, create, update, and delete contacts through the REST API.
Overview
The contacts endpoints expose every operation you can do on the contact book through the dashboard. Identity fields, tags, and "introduced by" links are all editable through PATCH. Manual contacts can be created and deleted; Google-sourced contacts can be edited but not created via the API (their identity is owned by Google).
For what each field means in product terms, see the user-facing Contacts page.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/contacts | List contacts with filters and pagination. |
POST | /api/v1/contacts | Create a manual contact. |
GET | /api/v1/contacts/:id | Get a single contact. |
PATCH | /api/v1/contacts/:id | Update a contact. |
DELETE | /api/v1/contacts/:id | Delete a contact. |
List Contacts
GET /api/v1/contacts?query=acme&source=google&sortBy=recent&limit=50&offset=0Query parameters
| Param | Type | Default | Notes |
|---|---|---|---|
query | string | — | Matches name, email, or company. |
tags | string | — | Comma-separated tag IDs. Filters to contacts who have all the listed tags. |
source | all | google | manual | all | Limits to a contact source. |
sortBy | recent | name | recent | Sort field. |
sortOrder | asc | desc | desc | Sort direction. |
limit | number | 50 | Max 100. |
offset | number | 0 | Skip count for pagination. |
Response
{
"data": [
{
"id": "8a1d...",
"givenName": "Alice",
"familyName": "Nguyen",
"primaryEmail": "alice@example.com",
"emails": ["alice@example.com"],
"phones": ["+1 555 0100"],
"company": "Acme Corp",
"jobTitle": "CTO",
"source": "google",
"tags": [{ "id": "...", "name": "investor" }],
"introducedBy": null,
"lastContactAt": "2026-04-30T19:21:00.000Z",
"createdAt": "2026-01-12T10:00:00.000Z",
"updatedAt": "2026-04-30T19:21:00.000Z"
}
],
"total": 247
}total is the count before pagination, so you can render "showing 1–50 of 247" client-side.
Create a Contact
POST /api/v1/contacts
Content-Type: application/json
{
"givenName": "Carol",
"familyName": "Mendes",
"primaryEmail": "carol@startup.io",
"company": "Startup",
"tagIds": ["8c2b...", "founder"],
"introducedById": "9d3e..."
}Body fields
At least one of givenName, familyName, company, or primaryEmail is required.
| Field | Type | Notes |
|---|---|---|
givenName, familyName | string (≤100 chars) | First / last name. |
primaryEmail | email (≤320 chars) | Optional. The "main" email for the contact. |
emails | array of emails | Additional emails. |
phones | array of strings (≤50 chars each) | Phone numbers in any format you use. |
company | string (≤200 chars) | |
jobTitle | string (≤200 chars) | |
birthdayYear, birthdayMonth, birthdayDay | integers | All optional. Year 1900–current. |
tagIds | array of strings | UUIDs of existing tags or plain tag names — names are upserted. |
introducedById | UUID or null | The contact who introduced you to this person. |
Response
201 Created
{
"data": { "id": "...", "givenName": "Carol", ... }
}Validation errors
422 Unprocessable Entity
{
"error": "Validation failed",
"details": {
"fieldErrors": {
"givenName": ["Too long"]
},
"formErrors": ["Enter at least one of First name, Last name, Company, or Email."]
}
}Get a Contact
GET /api/v1/contacts/8a1d...Response
{ "data": { "id": "...", "givenName": "Alice", ... } }404 if the contact doesn't exist or doesn't belong to your account.
Update a Contact
PATCH /api/v1/contacts/8a1d...
Content-Type: application/json
{
"company": "New Acme Corp",
"tagIds": ["customer"],
"introducedById": null
}The body uses the same field shape as create, with two differences:
- All fields are optional. Only the keys you include are updated.
- Most identity fields accept
nullto clear the value (e.g.,"company": nullclears the company). tagIds: []clears all tags.tagIds: ["a"]replaces the entire set with["a"]— patch semantics are full-set replacement, not delta.
After the update, the contact must still have at least one of givenName, familyName, company, or primaryEmail. A patch that empties all four is rejected with 422.
Response
{ "data": { ... } }Delete a Contact
DELETE /api/v1/contacts/8a1d...Response
{ "success": true }In read-only Google sync mode, deleting a Google-sourced contact returns an error — the next sync would resurrect it. See Why some deletes are blocked for the rules and what to do instead.
There is no recycle bin for deleted contacts today. The deleted record is kept in storage but not reachable from the API or the UI.
Example: Creating a Contact in JavaScript
const res = await fetch("https://app.remy.com/api/v1/contacts", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.REMY_API_KEY}`,
},
body: JSON.stringify({
givenName: "Carol",
primaryEmail: "carol@startup.io",
tagIds: ["founder"],
}),
});
if (!res.ok) {
console.error(await res.json());
throw new Error(`Failed: ${res.status}`);
}
const { data: contact } = await res.json();
console.log("Created", contact.id);Audit Trail
Every mutation through these endpoints lands in the Activity Log with authMethod: "api_key" and the IP and user-agent of the calling client. Helpful when debugging which script touched which contact.