Buy a Domain
Register a domain on Totalum and connect it in one step.
A domain bought on Totalum is registered in your name, paid with the card saved on your Totalum account (not with credits), renewed every year automatically (the renewal is charged 30 days before the expiry and retried 21 and 15 days before; if it is still unpaid 14 days before the expiry, automatic renewal is switched off and you are told by e-mail — renew by hand until the expiry), and connected to a project without any DNS work. Buying domains needs the Starter plan or above; a plan holds up to 3 (Starter), 10 (Builder), 50 (Professional) or unlimited (Enterprise) domains, and at most 5 can be bought a day. Prices are in your billing currency.
These endpoints are account-level (no project in the path). Purchases, renewals, contacts and transfers are the account owner's.
Before buying, create the registrant contact (the domain's legal owner, published in WHOIS with privacy on): Create Registrant Contact. The registrant must verify their e-mail address by the deadline shown in verification.verifyBy, or the domain is suspended until they do.
#Search Domains
/api/v1/vcaas/domains/searchapi-keyFreeWhich names are available, and their price.
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
q | string | Yes | A name (barbershop) or a full domain (barbershop.com) |
tlds | string | No | Comma-separated extensions to check (com,io); default: every extension sold |
Response fields
| Field | Type | Description |
|---|---|---|
data.results[].domain | string | The domain |
data.results[].available | boolean | Can be bought now |
data.results[].premium | boolean | Always false (premium names are not offered) |
data.results[].price | number | First-year price |
data.results[].renewalPrice | number | Yearly renewal price. It can be much higher than price (a registry's first-year promotion), so show both: "€3.59 first year, then €91.19/year" |
data.results[].currency | string | eur or usd |
Example request
curl -H "api-key: tlm_sk_your_key" \
"https://builder-api.totalum.app/api/v1/vcaas/domains/search?q=barbershop&tlds=com,io"const res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/search?q=barbershop&tlds=com,io", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "results": [{ "domain": "barbershop.io", "available": true, "premium": false, "price": 52.8, "renewalPrice": 52.8, "currency": "eur" }, { "domain": "barbershop.com", "available": false, "premium": false, "price": 0, "renewalPrice": 0, "currency": "eur" }] } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | q is missing |
#List TLDs
/api/v1/vcaas/domains/tldsapi-keyFreeThe extensions sold and their prices.
Response fields
| Field | Type | Description |
|---|---|---|
data.tlds[].tld | string | Extension (com) |
data.tlds[].price | number | First-year price |
data.tlds[].renewalPrice | number | Yearly renewal |
data.tlds[].currency | string | eur or usd |
data.tlds[].requirements | boolean | The registry asks for extra registrant data |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domains/tldsconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/tlds", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "tlds": [{ "tld": "com", "price": 14.4, "renewalPrice": 14.4, "currency": "usd", "requirements": false }] } }#Buy Domain
/api/v1/vcaas/domains/purchaseapi-keyCard payment (not credits)Charge the saved card and register the domain. If the registration fails, the payment is refunded at once. Send an Idempotency-Key header so a retried request never buys twice (a retry of a domain you already hold returns it without a new charge). With projectId, the domain is attached to that project in the same call.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | Yes | The domain to buy |
contactId | string | Yes | The registrant contact |
years | number | No | Always 1: a domain is bought for one year, then renews every year. Any other value is refused (400 VALIDATION_ERROR) |
projectId | string | No | Attach to this project after the purchase |
canonicalHost | string | No | Where the site answers: "www" (default), "app" or any DNS label (letters, digits, inner hyphens) — the site is served at <label>.<domain> and the bare domain redirects there (301, path and query kept) — or "apex" (the bare domain itself). Refused with 400 VALIDATION_ERROR before any charge when it is not a valid label |
expectedPrice | number | No | The price you showed: if the live price differs, nothing is bought (409 PRICE_CHANGED) |
Response fields
| Field | Type | Description |
|---|---|---|
data.domain.id | string | Domain id |
data.domain.domain | string | The domain |
data.domain.status | string | "active" |
data.domain.registeredAt | string | ISO 8601 |
data.domain.expiresAt | string | ISO 8601 |
data.domain.autoRenew | boolean | true |
data.domain.pricePaid | number | What was charged |
data.domain.renewalPriceEstimate | number | The expected yearly renewal |
data.domain.attachedProjectId | string | undefined | The project it serves |
data.domain.contactVerificationPending | boolean | The registrant still has to verify their e-mail |
data.warnings | object[] | undefined | { code } — e.g. the attach did not happen (attach it with Attach Domain) |
Example request
curl -X POST \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7d1e4c2a-buy-barbershop-io" \
-d '{"domain":"barbershop.io","contactId":"ct_01k6y2","projectId":"barber-bookings","expectedPrice":52.8}' \
https://builder-api.totalum.app/api/v1/vcaas/domains/purchaseconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/purchase", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": "7d1e4c2a-buy-barbershop-io",
},
body: JSON.stringify({
"domain": "barbershop.io",
"contactId": "ct_01k6y2",
"projectId": "barber-bookings",
"expectedPrice": 52.8
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "domain": { "id": "dom_01k6y3", "domain": "barbershop.io", "status": "active", "registeredAt": "2026-10-08T12:30:00.000Z", "expiresAt": "2027-10-08T12:30:00.000Z", "autoRenew": true, "pricePaid": 52.8, "renewalPriceEstimate": 52.8, "attachedProjectId": "barber-bookings", "contactVerificationPending": true } } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | domain or contactId is missing |
TLD_NOT_ALLOWED | 400 | The extension is not sold |
DOMAIN_PREMIUM_NOT_ALLOWED | 400 | Premium names are not sold |
NO_PAYMENT_METHOD | 402 | Add a card to your Totalum account first |
PAYMENT_FAILED | 402 | The card was declined (errorDetails.declineCode) |
FEATURE_DISABLED | 403 | Buying domains is not available yet on your account |
PLAN_REQUIRED | 403 | Needs the Starter plan or above |
DOMAIN_LIMIT_REACHED | 403 | Your plan's number of domains is reached |
OWNER_ONLY | 403 | Only the account owner can buy |
DOMAIN_UNAVAILABLE | 409 | The domain is taken |
PRICE_CHANGED | 409 | The price differs from expectedPrice (errorDetails.newPrice) |
DOMAIN_CONTACT_REQUIRED | 422 | Create the registrant contact first |
REGISTRATION_FAILED | 422 | The registration was refused; the payment was refunded |
RATE_LIMIT_EXCEEDED | 429 | More than 5 purchases today |
#List Domains
/api/v1/vcaas/domainsapi-keyFreeThe account's domains.
Response fields
| Field | Type | Description |
|---|---|---|
data.domains | object[] | Domains, with the fields of Get Domain |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domainsconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "domains": [{ "id": "dom_01k6y3", "domain": "barbershop.io", "status": "active", "registeredAt": "2026-10-08T12:30:00.000Z", "expiresAt": "2027-10-08T12:30:00.000Z", "autoRenew": true, "renewal": { "status": "scheduled", "nextChargeAt": "2027-09-08T12:30:00.000Z", "attempts": [] }, "pricing": { "pricePaid": 52.8, "currency": "eur", "renewalPriceEstimate": 52.8 }, "privacy": true, "locked": true, "contactId": "ct_01k6y2", "contactVerified": false, "nameservers": ["ns1.totalum-dns.com"], "dnsManaged": true, "attachedProjectId": "barber-bookings", "canonicalHost": "www", "exportAvailableAt": "2026-12-07T12:30:00.000Z", "verification": { "contactVerificationPending": true, "verifyBy": "2026-10-23T12:30:00.000Z" } }] } }#Get Domain
/api/v1/vcaas/domains/:domainIdapi-keyFreeOne domain.
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
Response fields
| Field | Type | Description |
|---|---|---|
data.id | string | Domain id |
data.domain | string | The domain |
data.status | string | "active", "expired_grace", "expired", "export_pending", "transferred_out", "external_dns" or "suspended_verification" |
data.registeredAt | string | ISO 8601 |
data.expiresAt | string | ISO 8601 |
data.autoRenew | boolean | Renews automatically |
data.renewal | object | { status: "scheduled" | "charging" | "renewed" | "cancelled", nextChargeAt, attempts[], lastRenewedAt?, cancelledAt? } |
data.pricing | object | { pricePaid, currency, renewalPriceEstimate } |
data.privacy | boolean | WHOIS privacy |
data.locked | boolean | Transfer lock |
data.contactId | string | Registrant contact |
data.contactVerified | boolean | Registrant e-mail verified |
data.nameservers | string[] | Nameservers |
data.dnsManaged | boolean | DNS is managed on Totalum |
data.attachedProjectId | string | undefined | The project it serves |
data.canonicalHost | string | undefined | "www" or "apex" |
data.exportAvailableAt | string | From when it can be transferred away (60 days after registration) |
data.verification | object | { contactVerificationPending, verifyBy } |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3const res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "id": "dom_01k6y3", "domain": "barbershop.io", "status": "active", "registeredAt": "2026-10-08T12:30:00.000Z", "expiresAt": "2027-10-08T12:30:00.000Z", "autoRenew": true, "renewal": { "status": "scheduled", "nextChargeAt": "2027-09-08T12:30:00.000Z", "attempts": [] }, "pricing": { "pricePaid": 52.8, "currency": "eur", "renewalPriceEstimate": 52.8 }, "privacy": true, "locked": true, "contactId": "ct_01k6y2", "contactVerified": true, "nameservers": ["ns1.totalum-dns.com"], "dnsManaged": true, "attachedProjectId": "barber-bookings", "canonicalHost": "www", "exportAvailableAt": "2026-12-07T12:30:00.000Z", "verification": { "contactVerificationPending": false, "verifyBy": null } } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
DOMAIN_NOT_FOUND | 404 | No such domain in your account |
#Update Domain
/api/v1/vcaas/domains/:domainIdapi-keyFreeTurn WHOIS privacy or automatic renewal on or off. Owner only.
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
privacy | boolean | No | WHOIS privacy |
autoRenew | boolean | No | Automatic yearly renewal |
Response fields
| Field | Type | Description |
|---|---|---|
data | object | The domain after the change, as in Get Domain |
Example request
curl -X PATCH \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"autoRenew":false}' \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3const res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3", {
method: "PATCH",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"autoRenew": false
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "id": "dom_01k6y3", "domain": "barbershop.io", "status": "active", "autoRenew": false, "renewal": { "status": "cancelled", "nextChargeAt": null, "attempts": [] } } }#Renew Domain
/api/v1/vcaas/domains/:domainId/renewapi-keyCard paymentRenew now for one year (within 90 days of expiry, or during the grace period after it). Owner only.
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
Response fields
| Field | Type | Description |
|---|---|---|
data.domain | object | The domain after the renewal |
data.order | object | null | The renewal order |
Example request
curl -X POST -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/renewconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/renew", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "domain": { "id": "dom_01k6y3", "domain": "barbershop.io", "status": "active", "expiresAt": "2028-10-08T12:30:00.000Z" }, "order": { "kind": "renewal", "status": "completed", "customerPrice": 52.8, "currency": "eur" } } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
DOMAIN_NOT_RENEWABLE | 409 | Not within 90 days of expiry (errorDetails.expiresAt) |
PAYMENT_FAILED | 402 | The card was declined |
REGISTRATION_FAILED | 422 | The renewal was refused; the payment was refunded |
#Attach Domain
/api/v1/vcaas/domains/:domainId/attachapi-key4 credits (first domain of the project)Serve a project from this domain; Totalum writes the DNS records itself (and the ones that prove ownership of each address). The project must be published. The site answers at <canonicalHost>.<domain> and the bare domain redirects there (301, path and query kept); follow both addresses with Get Custom Domain.
Change the prefix: call it again with the same projectId and another canonicalHost ({"projectId":"barber-bookings","canonicalHost":"app"}): the site moves to the new address in a few minutes and the old one stops answering. No second fee; the same prefix again changes nothing.
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string | Yes | The project |
canonicalHost | string | No | "www" (default), "app" or any DNS label, or "apex" — as in Buy Domain |
Response fields
| Field | Type | Description |
|---|---|---|
data.domain | object | The domain, as in Get Domain |
data.customDomain | object | The project's custom domain: { domain, hostnames, status, … } |
Example request
curl -X POST \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"projectId":"barber-bookings"}' \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/attachconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/attach", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"projectId": "barber-bookings"
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "domain": { "id": "dom_01k6y3", "domain": "barbershop.io", "attachedProjectId": "barber-bookings", "canonicalHost": "www" }, "customDomain": { "domain": "barbershop.io", "status": "pending" } } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | projectId is missing, or canonicalHost is not "apex" or one DNS label |
PROJECT_NOT_FOUND | 404 | No such project in your account |
NO_DEPLOYMENT | 404 | Publish the project first |
DOMAIN_ALREADY_ATTACHED | 409 | Attached to another project; detach it first |
INSUFFICIENT_CREDITS | 402 | Not enough credits for the domain fee |
PLATFORM_CAPABILITY_QUOTA_REACHED | 403 | Custom domains are not included on your plan |
#Detach Domain
/api/v1/vcaas/domains/:domainId/detachapi-keyFreeStop serving the project from this domain. The domain stays yours and is not refunded. Its automatic renewal keeps running unless you stop it: to remove the domain for good, detach it and then Update Domain with {"autoRenew":false} — it stays registered to you until expiresAt, then expires. Afterwards the project can attach another domain you bought, buy one with projectId, or connect one you own.
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
Response fields
| Field | Type | Description |
|---|---|---|
data | object | The domain, as in Get Domain |
Example request
curl -X POST -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/detachconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/detach", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "id": "dom_01k6y3", "domain": "barbershop.io", "status": "active" } }#List DNS Records
/api/v1/vcaas/domains/:domainId/dns/recordsapi-keyFreeThe domain's DNS records. managed: true records serve the attached project and cannot be changed.
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
Response fields
| Field | Type | Description |
|---|---|---|
data.records[].id | string | Record id |
data.records[].type | string | A, AAAA, CNAME, ALIAS, MX, TXT, SRV or NS |
data.records[].host | string | Host ("" = the domain itself) |
data.records[].answer | string | Value |
data.records[].ttl | number | Seconds (≥ 300) |
data.records[].priority | number | undefined | MX/SRV priority |
data.records[].managed | boolean | Written by Totalum for the attached project |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/dns/recordsconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/dns/records", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "records": [{ "id": "rec_1", "type": "MX", "host": "", "answer": "mx.mailprovider.com", "ttl": 300, "priority": 10, "managed": false }] } }#Create DNS Record
/api/v1/vcaas/domains/:domainId/dns/recordsapi-keyFreeAdd a record (for example your e-mail provider's MX and TXT records).
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
record | object | Yes | { type, host, answer, ttl, priority? } as in List DNS Records |
Response fields
| Field | Type | Description |
|---|---|---|
data | object | The record, with id and managed: false |
Example request
curl -X POST \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"record":{"type":"TXT","host":"","answer":"v=spf1 include:_spf.mailprovider.com ~all","ttl":300}}' \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/dns/recordsconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/dns/records", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"record": {
"type": "TXT",
"host": "",
"answer": "v=spf1 include:_spf.mailprovider.com ~all",
"ttl": 300
}
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "id": "rec_2", "type": "TXT", "host": "", "answer": "v=spf1 include:_spf.mailprovider.com ~all", "ttl": 300, "managed": false } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
DNS_RECORD_CONFLICT | 400 | Conflicts with an existing record |
DNS_RECORD_LIMIT | 400 | The domain already has 200 records |
VALIDATION_ERROR | 400 | Invalid record |
#Update DNS Record
/api/v1/vcaas/domains/:domainId/dns/records/:recordIdapi-keyFreeChange a record you created.
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
recordId | string | The record id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
record | object | Yes | { type, host, answer, ttl, priority? } |
Response fields
| Field | Type | Description |
|---|---|---|
data | object | The record after the change |
Example request
curl -X PATCH \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"record":{"type":"MX","host":"","answer":"mx2.mailprovider.com","ttl":300,"priority":20}}' \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/dns/records/rec_1const res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/dns/records/rec_1", {
method: "PATCH",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"record": {
"type": "MX",
"host": "",
"answer": "mx2.mailprovider.com",
"ttl": 300,
"priority": 20
}
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "id": "rec_1", "type": "MX", "host": "", "answer": "mx2.mailprovider.com", "ttl": 300, "priority": 20, "managed": false } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
DOMAIN_NOT_FOUND | 404 | No such record |
DNS_RECORD_MANAGED | 409 | The record serves the attached project; detach the domain first |
#Delete DNS Record
/api/v1/vcaas/domains/:domainId/dns/records/:recordIdapi-keyFreeDelete a record you created.
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
recordId | string | The record id |
Response fields
| Field | Type | Description |
|---|---|---|
data.deleted | boolean | true |
Example request
curl -X DELETE -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/dns/records/rec_2const res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/dns/records/rec_2", {
method: "DELETE",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "deleted": true } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
DOMAIN_NOT_FOUND | 404 | No such record |
DNS_RECORD_MANAGED | 409 | The record serves the attached project |
#Get Forwarding
/api/v1/vcaas/domains/:domainId/forwardingapi-keyFreeThe domain's URL forwarding rules (a host of the domain redirecting to another URL).
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
Response fields
| Field | Type | Description |
|---|---|---|
data.forwarding[].host | string | Host of the domain (shop.barbershop.io) |
data.forwarding[].forwardsTo | string | Target URL |
data.forwarding[].type | string | "redirect" (301), "302" or "masked" |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/forwardingconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/forwarding", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "forwarding": [{ "host": "shop.barbershop.io", "forwardsTo": "https://store.example.com", "type": "redirect" }] } }#Set Forwarding
/api/v1/vcaas/domains/:domainId/forwardingapi-keyFreeReplace the whole set of forwarding rules (up to 50). A host the attached project serves is refused.
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
forwarding | object[] | Yes | { host, forwardsTo, type }; [] removes every rule |
Response fields
| Field | Type | Description |
|---|---|---|
data.forwarding | object[] | The rules now in place |
Example request
curl -X PUT \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"forwarding":[{"host":"shop.barbershop.io","forwardsTo":"https://store.example.com","type":"redirect"}]}' \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/forwardingconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/forwarding", {
method: "PUT",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"forwarding": [
{
"host": "shop.barbershop.io",
"forwardsTo": "https://store.example.com",
"type": "redirect"
}
]
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "forwarding": [{ "host": "shop.barbershop.io", "forwardsTo": "https://store.example.com", "type": "redirect" }] } }#Get Transfer Code
/api/v1/vcaas/domains/:domainId/exportapi-keyFreeUnlock the domain and get its transfer (auth) code, to move it to another registrar. Also e-mailed to the owner. Possible 60 days after registration. Owner only.
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
Response fields
| Field | Type | Description |
|---|---|---|
data.authCode | string | The transfer code |
Example request
curl -X POST -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/exportconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/export", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "authCode": "Xk7#pQ2!mZ9w" } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
DOMAIN_TRANSFER_LOCKED | 423 | Still inside the 60-day lock (errorDetails.availableAt) |
DOMAIN_NOT_RENEWABLE | 409 | A renewal charge is in progress; retry shortly |
#Cancel Transfer
/api/v1/vcaas/domains/:domainId/export/cancelapi-keyFreeLock the domain again and cancel a pending transfer. Owner only.
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
Response fields
| Field | Type | Description |
|---|---|---|
data | object | The domain, as in Get Domain |
Example request
curl -X POST -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/export/cancelconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/export/cancel", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "id": "dom_01k6y3", "domain": "barbershop.io", "status": "active", "locked": true } }#List Domain Orders
/api/v1/vcaas/domains/:domainId/ordersapi-keyFreeThe domain's payments: registration, renewals and refunds.
Path parameters
| Parameter | Type | Description |
|---|---|---|
domainId | string | The domain id |
Response fields
| Field | Type | Description |
|---|---|---|
data[].kind | string | "registration", "renewal" or "refund" |
data[].status | string | Order status |
data[].customerPrice | number | Amount |
data[].currency | string | eur or usd |
data[].at | string | ISO 8601 |
data[].stripePaymentIntentId | string | undefined | The card payment |
data[].refunded | boolean | The payment was refunded |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/ordersconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/dom_01k6y3/orders", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": [{ "kind": "registration", "status": "completed", "customerPrice": 52.8, "currency": "eur", "at": "2026-10-08T12:30:00.000Z", "stripePaymentIntentId": "pi_3Q…", "refunded": false }] }#List Registrant Contacts
/api/v1/vcaas/domains/contactsapi-keyFreeThe account's registrant contacts.
Response fields
| Field | Type | Description |
|---|---|---|
data.contacts[].id | string | Contact id (for contactId) |
data.contacts[].label | string | undefined | Your label |
data.contacts[].contact | object | The contact data (see Create Registrant Contact) |
Example request
curl -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domains/contactsconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/contacts", {
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "contacts": [{ "id": "ct_01k6y2", "label": "Main", "contact": { "firstName": "Ana", "lastName": "García", "email": "ana@barbershop.io", "phone": "+34600111222", "address1": "Calle Mayor 1", "city": "Madrid", "zip": "28013", "country": "ES" } }] } }#Create Registrant Contact
/api/v1/vcaas/domains/contactsapi-keyFreeCreate the legal owner data used to register domains. Owner only.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
label | string | No | Up to 100 characters |
contact | object | Yes | The registrant |
contact.firstName | string | Yes | First name |
contact.lastName | string | Yes | Last name |
contact.organization | string | No | Company |
contact.email | string | Yes | E-mail (must be verified after a purchase, see verification.verifyBy) |
contact.phone | string | Yes | International format: +, the country calling code and the number, +34644479777. Spaces, dashes, dots and parentheses are accepted and removed, and a leading 00 is read as + (0034 644 47 97 77 is saved as +34644479777) |
contact.address1 | string | Yes | Address |
contact.address2 | string | No | Address, second line |
contact.city | string | Yes | City |
contact.state | string | No | State or province |
contact.zip | string | Yes | Postal code |
contact.country | string | Yes | Two-letter ISO 3166-1 alpha-2 code (ES, US), any case, saved uppercase. A country name (United States) is refused |
Response fields
| Field | Type | Description |
|---|---|---|
data.id | string | Contact id |
data.label | string | undefined | Label |
data.contact | object | The contact |
Example request
curl -X POST \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"label":"Main","contact":{"firstName":"Ana","lastName":"García","email":"ana@barbershop.io","phone":"+34600111222","address1":"Calle Mayor 1","city":"Madrid","zip":"28013","country":"ES"}}' \
https://builder-api.totalum.app/api/v1/vcaas/domains/contactsconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/contacts", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"label": "Main",
"contact": {
"firstName": "Ana",
"lastName": "García",
"email": "ana@barbershop.io",
"phone": "+34600111222",
"address1": "Calle Mayor 1",
"city": "Madrid",
"zip": "28013",
"country": "ES"
}
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "id": "ct_01k6y2", "label": "Main", "contact": { "firstName": "Ana", "lastName": "García", "email": "ana@barbershop.io", "phone": "+34600111222", "address1": "Calle Mayor 1", "city": "Madrid", "zip": "28013", "country": "ES" } } }Error codes
| Code | HTTP | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | A field is missing or invalid; the message names it, e.g. country must be a 2-letter ISO code like ES, US or phone must be in international format like +34644479777 (spaces are removed) |
OWNER_ONLY | 403 | Only the account owner |
#Update Registrant Contact
/api/v1/vcaas/domains/contacts/:contactIdapi-keyFreeChange a contact. Every active domain registered with it is updated; a change of registrant restarts the 60-day transfer lock of those domains.
Path parameters
| Parameter | Type | Description |
|---|---|---|
contactId | string | The contact id |
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
label | string | No | Label |
contact | object | Yes | The full contact, as in Create Registrant Contact |
Response fields
| Field | Type | Description |
|---|---|---|
data.contact | object | The saved contact |
data.warnings | object[] | { code: "DOMAIN_TRANSFER_LOCKED", domains } when domains were re-locked |
Example request
curl -X PATCH \
-H "api-key: tlm_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"contact":{"firstName":"Ana","lastName":"García","email":"ana@barbershop.io","phone":"+34600111222","address1":"Calle Mayor 2","city":"Madrid","zip":"28013","country":"ES"}}' \
https://builder-api.totalum.app/api/v1/vcaas/domains/contacts/ct_01k6y2const res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/contacts/ct_01k6y2", {
method: "PATCH",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"contact": {
"firstName": "Ana",
"lastName": "García",
"email": "ana@barbershop.io",
"phone": "+34600111222",
"address1": "Calle Mayor 2",
"city": "Madrid",
"zip": "28013",
"country": "ES"
}
}),
});
const { errors, data } = await res.json();{ "errors": null, "data": { "contact": { "id": "ct_01k6y2", "label": "Main" }, "warnings": [] } }#Resend Registrant Verification
/api/v1/vcaas/domains/contacts/:contactId/resend-verificationapi-keyFreeSend the registrant's verification e-mail again.
Path parameters
| Parameter | Type | Description |
|---|---|---|
contactId | string | The contact id |
Response fields
| Field | Type | Description |
|---|---|---|
data.sent | number | Verification e-mails sent (0 when nothing is pending) |
Example request
curl -X POST -H "api-key: tlm_sk_your_key" \
https://builder-api.totalum.app/api/v1/vcaas/domains/contacts/ct_01k6y2/resend-verificationconst res = await fetch("https://builder-api.totalum.app/api/v1/vcaas/domains/contacts/ct_01k6y2/resend-verification", {
method: "POST",
headers: {
"api-key": process.env.TOTALUM_API_KEY,
},
});
const { errors, data } = await res.json();{ "errors": null, "data": { "sent": 1 } }