# Domains

> Rules shared by every domain call: name format, period, name servers, extra registry fields (tldAttributes), the trustee service and how paid operations work.

Everything you can do to a domain name: check availability, register, renew, transfer, and manage it afterwards. This is the largest section — the shared rules come first, then the endpoints grouped by task.

## Rules that apply to every domain call

### Domain name format

We accept Unicode and punycode, normalise to lowercase, and validate against the public suffix list so that `apiname.co.uk` and `apiname.com.tr` are understood as whole domains rather than subdomains.

A name is rejected when:

- it contains no dot, or has an empty label (`apiname..com`);
- any label starts or ends with a hyphen (`-apiname.com`, `apiname-.com`);
- it is longer than 63 characters;
- it **mixes alphabets within one label** — see below.

:::caution[Mixed-alphabet names are rejected]
A single label may not combine Latin, Cyrillic, Greek, Arabic, Hebrew, Thai or Lao letters. Registries refuse these as look-alike (homograph) attacks, so we catch them first. The classic case is a Cyrillic `а` (U+0430) hidden among Latin letters in something like `fаcebook.com` — visually identical, a different domain entirely.

Chinese, Japanese and Korean scripts count as one family and may be combined with each other, but not with Latin. Digits and hyphens are neutral. Turkish characters (ç ö ü ğ ş ı İ) are Latin, so they never cause this. Sending the punycode form does not bypass the check — we decode it first.

