## Create a contact External contacts are Project Blue's native contact store for accounts **without a connected CRM**. They render in the Project Blue app's contact list and details panel. You can create them explicitly with the endpoints below, or implicitly by passing `firstName`, `lastName`, `email`, and `customFields` on `/send-api-message` — those fields persist on the auto-created contact for the recipient. > **For accounts without a connected CRM** > > If HighLevel or HubSpot is connected, contacts live in your CRM instead (see [CRM integration](https://api.tryprojectblue.com/#crm-integration)). External contacts apply to external/API-source accounts. `POST /create-external-contact` Creates a contact, or idempotently upserts by phone number if one already exists. Use this when you want the contact to exist before any message is sent. > **Upserts do not behave like updates** > > When the phone number already exists this endpoint merges rather than replaces, and the rules differ per field:`firstName` and `lastName` are only filled in when the stored value is empty. Sending a new name for a contact that already has one is silently discarded.`email` overwrites whenever you send a non-empty value.`customFields` is **shallow-merged** here — the opposite of [update](https://api.tryprojectblue.com/#update-contact), which replaces the whole object. Sending `{}` changes nothing.The response is the contact as it was _before_ the merge, so a discarded name is not visible in it. Read the contact back if you need to confirm. The same merge runs when `/send-api-message` auto-creates a contact. ### Request body - `phone` (string, required) — The contact's phone number. Accepts many formats — normalized to E.164. - `firstName` (string) — The contact's first name. - `lastName` (string) — The contact's last name. - `email` (string) — The contact's email address. - `customFields` (object) — Arbitrary JSON metadata (e.g. order IDs, links) shown in the contact details panel. Plain object only; max 10,000 characters when JSON-serialized. ### The contact object All three endpoints return the same object. Create and update return it as `{ contact }`; list returns `{ contacts, pagination }`. There is no `status` envelope. - `id` (string, required) — Opaque contact id. Pass it back as contactId when updating; do not parse it. - `firstName` (string | null, required) — The contact's first name, or null if never set. - `lastName` (string | null, required) — The contact's last name, or null if never set. - `phoneNumber` (string, required) — The contact's number in E.164. Note the asymmetry: requests take phone, responses return phoneNumber. - `email` (string | null, required) — The contact's email address, or null if never set. - `customFields` (object | null, required) — Whatever JSON metadata you last stored. Replaced wholesale on update, never merged. - `note` (string | null, required) — Free-form note stored on the contact. - `createdAt` (string (ISO-8601), required) — When the contact was first created. - `updatedAt` (string (ISO-8601), required) — When the contact was last modified. **Request — cURL** ```bash curl -X POST https://api.tryprojectblue.com/create-external-contact \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone": "+15551234567", "firstName": "Jamie", "lastName": "Rivera", "email": "jamie@example.com", "customFields": { "orderId": "ord_18342", "plan": "pro" } }' ``` **Response — 200 OK** ```json { "contact": { "id": "cmm4k2p1z0001s6ry9x8u7q3v", "firstName": "Jamie", "lastName": "Rivera", "phoneNumber": "+15551234567", "email": "jamie@example.com", "customFields": { "orderId": "ord_18342", "plan": "pro" }, "note": null, "createdAt": "2026-08-14T17:22:05.118Z", "updatedAt": "2026-08-14T17:22:05.118Z" } } ```