> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dotlet.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Direct integration

> Build your own domain search, purchase, and DNS management UI on top of the Dotlet API.

Direct integration means you build the UI (search results, purchase forms, DNS record tables) and call Dotlet's API from your backend to power it. You have full control over every screen; Dotlet just handles the registrar, DNS, and routing work behind it.

Choose this path if you need the domain flow to look and behave exactly like the rest of your product, or if you need to fold it into an existing multi-step onboarding flow rather than dropping in a separate screen.

<Tip>
  Want something working today with minimal UI work? See [Hosted checkout](/integration-paths/hosted-checkout) instead; one backend endpoint plus an iframe, and Dotlet's hosted UI does the rest.
</Tip>

## What you build vs. what Dotlet handles

| You build                                       | Dotlet handles                                                          |
| ----------------------------------------------- | ----------------------------------------------------------------------- |
| Search input and results UI                     | Availability checks and pricing against the registrar                   |
| Purchase form (contact details, plan selection) | Registration, billing with the registrar, renewals                      |
| DNS record editor                               | Creating, validating, and syncing DNS records                           |
| Domain-to-customer mapping in your own database | Routing traffic for a customer's domain to your platform (origin rules) |

All calls in this guide go through your backend, never call these endpoints from the browser, since they require your Dotlet API key.

## 1. Search and check availability

Search by keyword to show a list of suggestions, or check one exact domain.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dotlet.net/api/v1/registrar/search \
    -H "X-API-Key: dk_••••••••••••••••" \
    -H "Content-Type: application/json" \
    -d '{ "query": "acmewidgets", "tlds": ["com", "io", "dev"], "limit": 10 }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.dotlet.net/api/v1/registrar/search', {
    method: 'POST',
    headers: {
      'X-API-Key': 'dk_••••••••••••••••',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ query: 'acmewidgets', tlds: ['com', 'io', 'dev'], limit: 10 })
  });
  const data = await response.json();
  console.log(data);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.dotlet.net/api/v1/registrar/search',
      headers={'X-API-Key': 'dk_••••••••••••••••'},
      json={'query': 'acmewidgets', 'tlds': ['com', 'io', 'dev'], 'limit': 10}
  )
  print(response.json())
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"io"
  	"net/http"
  )

  func main() {
  	payload := map[string]interface{}{
  		"query": "acmewidgets",
  		"tlds":  []string{"com", "io", "dev"},
  		"limit": 10,
  	}
  	body, _ := json.Marshal(payload)

  	req, _ := http.NewRequest("POST", "https://api.dotlet.net/api/v1/registrar/search", bytes.NewBuffer(body))
  	req.Header.Set("X-API-Key", "dk_••••••••••••••••")
  	req.Header.Set("Content-Type", "application/json")

  	resp, _ := http.DefaultClient.Do(req)
  	defer resp.Body.Close()

  	respBody, _ := io.ReadAll(resp.Body)
  	fmt.Println(string(respBody))
  }
  ```
</CodeGroup>

```json theme={null}
{
  "query": "acmewidgets",
  "suggestions": [
    { "domain": "acmewidgets.com", "available": true, "price": "12.99", "currency": "USD" },
    { "domain": "acmewidgets.io", "available": true, "price": "34.99", "currency": "USD" }
  ]
}
```

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.dotlet.net/api/v1/registrar/availability/acmewidgets.com \
    -H "X-API-Key: dk_••••••••••••••••"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.dotlet.net/api/v1/registrar/availability/acmewidgets.com', {
    headers: { 'X-API-Key': 'dk_••••••••••••••••' }
  });
  const data = await response.json();
  console.log(data);
  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
      'https://api.dotlet.net/api/v1/registrar/availability/acmewidgets.com',
      headers={'X-API-Key': 'dk_••••••••••••••••'}
  )
  print(response.json())
  ```

  ```go Go theme={null}
  package main

  import (
  	"fmt"
  	"io"
  	"net/http"
  )

  func main() {
  	req, _ := http.NewRequest("GET", "https://api.dotlet.net/api/v1/registrar/availability/acmewidgets.com", nil)
  	req.Header.Set("X-API-Key", "dk_••••••••••••••••")

  	resp, _ := http.DefaultClient.Do(req)
  	defer resp.Body.Close()

  	body, _ := io.ReadAll(resp.Body)
  	fmt.Println(string(body))
  }
  ```
