# Create, read and update contacts

> Create a contact, read it together with its open verifications, and update it on every domain that uses it.

## POST /api/v1/contacts

Creates a contact and returns its handle code. Nothing is sent to any registry yet — that happens when you use the handle to register or update a domain.

**Request**

A single `contact` object. All fields follow the [contact field reference](https://www.domainnameapi.com/docs/contacts/#contact-field-reference). There is no `contactType` here — the role is decided when you use the handle.

```json
{
  "contact": {
    "firstName": "Ahmet",
    "lastName": "Yilmaz",
    "companyName": "Apiname Ltd",
    "eMail": "ahmet@apiname.com",
    "address": "Ataturk Caddesi No 12 Kat 3",
    "city": "Istanbul",
    "state": "Istanbul",
    "postalCode": "34100",
    "country": "TR",
    "phoneCountryCode": "90",
    "phone": "5321234567"
  }
}
```

**Response — 200**

```json
{
  "handle": "D-174829301847",
  "isSuccess": true,
  "operationMessage": "Operation completed successfully"
}
```

:::tip[What to do with it]
**Store `handle` against your customer record.** This is the only time it is returned for a newly created contact, and it is what you send to `POST /api/v1/domains/register`. Losing it means creating a duplicate contact.
:::

**Errors**

| Code | Meaning and fix |
| --- | --- |
| (validation) | HTTP 400 with `validationErrors`. Read `members` to see which field. Typical: a required field missing, a name that became empty after invalid characters were removed, or a `+` in `phoneCountryCode`. |
| `Dna.DomainService:Contact:10011` | Invalid e-mail address. |
| `Dna.DomainService:Contact:10013` | Phone country code contains something other than digits. |
| `Dna.DomainService:Contact:10014` | Phone number is not usable after normalisation. |

## GET /api/v1/contacts/{handleCode}

Reads a contact in your account, together with any verification process currently open on it.

**Request**

```http
GET /api/v1/contacts/D-174829301847
```

**Response — 200**

```json
{
  "contact": {
    "handle": "D-174829301847",
    "contactType": "Registrant",
    "firstName": "Ahmet",
    "lastName": "Yilmaz",
    "companyName": "Apiname Ltd",
    "eMail": "ahmet@apiname.com",
    "address": "Ataturk Caddesi No 12 Kat 3",
    "city": "Istanbul",
    "state": "Istanbul",
    "postalCode": "34100",
    "country": "TR",
    "phoneCountryCode": "90",
    "phone": "5321234567"
  },
  "verificationObjects": [
    {
      "fieldType": "Email",
      "fieldValue": "ahmet@apiname.com",
      "ownerName": "Ahmet Yilmaz",
      "operationStatus": "VerificationPending",
      "isUserVerified": false,
      "tokenExpireDate": "2026-11-14T10:22:31",
      "resendDate": "2026-10-30T10:23:31",
      "attemptNumber": 1,
      "resendRetryCount": 100
    }
  ]
}
```

:::tip[What to do with it]
Check `verificationObjects` first. An **empty array means nothing is outstanding**. If an entry has `isUserVerified: false`, the `tokenExpireDate` is **the date the domain will be suspended** — show it to your customer and chase them before it arrives.
:::

**Errors**

| Code | Meaning and fix |
| --- | --- |
| `Dna.DomainService:Contact:Verification:10006` | Handle not found in your account. Check for a typo, and that the handle is yours. |

## PUT /api/v1/contacts/{handleCode}

Updates a contact. This is not a local edit — the change is pushed to the registry of **every domain** using this handle, and the response reports what happened to each one.

:::caution[This is a replace, not a patch]
Send the **complete** contact. Any field you leave out is not preserved. Read the contact first, change what you need, send it all back.
:::

**Request**

```http
PUT /api/v1/contacts/D-174829301847

{
  "contact": {
    "firstName": "Ahmet",
    "lastName": "Yilmaz",
    "companyName": "Apiname Ltd",
    "eMail": "ahmet@apiname.com",
    "address": "Bagdat Caddesi No 45 Daire 7",
    "city": "Istanbul",
    "state": "Istanbul",
    "postalCode": "34728",
    "country": "TR",
    "phoneCountryCode": "90",
    "phone": "5329876543"
  }
}
```

**Response — 200**

```json
{
  "handle": "D-174829301847",
  "contactUpdateSuccess": true,
  "stats": {
    "totalDomainCount": 3,
    "successCount": 2,
    "pendingDocumentCount": 0,
    "failedCount": 0,
    "splitCount": 1
  },
  "affectedDomains": [
    {
      "domainName": "apiname.com",
      "status": "Success",
      "newHandle": null,
      "reason": "Updated",
      "message": "Handle update completed"
    },
    {
      "domainName": "apiname.net",
      "status": "Success",
      "newHandle": null,
      "reason": "RddsPending",
      "message": "Handle update completed, RDDS verification started"
    },
    {
      "domainName": "apiname.com.tr",
      "status": "Success",
      "newHandle": "D-R-1748293018",
      "reason": "SplitNotSubjectToRdds",
      "message": "Moved to a new handle, not subject to RDDS verification"
    }
  ]
}
```

**Reading the response**

| Field | Meaning |
| --- | --- |
| `contactUpdateSuccess` | Whether the contact record itself was saved. |
| `stats.totalDomainCount` | How many domains use this handle. |
| `stats.successCount` | Updated at the registry. |
| `stats.pendingDocumentCount` | The registry wants documents before accepting the change. |
| `stats.failedCount` | The registry rejected the change. |
| `stats.splitCount` | Domains moved onto a **new handle** instead of being updated. |
| `affectedDomains[].reason` | `Updated` · `RddsPending` (verification started) · `SplitForDocument` · `SplitNotSubjectToRdds` · `Failed` |
| `affectedDomains[].newHandle` | Set when that domain was moved to a new handle. `null` otherwise. |

:::danger[Always read `newHandle`]
When domains using one handle cannot all take the same change, we move the ones that cannot onto a **newly created handle** rather than leaving your records inconsistent. That domain is **no longer reachable through the handle you just updated**.

Loop over `affectedDomains` and, for every entry where `newHandle` is not null, store the new handle against that domain in your database. Skipping this is the most common integration bug in this endpoint.
:::

**Does the handle code change?**

**No.** The handle string stays the same — only its contents change. Your stored handle keeps working for future registrations. The two exceptions:

- A domain was split off (`newHandle` is set). For that domain the handle changed; your original handle is still valid for everything else.
- An identity change is awaiting verification. The contact is updated but the registry change is held until the owner confirms. A domain you register with the handle meanwhile uses the new details and starts its own verification.

**Errors**

| Code | Meaning and fix |
| --- | --- |
| `Dna.DomainService:Domain:10038` | Handle not found. |
| `Dna.DomainService:Contact:10012` | Invalid country — not a recognised ISO2 code. |
| `Dna.DomainService:Contact:10020` | The handle is linked to at least one domain that is not yours. Nothing was changed. |
| `Dna.DomainService:Contact:Verification:10008` | An ownership-change verification is already open on this contact. Wait for it to finish, or have the owner reject it, before changing identity again. |
| `Dna.DomainService:Contact:Verification:10010` | **You are changing the name, surname or company, and the contact's e-mail is not verified.** Verify the e-mail first — see the sequence below. |
| `Dna.DomainService:Contact:10026` | The handle is used by several domains in a way we cannot split safely. Open a support ticket. |
| `Dna.DomainService:Epp:10001` | The registry rejected the change. Per-domain detail is in `affectedDomains`. |
