API
/
Buying a domain
Buying a domain
The whole purchase runs over the API — search for a name, start an order, send the buyer to the payment page, then watch the order fulfil. Registration happens when the money lands, not when you call the mutation, so an unpaid order costs nothing.
6 min read
Everything here is GraphQL at https://api.naijacloud.com/graphql and needs the
PLATFORM_API scope. Buying a domain spends money, so it takes the broad
scope rather than a narrow one, and purchaseDomain additionally requires the
ADMIN role on the workspace: a service can be deleted, a registration cannot
be un-bought.
1. Search
One query answers "can I have this, and what does it cost". It takes whatever the customer typed — a bare name, a domain, or a pasted URL.
getDomainSearch
query($teamId: ID!, $query: String!) {
getDomainSearch(teamId: $teamId, query: $query) {
query
availableCount
available { domain priceKobo years premium available }
suggestions { domain priceKobo years premium available }
unavailable { domain unavailableCode unavailableReason }
}
}Three groups come back, and they mean different things:
| Group | Contains |
|---|---|
available | Exactly what they asked for and can have, cheapest first |
suggestions | Name variants they could have instead |
unavailable | What they cannot have, each row carrying its own reason |
priceKobo is firm, not an estimate — the checkout is created for exactly that
amount, so the search row, the payment page and the receipt agree. It is null
when a name cannot be sold or cannot be priced right now.
Branch on unavailableCode, never on unavailableReason. The reason is copy
and will be reworded; the code is stable. TAKEN and TLD_COMING_SOON are
opposite answers that read almost identically to a human.
unavailableCode is one of TAKEN, TLD_UNSUPPORTED, TLD_COMING_SOON,
PRICING_UNAVAILABLE or CHECK_FAILED. The last is worth retrying; the others
are not.
2. Start the order
purchaseDomain reserves the order and returns a hosted payment page. It does
not register anything.
purchaseDomain
mutation($teamId: ID!, $input: PurchaseDomainInput!) {
purchaseDomain(teamId: $teamId, PurchaseDomainInput: $input) {
reference
checkoutUrl
amountKobo
}
}PurchaseDomainInput:
| Field | Type | Required | Notes |
|---|---|---|---|
domain | String! | yes | The full name, e.g. acme.com |
years | Int! | yes | Registration term. Defaults to 1 |
contact | DomainContactInput! | yes | The registrant — see below |
DomainContactInput is what the registry requires of whoever will own the name.
Every field here is mandatory unless marked otherwise:
| Field | Type | Notes |
|---|---|---|
firstName, lastName | String! | |
email | String! | |
phone | String! | E.164, e.g. +2348012345678 |
street | String! | |
city | String! | |
postalCode | String! | |
country | String! | ISO 3166-1 alpha-2, e.g. NG |
state | String | Optional |
organization | String | Optional |
Ordering acme.ng
curl https://api.naijacloud.com/graphql \
-H "Authorization: Bearer $NC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation($teamId: ID!, $input: PurchaseDomainInput!) { purchaseDomain(teamId: $teamId, PurchaseDomainInput: $input) { reference checkoutUrl amountKobo } }",
"variables": {
"teamId": "3f2b…",
"input": {
"domain": "acme.ng",
"years": 1,
"contact": {
"firstName": "Ada", "lastName": "Obi",
"email": "[email protected]", "phone": "+2348012345678",
"street": "1 Marina", "city": "Lagos", "state": "Lagos",
"postalCode": "101001", "country": "NG"
}
}
}
}'Response
{
"data": {
"purchaseDomain": {
"reference": "dom_7f31…",
"checkoutUrl": "https://pay.bachs.co/chk_…",
"amountKobo": 2080000
}
}
}The registrant details are encrypted the moment they are stored and are not readable back through the API.
3. Pay
Send the buyer to checkoutUrl. Payment is a hosted page — a card or a bank
transfer in Naira — so this is the one step that needs a browser. An API-only
integration hands the URL to whoever is buying rather than completing it itself.
The page returns to your dashboard with ?purchase=<reference> on success or
?cancelled=<reference> if they backed out.
Do not treat a successful purchaseDomain call as ownership. It means an
order exists and a payment page is open, nothing more. A customer who never
pays costs nothing, and the domain is still available to somebody else until
the money lands.
4. Watch it register
Registration happens when the payment webhook arrives. Poll the order:
getDomainOrders
query($teamId: ID!) {
getDomainOrders(teamId: $teamId) {
reference
domain
status
amountKobo
failureReason
createdAt
}
}status | Means |
|---|---|
PENDING | Ordered, payment not yet settled |
PAID | Money in, registration in flight |
FULFILLED | Registered. The domain is yours |
ABANDONED | The checkout was never completed |
REFUNDED | Payment settled but registration failed, and we refunded it |
REFUND_DUE | Registration failed and the refund itself failed — support will settle it |
The history includes failed and refunded attempts on purpose: a customer who paid and got nothing needs to see that rather than an empty list.
Once fulfilled, the domain appears in:
getRegisteredDomains
query($teamId: ID!) {
getRegisteredDomains(teamId: $teamId) {
id domain tld status registeredAt expiresAt autoRenew nameserverMode
}
}5. Point it at something
A domain bought here arrives with its DNS zone already managed, so you can add records immediately.
addDnsRecord
mutation($zoneId: ID!, $input: DnsRecordInput!) {
addDnsRecord(zoneId: $zoneId, DnsRecordInput: $input) {
id name type content ttl
}
}DnsRecordInput is { name, type, content, ttl?, priority? }, where type is
one of A, AAAA, CNAME, TXT, MX, NS, SRV, CAA or ALIAS.
priority is for MX and SRV.
Find the zone with getDnsZones(teamId, OffsetPaginationArgs), or create one
for a domain you registered elsewhere with createDnsZone(teamId, name).
To put the domain in front of a service, attach it as a custom domain rather than pointing DNS by hand — that path also issues the certificate.