# Register a domain

> Register a domain with inline contact details or with handle codes, and how contactType is interpreted by each endpoint.

There are two ways to register, and the difference matters.

|   | `/register-with-contacts` | `/register` |
| --- | --- | --- |
| Contacts supplied as | Full details inline | Existing **handle codes** |
| Fewer than 4 contacts | **Allowed** — we fill the rest | **Not allowed** — all four required |
| `contactType` matching | Flexible | **Exact names only** |
| Creates new contact records | Yes, one per role | No, reuses yours |
| Trustee service | Supported | Not supported |
| Best for | One-off registrations, first-time customers | Volume, repeat customers, consistent WHOIS |

## How `contactType` is interpreted

This is the single most common source of confusion, because the two registration endpoints behave differently.

**On /register-with-contacts and /domains/contacts/update — flexible**

Values are resolved in this order:

1. **Numeric first.** `"0"` → Admin, `"1"` → Registrant, `"2"` → Billing, `"3"` → Tech. A number outside 0–3 falls through to the next step.
2. **First letter, case-insensitive.** Starts with `A` → Admin, `B` → Billing, `R` → Registrant, `T` → Tech.
3. **Anything else → Registrant.**

| You send | Resolves to | Why |
| --- | --- | --- |
| `"Registrant"`, `"registrant"`, `"REG"`, `"r"` | Registrant | first letter R |
| `"1"` | Registrant | numeric |
| `"Admin"`, `"administrative"`, `"a"` | Admin | first letter A |
| `"0"` | Admin | numeric |
| `"Tech"`, `"technical"`, `"t"` | Tech | first letter T |
| `"Billing"`, `"b"` | Billing | first letter B |
| `"7"` | **Registrant** | not a valid number, matches no letter rule → default |
| `"owner"`, `"xyz"` | **Registrant** | default |

:::caution[An unrecognised value silently becomes Registrant]
There is no error for a typo. `"Techncial"` starts with T and becomes Tech — fine. `"owner"` becomes **Registrant**, which may not be what you meant, and if you also sent a real Registrant you will get a duplicate-role error instead of a clear message about the typo. **Send the exact names** — `Registrant`, `Admin`, `Tech`, `Billing` — and you never have to think about any of this.
:::

**On /register — exact names only**

:::danger[`/register` is case-sensitive and does not accept numbers]
Handle matching on `/register` compares `contactType` literally against `Registrant`, `Admin`, `Billing`, `Tech`. The flexible rules above **do not apply**. These all fail:

```json
"contactType": "1"            ✗
"contactType": "registrant"   ✗
"contactType": "REGISTRANT"   ✗
"contactType": "Registrant"   ✓
```

A mismatch surfaces as `Dna.DomainService:Contact:10018` — "missing contact type" — which does not obviously point at a capitalisation problem. If you get that error on `/register`, check your casing first.
:::

## Registering with contact details

### POST /api/v1/domains/register-with-contacts

Registers a domain, creating contact records from the details you send.

**Request**

| Field | Required | Notes |
| --- | --- | --- |
| `domainName` | required | Must be `AVAILABLE`. |
| `period` | required | Years. Must be one of the TLD's `registrationPeriods`. Premium: 1 only. |
| `nameServers` | required | Array of host names. At least one. |
| `contacts` | required | 1 to 4 objects. See auto-fill below. |
| `tldAttributes` | varies | Required when the TLD defines required attributes. |
| `useTrusteeContact` | optional | Default `false`. |

**Minimal request — one contact for all four roles**

```json
{
  "domainName": "apiname.com",
  "period": 1,
  "nameServers": ["tr.apiname.com", "eu.apiname.com"],
  "contacts": [
    {
      "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"
    }
  ]
}
```

**With a required TLD attribute**

```json
{
  "domainName": "apiname.com.tr",
  "period": 1,
  "nameServers": ["tr.apiname.com", "eu.apiname.com"],
  "contacts": [
    {
      "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"
    }
  ],
  "tldAttributes": {
    "identityNumber": "12345678901"
  }
}
```

**Two separate contacts**