</CodeGroup>

## 2. Register the domain

Collect registrant contact details in your own form, then submit the purchase.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dotlet.net/api/v1/registrar/purchase \
    -H "X-API-Key: dk_••••••••••••••••" \
    -H "Content-Type: application/json" \
    -d '{
      "domain": "acmewidgets.com",
      "years": 1,
      "contact": {
        "first_name": "Ada",
        "last_name": "Lovelace",
        "email": "ada@acmewidgets.com",
        "phone": "+14155550100",
        "address1": "123 Main St",
        "city": "San Francisco",
        "state": "CA",
        "postal_code": "94105",
        "country": "US"
      },
      "privacy": true,
      "auto_renew": true
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.dotlet.net/api/v1/registrar/purchase', {
    method: 'POST',
    headers: {
      'X-API-Key': 'dk_••••••••••••••••',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      domain: 'acmewidgets.com',
      years: 1,
      contact: {
        first_name: 'Ada',
        last_name: 'Lovelace',
        email: 'ada@acmewidgets.com',
        phone: '+14155550100',
        address1: '123 Main St',
        city: 'San Francisco',
        state: 'CA',
        postal_code: '94105',
        country: 'US'
      },
      privacy: true,
      auto_renew: true
    })
  });
  const data = await response.json();
  console.log(data);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.dotlet.net/api/v1/registrar/purchase',
      headers={'X-API-Key': 'dk_••••••••••••••••'},
      json={
          'domain': 'acmewidgets.com',
          'years': 1,
          'contact': {
              'first_name': 'Ada',
              'last_name': 'Lovelace',
              'email': 'ada@acmewidgets.com',
              'phone': '+14155550100',
              'address1': '123 Main St',
              'city': 'San Francisco',
              'state': 'CA',
              'postal_code': '94105',
              'country': 'US'
          },
          'privacy': True,
          'auto_renew': True
      }
  )
  print(response.json())
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"io"
  	"net/http"
  )

  func main() {
  	payload := map[string]interface{}{
  		"domain": "acmewidgets.com",
  		"years":  1,
  		"contact": map[string]interface{}{
  			"first_name":  "Ada",
  			"last_name":   "Lovelace",
  			"email":       "ada@acmewidgets.com",
  			"phone":       "+14155550100",
  			"address1":    "123 Main St",
  			"city":        "San Francisco",
  			"state":       "CA",
  			"postal_code": "94105",
  			"country":     "US",
  		},
  		"privacy":    true,
  		"auto_renew": true,
  	}
  	body, _ := json.Marshal(payload)

  	req, _ := http.NewRequest("POST", "https://api.dotlet.net/api/v1/registrar/purchase", bytes.NewBuffer(body))
  	req.Header.Set("X-API-Key", "dk_••••••••••••••••")
  	req.Header.Set("Content-Type", "application/json")

  	resp, _ := http.DefaultClient.Do(req)
  	defer resp.Body.Close()

  	respBody, _ := io.ReadAll(resp.Body)
  	fmt.Println(string(respBody))
  }
  ```
</CodeGroup>

Check `success` on the response before treating the purchase as complete, a `200` with `success: false` means the request was valid but the registrar-side action failed (for example, insufficient funds). See [Errors](/essentials/errors) for this pattern.

## 3. Store the mapping to your own customer

Dotlet doesn't know which of your customers a domain belongs to — that mapping lives in your own database. When a purchase succeeds, store the `domain` and `order_id` from the response against the customer or account record that requested it.

## 4. Route the domain to your platform

Once a customer owns a domain, you need traffic for it to reach your platform; this is what an **origin rule** does. Create one and point it at the tenant's subdomain on your platform:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dotlet.net/api/v1/domains/{domain_id}/origin-rules \
    -H "X-API-Key: dk_••••••••••••••••" \
    -H "Content-Type: application/json" \
    -d '{ "target_subdomain": "acmewidgets.yourplatform.com" }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.dotlet.net/api/v1/domains/{domain_id}/origin-rules', {
    method: 'POST',
    headers: {
      'X-API-Key': 'dk_••••••••••••••••',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ target_subdomain: 'acmewidgets.yourplatform.com' })
  });
  const data = await response.json();
  console.log(data);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.dotlet.net/api/v1/domains/{domain_id}/origin-rules',
      headers={'X-API-Key': 'dk_••••••••••••••••'},
      json={'target_subdomain': 'acmewidgets.yourplatform.com'}
  )
  print(response.json())
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"io"
  	"net/http"
  )

  func main() {
  	payload := map[string]interface{}{
  		"target_subdomain": "acmewidgets.yourplatform.com",
  	}
  	body, _ := json.Marshal(payload)

  	req, _ := http.NewRequest("POST", "https://api.dotlet.net/api/v1/domains/{domain_id}/origin-rules", bytes.NewBuffer(body))
  	req.Header.Set("X-API-Key", "dk_••••••••••••••••")
  	req.Header.Set("Content-Type", "application/json")

  	resp, _ := http.DefaultClient.Do(req)
  	defer resp.Body.Close()

  	respBody, _ := io.ReadAll(resp.Body)
  	fmt.Println(string(respBody))
  }
  ```
