Contacts API
Contacts API
Contacts represent individual people at organizations. Each contact can have multiple email addresses and phone numbers, and can be associated with an organization.
Endpoints Overview
| Method | Endpoint | Description |
|---|---|---|
| GET | /api-v1-contacts | List all contacts with filtering, sorting, and pagination |
| GET | /api-v1-contacts/:id | Get a single contact with related data |
| POST | /api-v1-contacts | Create a new contact (with optional emails and phones) |
| PATCH | /api-v1-contacts/:id | Update an existing contact |
| DELETE | /api-v1-contacts/:id | Soft delete a contact |
Required Scopes
- Read operations:
read:contacts - Write operations:
write:contacts
List Contacts
List all contacts with support for filtering, sorting, pagination, and expanding related data.
Endpoint: GET /api-v1-contacts
Scope: read:contacts
Query Parameters
Pagination
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Number of records per page (max: 100) |
page | integer | 1 | Page number for offset-based pagination |
per_page | integer | 50 | Alternative to limit for offset-based pagination |
cursor | string | - | Cursor for cursor-based pagination |
Filtering
Filters can be applied using the following syntax:
- Simple equality:
?first_name=John - With operator:
?first_name[like]=Johnor?created_at[gte]=2024-01-01
Supported operators: eq, ne, gt, gte, lt, lte, like, in
Filterable fields:
first_name- Contact’s first name (string)last_name- Contact’s last name (string)job_title- Job title (string)organization_id- Associated organization UUIDis_primary- Primary contact flag (boolean)created_at- Creation timestamp (ISO 8601)
Sorting
Use the sort parameter with field name. Prefix with - for descending order.
Examples:
?sort=first_name- Sort by first name ascending?sort=-created_at- Sort by creation date descending
Sortable fields: first_name, last_name, created_at, updated_at, job_title
Including Related Data
Use the include parameter to expand related resources in the response:
?include=organization,emails,phonesAvailable includes:
organization- Include organization detailsemails- Include contact email addressesphones- Include contact phone numbers
Response
Returns a paginated list of contacts.
Without includes:
{ "data": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "first_name": "John", "last_name": "Doe", "job_title": "Purchasing Manager", "organization_id": "987e6543-e21b-12d3-a456-426614174999", "is_primary": true, "consent_to_record": true, "metadata": {}, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } ], "pagination": { "total": 250, "limit": 50, "page": 1, "per_page": 50, "total_pages": 5, "has_more": true }}With includes:
{ "data": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "first_name": "John", "last_name": "Doe", "job_title": "Purchasing Manager", "organization_id": "987e6543-e21b-12d3-a456-426614174999", "is_primary": true, "consent_to_record": true, "metadata": {}, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z", "organization": { "id": "987e6543-e21b-12d3-a456-426614174999", "name": "Acme Retail Shop", "status": "active" }, "contact_emails": [ { "id": "email-uuid-1", "email": "john.doe@acme-shop.de", "type": "work", "is_primary": true } ], "contact_phones": [ { "id": "phone-uuid-1", "phone_number": "+49301234567", "type": "work", "is_primary": true } ] } ], "pagination": { "total": 250, "limit": 50, "page": 1, "per_page": 50, "total_pages": 5, "has_more": true }}Example Request
curl -X GET "https://your-project.supabase.co/functions/v1/api-v1-contacts?limit=20&include=organization,emails&sort=-created_at" \ -H "Authorization: Bearer YOUR_API_KEY"Get Single Contact
Retrieve a single contact by its unique ID with all related data.
Endpoint: GET /api-v1-contacts/:id
Scope: read:contacts
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | Contact ID |
Query Parameters
| Parameter | Type | Description |
|---|---|---|
include | string | Comma-separated list: organization, emails, phones |
Note: By default, single contact requests include all related data (organization, emails, phones).
Response
Returns a single contact with all related data.
{ "data": { "id": "123e4567-e89b-12d3-a456-426614174000", "first_name": "John", "last_name": "Doe", "job_title": "Purchasing Manager", "organization_id": "987e6543-e21b-12d3-a456-426614174999", "is_primary": true, "consent_to_record": true, "metadata": {}, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z", "organization": { "id": "987e6543-e21b-12d3-a456-426614174999", "name": "Acme Retail Shop", "status": "active" }, "contact_emails": [ { "id": "email-uuid-1", "email": "john.doe@acme-shop.de", "type": "work", "is_primary": true } ], "contact_phones": [ { "id": "phone-uuid-1", "phone_number": "+49301234567", "type": "work", "is_primary": true } ] }}Example Request
curl -X GET "https://your-project.supabase.co/functions/v1/api-v1-contacts/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY"Error Responses
404 Not Found:
{ "error": { "code": "NOT_FOUND", "message": "Contact not found" }}Create Contact
Create a new contact with optional email addresses and phone numbers.
Endpoint: POST /api-v1-contacts
Scope: write:contacts
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
first_name | string | Yes | Contact’s first name (trimmed, non-empty) |
last_name | string | No | Contact’s last name |
job_title | string | No | Job title or role |
organization_id | UUID | No | Associated organization (must exist in your company) |
is_primary | boolean | No | Whether this is the primary contact (default: false) |
consent_to_record | boolean | No | GDPR consent for call recording (default: false) |
metadata | object | No | Custom metadata as JSON object |
emails | array | No | Array of email objects (see below) |
phones | array | No | Array of phone objects (see below) |
Email Object Structure
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address |
type | string | No | Email type (default: “work”) |
is_primary | boolean | No | Primary email flag (default: true for first email) |
Phone Object Structure
| Field | Type | Required | Description |
|---|---|---|---|
phone_number | string | Yes | Phone number (preferably E.164 format) |
type | string | No | Phone type (default: “work”) |
is_primary | boolean | No | Primary phone flag (default: true for first phone) |
Response
Returns the created contact with all related data and a 201 Created status.
{ "data": { "id": "123e4567-e89b-12d3-a456-426614174000", "first_name": "John", "last_name": "Doe", "job_title": "Purchasing Manager", "organization_id": "987e6543-e21b-12d3-a456-426614174999", "is_primary": true, "consent_to_record": false, "metadata": {}, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z", "organization": { "id": "987e6543-e21b-12d3-a456-426614174999", "name": "Acme Retail Shop", "status": "active" }, "contact_emails": [ { "id": "email-uuid-1", "email": "john.doe@acme-shop.de", "type": "work", "is_primary": true } ], "contact_phones": [ { "id": "phone-uuid-1", "phone_number": "+49301234567", "type": "work", "is_primary": true } ] }}Example Request
curl -X POST "https://your-project.supabase.co/functions/v1/api-v1-contacts" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "first_name": "John", "last_name": "Doe", "job_title": "Purchasing Manager", "organization_id": "987e6543-e21b-12d3-a456-426614174999", "is_primary": true, "emails": [ { "email": "john.doe@acme-shop.de", "type": "work", "is_primary": true }, { "email": "john@personal.com", "type": "personal", "is_primary": false } ], "phones": [ { "phone_number": "+49301234567", "type": "work", "is_primary": true } ] }'Error Responses
400 Validation Error (missing first_name):
{ "error": { "code": "VALIDATION_ERROR", "message": "First name is required", "details": [ { "field": "first_name", "message": "First name is required" } ] }}400 Validation Error (invalid organization):
{ "error": { "code": "VALIDATION_ERROR", "message": "Organization not found", "details": [ { "field": "organization_id", "message": "Organization not found or does not belong to your company" } ] }}Update Contact
Update an existing contact. Only provided fields will be updated. Note: This endpoint does not update emails or phones. Use separate endpoints for managing those.
Endpoint: PATCH /api-v1-contacts/:id
Scope: write:contacts
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | Contact ID |
Request Body
All fields are optional. Only include fields you want to update.
| Field | Type | Description |
|---|---|---|
first_name | string | Contact’s first name |
last_name | string | Contact’s last name |
job_title | string | Job title or role |
organization_id | UUID or null | Associated organization (set to null to remove) |
is_primary | boolean | Primary contact flag |
consent_to_record | boolean | GDPR consent for call recording |
metadata | object | Custom metadata (replaces existing) |
Response
Returns the updated contact with all related data.
{ "data": { "id": "123e4567-e89b-12d3-a456-426614174000", "first_name": "John", "last_name": "Doe", "job_title": "Senior Purchasing Manager", "organization_id": "987e6543-e21b-12d3-a456-426614174999", "is_primary": true, "consent_to_record": true, "metadata": {}, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-16T14:20:00Z", "organization": { "id": "987e6543-e21b-12d3-a456-426614174999", "name": "Acme Retail Shop", "status": "active" }, "contact_emails": [ { "id": "email-uuid-1", "email": "john.doe@acme-shop.de", "type": "work", "is_primary": true } ], "contact_phones": [ { "id": "phone-uuid-1", "phone_number": "+49301234567", "type": "work", "is_primary": true } ] }}Example Request
curl -X PATCH "https://your-project.supabase.co/functions/v1/api-v1-contacts/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "job_title": "Senior Purchasing Manager", "consent_to_record": true }'Error Responses
404 Not Found:
{ "error": { "code": "NOT_FOUND", "message": "Contact not found" }}400 No Fields:
{ "error": { "code": "INVALID_REQUEST", "message": "No fields to update" }}Delete Contact
Soft delete a contact. The contact is not permanently deleted but marked with a deleted_at timestamp and will no longer appear in API responses.
Endpoint: DELETE /api-v1-contacts/:id
Scope: write:contacts
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | Contact ID |
Response
Returns a deletion confirmation.
{ "data": { "deleted": true }}Example Request
curl -X DELETE "https://your-project.supabase.co/functions/v1/api-v1-contacts/123e4567-e89b-12d3-a456-426614174000" \ -H "Authorization: Bearer YOUR_API_KEY"Error Responses
404 Not Found:
{ "error": { "code": "NOT_FOUND", "message": "Contact not found" }}Contact Fields Explained
is_primary
Boolean flag indicating whether this contact is the primary contact for their organization. Useful for identifying the main point of contact.
consent_to_record
Boolean flag for GDPR compliance. Indicates whether the contact has given consent for call recording. This should be set to true before recording calls with this contact.
metadata
Custom JSON object for storing additional data specific to your use case. The API does not validate the structure of this field.
Multi-Tenant Isolation
All contacts are automatically scoped to your company. You can only access contacts that belong to your company (determined by your API key’s company_id).
When creating a contact with an organization_id, the organization must also belong to your company.
Related Resources
- Organizations: Contacts can belong to an organization. See Organizations API
- Deals: Contacts can be associated with deals. See Deals API