```json
{
  "domainName": "apiname.net",
  "period": 2,
  "nameServers": ["tr.apiname.com", "eu.apiname.com"],
  "contacts": [
    {
      "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"
    },
    {
      "contactType": "Tech",
      "firstName": "Mehmet", "lastName": "Kaya",
      "companyName": "Apiname Ltd", "eMail": "tech@apiname.com",
      "address": "Ataturk Caddesi No 12 Kat 3",
      "city": "Istanbul", "state": "Istanbul", "postalCode": "34100",
      "country": "TR", "phoneCountryCode": "90", "phone": "5329876543"
    }
  ]
}
```

**How missing roles are filled in**

You do not need to send four contacts. Roles are assigned, then any still-empty role is filled by **copying the first contact in your array**.

| You send | What the domain ends up with |
| --- | --- |
| 1 contact, no `contactType` | It becomes **Registrant**; Admin, Tech and Billing are copies of it. *The typical single-contact call.* |
| 1 contact, `"Admin"` | It becomes Admin; the other three are copies of it. |
| 2 contacts: Registrant + Tech | Those two as given; **Admin and Billing are copies of the first contact** (the registrant). |
| 2 contacts, both `"Registrant"` | The first is Registrant. The second's role is cleared and it takes **Admin**. Tech and Billing are copies of the first. |
| 4 contacts, all roles named | Used exactly as given. No copying. |
| 4 contacts, none with `contactType` | Assigned in array order: Registrant, Admin, Tech, Billing. |
| 5 or more contacts | The first four to claim a role win; extras are ignored. |

:::caution[An empty `contacts` array is not filled in]
Sending `"contacts": []` does **not** produce four default contacts — it fails with `Dna.DomainService:Contact:10018`. Send at least one.
:::

**Response — 200**

```json
{
  "success": true,
  "message": "Domain registration successful",
  "domainId": "3c9a7e14-82b5-4d60-9f31-c7e8a05b6d42",
  "status": "Active",
  "expirationDate": "2027-10-30T12:04:17"
}
```

