Partner API
Search, register, renew and manage domains through one API. Names ending in .bd go to get.bd; every other ending goes to OrderBox.
Overview
The base URL is your registry host followed by /api/v1.
https://domains.pagejar.com/api/v1
- Requests and responses are JSON. Send
Accept: application/jsonon every call. - Registering and renewing are asynchronous. The call returns
202and you read the domain until itsstatussettles. - A token only sees its own partner's domains. An owner partner's token sees all.
Quick start: search a name, then register it.
curl -s "https://domains.pagejar.com/api/v1/domains/check?name=example&tlds[]=com&tlds[]=com.bd" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json"
Authentication
Create a token in the admin under Partners. It is shown once, so store it in a secret manager. Send it as a bearer token:
Authorization: Bearer <token>
Keep the token on your server. Never put it in browser code or a mobile app. If one leaks, delete it in Partners and create a new one.
| Response | Meaning |
|---|---|
| 401 | No token, or the token is wrong or deleted. |
| 403 | The token is valid but its partner is disabled. |
Requests and responses
- Domain names are lowercase, full names such as
example.com.bd. Case is ignored in paths and on registration. - Dates are UTC, ISO 8601, for example
2026-11-12T10:00:00.000000Z. - Arrays in query strings use
tlds[]=com&tlds[]=net. - Pagination on lists is 50 per page with Laravel's page fields (
data,current_page,last_page,next_page_url,total). Add?page=2. - No idempotency key. Registering the same name twice returns
422, so a retry after a timeout is safe. Read the domain first to see what happened.
Errors
Errors are JSON with a message. Validation errors add an errors object keyed by field.
{
"message": "example.com is already in the registry.",
"errors": { "name": ["example.com is already in the registry."] }
}
| Status | When | What to do |
|---|---|---|
| 401 | Bad or missing token | Check the header and the token. |
| 403 | Partner disabled | Contact the registry owner. |
| 404 | Unknown domain, or it belongs to another partner | The two cases look the same on purpose. |
| 409 | Wrong state, for example renewing a domain that is not active | Read the domain and check its status. |
| 422 | Validation failed | Fix the fields in errors. Do not retry unchanged. |
| 429 | Rate limit hit | Wait Retry-After seconds. |
| 502 | The registrar refused or failed | Read retryable: true means try again later, false means change the request. |
| 5xx | Our side failed | Retry with backoff. |
{ "message": "Invalid nameserver", "retryable": false }
Rate limits
- 120 requests per minute per token on every endpoint.
- 30 requests per minute per token on Check availability and Check a transfer, as well.
- 20 requests per minute per token on Send an auth code.
Over the limit you get 429 with a Retry-After header. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining. Search up to 30 names per call rather than one call per name.
Check availability
Send either a label and the endings to try, or full names. Each name goes to the registrar for its ending.
| Parameter | Description |
|---|---|
| name | A label without an ending, for example example. Needed unless domains is sent. |
| tlds[] | Endings to try with name. Up to 30. Without it the registry's default list is used. |
| domains[] | Full names, up to 30. Needed unless name is sent. |
curl -s "https://domains.pagejar.com/api/v1/domains/check?domains[]=example.com&domains[]=example.com.bd" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json"
{
"data": [
{ "domain": "example.com", "provider": "orderbox", "in_registry": false, "available": true, "premium": false },
{ "domain": "example.com.bd", "provider": "get_bd", "in_registry": false, "available": false, "premium": false },
{ "domain": "example.xyz", "provider": "orderbox", "in_registry": false, "available": null, "error": "No answer for example.xyz" }
]
}
| Field | Description |
|---|---|
| available | true, false, or null when the registrar could not answer. Never offer a null name for sale; ask again later. |
| in_registry | true when a partner already holds the name here. Then available is always false. |
| premium | Premium names are priced differently by the registrar. |
| price, currency | Only some registrars return a price. |
| error | Why available is null, for example an invalid name for that ending. |
Register a domain
Queues the registration. Read Get a domain until status is active or failed. With import: true nothing is bought; the domain is only recorded as active so the registry can track it.
| Field | Description |
|---|---|
| namerequired | Full name, up to 253 characters. |
| years | 1 to 10. Default 1. |
| nameservers | Up to 13. get.bd uses the first 3. OrderBox needs at least 2. |
| external_ref | Your own id for this domain, up to 191 characters. Returned on every read. |
| provider | Override the registrar: get_bd, orderbox or dynadot. Normally leave it out. |
| import | true to record an existing domain without buying it. |
| registrant | The owner's details, see below. Stored encrypted and never returned. |
Registrant
| Field | Description |
|---|---|
| name, organization | Up to 255 characters. |
| A valid email address. | |
| phone_cc, phone | Country code without + (up to 5 characters) and the number (up to 30). |
| address1, address2, city, state, postcode | Postal address. |
| country | Two letters, ISO 3166-1, for example BD. |
| nid | National ID, for .bd. |
What each registrar needs:
- get.bd (
.bd):name,nid,emailand a Bangladeshiphone. The order may wait inpending_documentswhile get.bd reviews documents. - OrderBox (everything else): registrant is optional. Without it the registry's default contact is used.
- Dynadot:
name,email,phoneandcountry.
curl -s -X POST "https://domains.pagejar.com/api/v1/domains" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{
"name": "example.com.bd",
"years": 1,
"nameservers": ["ns1.procloudify.com", "ns2.procloudify.com"],
"external_ref": "whmcs-domain-1234",
"registrant": {
"name": "Rahim Uddin", "email": "rahim@example.com",
"phone_cc": "880", "phone": "01712345678",
"address1": "House 1, Road 2", "city": "Dhaka", "state": "Dhaka",
"postcode": "1207", "country": "BD", "nid": "1234567890"
}
}'const res = await fetch("https://domains.pagejar.com/api/v1/domains", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.REGISTRY_TOKEN}`,
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "example.com", years: 1 }),
});
if (res.status !== 202) throw new Error((await res.json()).message);
const domain = await res.json(); // status: "pending"$res = Http::withToken(config('services.registry.token'))
->acceptJson()
->post('https://domains.pagejar.com/api/v1/domains', [
'name' => 'example.com',
'years' => 1,
]);
$res->throw(); // 422 / 502 raise here
$domain = $res->json(); // status: "pending"Response 202 is the domain object. A failed domain of yours can be posted again to retry with new details. A name held by anyone else returns 422.
List domains
| Parameter | Description |
|---|---|
| status | One of the statuses. |
| q | Part of a domain name. |
| page | Page number. |
| per_page | 1 to 200. Default 50. |
| updated_since | A date or date and time, for example 2026-11-12T10:00:00Z. Only domains changed at or after it. For a sync, remember the time your last sync started and send it next time, and keep reading pages until the last one. |
curl -s "https://domains.pagejar.com/api/v1/domains?status=active&q=example" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json"
Get a domain
{
"id": 12,
"partner_id": 2,
"name": "example.com.bd",
"tld": "com.bd",
"provider": "get_bd",
"status": "active",
"years": 1,
"nameservers": ["ns1.procloudify.com", "ns2.procloudify.com"],
"provider_order_id": "ord_123",
"external_ref": "whmcs-domain-1234",
"registered_at": "2026-11-12T10:00:00.000000Z",
"expires_at": "2027-11-12T00:00:00.000000Z",
"synced_at": "2026-11-12T10:01:00.000000Z",
"last_error": null,
"kind": "register",
"transfer_status": null,
"created_at": "2026-11-12T09:59:30.000000Z",
"updated_at": "2026-11-12T10:01:00.000000Z"
}
Poll every 5 to 10 seconds for the first minute, then every few minutes. get.bd orders waiting on documents can take hours, so stop polling and check once a day instead. last_error explains a failed status or a failed renewal. kind is register or transfer; transfer_status is the registrar's own words while a transfer is in progress.
Renew a domain
| Field | Description |
|---|---|
| years | 1 to 10. Default 1. |
curl -s -X POST "https://domains.pagejar.com/api/v1/domains/example.com/renew" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"years": 1}'
{ "message": "Renewal queued.", "domain": { "name": "example.com", "status": "active", "...": "..." } }
Only active or expired domains can be renewed; others return 409. A second renewal request for the same domain inside 10 minutes also returns 409, so a retry cannot pay twice. The renewal is tried once. Compare expires_at before and after, and check last_error if it did not move.
Change nameservers
| Field | Description |
|---|---|
| nameserversrequired | 2 to 13 hostnames. |
curl -s -X PUT "https://domains.pagejar.com/api/v1/domains/example.com/nameservers" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{"nameservers": ["ns1.example.net", "ns2.example.net"]}'
Only active domains can change nameservers; others return 409. This runs at the registrar before the response returns, so it can take a few seconds. It returns the updated domain, or 502 with the registrar's message.
Sync from the registrar
Reads status, expiry and nameservers from the registrar now and returns the domain. The registry also refreshes every domain about once a day, so you only need this right after a change made outside the API.
Check a transfer
Asks the registrar that would take the domain whether it can be transferred in. Only OrderBox and Dynadot take transfers; a .bd name is refused with 422.
| Parameter | Description |
|---|---|
| namerequired | Full name, for example example.com. |
| provider | orderbox or dynadot. Normally leave it out. |
curl -s "https://domains.pagejar.com/api/v1/transfers/check?name=example.com" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json"
{ "name": "example.com", "provider": "orderbox", "transferable": true, "message": null }
transferable is true, false (see message, for example a name that is not registered at all), or null when the registrar cannot tell before the order is placed (Dynadot).
Transfer a domain in
Moves a domain from another registrar to yours. The order is paid and includes one year of renewal. Before you call it, unlock the domain at the current registrar and get its auth (EPP) code. The order is placed on the queue.
| Field | Description |
|---|---|
| namerequired | Full name. |
| auth_coderequired | The auth / EPP code. Stored encrypted only until the registrar has it, never returned and never logged. |
| nameservers | Up to 13. Applied when the transfer completes. |
| provider | orderbox or dynadot. Default from the registry's routing. |
| external_ref | Your own id for this domain. |
| registrant | Optional owner details, same fields as registration. |
curl -s -X POST "https://domains.pagejar.com/api/v1/transfers" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{ "name": "example.com", "auth_code": "Abc123!xyz", "nameservers": ["ns1.a.com", "ns2.a.com"], "external_ref": "pagejar-77" }'
Follow it
Read Get a domain. kind is transfer and status goes pending, then transferring while the old registrar and the owner approve, then active (or failed). transfer_status shows the registrar's own words, for example "Waiting for Domain-Secret" or "Validating domain transfer", and last_error says what to fix. Check every few hours, not every few seconds.
last_error asks for the right one. Send it with Send an auth code. A failed transfer can be started again by posting the name again with a fresh code.Send an auth code
Gives a waiting transfer a new auth code. Returns the domain.
curl -s -X PUT "https://domains.pagejar.com/api/v1/transfers/example.com/auth-code" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
-d '{ "auth_code": "NewCode1!" }'
| Status | Meaning |
|---|---|
| 200 | The registrar accepted the code. |
| 409 | No transfer for this domain is waiting for a code. |
| 502 | The registrar refused the code. message says why. |
Domain statuses
| Status | Meaning |
|---|---|
| pending | Queued. Nothing sent to the registrar yet. |
| registering | Sent. The registrar has not confirmed. Settles on its own. |
| pending_documents | get.bd is reviewing documents. The registry tries again every 10 minutes. |
| transferring | An inbound transfer was ordered and is waiting for the old registrar or the owner. Often 1 to 7 days. See Transfer a domain in. |
| active | Registered and working. |
| expired | Past its end date. Can still be renewed. |
| failed | See last_error. Post the name again with corrected details to retry. |
| cancelled | Closed. It is no longer managed here. |
Registrars and endings
| Endings | Registrar | Notes |
|---|---|---|
| .bd, .com.bd, .net.bd, .org.bd, .edu.bd, .co.bd, .info.bd, .tv.bd | get.bd | Registrant needs a national ID. Documents may be reviewed. |
| everything else | OrderBox | Some endings answer "unknown" at times. The registry retries before giving up. |
The routing table is managed in the admin under TLD routing.
Going live
- Searches always work. Registering and renewing against a live registrar needs the registry owner to switch live purchases on.
- Test against the sandbox first. Sandbox answers are test data and can differ from live.
- Treat
available: nullas "not sure", never as "taken" or "free". - Store the returned
nameand your ownexternal_ref, and reconcile withGET /domainsafter any timeout. - Handle
429with theRetry-Afterheader, and502by itsretryableflag.