# Create a contact

> Add a person to your project, with optional profile fields and custom properties.

`POST https://api-connect.nxiom.com/api/v1/emails/contacts`

## Authorization

Requires `email.full_access`.

## Body parameters

| Field | Type | Description |
| --- | --- | --- |
| `email` | string · required | The contact's email address. Must be unique in your project. |
| `userId` | string · optional | Your own identifier for this person, up to 255 characters. |
| `firstName` | string · optional | First name. |
| `lastName` | string · optional | Last name. |
| `phoneNumber` | string · optional | Phone number. |
| `properties` | object · optional | Values for [custom properties](/docs/learn/contacts#custom-properties) you have defined. Keys are case-insensitive. |

New contacts are `subscribed`.

## Request

### cURL

```shell
curl -X POST "https://api-connect.nxiom.com/api/v1/emails/contacts" \
  -H "Authorization: Bearer $NEXIOM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "reader@example.com",
  "userId": "user_123",
  "firstName": "Sam",
  "lastName": "Rivera",
  "properties": {
    "plan": "pro"
  }
}'
```

### Node.js

```typescript
import { NexiomConnect } from '@nexiom/connect';

const apiKey = process.env.NEXIOM_API_KEY;
if (!apiKey) throw new Error('Set NEXIOM_API_KEY');

const nexiom = new NexiomConnect({ apiKey });

const { data, error } = await nexiom.contacts.create({
  email: 'reader@example.com',
  userId: 'user_123',
  firstName: 'Sam',
  lastName: 'Rivera',
  properties: {
    plan: 'pro',
  },
});

if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data.id);
```

### PHP

```php
<?php

use Nexiom\Connect\NexiomConnect;

$apiKey = getenv('NEXIOM_API_KEY');
if (!$apiKey) {
    throw new RuntimeException('Set NEXIOM_API_KEY');
}

$nexiom = new NexiomConnect($apiKey);

// The plan property must be defined first; see Custom properties.
$contact = $nexiom->contacts->create([
    'email' => 'reader@example.com',
    'userId' => 'user_123',
    'firstName' => 'Sam',
    'lastName' => 'Rivera',
    'properties' => ['plan' => 'pro'],
]);

echo $contact['id'];
```

## Response

HTTP `200`. The contact is returned under `data`.

| Field | Type | Description |
| --- | --- | --- |
| `id` | string | Contact identifier, starting with `con_`. |
| `email` | string | Email address. |
| `email_status` | string | `subscribed` or `unsubscribed`. |
| `source` | string | How the contact was created, such as `manual`. |
| `created_at` | string | ISO 8601 timestamp. |

A contact with the same email already exists in the project returns `409`. An undefined property key returns `400`. Reaching your plan's contact allowance returns `429` with code `contact_limit_exceeded`.

### 200 OK

Example response. Identifiers are illustrative.

```json
{
  "data": {
    "id": "con_example",
    "email": "reader@example.com",
    "user_id": "user_123",
    "first_name": "Sam",
    "last_name": "Rivera",
    "phone_number": null,
    "email_status": "subscribed",
    "source": "manual",
    "created_at": "2026-10-02T10:00:00.000Z",
    "updated_at": "2026-10-02T10:00:00.000Z"
  }
}
```

### 400 Bad Request

The request is invalid. Correct it before retrying; `details` lists the failing fields.

```json
{
  "error": "invalid_argument",
  "message": "Request doesn't match the schema",
  "correlationId": "req_example",
  "details": {
    "context": "body",
    "issues": []
  }
}
```

### 401 Unauthorized

The API key is missing, malformed, revoked, or expired.

```json
{
  "error": "unauthorized",
  "message": "Invalid API key",
  "correlationId": "req_example"
}
```

### 403 Forbidden

The API key lacks a required permission, or its domain restriction does not allow this sender.

```json
{
  "error": "not_allowed",
  "message": "Missing required API key permissions: email.full_access",
  "correlationId": "req_example"
}
```

### 409 Conflict

The contact already exists.

```json
{
  "error": "duplicate_error",
  "message": "Contact with email reader@example.com already exists",
  "correlationId": "req_example"
}
```

### 429 Too Many Requests

Too many requests. Wait for the `Retry-After` header before retrying. A plan allowance can also return 429 with a `code`; it needs an allowance change, not a retry.

```json
{
  "error": "too_many_requests",
  "code": "RATE_LIMITED",
  "scope": "organization",
  "retryAfterSeconds": 12,
  "message": "Too many requests. Please try again later.",
  "correlationId": "req_example"
}
```

---

Source: https://connect.nxiom.com/docs/api/contacts/create
