Custom domains
Add your own domains, check their DNS and set where the home page goes.
Custom domains need the Business plan. A domain serves links once its TXT record proves ownership and its CNAME points here: status is then Active and canServeLinks is true.
Every path below starts with https://bgum.me/api/v1/workspaces/{workspaceId}. Send your API key in the Authorization header as described in the API overview.
| Operation | Method and path |
|---|---|
| List domains | GET /domains |
| Add a domain | POST /domains |
| Get a domain | GET /domains/{domainId} |
| Check a domain | POST /domains/{domainId}/check |
| Set the home page and not-found redirects | PUT /domains/{domainId}/redirects |
| Remove a domain | DELETE /domains/{domainId} |
List domains
GET /domains
Lists the workspace's custom domains with their status and the DNS records each needs.
- Full path
/api/v1/workspaces/{workspaceId}/domains- API key scope
-
domains:read - Role needed
- Viewer or above
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
Example request
curl
curl "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/domains" \
-H "Authorization: Bearer $BGL_API_KEY"
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/domains`, {
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);
Python
import os
import requests
res = requests.get(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/domains",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())
Response
200 OK · application/json
JSON
{
"items": [
{
"id": "WAgJQCtT4fGU",
"hostname": "go.example.com",
"status": "PendingVerification",
"certificateStatus": "NotRequested",
"canServeLinks": false,
"verifiedAt": null,
"lastCheckedAt": null,
"lastCheckMessage": null,
"verificationFailingSince": null,
"rootRedirectUrl": null,
"notFoundRedirectUrl": null,
"linkCount": 0,
"expiredRedirectUrl": null,
"records": [
{
"type": "TXT",
"name": "_blackgum-verify.go.example.com",
"value": "blackgum-verify=LkI0anhlUEo1ZVQC9xjjSg",
"purpose": "Proves you control the domain. Leave it in place."
},
{
"type": "CNAME",
"name": "go.example.com",
"value": "bgum.me",
"purpose": "Sends visitors to this service."
}
],
"createdAt": "2026-10-14T22:49:38.7348494+00:00"
}
]
}
Errors
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Add a domain
POST /domains
Adds a domain and returns the DNS records to publish. Use a subdomain you don't use for anything else, such as go.example.com.
- Full path
/api/v1/workspaces/{workspaceId}/domains- API key scope
-
domains:write - Role needed
- Administrator or above
- Plan
- Needs custom domains in your plan (compare plans)
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
Request body
JSON (Content-Type: application/json). Leave out fields you don't need.
| Name | Type | Required | Description |
|---|---|---|---|
hostname |
string | Yes | The domain, e.g. go.example.com. A pasted https:// address is reduced to its hostname. |
Example request
curl
curl -X POST "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/domains" \
-H "Authorization: Bearer $BGL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"hostname": "go.example.com"
}'
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/domains`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"hostname": "go.example.com"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);
Python
import os
import requests
res = requests.post(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/domains",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
json={
"hostname": "go.example.com",
},
timeout=30,
)
res.raise_for_status()
print(res.json())
Response
201 Created · application/json
JSON
{
"id": "WAgJQCtT4fGU",
"hostname": "go.example.com",
"status": "PendingVerification",
"certificateStatus": "NotRequested",
"canServeLinks": false,
"verifiedAt": null,
"lastCheckedAt": null,
"lastCheckMessage": null,
"verificationFailingSince": null,
"rootRedirectUrl": null,
"notFoundRedirectUrl": null,
"linkCount": 0,
"expiredRedirectUrl": null,
"records": [
{
"type": "TXT",
"name": "_blackgum-verify.go.example.com",
"value": "blackgum-verify=LkI0anhlUEo1ZVQC9xjjSg",
"purpose": "Proves you control the domain. Leave it in place."
},
{
"type": "CNAME",
"name": "go.example.com",
"value": "bgum.me",
"purpose": "Sends visitors to this service."
}
],
"createdAt": "2026-10-14T22:49:38.7348494+00:00"
}
Good to know
- Publish every record in records at your DNS provider, then call Check a domain (or wait: we check every few minutes).
Errors
| Status | Code | Meaning |
|---|---|---|
| 422 | domain.invalid | The hostname isn't a valid domain or subdomain. |
| 409 | domain.reserved | The hostname belongs to this service. |
| 409 | domain.taken | Another workspace has already added this hostname. |
| 429 | domain.limit_reached | The organisation has as many custom domains as its plan allows. |
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Get a domain
GET /domains/{domainId}
Returns one domain with its status, last check result and DNS records.
- Full path
/api/v1/workspaces/{workspaceId}/domains/{domainId}- API key scope
-
domains:read - Role needed
- Viewer or above
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
domainId |
string | Yes | The domain's ID, as returned when it was created or listed. |
Example request
curl
curl "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/domains/WAgJQCtT4fGU" \
-H "Authorization: Bearer $BGL_API_KEY"
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/domains/WAgJQCtT4fGU`, {
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);
Python
import os
import requests
res = requests.get(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/domains/WAgJQCtT4fGU",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())
Response
200 OK · application/json
JSON
{
"id": "WAgJQCtT4fGU",
"hostname": "go.example.com",
"status": "PendingVerification",
"certificateStatus": "NotRequested",
"canServeLinks": false,
"verifiedAt": null,
"lastCheckedAt": null,
"lastCheckMessage": null,
"verificationFailingSince": null,
"rootRedirectUrl": null,
"notFoundRedirectUrl": null,
"linkCount": 0,
"expiredRedirectUrl": null,
"records": [
{
"type": "TXT",
"name": "_blackgum-verify.go.example.com",
"value": "blackgum-verify=LkI0anhlUEo1ZVQC9xjjSg",
"purpose": "Proves you control the domain. Leave it in place."
},
{
"type": "CNAME",
"name": "go.example.com",
"value": "bgum.me",
"purpose": "Sends visitors to this service."
}
],
"createdAt": "2026-10-14T22:49:38.7348494+00:00"
}
Good to know
- status is PendingVerification (TXT record not found yet), Verified (ownership proved; waiting for the CNAME or for HTTPS), Active (serving links), DnsMisconfigured (was working, but the records have gone) or Suspended. lastCheckMessage says what's missing.
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | domain.not_found | No domain with that ID in this workspace, or it isn't available to it. |
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Check a domain
POST /domains/{domainId}/check
Checks the DNS records now and returns the updated domain.
- Full path
/api/v1/workspaces/{workspaceId}/domains/{domainId}/check- API key scope
-
domains:write - Role needed
- Administrator or above
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
domainId |
string | Yes | The domain's ID, as returned when it was created or listed. |
Example request
curl
curl -X POST "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/domains/WAgJQCtT4fGU/check" \
-H "Authorization: Bearer $BGL_API_KEY"
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/domains/WAgJQCtT4fGU/check`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);
Python
import os
import requests
res = requests.post(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/domains/WAgJQCtT4fGU/check",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())
Response
200 OK · application/json
JSON
{
"id": "WAgJQCtT4fGU",
"hostname": "go.example.com",
"status": "PendingVerification",
"certificateStatus": "NotRequested",
"canServeLinks": false,
"verifiedAt": null,
"lastCheckedAt": "2026-10-14T23:02:10+00:00",
"lastCheckMessage": "TXT record _blackgum-verify.go.example.com with the verification value wasn't found yet. DNS changes can take up to an hour.",
"verificationFailingSince": null,
"rootRedirectUrl": null,
"notFoundRedirectUrl": null,
"linkCount": 0,
"expiredRedirectUrl": null,
"records": [
{
"type": "TXT",
"name": "_blackgum-verify.go.example.com",
"value": "blackgum-verify=LkI0anhlUEo1ZVQC9xjjSg",
"purpose": "Proves you control the domain. Leave it in place."
},
{
"type": "CNAME",
"name": "go.example.com",
"value": "bgum.me",
"purpose": "Sends visitors to this service."
}
],
"createdAt": "2026-10-14T22:49:38.7348494+00:00"
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | domain.not_found | No domain with that ID in this workspace, or it isn't available to it. |
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Set the home page and not-found redirects
PUT /domains/{domainId}/redirects
Sets where the bare domain (go.example.com/) and unknown back-halves send people. Send null to use the defaults.
- Full path
/api/v1/workspaces/{workspaceId}/domains/{domainId}/redirects- API key scope
-
domains:write - Role needed
- Administrator or above
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
domainId |
string | Yes | The domain's ID, as returned when it was created or listed. |
Request body
JSON (Content-Type: application/json). Leave out fields you don't need.
| Name | Type | Required | Description |
|---|---|---|---|
rootRedirectUrl |
string | Yes | Where go.example.com/ goes, or null for this service's home page. |
notFoundRedirectUrl |
string | Yes | Where unknown back-halves go, or null for a "link not found" page. |
expiredRedirectUrl |
string | No | Where expired links on this domain go when the link doesn't set its own, or null (or left out) for a "link expired" page. |
Example request
curl
curl -X PUT "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/domains/WAgJQCtT4fGU/redirects" \
-H "Authorization: Bearer $BGL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rootRedirectUrl": "https://example.com/",
"notFoundRedirectUrl": null
}'
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/domains/WAgJQCtT4fGU/redirects`, {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"rootRedirectUrl": "https://example.com/",
"notFoundRedirectUrl": null
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const data = await res.json();
console.log(data);
Python
import os
import requests
res = requests.put(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/domains/WAgJQCtT4fGU/redirects",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
json={
"rootRedirectUrl": "https://example.com/",
"notFoundRedirectUrl": None,
},
timeout=30,
)
res.raise_for_status()
print(res.json())
Response
200 OK · application/json
JSON
{
"id": "WAgJQCtT4fGU",
"hostname": "go.example.com",
"status": "PendingVerification",
"certificateStatus": "NotRequested",
"canServeLinks": false,
"verifiedAt": null,
"lastCheckedAt": null,
"lastCheckMessage": null,
"verificationFailingSince": null,
"rootRedirectUrl": "https://example.com/",
"notFoundRedirectUrl": null,
"linkCount": 0,
"expiredRedirectUrl": null,
"records": [
{
"type": "TXT",
"name": "_blackgum-verify.go.example.com",
"value": "blackgum-verify=LkI0anhlUEo1ZVQC9xjjSg",
"purpose": "Proves you control the domain. Leave it in place."
},
{
"type": "CNAME",
"name": "go.example.com",
"value": "bgum.me",
"purpose": "Sends visitors to this service."
}
],
"createdAt": "2026-10-14T22:49:38.7348494+00:00"
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | domain.not_found | No domain with that ID in this workspace, or it isn't available to it. |
| 422 | destination.invalid | The destination isn't a valid web address. |
| 422 | destination.scheme | Only http and https addresses can be shortened. |
| 422 | destination.private | The address points to a private network, localhost or an internal name. |
| 422 | destination.loop | The address is itself a short link on this service, which would loop. |
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Remove a domain
DELETE /domains/{domainId}
Removes the domain from the workspace. Refused while links still use it, so nothing breaks silently.
- Full path
/api/v1/workspaces/{workspaceId}/domains/{domainId}- API key scope
-
domains:write - Role needed
- Administrator or above
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspaceId |
string | Yes | Your workspace ID, from Settings → Workspace. |
domainId |
string | Yes | The domain's ID, as returned when it was created or listed. |
Example request
curl
curl -X DELETE "https://bgum.me/api/v1/workspaces/$WORKSPACE_ID/domains/WAgJQCtT4fGU" \
-H "Authorization: Bearer $BGL_API_KEY"
JavaScript
const res = await fetch(`https://bgum.me/api/v1/workspaces/${process.env.WORKSPACE_ID}/domains/WAgJQCtT4fGU`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.BGL_API_KEY}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
// 204 No Content: nothing to read.
Python
import os
import requests
res = requests.delete(
f"https://bgum.me/api/v1/workspaces/{os.environ['WORKSPACE_ID']}/domains/WAgJQCtT4fGU",
headers={
"Authorization": f"Bearer {os.environ['BGL_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
# 204 No Content: nothing to read.
Response
204 No Content
Errors
| Status | Code | Meaning |
|---|---|---|
| 404 | domain.not_found | No domain with that ID in this workspace, or it isn't available to it. |
| 409 | domain.in_use | Links still use the domain, so it can't be removed. |
Any request can also return the general errors: 401 for a missing or wrong key, 403 for a missing scope or plan feature, 404 for an unknown workspace and 429 when you're sending too fast.
Something missing or unclear? Tell us and we'll improve this page.