| name | eliza-cloud-manage-domain |
| description | Use after a domain has been purchased through Eliza Cloud (via the `eliza-cloud-buy-domain` skill) when the user wants to look at, edit, or remove a domain or its DNS records. Covers org-wide domain listing, per-app DNS record CRUD on cloudflare-managed zones, status re-sync, external-attachment verification, and detaching a domain from an app. Use this skill any time the user references domains they already own (`my domains`, `edit dns for myapp.com`, `delete that A record`, `is myapp.com still pointing at my app`). |
Manage your Eliza Cloud domains
Use this skill once the user already has at least one domain attached to one of their apps (registered via eliza-cloud-buy-domain or attached as external via the /domains POST). It covers everything after the buy: looking at what they own, editing DNS records on cloudflare-managed zones, refreshing live status, and detaching from an app.
It does NOT register new domains — that's eliza-cloud-buy-domain. It does not modify DNS on external (user-owned-elsewhere) domains — those records live at the user's existing DNS provider.
In a conversational agent running @elizaos/plugin-cloud-apps, the read path
("what domains does my app have", "is it verified") is already a first-class
action — LIST_APP_DOMAINS — so prefer that from chat. DNS record CRUD,
verify, sync, and detach remain SDK/skill territory below.
For live status checks, use the Cloud API plus direct HTTP/DNS/RDAP requests.
Do not use web search snippets or registrar-search pages to decide whether a
domain is bought, attached, or serving an app; those results can be stale.
What this skill can do
| User intent | Endpoint | Notes |
|---|
| "list my domains" / "what domains do I own" | GET /api/v1/domains | org-wide across all their apps |
| "what domains does {app} have" | GET /api/v1/apps/{appId}/domains | per-app; from chat prefer the LIST_APP_DOMAINS action |
| "did myapp.com get bought" / "is it active" | POST /api/v1/apps/{appId}/domains/status | one attached domain's live registrar status |
| "show dns records for myapp.com" | GET /api/v1/apps/{appId}/domains/{domain}/dns | cloudflare zones only |
| "add a CNAME pointing www.myapp.com to ..." | POST /api/v1/apps/{appId}/domains/{domain}/dns | cloudflare zones only |
| "change the A record for myapp.com to ..." | PATCH /api/v1/apps/{appId}/domains/{domain}/dns/{recordId} | get the recordId from the list call first |
| "delete that record" | DELETE /api/v1/apps/{appId}/domains/{domain}/dns/{recordId} | irreversible; warn the user |
| "sync my domains" / "refresh domain status" | POST /api/v1/apps/{appId}/domains/sync | refresh all attached cloudflare domains into managed_domains |
| "I added the verification record, check it" | POST /api/v1/apps/{appId}/domains/verify | external domains: re-check the TXT challenge |
| "remove this domain from my app" | DELETE /api/v1/apps/{appId}/domains | detach only; the cloudflare registration remains until expiry |
Default flow
import { ElizaCloudClient } from "@elizaos/cloud-sdk";
const cloud = new ElizaCloudClient({
apiKey: process.env.ELIZAOS_CLOUD_API_KEY,
baseUrl: process.env.ELIZA_CLOUD_BASE_URL,
});
const allDomains = await cloud.routes.getApiV1Domains();
const target = allDomains.domains.find((d) => d.domain === "myapp.com");
if (!target) {
return;
}
const appId = target.appId;
const status = await cloud.routes.postApiV1AppsByIdDomainsStatus({
pathParams: { id: appId },
json: { domain: "myapp.com" },
});
const records = await cloud.routes.getApiV1AppsByIdDomainsByDomainDns({
pathParams: { id: appId, domain: "myapp.com" },
});
const created = await cloud.routes.postApiV1AppsByIdDomainsByDomainDns({
pathParams: { id: appId, domain: "myapp.com" },
json: { type: "A", name: "www", content: "203.0.113.5", ttl: 1, proxied: true },
});
const updated = await cloud.routes.patchApiV1AppsByIdDomainsByDomainDnsByRecordId({
pathParams: { id: appId, domain: "myapp.com", recordId: created.record.id },
json: { content: "203.0.113.99" },
});
await cloud.routes.deleteApiV1AppsByIdDomainsByDomainDnsByRecordId({
pathParams: { id: appId, domain: "myapp.com", recordId: created.record.id },
});
In an orchestrated worker, do not request or handle the parent's Cloud API key.
Use parent-agent Cloud commands for account-bound domain operations:
USE_SKILL parent-agent {"mode":"list-cloud-commands","query":"domains"}
USE_SKILL parent-agent {"mode":"cloud-command","command":"domains.app.list","params":{"id":"<appId>"}}
USE_SKILL parent-agent {"mode":"cloud-command","command":"domains.dns.list","params":{"id":"<appId>","domain":"myapp.com"}}
Where the boundaries are
Cloudflare-registered domains (registrar=cloudflare): full CRUD on every record type (A, AAAA, CNAME, TXT, MX, SRV, CAA). Cloudflare is the nameserver, so changes propagate within ~minutes.
External domains (registrar=external): read-only here. The user must edit those at the DNS provider where they originally bought the domain (Namecheap, Google Domains, etc.). The DNS endpoints return 409 with that explanation.
Read these references in order
references/api-shape.md — exact request/response shapes
references/dns-records.md — when to use which record type, ttl + proxied semantics
references/failure-modes.md — the failures you'll actually hit
What this skill is NOT
- Not for buying a new domain. Use
eliza-cloud-buy-domain.
- Not for transferring a domain into Eliza Cloud. Out of scope for v1.
- Not for renewing. Renewal billing for cloudflare-registered domains is handled by a cloud-side renewal cron (#10246), not by this skill. Don't promise a specific auto-renew behavior from here; point the user to the dashboard for renewal settings.
- Not a billing surface. DNS edits are free; this skill does not debit credits.