A sales rep finishes a call and you want to save the new lead in their provider address book. Writing to each provider natively means juggling the Google People API, Microsoft Graph, EWS, hosted IMAP behavior, and CardDAV for iCloud and Yahoo. The Nylas Contacts API gives you one create, update, and delete flow across those systems.
This recipe walks through the three write operations and the provider rules that decide whether a write sticks.
Create a contact
Section titled “Create a contact”Create a contact with a single POST to /v3/grants/{grant_id}/contacts. The body accepts 17 contact fields, and only given_name is required. Nylas writes the record into the user’s address book and returns the full contact with its new id, so you can store that ID and update or delete the contact later.
The request below sends a complete contact: name parts, two email addresses, two phone numbers, a birthday, groups, and a source of address_book. Every field except given_name is optional, so a minimal create can be just a name plus one email address. The response echoes the stored object with a server-assigned id.
curl --compressed --request POST \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/contacts' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json' \ --data '{ "birthday": "1960-12-31", "company_name": "Nylas", "emails": [ { "email": "[email protected]", "type": "work" }, { "email": "[email protected]", "type": "home" } ], "given_name": "Leyah", "groups": [ { "id": "starred" }, { "id": "friends" } ], "im_addresses": [ { "type": "jabber", "im_address": "leyah_jabber" }, { "type": "msn", "im_address": "leyah_msn" } ], "job_title": "Software Engineer", "manager_name": "Bill", "middle_name": "Allison", "metadata": { "key1": "customer-123", "crm_record": "crm-456" }, "nickname": "Allie", "notes": "Loves Ramen", "office_location": "123 Main Street", "phone_numbers": [ { "number": "+1-555-555-5555", "type": "work" }, { "number": "+1-555-555-5556", "type": "home" } ], "physical_addresses": [ { "type": "work", "street_address": "123 Main Street", "postal_code": "94107", "state": "CA", "country": "USA", "city": "San Francisco" }, { "type": "home", "street_address": "456 Main Street", "postal_code": "94107", "state": "CA", "country": "USA", "city": "San Francisco" } ], "source": "address_book", "surname": "Miller", "web_pages": [ { "type": "work", "url": "<WEBPAGE_URL>" }, { "type": "home", "url": "<WEBPAGE_URL>" } ] }'{ "request_id": "1", "data": { "birthday": "1960-12-31", "company_name": "Nylas", "emails": [ { "type": "work", }, { "type": "home", } ], "given_name": "Leyah", "grant_id": "<NYLAS_GRANT_ID>", "groups": [{ "id": "starred" }, { "id": "friends" }], "id": "<CONTACT_ID>", "im_addresses": [ { "type": "jabber", "im_address": "leyah_jabber" }, { "type": "msn", "im_address": "leyah_msn" } ], "job_title": "Software Engineer", "manager_name": "Bill", "middle_name": "Allison", "metadata": { "key1": "customer-123", "crm_record": "crm-456" }, "nickname": "Allie", "notes": "Loves ramen", "object": "contact", "office_location": "123 Main Street", "phone_numbers": [ { "type": "work", "number": "+1-555-555-5555" }, { "type": "home", "number": "+1-555-555-5556" } ], "physical_addresses": [ { "type": "work", "street_address": "123 Main Street", "postal_code": "94107", "state": "CA", "country": "US", "city": "San Francisco" }, { "type": "home", "street_address": "321 Pleasant Drive", "postal_code": "94107", "state": "CA", "country": "US", "city": "San Francisco" } ], "picture_url": "https://example.com/picture.jpg", "source": "address_book", "surname": "Miller", "web_pages": [ { "type": "work", "url": "<WEBPAGE_URL>" }, { "type": "home", "url": "<WEBPAGE_URL>" } ] }}import Nylas from "nylas";
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",});
async function createContact() { try { const contact = await nylas.contacts.create({ identifier: "<NYLAS_GRANT_ID>", requestBody: { givenName: "My", middleName: "Nylas", surname: "Friend", notes: "Make sure to keep in touch!", phoneNumbers: [{ type: "work", number: "(555) 555-5555" }], webPages: [{ type: "other", url: "nylas.com" }], }, });
console.log("Contact:", JSON.stringify(contact)); } catch (error) { console.error("Error to create contact:", error); }}
createContact();from nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<NYLAS_GRANT_ID>"
contact = nylas.contacts.create( grant_id, request_body={ "middle_name": "Nylas", "surname": "Friend", "notes": "Make sure to keep in touch!", "phone_numbers": [{"type": "work", "number": "(555) 555-5555"}], "web_pages": [{"type": "other", "url": "nylas.com"}] })
print(contact)Writes land in the address_book source on Google, Microsoft, EWS, hosted IMAP, native iCloud, and native Yahoo grants. Native iCloud and Yahoo grants write CardDAV data; a generic hosted IMAP grant doesn’t gain CardDAV support. You can’t create a contact in the read-only inbox or domain sources. The full field list lives in the Contacts API reference.
Update a contact
Section titled “Update a contact”Update a contact with a PUT to /v3/grants/{grant_id}/contacts/{contact_id}, using the id you got back from the create call. You can omit top-level fields that you don’t want to change. If you include a nested object or array, send the complete replacement value for that field.
To change one phone number, read the contact first, replace the phone_numbers array, and send that complete array. The response normally returns the same id. For native CardDAV contacts, a provider-side move can change the href and public ID, so always persist the latest returned ID.
curl --compressed --request PUT \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/contacts/<CONTACT_ID>' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json' \ --data '{ "birthday": "1960-12-31", "company_name": "Nylas", "emails": [ { "email": "[email protected]", "type": "work" }, { "email": "[email protected]", "type": "home" } ], "given_name": "Leyah", "groups": [ { "id": "starred" }, { "id": "all" } ], "im_addresses": [ { "type": "jabber", "im_address": "leyah_jabber" }, { "type": "msn", "im_address": "leyah_msn" } ], "job_title": "Software Engineer", "manager_name": "Bill", "middle_name": "Allison", "metadata": { "key1": "customer-123", "crm_record": "crm-789" }, "nickname": "Allie", "notes": "Loves Ramen", "office_location": "123 Main Street", "phone_numbers": [ { "number": "+1-555-555-5555", "type": "work" }, { "number": "+1-555-555-5556", "type": "home" } ], "physical_addresses": [ { "type": "work", "street_address": "123 Main Street", "postal_code": "94107", "state": "CA", "country": "USA", "city": "San Francisco" }, { "type": "home", "street_address": "456 Main Street", "postal_code": "94107", "state": "CA", "country": "USA", "city": "San Francisco" } ], "source": "address_book", "surname": "Miller", "web_pages": [ { "type": "work", "url": "<WEBPAGE_URL>" }, { "type": "home", "url": "<WEBPAGE_URL>" } ] }'import Nylas from "nylas";
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",});
async function updateContact() { try { const contact = await nylas.contacts.update({ identifier: "<NYLAS_GRANT_ID>", contactId: "<CONTACT_ID>", requestBody: { givenName: "Nyla", }, });
console.log("Contact:", JSON.stringify(contact)); } catch (error) { console.error("Error to create contact:", error); }}
updateContact();from nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<NYLAS_GRANT_ID>"contact_id = "<CONTACT_ID>"
contact = nylas.contacts.update( grant_id, contact_id, request_body={ "given_name": "Nyla", })
print(contact)Updates need the same write scope as create. For generic hosted IMAP, updating an automatically generated inbox contact changes its source to address_book. Native iCloud and Yahoo inbox contacts are read-only, so update their CardDAV-backed address_book contacts instead.
Contact metadata has its own update rule: omission preserves it, an object replaces it, and {} clears it. A metadata-only request skips the provider write. See Add metadata to a contact for examples and the non-atomic provider-write boundary.
Delete a contact
Section titled “Delete a contact”Delete a contact with a DELETE to /v3/grants/{grant_id}/contacts/{contact_id}. This is a single call with no request body, and it removes the contact from the user’s actual provider account, not just from a Nylas cache. The contact disappears from Gmail or Outlook too, so treat it as a destructive action and confirm with the user first.
The examples below delete one contact by ID. A successful delete returns a request_id so you can correlate the call in logs. There’s no soft-delete or trash step here, so once the call succeeds the record is gone from the provider.
curl --compressed --request DELETE \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/contacts/<CONTACT_ID>' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json'import Nylas from "nylas";
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",});const identifier = "<NYLAS_GRANT_ID>";const contactId = "<CONTACT_ID>";
const deleteContact = async () => { try { await nylas.contacts.destroy({ identifier, contactId }); console.log(`Contact with ID ${contactId} deleted successfully.`); } catch (error) { console.error(`Error deleting contact with ID ${contactId}:`, error); }};
deleteContact();from nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<NYLAS_GRANT_ID>"contact_id = "<CONTACT_ID>"
request = nylas.contacts.destroy( grant_id, contact_id,)
print(request)Like update, delete only applies to address_book contacts. You can’t delete automatically generated inbox contacts through the API because the provider regenerates them from message participants.
Manage contacts from the terminal
Section titled “Manage contacts from the terminal”The Nylas CLI creates and edits contacts without code: nylas contacts create adds one with a name, email, and company, and contacts update edits an existing contact by ID. Both write back to the provider, so changes show up in the user’s address book.
# Create a contactnylas contacts create --first-name "Jane" --last-name "Smith" --email "[email protected]" --company "Acme"
# Update an existing contact by IDnylas contacts update <contact-id> --company "Acme Inc" --job-title "Engineer"One flag difference to watch: create uses --first-name and --last-name, while update uses --given-name and --surname (matching the API field names). Contact writes are supported on Google, Microsoft, EWS, hosted IMAP, native iCloud, and native Yahoo grants. See the contacts create and contacts update command reference.
Things to know about writing contacts
Section titled “Things to know about writing contacts”Writes behave differently from reads, and a handful of provider rules decide whether a POST or PUT sticks. The most important one is the source field, which Nylas recognizes in three values: address_book, domain, and inbox. Creating a contact always uses address_book. For generic hosted IMAP, updating an inbox contact promotes it to address_book. Native iCloud and Yahoo inbox contacts and all domain contacts are read-only.
Write scopes are stricter than read scopes. Create, update, and delete all require Google’s https://www.googleapis.com/auth/contacts scope or Microsoft’s Contacts.ReadWrite. The read-only contacts.readonly or Contacts.Read scopes won’t authorize a write, so request the read-write scope at OAuth time if your app edits contacts. The Contacts API scopes page lists the exact strings per provider.
Field support varies by provider, and you’ll hit these limits in practice:
| Field | Microsoft / EWS | Native iCloud | Native Yahoo | Hosted IMAP | |
|---|---|---|---|---|---|
groups | Supported | Microsoft only; EWS unsupported | Supported | Supported | Provider-dependent |
web_pages | Multiple | One; type must be work | Multiple | Multiple | At most one |
emails | Multiple | Up to three | Multiple | At most one | Provider-dependent |
manager_name | Supported | Supported where mapped | Unsupported | Unsupported | Provider-dependent |
office_location | Supported | Supported where mapped | Unsupported | Unsupported | Provider-dependent |
Groups also work differently on write. Pass groups as an array of { "id": "..." } objects, and reference only explicit groups that already exist at the provider. The API doesn’t create, rename, or delete Contact Group resources. Native iCloud and Yahoo membership writes use conditional CardDAV updates and can fail if provider state changes concurrently. To find valid group IDs first, see Organize contacts with groups.
What’s next
Section titled “What’s next”- Contacts API reference for the full schema, field reference, and provider limits
- How to list Google contacts to read an address book before you write to it
- Organize contacts with groups to create and reference contact groups on write
- Contacts API scopes for the read-write scope strings on each provider
- API reference for every Contacts endpoint and parameter