Registrar connections
Connect your domain registrar accounts so we set your sites' nameservers for you: connections, automatic updates, per-site status and history.
Registrars you can connect
GET/api/v1/registrars/providersScope registrars:read
Every registrar we can set nameservers at, with the steps to create an API key there, the IP addresses to allow (when the registrar needs an allow-list) and the fields to send in secret_credentials when you connect it.
Example
curl -s "https://app.pbn.ltd/api/v1/registrars/providers" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/registrars/providers", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/registrars/providers", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": [
{
"code": "namecheap",
"name": "Namecheap",
"ip_allowlist": "required",
"allow_ips": [
"116.202.166.25",
"65.109.76.2"
],
"about": "Uses the Namecheap API. API access must be switched on and our IP addresses whitelisted.",
"needs": "...",
"steps": [
"In Namecheap open Profile > Tools > ..."
],
"fields": [
{
"name": "username",
"label": "Namecheap username",
"masked": false,
"required": true
},
{
"name": "api_key",
"label": "API key",
"masked": true,
"required": true
}
]
}
]
}
Errors: rate_limited, scope_missing, unauthorized
List registrar connections
GET/api/v1/registrars/connectionsScope registrars:read
Your connected registrar accounts: status, how many domains we can see in each, and whether automatic nameserver updates are on. The keys are never returned - only a masked hint such as "key …a1b2".
Example
curl -s "https://app.pbn.ltd/api/v1/registrars/connections" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/registrars/connections", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/registrars/connections", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": [
{
"id": 41,
"registrar": "namecheap",
"registrar_name": "Namecheap",
"label": "Main account",
"hint": "myuser \u00b7 key \u2026a1b2",
"status": "ok",
"status_message": "",
"last_checked": "2026-09-24T09:00:00Z",
"domains": 57,
"domains_refreshed_at": "2026-09-24T09:01:00Z",
"automatic_updates": true,
"created_at": "2026-09-15T10:00:00Z"
}
]
}
Errors: rate_limited, scope_missing, unauthorized
One registrar connection
GET/api/v1/registrars/connections/{connection_id}Scope registrars:read
One connection, with the domain names we can see in that registrar account (up to 1,000).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
connection_id | path | integer | yes | The connection id. |
Example
curl -s "https://app.pbn.ltd/api/v1/registrars/connections/connection_id" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/registrars/connections/connection_id", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/registrars/connections/connection_id", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"id": 41,
"registrar": "namecheap",
"registrar_name": "Namecheap",
"label": "Main account",
"hint": "myuser \u00b7 key \u2026a1b2",
"status": "ok",
"status_message": "",
"last_checked": "2026-09-24T09:00:00Z",
"domains": 57,
"domains_refreshed_at": "2026-09-24T09:01:00Z",
"automatic_updates": true,
"created_at": "2026-09-15T10:00:00Z",
"domain_names": [
"example.com"
]
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized
Connect a registrar account
POST/api/v1/registrars/connectionsScope registrars:write
Checks the key with the registrar FIRST and only saves a working account (same as the Registrar connections page). We then read the domain list; sites waiting for nameservers on those domains are updated automatically within a minute or two. Some registrars need our IP addresses on an allow-list first (see GET /registrars/providers).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
registrar | body | string | yes | The registrar code from GET /registrars/providers (e.g. namecheap, godaddy, porkbun, dynadot, namesilo, spaceship, namebright, zinn). |
secret_credentials | body | object | yes | The fields that registrar needs, by name (see fields in GET /registrars/providers), e.g. {"username": "...", "api_key": "..."}. Checked with the registrar before anything is saved; stored encrypted; never returned and never written to the API call log. |
label | body | string | no | Your own name for this account (optional, 80 characters). |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/registrars/connections" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/registrars/connections", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/registrars/connections", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
201
{
"data": {
"id": 41,
"registrar": "namecheap",
"registrar_name": "Namecheap",
"label": "Main account",
"hint": "myuser \u00b7 key \u2026a1b2",
"status": "ok",
"status_message": "",
"last_checked": "2026-09-24T09:00:00Z",
"domains": 57,
"domains_refreshed_at": "2026-09-24T09:01:00Z",
"automatic_updates": true,
"created_at": "2026-09-15T10:00:00Z"
}
}
Errors: conflict, rate_limited, scope_missing, unauthorized, validation_failed
Test a registrar connection
POST/api/v1/registrars/connections/{connection_id}/testScope registrars:write
Asks the registrar whether the stored key still works and records the answer on the connection.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
connection_id | path | integer | yes | The connection id. |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/registrars/connections/connection_id/test" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/registrars/connections/connection_id/test", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/registrars/connections/connection_id/test", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"id": 41,
"registrar": "namecheap",
"registrar_name": "Namecheap",
"label": "Main account",
"hint": "myuser \u00b7 key \u2026a1b2",
"status": "ok",
"status_message": "",
"last_checked": "2026-09-24T09:00:00Z",
"domains": 57,
"domains_refreshed_at": "2026-09-24T09:01:00Z",
"automatic_updates": true,
"created_at": "2026-09-15T10:00:00Z",
"works": true
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized
Re-read the domain list
POST/api/v1/registrars/connections/{connection_id}/refreshScope registrars:write
Reads the domain list of this registrar account again (about a minute). Once every 2 minutes.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
connection_id | path | integer | yes | The connection id. |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/registrars/connections/connection_id/refresh" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/registrars/connections/connection_id/refresh", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/registrars/connections/connection_id/refresh", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
202
{
"data": {
"refreshing": true
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized
Automatic nameserver updates on or off
POST/api/v1/registrars/connections/{connection_id}/automaticScope registrars:write
Switches automatic nameserver updates for this connection on or off (the page's toggle, but with an explicit value, so repeating the call is safe).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
connection_id | path | integer | yes | The connection id. |
enabled | body | boolean | yes | true = we set nameservers for waiting sites on this account's domains. |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/registrars/connections/connection_id/automatic" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/registrars/connections/connection_id/automatic", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/registrars/connections/connection_id/automatic", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"id": 41,
"registrar": "namecheap",
"registrar_name": "Namecheap",
"label": "Main account",
"hint": "myuser \u00b7 key \u2026a1b2",
"status": "ok",
"status_message": "",
"last_checked": "2026-09-24T09:00:00Z",
"domains": 57,
"domains_refreshed_at": "2026-09-24T09:01:00Z",
"automatic_updates": true,
"created_at": "2026-09-15T10:00:00Z"
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized
Disconnect a registrar account
DELETE/api/v1/registrars/connections/{connection_id}Scope registrars:write · destructive
Deletes the connection and its stored keys (the nameserver update history is kept). Nothing changes at the registrar. Needs "confirm": true.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
connection_id | path | integer | yes | The connection id. |
confirm | body | boolean | yes | Must be true: the stored keys are deleted and automatic nameserver updates through this account stop. |
Example
curl -s -X DELETE "https://app.pbn.ltd/api/v1/registrars/connections/connection_id" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.delete("https://app.pbn.ltd/api/v1/registrars/connections/connection_id", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/registrars/connections/connection_id", {
method: "DELETE",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"deleted": true,
"id": 41
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized, validation_failed
Nameserver update history
GET/api/v1/registrars/historyScope registrars:read
Every nameserver change we made (or tried) at your registrars: what the registrar had before (the value to go back to), what we set, the result, and whether the domain registry already shows it.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | query | integer | no | Only this site. |
domain | query | string | no | Only this domain. |
limit | query | integer | no | How many, newest first. Default: 30. |
Example
curl -s "https://app.pbn.ltd/api/v1/registrars/history" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/registrars/history", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/registrars/history", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": [
{
"id": 9001,
"site_id": 12345,
"domain": "example.com",
"registrar": "namecheap",
"registrar_name": "Namecheap",
"connection_id": 41,
"trigger": "autopilot",
"result": "ok",
"message": "",
"old_nameservers": [
"dns1.registrar-servers.com",
"dns2.registrar-servers.com"
],
"new_nameservers": [
"ada.ns.cloudflare.com",
"bob.ns.cloudflare.com"
],
"registry": "confirmed",
"at": "2026-09-24T09:05:00Z"
}
]
}
Errors: rate_limited, scope_missing, unauthorized
Set nameservers for every waiting site now
POST/api/v1/registrars/update-waitingScope registrars:write
Sets the nameservers now for every site waiting for DNS whose domain is in a connected account (the page's "update all" button). Paced to stay inside the registrars' limits, so large accounts take a few minutes; results appear in GET /registrars/history. Once every 15 minutes.
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/registrars/update-waiting" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/registrars/update-waiting", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/registrars/update-waiting", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
202
{
"data": {
"started": true
}
}
Errors: rate_limited, scope_missing, unauthorized
Will this domain get its nameservers set for me?
GET/api/v1/registrars/domain-checkScope registrars:read
The question the add-a-site form asks while you type: is this domain in one of your connected accounts (or registered with Zinn Digital), so we point it at the site ourselves?
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
domain | query | string | yes | The domain (no site needed yet). |
Example
curl -s "https://app.pbn.ltd/api/v1/registrars/domain-check" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/registrars/domain-check", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/registrars/domain-check", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"automatic": true,
"headline": "We will set this domain's nameservers for you.",
"detail": "example.com is in your connected Namecheap account. ..."
}
}
Errors: rate_limited, scope_missing, unauthorized
Automatic nameservers for a site
GET/api/v1/sites/{site_id}/registrarScope registrars:read
What the site page says about automatic nameserver updates for this site: whether we set its nameservers for you, through which registrar, what happened last, and the nameservers it needs.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
Example
curl -s "https://app.pbn.ltd/api/v1/sites/123/registrar" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/registrar", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/registrar", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"automatic": true,
"state": "pending",
"registrar_name": "Namecheap",
"headline": "We are setting the nameservers for you - nothing for you to do.",
"detail": "This domain is in your connected Namecheap account, so we set its nameservers automatically, usually within 10 minutes.",
"nameservers": [
"ada.ns.cloudflare.com",
"bob.ns.cloudflare.com"
],
"waiting_for_dns": true
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized
Set this site's nameservers at the registrar now
POST/api/v1/sites/{site_id}/registrar/update-nameserversScope registrars:write
The site page's "Update nameservers at my registrar" button: sets the nameservers this site needs at the registrar that holds its domain, now, and returns the history entry. "already" means the registrar already had them - the site goes live as soon as DNS catches up.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
site_id | path | integer | yes | The site id (see GET /sites). |
connection_id | body | integer | no | Use this connection (optional; by default the one holding the domain). |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/registrar/update-nameservers" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/registrar/update-nameservers", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/registrar/update-nameservers", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"id": 9001,
"site_id": 12345,
"domain": "example.com",
"registrar": "namecheap",
"registrar_name": "Namecheap",
"connection_id": 41,
"trigger": "manual",
"result": "ok",
"message": "",
"old_nameservers": [
"dns1.registrar-servers.com",
"dns2.registrar-servers.com"
],
"new_nameservers": [
"ada.ns.cloudflare.com",
"bob.ns.cloudflare.com"
],
"registry": "confirmed",
"at": "2026-09-24T09:05:00Z"
}
}
Errors: conflict, not_found, rate_limited, scope_missing, unauthorized