These endpoints read and create the people and companies in your workspace. They follow the same rules as the screens: duplicates are caught, plan limits apply, and erased or merged contacts are gone. The shared rules for keys, JSON and limits are in REST API.
The contact object
Every contact endpoint returns contacts with these fields:
| Field | Type | Meaning |
|---|---|---|
id |
string | The contact's UUID |
name |
string | Full name of the person, or the company's name |
company_name |
string or null | Company the person works for |
job_title |
string or null | Job title |
email |
string or null | Email address, lower-cased |
phone |
string or null | Phone in one comparable form, such as +966500000000 |
status |
string | lead, prospect, customer or inactive |
source |
string | manual, website, referral, phone, messaging, social, event or other |
country |
string or null | Two-letter country code, such as SA |
preferred_locale |
string or null | en, ar, tr or fr |
marketing_opt_out |
boolean | true when the contact must not receive marketing |
archived |
boolean | true when the contact is archived |
The owner, the revision number and the internal history are not part of the API.
List and search contacts
GET /contacts?q=SEARCH&page=1
| Parameter | Rules |
|---|---|
q |
Optional. Up to 120 characters. Matches words in the name, company name, email and phone |
page |
Optional. A whole number from 1 to 99999. Defaults to 1 |
curl "https://rutbacrm.com/api/v1/contacts?q=maryam" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
{
"data": [
{
"id": "CONTACT_ID",
"archived": false,
"marketing_opt_out": false,
"name": "Maryam Saleh",
"company_name": null,
"job_title": null,
"email": "maryam@example.com",
"phone": "+966500000001",
"preferred_locale": "ar",
"country": "SA",
"source": "website",
"status": "lead"
}
],
"page": 1,
"pages": 1,
"total": 1
}
Results come 25 a page, most recently updated first. The list leaves out archived contacts, and contacts that were merged into another or erased. A q longer than 120 characters or a page that is not a valid number returns 422.
Read one contact
GET /contacts/CONTACT_ID
Returns the contact with two extra fields:
| Field | Meaning |
|---|---|
tags |
The contact's tag names, alphabetical, up to 20 |
custom_fields |
An object of custom field keys and values, when your plan includes Custom fields. Values are strings, or null when empty. A ticked checkbox is "1". Without the feature, or with no fields, this is an empty list [] |
{
"data": {
"id": "CONTACT_ID",
"archived": false,
"marketing_opt_out": false,
"name": "Maryam Saleh",
"company_name": null,
"job_title": null,
"email": "maryam@example.com",
"phone": "+966500000001",
"preferred_locale": "ar",
"country": "SA",
"source": "website",
"status": "lead",
"tags": ["VIP"],
"custom_fields": {
"city": "Riyadh"
}
}
}
A custom field's key is made from its label when the field is created, so a field labelled "City" has the key city. An archived contact is still returned, with "archived": true. A contact that was erased or merged into another returns 404, as does an ID from another company.
Create a contact
POST /contacts
Needs a Read and write key whose creator is a member, administrator or owner.
| Field | Required | Rules |
|---|---|---|
name |
Yes | Up to 160 characters |
email |
No | A valid address, up to 254 characters. Stored lower-cased |
phone |
No | 6 to 15 digits, with an optional leading +. Spaces, dashes, brackets and Arabic digits are cleaned up, and a leading 00 becomes + |
company_name |
No | Up to 160 characters |
job_title |
No | Up to 120 characters |
status |
No | lead, prospect, customer or inactive. Defaults to lead |
source |
No | One of the sources in the table above. Defaults to manual |
country |
No | Two letters. Stored upper-case |
preferred_locale |
No | en, ar, tr or fr |
confirm_duplicate |
No | true to create the contact even when it may be a duplicate |
Other fields are ignored. Contacts created through the API are always people, with no owner. Tags and custom fields cannot be set through the API.
curl -X POST https://rutbacrm.com/api/v1/contacts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"name": "Maryam Saleh", "email": "maryam@example.com", "phone": "+966 50 000 0001", "source": "website"}'
A new contact returns 201:
{
"data": {
"id": "CONTACT_ID"
}
}
Possible duplicates
If another contact already has the same email or the same phone, nothing is created and the response is 409, with the IDs of up to five matching contacts:
{
"message": "Possible duplicate.",
"matches": ["EXISTING_CONTACT_ID"]
}
Use the existing contact, or send the same request again with "confirm_duplicate": true if it really is a different person.
Validation errors
Invalid fields return 422 with an errors object:
{
"message": "The Name field is required. (and 1 more error)",
"errors": {
"name": ["The Name field is required."],
"email": ["The Email field must be a valid email address."]
}
}
When your plan has a contact limit and it is reached, the response is 422 with the error under limit.
Read a contact's orders
GET /contacts/CONTACT_ID/orders
Returns the orders that connected stores sent for this contact. Quote requests are not included.
{
"data": {
"orders": [
{
"number": "1001",
"status": "completed",
"total": "1,250.50 SAR",
"campaign": "ramadan-sale",
"placed_at": "2026-11-06T10:00:00.000000Z"
}
],
"count": 3,
"totals": {
"SAR": "3,480.00 SAR"
}
}
}
| Field | Meaning |
|---|---|
orders |
The 10 newest orders, newest first |
orders[].number |
The store's order number, or the store's order ID when it sent no number |
orders[].status |
The status the store sent, lower-cased |
orders[].total |
The total formatted with its currency, for display |
orders[].campaign |
The campaign name, or the campaign source, or null |
count |
How many of the contact's orders count as sales: paid, processing, completed or delivered |
totals |
The sum of those sales for each currency, formatted |
Add a note
POST /contacts/CONTACT_ID/notes
Needs a Read and write key whose creator is a member, administrator or owner.
| Field | Required | Rules |
|---|---|---|
body |
Yes | Text up to 5000 characters. Leading and trailing spaces are removed |
curl -X POST https://rutbacrm.com/api/v1/contacts/CONTACT_ID/notes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"body": "Asked about delivery to Jeddah."}'
{
"data": {
"saved": true
}
}
The note appears on the contact's page and in its activity, written by the person who created the key. An erased or merged contact returns 404 and takes no note.
Good to know
- Contacts created through the API do not count as new leads for automations, sequences or the A new lead arrives webhook. That event comes from website forms, stores and messaging channels.
- The API never overwrites a contact. To change a contact, use the app.
- Notes are not deduplicated. Sending the same note twice adds it twice.
- Merging contacts in the app makes the merged one return
404. Look the person up again withGET /contacts?q=to find the contact that was kept.
Last updated 2026-11-08