Client API Reference
Authentication
There are two ways to authenticate. Which one you use depends on what you're building.
| Method | Use it for | How |
|---|---|---|
| Session login | Signing a human into this portal | Two-step email/password login, returns a bearer token |
| API key | Scripts, integrations, anything unattended | A long-lived key you generate yourself, below |
You don't need both. A couple of endpoints — managing sub-accounts, managing your own API keys — are portal-login only; noted where relevant.
Getting an API key
- Log in to this portal, then go to Account.
- Create a new key, giving it a label and at least one allowed IP address or CIDR range (e.g.
203.0.113.4or203.0.113.0/24). - Copy the key immediately — it's shown exactly once and cannot be retrieved again. If you lose it, revoke it and create a new one.
- Requests from any IP not in that key's allowlist are rejected, even with a valid key. There is no "allow from anywhere" option — this is by design.
Send it as a bearer token on every request:
Session login
Login is two steps, because the email on an account can be shared across more than one of your organization's client records — for example, if you manage several accounts from one inbox. Step 1 tells you which account(s) that email belongs to; step 2 verifies the password for the one you pick.
Exactly one account? Skip straight to step 2. More than one? Show a picker using client_display_name before asking for a password.
Use token as a bearer token on every subsequent request. Watch for 401 UNAUTHORIZED as the signal to log in again.
/v1/auth/lookup and /v1/auth/login are rate-limited per source IP (10 attempts / 5 minutes) as a brute-force guard. Expect 429 RATE_LIMITED if you exceed that — back off rather than retry immediately.Response format
Every response uses the same envelope:
On failure:
request_id is worth logging on your end — quote it if you ever report an issue, it lets us trace the exact request server-side.
Common error codes
| Status | error.code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | A required field was missing or invalid |
| 401 | UNAUTHORIZED | Missing, invalid, or expired credentials |
| 401 | INVALID_CREDENTIALS | Wrong password on login |
| 403 | FORBIDDEN | Authenticated, but not allowed to do this |
| 404 | NOT_FOUND | Doesn't exist, or doesn't belong to your account |
| 409 | CONFLICT | Doesn't make sense in the resource's current state |
| 429 | RATE_LIMITED | Too many requests — back off and retry later |
| 500 / 502 | INTERNAL_ERROR, etc. | Something went wrong on our end — safe to retry |
Data isolation
Every endpoint below only ever returns or affects data belonging to your own
account. Passing another account's ID anywhere returns 404 NOT_FOUND,
the same as if it didn't exist. This is enforced on every request server-side, not just hidden
in the portal UI.
Domains
Lists every domain on your account.
A single domain, including its owner/admin/tech contacts.
Lists contacts linked to your account. Use a returned id for bulk contact replacement.
Pricing
Resolves the price for a given TLD + action (+ period, for period-based actions like registration/renewal/transfer). Reflects any account-specific pricing you have — this is the price you'd actually be charged.
Returns 404 PRICE_NOT_FOUND if that TLD/action combination isn't orderable for your account.
Domain actions
The way you request anything to actually happen to a domain — a renewal, a DNS change, an ownership transfer, and so on.
Registering a brand-new domain? Omit domain_id and pass tld and domain_name instead — the domain is created as part of the request.
What happens next
- Free (
price_amount: "0.00") — dispatched immediately;statusissent_to_supplier(orpendingwith awarningif dispatch needs a retry on our end — not something you need to act on). - Paid — queued onto an open order; the response includes
order_id. The action stayspendinguntil the resulting invoice is paid, then dispatches automatically.
Lists actions requested on a specific domain. domain_id is required — this endpoint always scopes to one domain.
Requests the same action across up to 500 domains. The request continues after an individual failure and returns one result per domain. Supported actions are renewal, nameserver change, and contact replacement.
Nameservers and contacts are updated per domain before the matching registry action is requested. Paid actions retain the normal open-order and checkout flow.
Idempotency-Key header on POST /v1/domain-actions if you might retry the same request (e.g. after a timeout) — a retried request with the same key won't create a duplicate action.Invoices
List / fetch a single invoice, including its line items.
Decode content_base64 to get the raw PDF file bytes.
Sub-accounts portal login only
Lets an account owner create and manage additional logins under the same account — separate
billing/tech contacts, each with their own credentials. Requires a portal session with
the owner role; not available via API key.
API keys portal login only
Self-service management of your own API keys, including the one you're currently using — also portal-login only, so a compromised key can never mint another key or widen its own access.
Returns the raw key once, in the response — save it now.
Rate limits
| Bucket | Limit |
|---|---|
Login attempts (/v1/auth/lookup, /v1/auth/login) | 10 / 5 min, per source IP |
| API key usage | 120 / min, per key |
Exceeding either returns 429 RATE_LIMITED. Build in a backoff/retry — repeatedly retrying immediately will just keep tripping the limit.
Health check
Returns {"status":"ok"} — useful for your own monitoring, doesn't touch anything else.
Questions, or something not behaving as documented? Include the request_id from the response when you reach out.