Custom Domains API
Custom domain management — add, verify, list, and remove custom domains for branded access
Custom Domains API
The custom domains API manages per-organization custom domain mappings. Organizations on the Enterprise plan can serve ProBeya under their own branded domain (e.g., ops.mycompany.com) instead of the default {slug}.probeya.com subdomain. All procedures require organization-level authentication (orgProcedure).
Domain Lifecycle
Custom domains follow a strict status progression:
pending_dns --> dns_verified --> ssl_provisioning --> active
| Status | Description |
|---|---|
pending_dns | Domain registered, awaiting DNS CNAME configuration |
dns_verified | DNS CNAME verified via server-side lookup |
ssl_provisioning | SSL certificate issuance in progress (ACME/Let’s Encrypt) |
active | Domain is fully configured and serving traffic |
customDomains.list
List all custom domains configured for the organization. Returns domains ordered by creation date descending (newest first).
Type: Query
Input: None (uses organization context from session)
Output:
Array<{
id: string; // cuid2 record identifier
organizationId: string;
domain: string; // e.g., "obeya.yourcompany.com"
status: string; // "pending_dns" | "dns_verified" | "ssl_provisioning" | "active"
cnameTarget: string; // e.g., "acme-corp.custom.probeya.com"
verifiedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}>
customDomains.create
Register a new custom domain for the organization. Validates platform-wide uniqueness (a domain can only belong to one organization), generates the CNAME target ({orgSlug}.custom.probeya.com), and returns setup instructions for the administrator.
Type: Mutation
Input Schema:
{
domain: string; // Fully qualified domain name (e.g., "obeya.yourcompany.com")
}
Output:
{
id: string;
domain: string;
status: "pending_dns";
cnameTarget: string; // e.g., "acme-corp.custom.probeya.com"
instructions: string; // Human-readable CNAME setup instructions
createdAt: Date;
}
Error Codes:
| Code | Condition |
|---|---|
CONFLICT | Domain is already registered by another organization |
FORBIDDEN | Caller is not an admin or owner |
NOT_FOUND | Organization not found |
Side Effects:
- Logs
custom_domain.createdactivity with domain and CNAME target metadata.
customDomains.verify
Trigger a server-side DNS CNAME lookup to verify the administrator has correctly configured their DNS. The server performs its own lookup using dns.promises.resolveCname rather than trusting client-reported results.
Type: Mutation
Input Schema:
{
domainId: string; // cuid2 identifier of the domain record
}
Output (success):
{
verified: true;
domain: { ... }; // Updated domain record with status "dns_verified"
message: "DNS verification successful. You can now provision an SSL certificate.";
}
Output (failure):
{
verified: false;
message: string; // Explains what went wrong (wrong target, lookup failed, etc.)
}
Error Codes:
| Code | Condition |
|---|---|
NOT_FOUND | Domain not found or belongs to another organization |
Side Effects:
- On success: updates domain status to
dns_verified, setsverifiedAttimestamp. - On success: logs
custom_domain.verifiedactivity.
customDomains.provisionSsl
Initiate SSL certificate provisioning for a DNS-verified domain. The domain must be in dns_verified status before SSL can be provisioned. In production, this triggers a background ACME job; the domain status updates to active when the certificate is issued.
Type: Mutation
Input Schema:
{
domainId: string; // cuid2 identifier of the domain record
}
Output:
{
domain: { ... }; // Updated domain record with status "ssl_provisioning"
message: "SSL provisioning has been initiated. This process typically takes 1-5 minutes.";
}
Error Codes:
| Code | Condition |
|---|---|
NOT_FOUND | Domain not found or belongs to another organization |
PRECONDITION_FAILED | Domain is not in dns_verified status |
Side Effects:
- Updates domain status to
ssl_provisioning. - Logs
custom_domain.ssl_provisionedactivity with previous and new status.
customDomains.delete
Permanently remove a custom domain registration. This is a destructive operation that can break existing bookmarks and links if the domain was active. Requires owner role (stricter than create, which allows admin) because of the higher blast radius.
Type: Mutation
Input Schema:
{
domainId: string; // cuid2 identifier of the domain record
}
Output:
{
deleted: true;
message: "Custom domain \"ops.mycompany.com\" has been removed.";
}
Error Codes:
| Code | Condition |
|---|---|
NOT_FOUND | Domain not found or belongs to another organization |
FORBIDDEN | Caller is not an owner (admin is not sufficient for deletion) |
Side Effects:
- Logs
custom_domain.deletedactivity with domain and status at time of deletion.
Permissions
| Procedure | Required Role |
|---|---|
customDomains.list | Any authenticated member |
customDomains.create | Admin or Owner |
customDomains.verify | Any authenticated member |
customDomains.provisionSsl | Any authenticated member |
customDomains.delete | Owner only |
Security
- All procedures are scoped via
orgProcedurefor tenant isolation. - Domain uniqueness is enforced platform-wide: no two organizations can claim the same domain.
- DNS verification is performed server-side to prevent spoofing.
- Tenant isolation is checked on every operation: the domain’s
organizationIdmust match the caller’s context.
DNS Configuration Guide
After creating a custom domain, configure the following DNS record:
| Record Type | Host | Target |
|---|---|---|
| CNAME | obeya.yourcompany.com | {orgSlug}.custom.probeya.com |
DNS propagation typically takes 5-30 minutes but can take up to 48 hours in some cases. Use the verify endpoint to check whether the CNAME has propagated.
Was this page helpful?