Skip to main content

Contacts API

The Contacts API is available under /apis/v1/contact and requires a Ration bearer token. All contacts and address books are scoped to the authenticated user and tenant.

Authorization: Bearer <ration_access_token>
Content-Type: application/json

Ration supports CardDAV interoperability. The REST routes below are the API-client equivalent for address-book and contact management.

Address books

Default books

GET /apis/v1/contact/addressbooks returns { "results": [AddressBook] }, including these built-in concepts:

Bookspecial_useBehavior
My ContactselfThe caller's own profile contact. It is read-only as an address-book container: it cannot be renamed, deleted, bulk-moved, imported into, or used for CardDAV-style sync. Update it through /contact/self.
Global directoryglobalTenant directory of organization members plus administrator-managed contacts. Retrieve it with the dedicated /global endpoint. It is not a writable personal import target.
Personal booksBooks created by the caller and used for ordinary contact CRUD, imports, and sync.

An address-book object contains id, name, destructible, type, sync_token, created_at, updated_at, and special_use.

Create, update, delete, or bulk move

MethodPathRequestSuccessful response
POST/addressbooks{ "name": "Customers" }Created address book (201).
GET/addressbooks/:idAddress book.
PUT/addressbooks/:id{ "addressbook": { "name": "Customers 2026" } }Text Address book updated successfully.
DELETE/addressbooks/:id204 No Content.
POST/addressbooks/:id/move_contacts{ "to_addressbook_id": "destination-id" }{ "message": "Contacts successfully moved", "contacts_moved": ... }.

My Contact cannot be renamed, deleted, or used as either side of a bulk move. A missing book returns 404.

Contacts

Contact representation

Contact responses use this stable shape:

FieldTypeDescription
id, external_idStringInternal and interoperable contact identifiers. Use external_id in the contact routes.
addressbook_id, owner_idStringOwning book and user.
given_name, family_name, additional_names, nicknameStringPersonal names.
organizationObjectcompany, department, and title.
emails, phone_numbers, urls, ims, relatedArrayValues shaped as { "type": "…", "value": "…" }.
addressesArrayPostal addresses with type, street_address, extended_address, locality, region, postal_code, country, and post_office_box.
categories, custom_fields, vcard_propertiesArrayCategories and extensible vCard fields.
birthday, anniversary, timezone, language, gender, notes, photo, logo, kindMixedOptional profile and vCard details.
vcardStringSerialized vCard representation.
created_at, updated_atStringTimestamps.

List, read, create, update, and delete

MethodPathDescription
GET/addressbooks/:addressbook_id/contactsList contacts in an owned address book: { "results": [Contact] }.
GET/addressbooks/:addressbook_id/contacts/:external_idRead one contact.
POST/addressbooks/:addressbook_id/contactsCreate a contact.
PUT/addressbooks/:addressbook_id/contacts/:external_idUpdate a contact; upsert: true permits creation for a missing external ID.
DELETE/addressbooks/:addressbook_id/contacts/:external_idDelete an owned contact (204).
POST/addressbooks/:addressbook_id/contacts/:external_id/moveMove one contact; body requires to_addressbook_id.

For create/update, send fields either under contact or at the top level. Example:

{
"contact": {
"given_name": "Ada",
"family_name": "Okafor",
"organization": { "company": "Ration", "title": "Designer" },
"emails": [{ "type": "work", "value": "ada@example.com" }],
"phone_numbers": [{ "type": "mobile", "value": "+234…" }]
}
}

POST can include a top-level external_id for idempotent imports; if it already exists, the API returns 409 { "error": "Contact already exists" }. Do not place that field only inside contact and expect it to be an import key.

Global tenant directory

GET /apis/v1/contact/addressbooks/:addressbook_id/global

This lists the Global directory with pagination:

Query parameterTypeDescription
query or qStringSearch name, email, and vCard data.
name, email, organization/company, phone, stateStringField-specific filters.
pageIntegerPage number; defaults to 1.
limitIntegerPage size; defaults to 50, maximum 200.

Response: { "success": true, "results": [Contact], "total": Number, "page": Number, "limit": Number, "has_more": Boolean }.

Self contact and card

MethodPathDescription
GET/selfRead the caller's own Contact object.
PUT/selfUpdate the caller's own contact; accepts contact nesting or direct contact fields.
GET/self/cardReturns contact, vcard, vcard_url, profile_url, and host_origin.

Sync and phone contacts

Address-book synchronization

MethodPathRequest / response
GET/addressbooks/:addressbook_id/contacts/sync?sync_token=…Returns { "sync_token": "…", "changes": [...] }. Each change has external_id, change_type, and (except deletes) contact.
POST/addressbooks/:addressbook_id/contacts/syncApplies sync operations and returns addressbook_id, sync_token, counts, and per-operation results.

The Self book cannot be synchronized. A sync apply with no operations returns 400 { "error": "No sync operations provided" }.

Phone sync

GET /apis/v1/contact/phone-sync/export

Use include_self (Boolean; default true) and supported contact-source filters. Response: { "contacts": [Contact], "count": Number, "include_self": Boolean }.

POST /apis/v1/contact/phone-sync/import

Send a contact collection plus optional target_addressbook_id (or addressbook_id) and prefer_fetched:

{
"target_addressbook_id": "personal-book-id",
"contacts": [{ "given_name": "Ada", "emails": [{ "type": "work", "value": "ada@example.com" }] }]
}

Response supplies addressbook_id, addressbook_name, processed, created_count, updated_count, failure_count, and individual results. Global and My Contact are read-only import targets.

Errors

StatusMeaning
401Missing or invalid bearer authentication.
404Address book or contact not found.
409Contact already exists for an idempotent import key.
400Missing phone-sync contacts or sync operations.
422Validation error, attempt to modify a protected book/contact, or invalid writable target.