Error: `Dna.DomainService:Domain:10058`, message "Domain name mixes multiple scripts". Note that this code is also used for a completely different, far more serious condition — see [Error codes](https://www.domainnameapi.com/docs/reference/errors/).
:::

### The `period` field

Registration, renewal and transfer term, in **years**.

- Valid range is 1–10, but **the real constraint is per TLD**. The catalogue's `registrationPeriods` and `renewalPeriods` arrays list the exact terms you may send. A registration outside `registrationPeriods` gives `Dna.DomainService:Domain:10023`; a renewal outside `renewalPeriods` gives `Dna.DomainService:Domain:10002`.
- On renewal, `0` is treated as `1`.
- **Premium domains accept `period: 1` only.** Registry premium fees are quoted a year at a time. Anything higher is rejected with `Dna.DomainService:Domain:10071`.

### The `nameServers` field

Required when registering. At least one entry; the per-TLD minimum and maximum are enforced (usually 2–13). To use our own DNS — which is what makes the [DNS records API](https://www.domainnameapi.com/docs/dns-records/) work — send:

```json
["tr.apiname.com", "eu.apiname.com"]
```

### Extra registry fields: `tldAttributes`

Some extensions demand data beyond the standard contact set — a tax number for `.com.tr`, a citizenship declaration for `.eu`, a nexus category for `.us`. You supply these as a flat key/value object alongside the rest of the registration request.

#### Where the keys and values come from

**Do not guess them and do not hard-code them.** Every valid key is published in the `attributes` array of the TLD catalogue:

```http
GET /api/v1/products/tlds?filterText=com.tr
```

See [the TLD catalogue](https://www.domainnameapi.com/docs/catalogue/) for the full response, or the extension's own page under [TLDs](https://www.domainnameapi.com/docs/tlds/). Each entry in `attributes` gives you everything you need to build the form:

| Catalogue field | Use it for |
| --- | --- |
| `key` | **The key to send in `tldAttributes`.** |
| `description` | The label to show. |
| `type` | `Text`, `DropdownList`, `RadioButton`, `Checkbox`, `CheckboxWithContract`, `FileUpload` or `Label` — tells you which input to render. `Label` entries are informational only. |
| `options` | **The exact permitted values**, each as `{ "value": "..." }`. Send one of these verbatim; anything else is rejected. |
| `isRequired` | Whether registration fails without it. |

:::tip[Build the form from the catalogue, not from a hard-coded list]
Render the extra fields dynamically from `attributes`. New extension requirements then work without a code change, you never send a key the registry does not recognise, and the permitted values for dropdowns always match what the registry accepts.
:::

#### Worked example: `.tr` domains

Turkish domains are the most involved case. In the test environment you can register them with the following attributes:

```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": {
    "TRABISDOMAINCATEGORY": "Corporate",
    "TRABISTAXNUMBER": "11111111111",
    "TRABISTAXOFFICE": "Yok"
  }
}
```

:::note[These values are for the test environment]
`11111111111` and `"Yok"` are accepted by the test registry so you can exercise the flow end to end. **In production you must send the customer's real tax number and real tax office** — the registry verifies them against official records.
:::

#### The attributes depend on each other

`.tr` is a good illustration of something that applies to several extensions: one attribute decides which others are required and how they are checked.

| `TRABISDOMAINCATEGORY` | What is then required and validated |
| --- | --- |
| `Corporate` | `TRABISTAXNUMBER` — checked as a tax number (or an 11-digit identity number)<br>`TRABISTAXOFFICE` — checked as a tax office |
| `Owner` | `TRABISCITIZENID` — checked as an 11-digit national identity number |

Send the wrong category and you will either be asked for a field you were not expecting, or the field you did send will go unchecked and reach the registry unverified.

:::danger[The contact and the attributes must agree — this one has consequences]
The extra attributes are validated **together with the contact you send**, not in isolation. In particular, the **registrant's `country`** changes how they are checked: Turkish tax and identity numbers are only verified when the registrant's country is `TR`. Send a different country and that check is skipped — your value is accepted here and passed straight to the registry.

The registry then does its own verification, after registration. If the details do not match official records, the domain can be suspended or **deleted**, and a deleted domain is not something we can recover for you.

So before you register:

- Make sure `country`, the address and the tax or identity number describe the **same real entity**.
- Use the customer's actual registered address, not your own and not a placeholder.
- Treat a successful registration as "accepted", not "verified" — the registry's own check comes later.
:::

#### What a bad attribute value gives you

| Code | Meaning and fix |
| --- | --- |
| `Dna.DomainService:TldAttribute:10001` | You sent a key this extension does not define. `details` lists the offending keys. Check them against `attributes[].key` in the catalogue — a common cause is reusing keys from a different extension. |
| `Dna.DomainService:TldAttribute:10002` | A required attribute is missing. `details` names it in readable form. Remember that which attributes are required can depend on the value of another one. |
| `Dna.DomainService:TldAttribute:10003` | A value failed validation. The message names the field; `data.ValidationError` carries the specific rule that failed — see the table below. |
| `Dna.DomainService:TldAttribute:10004` | This extension does not support internationalised (IDN) names. |
| `Dna.DomainService:TldAttribute:10005` | Value longer than the attribute allows. `details` gives the limit. |

When `10003` is raised, `data.ValidationError` tells you exactly which rule rejected the value:

| `data.ValidationError` | What was wrong |
| --- | --- |
| `Validation:IdentityNumber:Length` | The identity number is not exactly 11 digits. |
| `Validation:IdentityNumber:Format` | It is not 11 digits, or it starts with `0`. |
| `Validation:IdentityNumber:RepeatedDigits` | The same digit repeats 6 or more times in a row — rejected as an obviously fake number even when the checksum passes. |
| `Validation:IdentityNumber:Checksum` | The number is well-formed but fails its check-digit calculation. Almost always a typo. |
| `Dna.TaxNoValidatorError` | The tax number is not a valid 10-digit tax number and not a valid 11-digit identity number. |
| `Dna.RequiredValidatiorError` | An attribute the registry treats as mandatory was left empty. (The key is spelled this way in the response — match it literally.) |

:::caution[Validate before you buy]
Attribute validation happens during registration, which is also when your balance is checked. A rejected attribute does not charge you, but it does mean a round trip and a failed order on your record. Validate tax and identity numbers in your own form first — the rules above are enough to catch almost every bad value before it reaches us.
:::

### Trustee service: `useTrusteeContact`

Some country extensions require a local presence. Setting `useTrusteeContact: true` on registration buys our trustee service and uses the trustee's details as the domain's contacts.

:::caution[What changes when you use a trustee]
- Your `contacts` array and `tldAttributes` are both **replaced** by the trustee's. Anything you send in them is discarded.
- The domain costs more — a second line item appears on the order.
- Contact verification does not apply: the contacts are the trustee's, not your customer's.
- `PUT /api/v1/domains/contacts/update` is afterwards refused with `Dna.DomainService:Domain:10037`.
- `GET /api/v1/domains/info` returns an **empty** `contacts` array.

Check `isSupportTrustee` and `isTrusteeRequired` in the catalogue. If `isTrusteeRequired` is true and you omit the flag, registration fails with `Dna.DomainService:Trustee:10006`.
:::

### How paid operations work

Register, renew, transfer and restore all debit your balance. The sequence is: check your account is in good standing → look up your price → check your balance → send the command to the registry → record it → charge you.

Two consequences worth designing for:

- A **price seal** is taken when you search. For premium domains the registry quote is re-checked at purchase; if it moved, the purchase is refused with `Dna.DomainService:Domain:10072` and **nothing is charged**. Search again to get the current price.
- If the registry succeeds but we cannot record it, you get a **never-retry** error. See the box below — this is the most important operational rule in the API.

:::danger[Three codes that mean "do not retry"]
| Code | What happened |
| --- | --- |
| `Dna.DomainService:Domain:10058` | The domain **was registered** at the registry, but we could not write our own record. |
| `Dna.DomainService:Domain:10059` | The domain **was renewed** at the registry, but we could not record it. |
| `Dna.DomainService:Domain:10060` | The transfer **was started** at the registry, but we could not record it. |

In all three cases the operation **succeeded**. A recovery record has been created and our team will reconcile it. **Retrying creates a duplicate registration or a double charge.** Open a support ticket with the domain name and your correlation ID instead.

The message text tells you which of the two meanings of `10058` you have: a registry message means do-not-retry; "mixes multiple scripts" means your input was rejected and nothing happened.
:::
