NC

Naijacloud

Docs

DashboardStart free

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.

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:

GroupContains
availableExactly what they asked for and can have, cheapest first
suggestionsName variants they could have instead
unavailableWhat 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.

i

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:

FieldTypeRequiredNotes
domainString!yesThe full name, e.g. acme.com
yearsInt!yesRegistration term. Defaults to 1
contactDomainContactInput!yesThe registrant — see below

DomainContactInput is what the registry requires of whoever will own the name. Every field here is mandatory unless marked otherwise:

FieldTypeNotes
firstName, lastNameString!
emailString!
phoneString!E.164, e.g. +2348012345678
streetString!
cityString!
postalCodeString!
countryString!ISO 3166-1 alpha-2, e.g. NG
stateStringOptional
organizationStringOptional

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 } }
statusMeans
PENDINGOrdered, payment not yet settled
PAIDMoney in, registration in flight
FULFILLEDRegistered. The domain is yours
ABANDONEDThe checkout was never completed
REFUNDEDPayment settled but registration failed, and we refunded it
REFUND_DUERegistration 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.