PBN.LTD API docs
View as Markdown

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/providers

Scope 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/connections

Scope 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

NameInTypeRequiredDescription
connection_idpathintegeryesThe 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/connections

Scope 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

NameInTypeRequiredDescription
registrarbodystringyesThe registrar code from GET /registrars/providers (e.g. namecheap, godaddy, porkbun, dynadot, namesilo, spaceship, namebright, zinn).
secret_credentialsbodyobjectyesThe 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.
labelbodystringnoYour 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}/test

Scope registrars:write

Asks the registrar whether the stored key still works and records the answer on the connection.

Parameters

NameInTypeRequiredDescription
connection_idpathintegeryesThe 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}/refresh

Scope registrars:write

Reads the domain list of this registrar account again (about a minute). Once every 2 minutes.

Parameters

NameInTypeRequiredDescription
connection_idpathintegeryesThe 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}/automatic

Scope 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

NameInTypeRequiredDescription
connection_idpathintegeryesThe connection id.
enabledbodybooleanyestrue = 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

NameInTypeRequiredDescription
connection_idpathintegeryesThe connection id.
confirmbodybooleanyesMust 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/history

Scope 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

NameInTypeRequiredDescription
site_idqueryintegernoOnly this site.
domainquerystringnoOnly this domain.
limitqueryintegernoHow 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-waiting

Scope 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-check

Scope 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

NameInTypeRequiredDescription
domainquerystringyesThe 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}/registrar

Scope 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

NameInTypeRequiredDescription
site_idpathintegeryesThe 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-nameservers

Scope 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

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
connection_idbodyintegernoUse 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