:::danger[`status` is not always `Active`]
A 200 means the registration was accepted, not that the domain is live. For extensions that require documents you will get `WaitingForRegistration` or `WaitingForDocument`, and the domain does not work until you [upload documents](https://www.domainnameapi.com/docs/domains/documents/) and the registry approves them.

**Always read `status`** and branch on it. The registration confirmation e-mail to the registrant is only sent when the status is `Active`.
:::

:::tip[What to do next]
1. Store `domainId` — quote it in support tickets.
2. If `status` is not `Active`, go to the document flow.
3. Either way, **check verification**: `GET /api/v1/contacts/verification/check`. The 15-day clock has started.
4. If you sent our name servers, configure DNS records with the [zones API](https://www.domainnameapi.com/docs/dns-records/).
:::

**Errors**

| Code | Meaning and fix |
| --- | --- |
| `Dna.DomainService:Domain:1026` | Already registered. Search first; consider transfer. |
| `Dna.DomainService:Domain:10044` | Invalid domain name. |
| `Dna.DomainService:Domain:10058` | Mixed alphabets in the name — *or* the never-retry condition. Read `message`. |
| `Dna.DomainService:Domain:10004` | Extension not sold. |
| `Dna.DomainService:Domain:10023` | `period` not offered for this TLD. |
| `Dna.DomainService:Domain:10071` | Premium domain with `period` above 1. |
| `Dna.DomainService:Domain:10072` | Premium price changed since your search. Nothing charged — search again. |
| `Dna.DomainService:Dns:10022` | Too few or too many name servers for this TLD. |
| `Dna.DomainService:Dns:10013` | Duplicate entry in `nameServers`. |
| `Dna.DomainService:Dns:10026` | IPv6 glue not supported by this TLD. |
| `Dna.DomainService:Contact:10018` | Fewer than four roles resolved, or `contacts` empty. |
| `Dna.DomainService:Contact:10022` | Two contacts resolved to the same role. |
| `Dna.DomainService:Contact:10011` | Invalid contact e-mail. |
| `Dna.DomainService:Contact:10012` | Invalid country code. |
| `Dna.DomainService:Contact:10013 / :10014` | Phone country code or number invalid. |
| `Dna.DomainService:Contact:10023 / :10024` | First or last name empty after invalid characters were removed. |
| `Dna.DomainService:TldAttribute:10001–10005` | Extra-field problems — see above. |
| `Dna.DomainService:Trustee:10002` | Trustee not available for this domain. |
| `Dna.DomainService:Trustee:10006` | This TLD requires the trustee service; set `useTrusteeContact: true`. |
| `Dna.DomainService:Domain:10049` | No price configured for this TLD and period. |
| `Dna.DomainService:Deposit:10001` | Insufficient balance. `data` tells you how much is needed. |
| `Dna.DomainService:Reseller:10001` | Your account balance is suspended. Contact us. |
| `Dna.DomainService:Epp:10001` | The registry refused for a reason we have no specific code for. Read `details`. |

## Registering with handle codes

### POST /api/v1/domains/register

Registers a domain using contact handles that already exist in your account. No new contact records are created. This is the endpoint to use at volume.

**Request**

| Field | Required | Notes |
| --- | --- | --- |
| `domainName` | required |   |
| `period` | required | Years. |
| `nameServers` | required | Must not be empty. |
| `handles` | required | **All four roles.** Must not be empty. |
| `handles[].contactType` | required | **Exactly** `Registrant`, `Admin`, `Billing` or `Tech`. |
| `handles[].handle` | required | A handle in your account. |
| `tldAttributes` | varies | As above. |

**One handle for all four roles**

```json
{
  "domainName": "apiname.com",
  "period": 1,
  "nameServers": ["tr.apiname.com", "eu.apiname.com"],
  "handles": [
    { "contactType": "Registrant", "handle": "D-174829301847" },
    { "contactType": "Admin",      "handle": "D-174829301847" },
    { "contactType": "Tech",       "handle": "D-174829301847" },
    { "contactType": "Billing",    "handle": "D-174829301847" }
  ]
}
```

**Different handles per role**

```json
{
  "domainName": "apiname.net",
  "period": 1,
  "nameServers": ["tr.apiname.com", "eu.apiname.com"],
  "handles": [
    { "contactType": "Registrant", "handle": "D-174829301847" },
    { "contactType": "Admin",      "handle": "D-174829301847" },
    { "contactType": "Tech",       "handle": "D-174830552193" },
    { "contactType": "Billing",    "handle": "D-174830998471" }
  ],
  "tldAttributes": {}
}
```

**Response — 200**

Identical to `/register-with-contacts`:

```json
{
  "success": true,
  "message": "Domain registration successful",
  "domainId": "3c9a7e14-82b5-4d60-9f31-c7e8a05b6d42",
  "status": "Active",
  "expirationDate": "2027-10-30T12:04:17"
}
```

:::caution[All four roles are mandatory here]
There is no auto-fill on this endpoint. Omitting a role gives `Dna.DomainService:Contact:10018`. The simplest correct call repeats the same handle four times, as in the first example.
:::

**Errors — handles**

| Code | Meaning and fix |
| --- | --- |
| `Dna.DomainService:Contact:10018` | A role is missing. **Most often a capitalisation problem** — see the box above. Also fires if you genuinely omitted a role. |
| `Dna.DomainService:Contact:10006` | `handles` is empty. |
| `Dna.DomainService:Contact:10005` | `contactType` could not be resolved at all. |
| `Dna.DomainService:Contact:10008` | The handle does not exist **in your account**. Check the code, and that it is yours. |
| `Dna.DomainService:Contact:10019` | Handle string empty or malformed. |

All the registration errors listed under `/register-with-contacts` apply here too.

## Can you register with an unverified contact?

**Yes** — and this catches people out, so it is worth being explicit.

Verification is a *consequence* of registration, not a precondition. The domain registers and goes live immediately. Then the 15-day clock starts. If the registrant never clicks the link, the domain is suspended and stops resolving. Full detail is in [Contact verification](https://www.domainnameapi.com/docs/contacts/verification/).

:::tip[The recommended registration flow]
1. `POST /api/v1/domains/search` — confirm `info.status` is `AVAILABLE` and note `isPremium` and `isDocumentRequired`.
2. `POST /api/v1/contacts` — create the contact once, store the handle. (Skip if you already have one for this customer.)
3. `POST /api/v1/domains/register` — register with the handle.
4. Read `status`. Not `Active`? Go to the document flow.
5. `GET /api/v1/contacts/verification/check` — record `tokenExpireDate` and surface it in your dashboard.
6. Configure DNS: [zone records](https://www.domainnameapi.com/docs/dns-records/) if you used our name servers, otherwise [change the name servers](https://www.domainnameapi.com/docs/name-servers/).
:::
