Domain
A Domain is a hostname that points at a Thing, with a role, a status, and a lifecycle tracked as data.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
hostname | string | required | The name, such as example.com. |
role | string | required | What the Domain does for the Thing it serves; a defensive name takes redirect. |
acquired | string | One of claimed, purchased, or connected. | |
state | string | required | Where the Domain stands; verifying, live, degraded, expiring and lapsed are observed, the rest follow an Action. |
registrant | Party | The owner of record: the holding Company while held, or the holder of a self-serve Startup’s Stripe account. | |
expiresAt | datetime | When the registration ends, as the registrar reports it; empty for a claimed Domain. | |
autoRenew | boolean | True when renewal runs without a Decision; false sends the Domain through renewal review. | |
renewalPrice | money | What the next renewal costs, which for a premium name differs from the purchase price. Never randomized. | |
transferableAt | datetime | The first moment the registrar allows a transfer out, after any registration, transfer or registrant lock. | |
names | string | How its subdomains are given out: explicit records only, or one-label claims by other Startups. | |
publicSuffix | boolean | True once the public suffix list browsers ship carries the Domain; an issuing Domain gives out no label before then. | |
registrarAccount | string | The registrar account that holds the Domain: one per Startup, never shared with a sibling. | |
registryLock | boolean | True when the registry refuses transfer, update and delete until a verified out-of-band request lifts it. | |
certificate | json | Certificate state (pending, active, renewing or failed), its expiry, and the failing check with its fix. | |
validation | string | delegated proves control through a CNAME to the platform; serving is for an apex that cannot hold one. | |
walletsEnabled | boolean | True once Stripe lists the Domain as a payment method domain on the Startup’s account. | |
carriedIdentity | boolean | True once the Domain received mail, hosted a Mailbox or verified an outside account; it never resets. | |
to | Domain | For a redirect: the canonical Domain it forwards to permanently, path and query kept. |
Object
{
"$id": "https: //api.sb/ kilnbakery.com/ domains/ domain_1",
"$type": "Domain",
"hostname": "kilnbakery.com",
"role": "canonical",
"acquired": "claimed",
"state": "verifying",
"registrant": "party_1",
"expiresAt": "2026-10-22T12: 00: 00Z",
"autoRenew": true,
"renewalPrice": {
"amount": 9,
"currency": "USD"
},
"transferableAt": "2026-10-22T12: 00: 00Z",
"names": "closed",
"publicSuffix": true,
"registrarAccount": "registrar_account_1",
"registryLock": true,
"certificate": {},
"validation": "delegated",
"walletsEnabled": true,
"carriedIdentity": true,
"to": "domain_1"
}Relationships
- A Domain hosts one Site: a Site answers on one or more Domains; each Domain serves one Site.
hosts, inversehostedAt. - A Domain mounts many Interfaces: an Interface answers requests on the hostnames it is mounted on.
mounts, inversemountedOn. - A Domain records many DNS Records: a Domain answers DNS from its records; each record belongs to one Domain.
records, inverseanswersFor. - A Domain is registered to one Startup: a Startup holds each Domain registration and pays for the hostname.
registeredTo, inversedomains. - A Domain serves one Tenant: a Tenant answers on the names its Customer connects; each such Domain serves one Tenant.
serves, inversehostnames. - A Domain is verified by one Tenant: a Tenant proves control of its Customer’s email domain, so sign-ins on that domain follow the Tenant’s policy.
verifiedBy, inverseverifies.
Lifecycle
| State | Meaning | Moves to |
|---|---|---|
candidate (first) | A name found by search that no one here holds yet. | held by Domain.register; registered by Domain.register; claimed by Domain.claim; connected by Domain.connect |
held | Registered to the holding Company and not yet assigned to a Startup. | claimed by Domain.claim; released by Domain.release |
claimed | Assigned to a Startup: a label under an issuing Domain, or a held Domain. | live by Domain.verify; released by Domain.release |
registered | Registered to its owner of record and pointed at the Startup’s address. | verifying by Domain.verify |
connected | Attached from another registrar while its ownership proof is checked. | verifying by Domain.verify |
verifying | Its first DNS, ownership and certificate checks are running. | live by Domain.verify; degraded by Domain.verify |
live | Serving, with its DNS, ownership and certificate checks passing. | degraded by Domain.verify; expiring on Domain.renewalDue; renewed by Domain.renew; transferred by Domain.transfer; released by Domain.release |
degraded | A check failed; a degraded canonical Domain opens an Incident. | live by Domain.verify; released by Domain.release |
expiring | Inside its renewal window with no renewal yet. | renewed by Domain.renew; lapsed on Domain.expired |
lapsed | Its registration ended unrenewed, and the registrar may still restore it. | renewed by Domain.renew; released by Domain.release |
renewed | Extended by whole years at its renewal price. | live by Domain.verify |
transferred | Moved to another holder or registrar, with its record and history kept. | None: it ends here. |
released | Its registration or claim ended, and its record, history and aliases are kept. | None: it ends here. |
Verbs
createCreates a Domain.POST /{namespace}/domainsgetReturns one Domain by id.GET /{namespace}/domains/{id}listReturns Domains, newest first, one page at a time.GET /{namespace}/domainsupdateChanges the given properties of a Domain.PATCH /{namespace}/domains/{id}deleteDeletes a Domain; its history is kept.DELETE /{namespace}/domains/{id}authenticateAuthenticate writes a Domain’s SPF, DKIM and DMARC records and verifies them before its first send.POST /{namespace}/domains/{domain}/authenticatebenchmarkBenchmark measures a Domain’s estimated traffic, ranking Keywords, authority and referring domains beside the competitors named.POST /{namespace}/domains/benchmarkclaimClaim gives a Startup a one-label name under an issuing Domain, or assigns it a held Domain through an approved Change.POST /{namespace}/domains/claimconnectConnect attaches a hostname a Startup already holds and verifies ownership through DNS.POST /{namespace}/domains/connectregisterRegister buys a hostname for a Startup and points it at the Startup’s address.POST /{namespace}/domains/registerreleaseRelease lets a Domain’s registration or claim end, keeping its record, history and aliases.POST /{namespace}/domains/{domain}/releaserenewRenew extends a Domain’s registration by whole years at its renewal price, paid by its owner of record.POST /{namespace}/domains/{domain}/renewsearchSearch lists candidate names with availability, first-year price, renewal price and the rules of each extension.POST /{namespace}/domains/searchtransferTransfer moves a Domain’s registration to another holder or registrar, with its records intact.POST /{namespace}/domains/{domain}/transferverifyVerify checks a Domain’s DNS, ownership and certificate again and records each result, with the fix for any failure.POST /{namespace}/domains/{domain}/verify
Events
Events no Verb emits. Each Verb’s Event is in the Verbs table above; every Event is delivered in the same shape.
Domain.expired
A Domain’s registration passed expiresAt unrenewed; the registrar may still restore it. Raised by a clock.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | required | Domain.expired. |
at | datetime | required | When it happened. |
seq | integer | Its position in the namespace, one more than the Event before; any seq is a cursor into the history. | |
data | json | required | The Thing as the Action left it. |
data.domain | Domain | required | The Domain as the change left it. |
data.domain.hostname | string | required | The name, such as example.com. |
data.domain.role | string | required | What the Domain does for the Thing it serves; a defensive name takes redirect. |
data.domain.acquired | string | One of claimed, purchased, or connected. | |
data.domain.state | string | required | Where the Domain stands; verifying, live, degraded, expiring and lapsed are observed, the rest follow an Action. |
data.domain.registrant | Party | The owner of record: the holding Company while held, or the holder of a self-serve Startup’s Stripe account. | |
data.domain.expiresAt | datetime | When the registration ends, as the registrar reports it; empty for a claimed Domain. | |
data.domain.autoRenew | boolean | True when renewal runs without a Decision; false sends the Domain through renewal review. | |
data.domain.renewalPrice | money | What the next renewal costs, which for a premium name differs from the purchase price. | |
data.domain.transferableAt | datetime | The first moment the registrar allows a transfer out, after any registration, transfer or registrant lock. | |
data.domain.names | string | How its subdomains are given out: explicit records only, or one-label claims by other Startups. | |
data.domain.publicSuffix | boolean | True once the public suffix list browsers ship carries the Domain; an issuing Domain gives out no label before then. | |
data.domain.registrarAccount | string | The registrar account that holds the Domain: one per Startup, never shared with a sibling. | |
data.domain.registryLock | boolean | True when the registry refuses transfer, update and delete until a verified out-of-band request lifts it. | |
data.domain.certificate | json | Certificate state (pending, active, renewing or failed), its expiry, and the failing check with its fix. | |
data.domain.validation | string | delegated proves control through a CNAME to the platform; serving is for an apex that cannot hold one. | |
data.domain.walletsEnabled | boolean | True once Stripe lists the Domain as a payment method domain on the Startup’s account. | |
data.domain.carriedIdentity | boolean | True once the Domain received mail, hosted a Mailbox or verified an outside account; it never resets. | |
data.domain.to | Domain | For a redirect: the canonical Domain it forwards to permanently, path and query kept. | |
data.previous.state | string | required | The state before this Event: expiring. |
{
"$id": "https: //api.sb/ kilnbakery.com/ events/ event_1",
"$type": "Event",
"name": "Domain.expired",
"at": "2026-10-22T12: 00: 00Z",
"data": {
"domain": {
"$id": "https: //api.sb/ kilnbakery.com/ domains/ domain_1",
"$type": "Domain",
"hostname": "kilnbakery.com",
"role": "canonical",
"acquired": "claimed",
"state": "lapsed",
"registrant": "party_1",
"expiresAt": "2026-10-22T12: 00: 00Z",
"autoRenew": true,
"renewalPrice": {
"amount": 9,
"currency": "USD"
},
"transferableAt": "2026-10-22T12: 00: 00Z",
"names": "closed",
"publicSuffix": true,
"registrarAccount": "registrar_account_1",
"registryLock": true,
"certificate": {},
"validation": "delegated",
"walletsEnabled": true,
"carriedIdentity": true,
"to": "domain_1"
},
"previous": {
"state": "expiring"
}
},
"seq": 1
}Domain.renewalDue
A live Domain entered its renewal window with no renewal yet. Raised by a clock.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | required | Domain.renewalDue. |
at | datetime | required | When it happened. |
seq | integer | Its position in the namespace, one more than the Event before; any seq is a cursor into the history. | |
data | json | required | The Thing as the Action left it. |
data.domain | Domain | required | The Domain as the change left it. |
data.domain.hostname | string | required | The name, such as example.com. |
data.domain.role | string | required | What the Domain does for the Thing it serves; a defensive name takes redirect. |
data.domain.acquired | string | One of claimed, purchased, or connected. | |
data.domain.state | string | required | Where the Domain stands; verifying, live, degraded, expiring and lapsed are observed, the rest follow an Action. |
data.domain.registrant | Party | The owner of record: the holding Company while held, or the holder of a self-serve Startup’s Stripe account. | |
data.domain.expiresAt | datetime | When the registration ends, as the registrar reports it; empty for a claimed Domain. | |
data.domain.autoRenew | boolean | True when renewal runs without a Decision; false sends the Domain through renewal review. | |
data.domain.renewalPrice | money | What the next renewal costs, which for a premium name differs from the purchase price. | |
data.domain.transferableAt | datetime | The first moment the registrar allows a transfer out, after any registration, transfer or registrant lock. | |
data.domain.names | string | How its subdomains are given out: explicit records only, or one-label claims by other Startups. | |
data.domain.publicSuffix | boolean | True once the public suffix list browsers ship carries the Domain; an issuing Domain gives out no label before then. | |
data.domain.registrarAccount | string | The registrar account that holds the Domain: one per Startup, never shared with a sibling. | |
data.domain.registryLock | boolean | True when the registry refuses transfer, update and delete until a verified out-of-band request lifts it. | |
data.domain.certificate | json | Certificate state (pending, active, renewing or failed), its expiry, and the failing check with its fix. | |
data.domain.validation | string | delegated proves control through a CNAME to the platform; serving is for an apex that cannot hold one. | |
data.domain.walletsEnabled | boolean | True once Stripe lists the Domain as a payment method domain on the Startup’s account. | |
data.domain.carriedIdentity | boolean | True once the Domain received mail, hosted a Mailbox or verified an outside account; it never resets. | |
data.domain.to | Domain | For a redirect: the canonical Domain it forwards to permanently, path and query kept. | |
data.daysLeft | integer | required | Days until expiresAt. |
data.previous.state | string | required | The state before this Event: live. |
{
"$id": "https: //api.sb/ kilnbakery.com/ events/ event_1",
"$type": "Event",
"name": "Domain.renewalDue",
"at": "2026-10-22T12: 00: 00Z",
"data": {
"domain": {
"$id": "https: //api.sb/ kilnbakery.com/ domains/ domain_1",
"$type": "Domain",
"hostname": "kilnbakery.com",
"role": "canonical",
"acquired": "claimed",
"state": "expiring",
"registrant": "party_1",
"expiresAt": "2026-10-22T12: 00: 00Z",
"autoRenew": true,
"renewalPrice": {
"amount": 9,
"currency": "USD"
},
"transferableAt": "2026-10-22T12: 00: 00Z",
"names": "closed",
"publicSuffix": true,
"registrarAccount": "registrar_account_1",
"registryLock": true,
"certificate": {},
"validation": "delegated",
"walletsEnabled": true,
"carriedIdentity": true,
"to": "domain_1"
},
"daysLeft": 30,
"previous": {
"state": "live"
}
},
"seq": 1
}Permissions
Which Access level may call each Verb. One-way Verbs wait their delay window.
| Operation | owner | admin | member | viewer |
|---|---|---|---|---|
Domain.create | Yes | Yes | Yes | No |
Domain.get | Yes | Yes | Yes | Yes |
Domain.list | Yes | Yes | Yes | Yes |
Domain.update | Yes | Yes | Yes | No |
Domain.delete | Yes | Yes | Yes | No |
Domain.authenticate | Yes | Yes | Yes | No |
Domain.benchmark | Yes | Yes | Yes | No |
Domain.claim | Yes | Yes | Yes | No |
Domain.connect | Yes | Yes | Yes | No |
Domain.register | Yes | No | No | No |
Domain.release | Yes | No | No | No |
Domain.renew | Yes | No | No | No |
Domain.search | Yes | Yes | Yes | No |
Domain.transfer | Yes | No | No | No |
Domain.verify | Yes | Yes | Yes | No |
Call it
Register the domain “patentpad.com” with a startup.Example
hostname role state to
permitly.dev canonical live the Site, /docs, /portal, /api and /mcp
api.permitly.dev alias live the same API as /api
permitly.com redirect live permitly.dev