## Create or update a contact **post** `/v2/contacts` Creates a new contact or updates an existing one. If a contact with the given `email` or `userId` already exists, it will be updated. Otherwise, a new contact will be created. **At least one of `email` or `userId` must be provided for identification.** ### Request Body | Field | Type | Required | Description | | ----------------------- | ------- | ------------------- | --------------------------------------- | | `email` | string | One of email/userId | Contact email address | | `userId` | string | One of email/userId | External user ID from your system | | `name` | string | No | Contact display name | | `profilePicture` | string | No | Profile picture URL | | `companies` | array | No | Companies the contact belongs to | | `customFields` | object | No | Custom field values | | `subscribedToChangelog` | boolean | No | Whether subscribed to changelog | | `locale` | string | No | Contact locale/language | | `phone` | string | No | Contact phone number | | `roles` | array | No | Role IDs to assign | | `userHash` | string | No | HMAC hash for identity verification | | `createdAt` | string | No | When the contact was created (ISO 8601) | ### Company Object Each company in the `companies` array can have: - `id` (required) - External company ID from your system - `name` (required) - Company name - `monthlySpend` - Monthly spend/revenue - `customFields` - Custom field values - `industry` - Industry - `website` - Company website URL - `plan` - Current plan/subscription - `companySize` - Number of employees - `createdAt` - When the company was created ### Response Returns the created or updated contact object. - **201 Created** - A new contact was created - **200 OK** - An existing contact was updated ### Example Request ```json { "email": "john@example.com", "name": "John Doe", "userId": "usr_12345", "companies": [ { "id": "company_123", "name": "Acme Inc", "monthlySpend": 500, "plan": "enterprise" } ], "customFields": { "plan": "pro", "signupSource": "website" }, "subscribedToChangelog": true } ``` ### Example Response ```json { "object": "contact", "id": "676f0f6765bdaa7d7d760f88", "email": "john@example.com", "name": "John Doe", "userId": "usr_12345", "type": "customer", "companies": [...], "customFields": {...}, ... } ``` ### Version Availability This endpoint is only available in API version 2026-01-01.nova and newer. ### Header Parameters - `"Featurebase-Version": optional "2026-01-01.nova" or "2025-12-12.clover"` - `"2026-01-01.nova"` - `"2025-12-12.clover"` ### Body Parameters - `companies: optional array of object { id, name, companyHash, 7 more }` Companies the contact belongs to - `id: string` External company ID from your system - `name: string` Company name - `companyHash: optional string` HMAC-SHA256 hash of company ID for identity verification - `companySize: optional number` Number of employees in the company - `createdAt: optional string` When the company was created (ISO 8601) - `customFields: optional object { industry, plan, priority }` Custom field values on the company. Values can be string, number, boolean, null, or array of primitives. - `industry: optional string` - `plan: optional string` - `priority: optional string` - `industry: optional string` Industry the company operates in - `monthlySpend: optional number` Monthly spend/revenue from this company - `plan: optional string` Current plan/subscription name - `website: optional string` Company website URL - `createdAt: optional string` When the contact was created in your system (ISO 8601) - `customFields: optional object { accountType, plan, signupSource }` Custom field values on the contact. Values can be string, number, boolean, null, or array of primitives. - `accountType: optional string` - `plan: optional string` - `signupSource: optional string` - `email: optional string` Contact email address. Used for identification if userId is not provided. - `locale: optional string` Contact locale/language preference - `name: optional string` Contact display name - `phone: optional string` Contact phone number - `profilePicture: optional string` Profile picture URL - `roles: optional array of string` Array of role IDs to assign to the contact - `subscribedToChangelog: optional boolean` Whether the contact is subscribed to changelog updates - `userHash: optional string` HMAC-SHA256 hash of userId or email for identity verification - `userId: optional string` External user ID from your system. Takes precedence over email for identification. ### Example ```http curl https://do.featurebase.app/v2/contacts \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $FEATUREBASE_API_KEY" \ -d '{ "createdAt": "2024-01-15T10:30:00Z", "email": "john@example.com", "locale": "en", "name": "John Doe", "phone": "+1234567890", "profilePicture": "https://example.com/avatar.png", "roles": [ "role_vip", "role_beta" ], "subscribedToChangelog": true, "userHash": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6", "userId": "usr_12345" }' ``` #### Response ```json { "id": "676f0f6765bdaa7d7d760f88", "name": "John Steezy", "object": "contact", "type": "customer", "commentsCreated": 0, "companies": [ { "id": "507f1f77bcf86cd799439011", "companyId": "comp_12345", "companySize": 250, "createdAt": "2025-01-01T12:00:00.000Z", "industry": "Technology", "lastActivity": "2025-01-15T00:00:00.000Z", "linkedUsers": 15, "monthlySpend": 5000, "name": "Acme Inc", "object": "company", "plan": "enterprise", "updatedAt": "2025-01-10T15:30:00.000Z", "website": "https://acme.com", "customFields": { "location": "bar", "priority": "bar" } } ], "customFields": { "foo": "bar" }, "description": "", "email": "john@example.com", "lastActivity": "2025-01-03T21:42:30.181Z", "locale": "en", "manuallyOptedOutFromChangelog": false, "organizationId": "5febde12dc56d60012d47db6", "postsCreated": 0, "profilePicture": "https://fb-usercontent.fra1.cdn.digitaloceanspaces.com/anon_23.png", "roles": [ "string" ], "subscribedToChangelog": true, "userId": "676f0f673dbb299c8a4f3057", "verified": true } ```