</CodeGroup>

The response returns the rule's own `id`, the `domain_id` it belongs to, a `status` (`pending`, `active`, or `failed`), and whether it's currently `enabled`:

```json theme={null}
{
  "id": "b7e4c1a2-9f3d-4e5a-8c6b-1d2e3f4a5b6c",
  "domain_id": "6f9b3a2e-1c4d-4f5e-8a7b-2d3e4f5a6b7c",
  "rule_id": null,
  "status": "pending",
  "enabled": false,
  "rollback_data": null
}
```

Once the rule is created, enable it to start routing traffic:

```bash cURL theme={null}
curl -X POST https://api.dotlet.net/api/v1/domains/origin-rules/{origin_rule_id}/enable \
  -H "X-API-Key: dk_••••••••••••••••"
```

## 5. Manage DNS records

For anything beyond the origin rule (custom subdomains, MX records for customer email, TXT records for verification) manage DNS records directly.

<Note>
  Listing records accepts either an API key or a bearer token. Creating, reading a single record, updating, deleting, and syncing currently accept an **API key only**. See [Authentication](/authentication) for the full breakdown.
</Note>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dotlet.net/api/v1/registrar/dns/acmewidgets.com/records \
    -H "X-API-Key: dk_••••••••••••••••" \
    -H "Content-Type: application/json" \
    -d '{ "name": "acmewidgets.com", "record_type": "A", "content": "203.0.113.10", "ttl": 300 }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.dotlet.net/api/v1/registrar/dns/acmewidgets.com/records', {
    method: 'POST',
    headers: {
      'X-API-Key': 'dk_••••••••••••••••',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ name: 'acmewidgets.com', record_type: 'A', content: '203.0.113.10', ttl: 300 })
  });
  const data = await response.json();
  console.log(data);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.dotlet.net/api/v1/registrar/dns/acmewidgets.com/records',
      headers={'X-API-Key': 'dk_••••••••••••••••'},
      json={'name': 'acmewidgets.com', 'record_type': 'A', 'content': '203.0.113.10', 'ttl': 300}
  )
  print(response.json())
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"io"
  	"net/http"
  )

  func main() {
  	payload := map[string]interface{}{
  		"name":        "acmewidgets.com",
  		"record_type": "A",
  		"content":     "203.0.113.10",
  		"ttl":         300,
  	}
  	body, _ := json.Marshal(payload)

  	req, _ := http.NewRequest("POST", "https://api.dotlet.net/api/v1/registrar/dns/acmewidgets.com/records", bytes.NewBuffer(body))
  	req.Header.Set("X-API-Key", "dk_••••••••••••••••")
  	req.Header.Set("Content-Type", "application/json")

  	resp, _ := http.DefaultClient.Do(req)
  	defer resp.Body.Close()

  	respBody, _ := io.ReadAll(resp.Body)
  	fmt.Println(string(respBody))
  }
  ```
</CodeGroup>

See [Manage DNS records](/guides/manage-dns-records) for the full create/update/delete/sync flow.

## Next steps

<CardGroup cols={2}>
  <Card title="Hosted checkout" icon="window" href="/integration-paths/hosted-checkout">
    Compare against the lower-effort iframe-based path.
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Full parameter and response details for every endpoint used above.
  </Card>
</CardGroup>
