Authentication

All authenticated endpoints require a Bearer token in the Authorization header.

curl https://domani.run/api/domains/search?q=example.com \
  -H "Authorization: Bearer domani_sk_xxx"

Get an API key by creating an account or from the dashboard. Some endpoints (search, TLDs, WHOIS) are public but have lower rate limits without auth.

Token Scopes

Tokens can be restricted to specific permission scopes. Use * for full access (default). Create scoped tokens via POST /api/tokens with a scopes array.

ScopeGrants access to
domains:readGET /api/domains, GET /api/domains/{domain}, GET /api/domains/{domain}/dns, /dnssec, /status, /email/check, /auth-code, /transfer-away, /transfer-status, /analytics
domains:writePUT /api/domains/{domain}/dns, POST/DELETE /api/domains/{domain}/dnssec, POST /connect, POST /verify, PUT /settings, PUT /parking, PUT/DELETE /api/domains/{domain}/for-sale, POST /api/domains/import, POST /import/verify
domains:transferPOST /api/domains/buy, POST /transfer, POST /renew (involves payment, includes marketplace purchases)
tokens:readGET /api/tokens
tokens:writePOST /api/tokens, DELETE /api/tokens/{id}
webhooks:readGET /api/webhooks, GET /api/webhooks/{id}/deliveries
webhooks:writePOST /api/webhooks, PATCH /api/webhooks/{id}, DELETE /api/webhooks/{id}
email:readGET /api/emails, /api/emails/{address}, /api/emails/{address}/messages, /api/emails/{address}/aliases, /api/domains/{domain}/email/status, /api/domains/{domain}/email/deliverability, /api/suppressions
email:writePOST /api/emails, POST /api/emails/{address}/send, POST /api/domains/{domain}/email/setup, aliases + catch-all, POST/DELETE /api/suppressions
email:deletePermanently delete messages already in Trash. Moving messages to Trash only requires email:write. Grant this scope only to agents allowed to irreversibly erase email content
email:auth_secretsRead messages classified as authentication mail (OTP / verification codes, password resets, magic links). Without it, email:read still lists them but subject and body come back redacted - so a stolen agent token can't harvest 2FA codes. Grant it only to agents that genuinely need to complete logins
account:readGET /api/me, GET /api/agents/identity
account:writeDELETE /api/me, POST /api/billing/setup, POST /api/billing/subscribe, POST /api/billing/cancel, POST /api/me/resend-verification, POST/PATCH/DELETE /api/agents/identity
billing:readGET /api/billing/invoices
searchGET /api/domains/search, /suggest, /whois, /dns-check, GET /api/tlds
deals:readGET /api/deals, GET /api/deals/{id}
deals:writePOST /api/domains/sell, PATCH /api/deals/{id}
notifications:readGET /api/notifications, GET /api/notifications/count
backorders:readGET /api/backorders, GET /api/backorders/{id}
backorders:writePOST /api/backorders, DELETE /api/backorders/{id}

Scope attenuation: a token can only create child tokens with equal or fewer scopes — never more.

Missing scope returns 403 with code INSUFFICIENT_SCOPE and a hint naming the required scope.

Errors

All errors return a consistent JSON envelope. Use code for programmatic handling and hint for recovery guidance.

{
  "error": "Missing API key",
  "code": "MISSING_API_KEY",
  "hint": "Include 'Authorization: Bearer domani_sk_xxx' header",
  "documentation_url": "https://domani.run/llms.txt"
}
CodeHTTP
MISSING_API_KEY401
INVALID_API_KEY401
EXPIRED_API_KEY401
MISSING_PARAMETER400
INVALID_DOMAIN400
PAYMENT_REQUIRED402
DOMAIN_UNAVAILABLE409
RATE_LIMIT_EXCEEDED429
USER_EXISTS409
NOT_FOUND404
UNKNOWN_PROVIDER400
UNKNOWN_METHOD400
TARGET_REQUIRED400
INVALID_PARAMETER400
CONTACT_REQUIRED400
INVALID_CONTACT400
INSUFFICIENT_SCOPE403
SCOPE_ESCALATION400
DOMAIN_EXISTS400
DOMAIN_NOT_ACTIVE400
UNSUPPORTED_TLD400
NOT_AVAILABLE400
ALREADY_REGISTERED400
TRANSFER_NOT_ELIGIBLE400
ALREADY_LISTED400
LISTING_SOLD400
SELF_PURCHASE400
DESCRIPTION_TOO_LONG400
DEAL_NOT_FOUND400
INVALID_DEAL_STATE400
EPP_ATTEMPTS_EXCEEDED400
ACTIVE_DEAL_EXISTS400
AGENT_IDENTITY_NOT_FOUND400
AGENT_IDENTITY_ESCALATION400
CREDENTIAL_REVOKE_FAILED400

Rate Limits

Every response includes rate limit headers:

  • X-RateLimit-Limit — max requests per window
  • X-RateLimit-Remaining — requests left
  • X-RateLimit-Reset — unix timestamp when window resets

When rate limited (HTTP 429), check the Retry-After header for seconds to wait.

Authentication

POST/api/auth/registerPublic

Create a new account

Creates a new user account and returns an API key. Use this key as a Bearer token for all authenticated endpoints. Each account also receives a unique referral code. Pass a ref code to permanently link the new user to a referrer - the referrer earns a commission on all future domain purchases by this user.

Request body

email*string (email)
refstringReferral code - permanently links this user to the referrer. The referrer earns a commission on every domain purchase.

Response 201

api_keystringYour API key - save this securely
referral_codestringYour unique referral code for earning commissions
has_payment_methodbooleanWhether the user has a card on file (always false for new accounts)
userobject

Example response

{
  "api_key": "domani_sk_a1B2c3D4e5F6g7H8i9J0kLmN",
  "referral_code": "x7k9m2",
  "has_payment_method": false,
  "user": {
    "id": "cm5abc123",
    "email": "dev@example.com"
  }
}

Example

curl -X POST https://domani.run/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "dev@example.com"}'
POST/api/auth/loginPublic

Send a magic link sign-in email

Sends a sign-in link to the provided email. If the user doesn't exist, creates an account first. The magic link redirects to the dashboard with auth credentials. For programmatic API access, use POST /api/auth/register to get an API key directly.

Request body

email*string (email)

Response 200

okboolean
messagestring

Example response

{
  "ok": true,
  "message": "Check your email for a sign-in link."
}

Example

curl -X POST https://domani.run/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "dev@example.com"}'

Account

GET/api/me

Get current account details

Returns the authenticated user's account information including email, payment status, contact info status, referral code, and usage counts. Agents should call this after authentication to check has_payment_method and has_contact before attempting purchases.

Response 200

idstring
emailstring (email)
has_payment_methodbooleanWhether the user has a card on file (required before buying domains)
has_contactbooleanWhether the user has set WHOIS contact info (required before purchasing domains)
contactRegistrantContactRegistrant contact info (null if not set)
referral_codestringYour unique referral code
referral_rateintegerCommission percentage on referred purchases
balancenumberMarketplace credit balance in USD (earned from sales)
domain_countintegerNumber of domains owned
token_countintegerNumber of active API tokens
created_atstring (date-time)

Example response

{
  "id": "cm5abc123",
  "email": "dev@example.com",
  "has_payment_method": true,
  "has_contact": true,
  "referral_code": "x7k9m2",
  "referral_rate": 20,
  "domain_count": 3,
  "token_count": 2,
  "created_at": "2025-01-15T10:30:00.000Z"
}

Example

curl https://domani.run/api/me \
  -H "Authorization: Bearer domani_sk_xxx"
PUT/api/me

Update account preferences

Update the authenticated user's account preferences such as preferred payment method.

Request body

preferred_payment_method*string | null(card | usdc)Default payment method for purchases (card, usdc, or null for auto-select)

Response 200

preferred_payment_methodstring | null
hintstring
DELETE/api/me

Delete account (blocked while domains are held)

Permanently delete the authenticated user's account, tokens, webhooks, mailboxes, and other application data. Registered domains are NOT deleted with the account: the request fails with 409 HAS_DOMAINS until every domain is transferred out. This action cannot be undone.

Response 200

deletedboolean
hintstring
GET/api/me/contact

Get registrant contact info

Returns the authenticated user's WHOIS registrant contact information. Must be set before purchasing or transferring domains.

Response 200

has_contactboolean
contactRegistrantContact

Example response

{
  "has_contact": true,
  "contact": {
    "first_name": "Jane",
    "last_name": "Doe",
    "address1": "123 Main St",
    "city": "New York",
    "state": "NY",
    "postal_code": "10001",
    "country": "US",
    "phone": "+1.2125551234",
    "email": "jane@example.com"
  }
}

Example

curl https://domani.run/api/me/contact \
  -H "Authorization: Bearer domani_sk_xxx"
PUT/api/me/contact

Set registrant contact info

Set or update your WHOIS registrant contact information. Required by ICANN before purchasing domains. Used as the domain registrant and billing contact. When updated, the new contact is automatically propagated to all active domains at registrars that support contact updates.

Response 200

contactRegistrantContact

Example

curl -X PUT https://domani.run/api/me/contact \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"first_name":"Jane","last_name":"Doe","address1":"123 Main St","city":"New York","state":"NY","postal_code":"10001","country":"US","phone":"+1.2125551234","email":"jane@example.com"}'
POST/api/me/resend-verification

Resend contact email verification

Resend the verification email for the account's contact email address. Rate limited to once per 15 minutes per email.

Request body

emailstring (email)Email address to verify (defaults to account contact email)

Response 200

sentboolean
already_verifiedboolean
hintstring

TLDs

GET/api/tldsPublic

List all available TLDs with pricing

Returns all 1014+ available TLDs with registration, renewal, and transfer pricing. Supports filtering by price range, searching by TLD name, sorting, and pagination via limit/offset. When paginated, includes a Link header with the next page URL. Public endpoint - no authentication required. Authenticated requests get higher rate limits (60/min vs 30/min).

Parameters

max_pricenumberMaximum registration price in USD
min_pricenumberMinimum registration price in USD
sortstring(tld | price | renewal)Sort field - 'tld' (alphabetical), 'price' (registration), or 'renewal'
orderstring(asc | desc)Sort order
searchstringFilter TLD names containing this string
limitintegerMax results per page
offsetintegerNumber of results to skip

Response 200

tldsTldInfo[]
totalintegerTotal matching TLDs (before pagination)
offsetinteger
limitinteger | null

Example response

{
  "tlds": [
    {
      "tld": "xyz",
      "registration": 4.04,
      "renewal": 4.04,
      "transfer": 4.04
    },
    {
      "tld": "com",
      "registration": 10.88,
      "renewal": 13.08,
      "transfer": 13.08
    },
    {
      "tld": "dev",
      "registration": 15.85,
      "renewal": 15.85,
      "transfer": 15.85
    }
  ],
  "total": 1014,
  "offset": 0,
  "limit": null
}

Example

curl https://domani.run/api/tlds?max_price=10&sort=price

Domains

GET/api/domains

List all your registered domains

Returns all domains owned by the authenticated user, ordered by purchase date (newest first). Includes domain name, status, purchase date, and expiration date.

Response 200

domainsDomainRecord[]

Example response

{
  "domains": [
    {
      "domain": "mysite.com",
      "status": "active",
      "purchasedAt": "2025-01-15T10:30:00.000Z",
      "expiresAt": "2026-01-15T10:30:00.000Z",
      "emailEnabled": true,
      "emailVerified": true,
      "emailVerifiedAt": "2025-01-15T10:35:00.000Z",
      "mailboxCount": 2
    },
    {
      "domain": "myapp.dev",
      "status": "active",
      "purchasedAt": "2025-02-01T14:00:00.000Z",
      "expiresAt": "2026-02-01T14:00:00.000Z",
      "emailEnabled": false,
      "emailVerified": false,
      "emailVerifiedAt": null,
      "mailboxCount": 0
    }
  ]
}

Example

curl https://domani.run/api/domains \
  -H "Authorization: Bearer domani_sk_xxx"
POST/api/agents/identity

Claim a free agent identity (<handle>.domani.run)

Gives an agent a free identity at <slug>.domani.run - a live profile page, no domain purchase, instantly. The frictionless way to start; upgrade to a real owned domain later. Requires account:write.

Request body

slug*stringThe handle - becomes <slug>.domani.run
namestringDisplay name
biostring
emojistringAvatar emoji
emailstring (email)Public contact (e.g. your free @domani.run inbox)
linksobject[]

Response 200

idstringStable identity id used to bind scoped API tokens
slugstring
urlstringThe identity subdomain (canonical)
page_urlstringPath fallback that always works
namestring | null
biostring | null
emojistring | null
emailstring | null
linksobject[]
viewsinteger
created_atstring (date-time)

Example response

{
  "success": true,
  "id": "agent_abc123",
  "slug": "myagent",
  "url": "https://myagent.domani.run",
  "page_url": "https://domani.run/agents/myagent",
  "name": "My Agent",
  "bio": "I broker domains.",
  "emoji": "🤖",
  "email": null,
  "links": [],
  "views": 0,
  "created_at": "2026-07-11T00:00:00.000Z"
}

Example

curl -X POST https://domani.run/api/agents/identity \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"slug": "myagent", "name": "My Agent", "bio": "I broker domains.", "emoji": "🤖"}'
GET/api/agents/identity

List your agent identities

Lists the free identities (<handle>.domani.run) you've claimed. Requires account:read.

Response 200

identitiesAgentIdentity[]
PATCH/api/agents/identity/{slug}

Update an agent identity

Update a free identity's name/bio/emoji/email/links. Requires account:write.

Parameters

slug*stringThe handle to update

Request body

namestring
biostring
emojistring
emailstring
linksobject[]

Response 200

idstringStable identity id used to bind scoped API tokens
slugstring
urlstringThe identity subdomain (canonical)
page_urlstringPath fallback that always works
namestring | null
biostring | null
emojistring | null
emailstring | null
linksobject[]
viewsinteger
created_atstring (date-time)
DELETE/api/agents/identity/{slug}

Release an agent identity

Releases a free identity handle so it's available again. Requires account:write.

Parameters

slug*stringThe handle to release

Response 200

successboolean
slugstring
releasedboolean
POST/api/agents/provision

Provision a complete agent identity in one call

Gives an AI agent a complete internet identity in a single request: buys the domain (if not already owned), sets up email DNS, creates a mailbox, and optionally registers a webhook for inbound email and domain events. The domain purchase is the only step that can charge and the only hard prerequisite - if it fails, the whole call fails (402 for payment, 409 if taken by another account). Email setup, mailbox creation, and webhook registration are best-effort: transient issues (e.g. DNS still propagating) are returned in warnings rather than failing the call, so the agent always gets its domain. Payment works exactly like POST /api/domains/buy (card, marketplace balance, or USDC via the PAYMENT-SIGNATURE header / two-step flow).

Request body

domain*stringDomain to give the agent. Bought if not already owned.
slugstringMailbox local part (hi@domain). Default 'hi'.
namestringDisplay name for outbound email.
webhook_urlstring (uri)Optional HTTPS URL to receive inbound email + domain events.
webhook_eventsstring[]Webhook event types. Defaults to email.received/delivered/bounced.
payment_methodstring(card | usdc | balance)How to pay for the domain.
payment_txstringUSDC tx hash (two-step flow).
payment_chainstring(base | ethereum)
yearsinteger

Example

curl -X POST https://domani.run/api/agents/provision \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"domain": "myagent.run", "slug": "hi", "webhook_url": "https://my-agent.example.com/inbox"}'
POST/api/domains/buy

Purchase one or more domains

Purchases one or more domains and registers them immediately. Send {"domain": "..."} for single or {"domains": ["...", "..."]} for bulk (max 10). If the domain is listed for sale on the Domani marketplace, this endpoint automatically routes to a marketplace purchase instead of a fresh registration. The response includes years: 0 and marketplace_purchase: true to distinguish it from a registration. Marketplace purchases are single-domain only. The buyer pays the full listed price; the seller receives the price minus a platform commission (default 10%). Bulk purchases process each domain independently - failures don't block other purchases. The response includes results (succeeded), errors (failed), and summary with counts. Accepts three payment methods: 1. Card (default): Requires a card on file via POST /api/billing/setup. Charged per domain. 2. USDC (two-step): First call without payment to get a 402 with payment_options.crypto (wallet address, amount, chains). Send USDC on-chain, then retry with payment_tx and payment_chain in the request body. 3. x402 USDC (atomic): Include a PAYMENT-SIGNATURE header with a signed USDC payment on Base. Bulk purchases require a card on file (USDC not supported for multi-domain). If no payment is provided for single domain, returns HTTP 402 with payment instructions.

Request body

domainstringSingle domain to purchase (e.g. mysite.com)
domainsstring[]Array of domains to purchase (max 10, requires card on file)
payment_methodstring(card | usdc | balance)Payment method: 'card' (default), 'usdc' (x402 - first call returns 402 with payment instructions), or 'balance' (spend marketplace credit).
payment_txstringTransaction hash of a USDC payment already sent on-chain. Required on the second call of the two-step USDC flow.
payment_chainstring(base | ethereum)Chain the USDC payment was sent on (e.g. 'base' or 'ethereum').
yearsintegerNumber of years to register (1-10, default 1). Price is multiplied by years.
max_pricenumberCeiling in USD for the total charge. If the real price exceeds it, the request fails with 400 PRICE_ABOVE_MAX and nothing is charged - a safety belt for autonomous agents.
refstringReferral code (optional) - the referrer earns a commission

Response 201

domainstring
statusstring
expiresstring (date-time)
pricenumber
currencystring
yearsintegerNumber of years registered
payment_methodstringHow the domain was paid for
payment_txstringOn-chain transaction hash (present for USDC payments)
marketplace_purchasebooleanTrue when the domain was bought from the Domani marketplace (not a fresh registration)

Example

curl -X POST https://domani.run/api/domains/buy \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"domain": "mysite.com"}'
GET/api/domains/{domain}

Get detailed information about a domain you own

Returns comprehensive information about a domain: status, purchase date, expiry, payment method, security lock, WHOIS privacy, auto-renew status, and registration dates. The registrar object may be null if extended data is unavailable.

Parameters

domain*stringDomain name you own

Response 200

domainstring
statusstring
auto_renewbooleanWhether auto-renew is enabled
purchased_atstring (date-time)
expires_atstring (date-time)
days_until_expiryintegerDays until domain expiry
payment_methodstringHow the domain was paid for
payment_txstring | nullOn-chain tx hash (USDC payments)
registrar_statusstringWhether live registrar data was refreshed or is managed externally
registrar_checked_atstring (date-time)When the registrar refresh was attempted
registrarobject | nullDomain registration details (null if unavailable)

Example response

{
  "domain": "mysite.com",
  "status": "active",
  "auto_renew": true,
  "purchased_at": "2025-06-01T12:00:00.000Z",
  "expires_at": "2026-06-01T12:00:00.000Z",
  "days_until_expiry": 365,
  "payment_method": "card",
  "registrar_status": "connected",
  "registrar_checked_at": "2025-06-01T12:00:00.000Z",
  "registrar": {
    "security_lock": true,
    "whois_privacy": true,
    "auto_renew": true,
    "create_date": "2025-06-01T12:00:00Z",
    "expire_date": "2026-06-01T12:00:00Z"
  }
}

Example

curl https://domani.run/api/domains/mysite.com \
  -H "Authorization: Bearer domani_sk_xxx"
PUT/api/domains/{domain}/settings

Update domain settings

Update settings for a domain you own. Supports auto_renew, whois_privacy, and security_lock. At least one field must be provided.

Parameters

domain*stringDomain name you own

Request body

auto_renewbooleanWhether to enable auto-renew for this domain
whois_privacybooleanWhether to enable WHOIS privacy protection
security_lockbooleanWhether to lock the domain against transfers

Response 200

domainstring
auto_renewboolean
whois_privacyboolean
security_lockboolean
hintstring

Example

curl -X PUT https://domani.run/api/domains/mysite.com/settings \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"auto_renew": false}'
PUT/api/domains/{domain}/parking

Update parking settings

Update parking and listing settings for a domain you own. Enable/disable the parking page and set a 'For Sale' listing price. When parking is enabled, visitors to the domain see a branded page. When a listing price is set, visitors see a 'For Sale' page with a contact form.

Parameters

domain*stringDomain name

Request body

enabledbooleanWhether to enable the parking page
listing_pricenumber | nullSale price in USD, or null to remove listing

Response 200

domainstring
parking_enabledboolean
listing_pricenumber | null
hintstring
PUT/api/domains/{domain}/redirect

Forward a domain to a URL

Forward a domain you own to another URL (e.g. brand.com -> https://brand.dev) - useful for multi-TLD brands. The domain must point at domani's nameservers/parking IP for the redirect to serve. Permanent (308) by default or temporary (307). Requires domains:write scope.

Parameters

domain*stringDomain name

Request body

url*stringTarget URL (or bare domain) to forward to, e.g. https://brand.dev
permanentbooleantrue = permanent (308, default), false = temporary (307)

Response 200

domainstring
redirect_urlstring | null
permanentboolean
DELETE/api/domains/{domain}/redirect

Stop forwarding a domain

Remove a domain's redirect so it reverts to the parking page. Requires domains:write scope.

Parameters

domain*stringDomain name

Response 200

domainstring
redirect_urlstring | null
PUT/api/domains/{domain}/for-sale

List a domain for sale

List a domain you own for sale on the Domani marketplace. Buyers can purchase it directly via the API. The domain must be active and not already listed. Fees: Domani charges a platform commission (default 10%) on successful sales. The seller receives the listed price minus the commission. The buyer pays the full listed price. Requires domains:write scope.

Parameters

domain*stringDomain name to list for sale

Request body

price*numberSale price in USD
descriptionstringOptional listing description

Response 200

listingobject
hintstring

Example response

{
  "listing": {
    "id": "lst_abc123",
    "domain": "example.com",
    "price": 5000,
    "currency": "USD",
    "description": null,
    "status": "active",
    "created_at": "2026-03-14T12:00:00.000Z"
  },
  "hint": "example.com listed for sale at $5,000. Buyers can purchase it on the marketplace."
}
PATCH/api/domains/{domain}/for-sale

Update a domain listing

Update the price and/or description of an active marketplace listing. At least one field must be provided. Requires domains:write scope.

Parameters

domain*stringDomain name with active listing

Request body

pricenumberNew sale price in USD
descriptionstring | nullNew description (null to clear)

Response 200

listingobject
hintstring

Example response

{
  "listing": {
    "id": "lst_abc123",
    "domain": "example.com",
    "price": 7000,
    "currency": "USD",
    "description": "Premium domain",
    "status": "active",
    "created_at": "2026-03-14T12:00:00.000Z"
  },
  "hint": "Listing for example.com has been updated. New price: $7,000."
}
DELETE/api/domains/{domain}/for-sale

Cancel a domain listing

Remove an active for-sale listing for a domain you own. The domain remains in your account but is no longer available for purchase on the marketplace. Requires domains:write scope.

Parameters

domain*stringDomain name to unlist

Response 200

domainstring
statusstring
hintstring

Example response

{
  "domain": "example.com",
  "status": "cancelled",
  "hint": "Listing for example.com has been cancelled."
}
GET/api/domains/{domain}/analytics

Get parking analytics

Get visitor analytics for a parked domain - page views, inquiries, conversion rate, 30-day daily breakdown, and recent inquiries.

Parameters

domain*stringDomain name

Response 200

domainstring
views_7dintegerTotal page views in the last 7 days
views_30dintegerTotal page views in the last 30 days
inquiries_30dintegerTotal inquiries in the last 30 days
conversion_ratenumberInquiry-to-view ratio as percentage
daily_viewsobject[]Daily view counts for the last 30 days
recent_inquiriesobject[]5 most recent inquiries
hintstring

Example response

{
  "domain": "example.com",
  "views_7d": 42,
  "views_30d": 156,
  "inquiries_30d": 3,
  "conversion_rate": 1.92,
  "daily_views": [
    {
      "date": "2026-02-25",
      "views": 7
    }
  ],
  "recent_inquiries": [
    {
      "email": "buyer@example.com",
      "offer": 500,
      "date": "2026-02-25T10:00:00Z"
    }
  ],
  "hint": "example.com received 156 views and 3 inquiries in the last 30 days (1.92% conversion)."
}
GET/api/domains/{domain}/auth-code

Get EPP auth code

Get the EPP/auth code needed to transfer a domain to another registrar. Automatically unlocks the domain if it's locked.

Parameters

domain*stringDomain name you own

Response 200

domainstring
auth_codestringEPP auth code to give to the new registrar
was_unlockedbooleanWhether the domain was auto-unlocked
hintstring
next_stepsstring[]
GET/api/domains/{domain}/transfer-away

Get outbound transfer status

Check the status of an outbound domain transfer. Use after getting an auth code and initiating the transfer at the new registrar.

Parameters

domain*stringDomain name you own

Response 200

domainstring
statusstring
gaining_registrarstring
request_datestring (date-time)
hintstring
GET/api/domains/{domain}/transfer-status

Check inbound transfer status

Check the status of an inbound domain transfer. Returns detailed status with actionable hints. Use after initiating a transfer with POST /api/domains/transfer.

Parameters

domain*stringDomain name you own

Response 200

domainstring
statusstring
timestampstringTimestamp of last status update
hintstringActionable guidance based on the current status
continuityobjectCaptured delegation and post-transfer verification for safe transfers.
POST/api/domains/import

Import an external domain

Import a domain you own at another registrar (GoDaddy, Namecheap, Cloudflare, etc.) to manage through domani.run. Free, no transfer needed. Returns a TXT record for DNS-based ownership verification.

Request body

domain*stringDomain to import

Response 200

domainstring
statusstring
tokenstring
txt_recordobject
hintstring
POST/api/domains/import/verify

Verify and complete domain import

Verify DNS TXT record and complete domain import. Call after adding the TXT record returned by POST /api/domains/import. Uses Cloudflare DNS-over-HTTPS to check propagation.

Request body

domain*stringDomain to verify

Response 201

domainstring
statusstring
expiresstring (date-time)
registrarstring
payment_methodstring
hintstring
GET/api/domains/adoption-plan

Plan how to adopt an existing domain

Read-only inspection of an existing domain before connecting or transferring it. Returns the current registrar, authoritative nameservers, DNS provider, DNSSEC state, account state, transfer price, safety invariants, and exact next API actions. Call this before requesting an EPP code. Connecting leaves registrar and DNS unchanged. Transferring preserves nameservers and does not migrate DNS.

Parameters

domain*stringExisting domain to inspect, e.g. mysite.com

Response 200

versioninteger
domainstring
registeredboolean
account_statestring
recommended_actionstring
currentobject
optionsobject
invariantsstring[]
warningsstring[]
GET/api/domains/transfer-check

Pre-check transfer eligibility

Check whether a domain is eligible for transfer before initiating. Returns transfer price, eligibility status, and any blockers (unsupported TLD, ICANN waiting period, domain locked). Call this before POST /api/domains/transfer.

Parameters

domain*stringDomain to check, e.g. mysite.com

Response 200

domainstring
tldstring
eligibleboolean
pricenumber | nullTransfer price in USD (null if TLD unsupported)
currencystring
reasonstringHuman-readable reason if not eligible
codestring
eligible_atstring (date)ISO date when domain becomes eligible (ICANN waiting period)
hintstring
POST/api/domains/transfer-watch

Watch a domain for transfer eligibility

Watch a domain and get notified (email + transfer.eligible webhook) when it becomes eligible for transfer. Uses RDAP to check EPP status codes and ICANN 60-day lock windows. If already eligible, returns immediately without creating a watch.

Request body

domain*stringDomain to watch, e.g. mysite.com

Response 200

domainstring
eligiblebooleanWhether the domain is currently eligible for transfer
eligible_atstring (date)ISO date when domain becomes eligible (if not yet eligible)
hintstringActionable guidance
watchingbooleanWhether a notification has been set up
POST/api/domains/transfer

Transfer domain from another provider

Initiate a domain transfer-in after planDomainAdoption and explicit price confirmation. Requires the authorization/EPP code from the current provider. The existing authoritative nameservers are preserved and verified. DNS migration is never implicit. Transfer typically takes 1-5 days to complete. Payment is charged upfront via card or USDC.

Request body

domain*stringDomain to transfer, e.g. mysite.com
auth_code*stringAuthorization/EPP code from the current provider
payment_methodstring(card | usdc | balance)Payment method: 'card' (default), 'usdc', or 'balance' (marketplace credit).
payment_txstringTransaction hash of a USDC payment already sent on-chain.
payment_chainstring(base | ethereum)Chain the USDC payment was sent on.
extra_subdomainsstring[]Additional subdomains to include in the pre-transfer DNS snapshot. We auto-discover subdomains via CT logs, SPF, MX/DKIM inference, and a common wordlist - use this for any custom subdomains we might miss.

Response 202

domainstring
statusstring
expiresstring (date-time)
pricenumber
currencystring
payment_methodstring
payment_txstring
auto_renewboolean
hintstring
dns_snapshotobjectRecovery backup summary captured before transfer. It is not applied unless an explicit recovery operation is needed.
nameservers_preservedboolean
dns_migrationstring
continuityobject

Example

curl -X POST https://domani.run/api/domains/transfer \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"domain": "mysite.com", "auth_code": "EPP-AUTH-CODE"}'
POST/api/domains/{domain}/renew

Renew a domain

Renew a domain you own for additional years (1-10). Payment is charged upfront. The new expiry date is returned in the response.

Parameters

domain*stringDomain name you own

Request body

yearsintegerNumber of years to renew (default 1)
payment_methodstring(card | usdc | balance)Payment method: 'card' (default), 'usdc', or 'balance' (marketplace credit).
payment_txstringTransaction hash of a USDC payment already sent on-chain.
payment_chainstring(base | ethereum)Chain the USDC payment was sent on.
max_pricenumberCeiling in USD for the renewal charge. If the real price exceeds it, the request fails with 400 PRICE_ABOVE_MAX and nothing is charged.

Response 200

domainstring
renewed_yearsinteger
new_expirystring (date-time)
pricenumber
currencystring
payment_methodstring
payment_txstring

Example

curl -X POST https://domani.run/api/domains/mysite.com/renew \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"years": 1}'
POST/api/domains/{domain}/buy-aftermarket

Buy an aftermarket domain

Buy a taken domain that's listed for sale on an aftermarket (Afternic / Sedo) at its buy-now price, through Domani. The listing price is re-checked live before charging; pass max_price to cap what you'll pay. Make-offer-only listings can't be bought here - use the broker (POST /api/broker) to negotiate a price instead. Payment works like a registration: card, USDC on Base (x402), or marketplace balance. Requires the domains:transfer scope. On success the domain lands in your account (status: active, or provisioning while a staged transfer finalizes).

Parameters

domain*stringThe aftermarket-listed domain to buy

Request body

max_pricenumberHard price ceiling in USD. Defaults to the listing's buy-now price; the purchase is rejected if the live price exceeds this.
payment_methodstring(card | usdc | balance)Payment method: 'card' (default), 'usdc', or 'balance' (marketplace credit).
payment_txstringTransaction hash of a USDC payment already sent on-chain.
payment_chainstring(base | ethereum)Chain the USDC payment was sent on.

Response 201

domainstring
statusstring'active' when the purchase completed, or 'provisioning' while a staged transfer finalizes.
pricenumber
currencystring
payment_methodstring
payment_txstring
marketplacestringAftermarket the domain was bought on (e.g. 'Afternic', 'Sedo').
hintstring
next_stepsstring[]

Example

curl -X POST https://domani.run/api/domains/coolname.com/buy-aftermarket \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{}'

DNS

GET/api/domains/{domain}/dns

Get DNS records for a domain you own

Returns all DNS records configured for a domain you own (A, AAAA, CNAME, MX, TXT, NS, ...), each with a stable id, plus a zone_version token (also sent as the ETag header). Pass zone_version back on PUT to detect concurrent zone changes.

Parameters

domain*stringDomain name you own

Response 200

domainstring
recordsDnsRecord[]
zone_versionstringOptimistic-concurrency token for the whole zone. Pass it back on PUT (body zone_version or If-Match header) to fail with 409 DNS_VERSION_CONFLICT instead of clobbering a concurrent change.

Example response

{
  "domain": "mysite.com",
  "records": [
    {
      "id": "rec_1a2b3c4d5e6f",
      "type": "A",
      "name": "@",
      "value": "76.76.21.21",
      "ttl": 3600
    },
    {
      "id": "rec_2b3c4d5e6f7a",
      "type": "CNAME",
      "name": "www",
      "value": "mysite.com",
      "ttl": 3600
    },
    {
      "id": "rec_3c4d5e6f7a8b",
      "type": "MX",
      "name": "@",
      "value": "mail.example.com",
      "ttl": 3600,
      "priority": 10
    }
  ],
  "zone_version": "zv_9f8e7d6c5b4a3f2e"
}

Example

curl https://domani.run/api/domains/mysite.com/dns \
  -H "Authorization: Bearer domani_sk_xxx"
PUT/api/domains/{domain}/dns

Set DNS records for a domain you own

Upserts DNS records at the rrset level: for each (type, name) present in the payload, existing records at that (type, name) are replaced by the ones you send; rrsets NOT mentioned in the payload are preserved, and NS records are never touched. An automatic server-side backup of the zone is taken before every write. Concurrency: pass the zone_version from a prior GET (body field or If-Match header). If the zone changed since, the write fails with 409 DNS_VERSION_CONFLICT and returns the current records + version so you can re-merge and retry.

Parameters

domain*stringDomain name you own

Request body

records*DnsRecord[]
zone_versionstringThe zone_version from a prior GET. On mismatch the write is rejected with 409 DNS_VERSION_CONFLICT (nothing written). Recommended whenever another actor may edit the same zone.

Response 200

successboolean
domainstring
recordsDnsRecord[]

Example response

{
  "success": true,
  "domain": "mysite.com",
  "records": [
    {
      "type": "A",
      "name": "@",
      "value": "76.76.21.21",
      "ttl": 3600
    },
    {
      "type": "CNAME",
      "name": "www",
      "value": "cname.vercel-dns.com",
      "ttl": 3600
    }
  ]
}

Example

curl -X PUT https://domani.run/api/domains/mysite.com/dns \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"records": [
    {"type": "A", "name": "@", "value": "76.76.21.21", "ttl": 3600},
    {"type": "CNAME", "name": "www", "value": "cname.vercel-dns.com", "ttl": 3600}
  ]}'
GET/api/domains/{domain}/dnssec

List DNSSEC DS records for a domain you own

Returns the DNSSEC delegation-signer (DS) records published at the registry for a domain you own, and whether DNSSEC is enabled. Pairs with TLSA records (set via the DNS endpoint) for DANE.

Parameters

domain*stringDomain name you own

Response 200

domainstring
enabledbooleanTrue when at least one DS record is published
recordsDsRecord[]

Example response

{
  "domain": "mysite.com",
  "enabled": true,
  "records": [
    {
      "keyTag": "12345",
      "algorithm": "13",
      "digestType": "2",
      "digest": "A1B2C3..."
    }
  ]
}

Example

curl https://domani.run/api/domains/mysite.com/dnssec \
  -H "Authorization: Bearer domani_sk_xxx"
POST/api/domains/{domain}/dnssec

Publish a DNSSEC DS record (enable DNSSEC)

Publishes a delegation-signer (DS) record at the registry, enabling DNSSEC for the domain. Get the DS values from your DNS/zone provider after signing the zone.

Parameters

domain*stringDomain name you own

Response 200

successboolean
domainstring
enabledboolean
recordsDsRecord[]

Example

curl -X POST https://domani.run/api/domains/mysite.com/dnssec \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"keyTag": "12345", "algorithm": "13", "digestType": "2", "digest": "A1B2C3..."}'
DELETE/api/domains/{domain}/dnssec

Remove a DNSSEC DS record (disable DNSSEC)

Removes a delegation-signer (DS) record at the registry by its key tag. Removing all DS records disables DNSSEC for the domain.

Parameters

domain*stringDomain name you own
keyTag*stringKey tag of the DS record to remove

Response 200

successboolean
domainstring
enabledboolean
recordsDsRecord[]

Example

curl -X DELETE "https://domani.run/api/domains/mysite.com/dnssec?keyTag=12345" \
  -H "Authorization: Bearer domani_sk_xxx"
POST/api/domains/{domain}/dns/snapshot

Capture a DNS snapshot

Captures all DNS records for a domain via public DNS lookups (Cloudflare DoH). Discovers subdomains from Certificate Transparency logs, SPF records, and common names. The snapshot is also stored server-side as a backup.

Parameters

domain*stringDomain name you own

Request body

extra_subdomainsstring[]Additional subdomains to include in the snapshot

Response 200

domainstring
recordsDnsRecord[]
subdomainsstring[]
sourcesstring[]
capturedAtstring (date-time)

Example response

{
  "domain": "mysite.com",
  "records": [
    {
      "type": "A",
      "name": "@",
      "value": "76.76.21.21",
      "ttl": 3600
    },
    {
      "type": "CNAME",
      "name": "www",
      "value": "mysite.com",
      "ttl": 3600
    },
    {
      "type": "MX",
      "name": "@",
      "value": "aspmx.l.google.com",
      "ttl": 3600,
      "priority": 1
    }
  ],
  "subdomains": [
    "www",
    "mail",
    "api"
  ],
  "sources": [
    "ct",
    "wordlist"
  ],
  "capturedAt": "2026-03-06T12:00:00.000Z"
}

Example

curl -X POST https://domani.run/api/domains/mysite.com/dns/snapshot \
  -H "Authorization: Bearer domani_sk_xxx"
POST/api/domains/{domain}/dns/restore

Restore DNS from a snapshot

Applies DNS records from a snapshot to the domain's current registrar. Uses a diff algorithm to avoid duplicating records that already exist. If no snapshot is provided in the body, restores from the server-side backup (created by snapshot or transfer).

Parameters

domain*stringDomain name you own

Request body

snapshotobjectA DNS snapshot object (as returned by the snapshot endpoint). If omitted, the stored server-side backup is used.
dry_runbooleanPreview only: returns what the restore would create (would_apply) and skip, without writing anything.

Response 200

domainstring
appliedintegerNumber of records created
skippedintegerNumber of records already present
recordCountintegerTotal records in the snapshot
errorsstring[]
dry_runbooleanTrue when this was a preview - nothing was written
would_applyDnsRecord[]Dry run only: the records the restore would create

Example response

{
  "domain": "mysite.com",
  "applied": 5,
  "skipped": 2,
  "recordCount": 7,
  "errors": []
}

Example

curl -X POST https://domani.run/api/domains/mysite.com/dns/restore \
  -H "Authorization: Bearer domani_sk_xxx"
POST/api/domains/{domain}/dns/clone

Clone another domain's DNS onto this one

Copies the DNS setup of a source domain you own onto this domain - useful for multi-TLD brands (brand.com → brand.dev) that share the same A/CNAME/MX. Merges by default (source records win on any type+name collision, this domain's other records are kept); set replace: true for an exact mirror.

Parameters

domain*stringTarget domain (DNS is written here)

Request body

from*stringSource domain to copy DNS from (must be yours)
replacebooleanExact mirror - drop target records the source doesn't have. Default: merge

Response 200

successboolean
from_domainstring
to_domainstring
recordsDnsRecord[]

Example

curl -X POST https://domani.run/api/domains/brand.dev/dns/clone \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"from": "brand.com"}'
GET/api/domains/{domain}/nameservers

Get nameservers for a domain

Returns the authoritative nameservers configured for a domain you own. If empty, DNS operations (parking, email, connect) will fail until nameservers are assigned.

Parameters

domain*stringDomain name you own

Response 200

domainstring
nameserversstring[]
default_nameserversstring[]Registrar default nameservers (null if unknown)
hintstring

Example response

{
  "domain": "mysite.com",
  "nameservers": [
    "ns1.systemdns.com",
    "ns2.systemdns.com",
    "ns3.systemdns.com"
  ],
  "default_nameservers": [
    "ns1.systemdns.com",
    "ns2.systemdns.com",
    "ns3.systemdns.com"
  ],
  "hint": "3 nameservers configured for mysite.com."
}

Example

curl https://domani.run/api/domains/mysite.com/nameservers \
  -H "Authorization: Bearer domani_sk_xxx"
PUT/api/domains/{domain}/nameservers

Set nameservers for a domain

Replace the nameservers for a domain you own. Requires 2–13 valid hostnames. Common values: OpenSRS DNS (ns1.systemdns.com, ns2.systemdns.com, ns3.systemdns.com).

Parameters

domain*stringDomain name you own

Request body

nameservers*string[]Nameserver hostnames (2–13 required)

Response 200

successboolean
domainstring
nameserversstring[]
hintstring

Example response

{
  "success": true,
  "domain": "mysite.com",
  "nameservers": [
    "ns1.systemdns.com",
    "ns2.systemdns.com",
    "ns3.systemdns.com"
  ],
  "hint": "Nameservers for mysite.com updated to ns1.systemdns.com, ns2.systemdns.com, ns3.systemdns.com. Changes may take up to 48 hours to propagate."
}

Example

curl -X PUT https://domani.run/api/domains/mysite.com/nameservers \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"nameservers": ["ns1.systemdns.com", "ns2.systemdns.com", "ns3.systemdns.com"]}'

Connect

POST/api/domains/{domain}/connect

Connect a domain to a hosting or email provider

Connects a domain to a hosting or email provider by auto-detecting the provider from a target URL or accepting an explicit provider name. For domains managed through the platform, DNS records are set automatically (status: dns_set). For imported domains (external registrar), returns the records as instructions to add manually at your registrar (status: manual_setup_required, records have status pending). Supported hosting providers: vercel, netlify, cloudflare-pages, github-pages, railway, fly. Supported email providers: google-workspace, fastmail, proton. Use GET /api/domains/{domain}/connect to list all available providers and methods.

Parameters

domain*stringDomain name you own

Request body

targetstringTarget URL for auto-detection (e.g. my-app.vercel.app)
providerstringExplicit provider name (e.g. vercel, google-workspace)
methodstringConnection method if provider has multiple (e.g. cname-only)
dry_runbooleanPreview only: returns the diff (create/replace/already_set/keep) without writing anything.
confirm_replace_mxbooleanRequired when the connect would replace existing MX records pointing at another provider (409 MX_REPLACEMENT_REQUIRES_CONFIRMATION otherwise) - it reroutes the domain's email.

Response 200

domainstring
providerstring
categorystring
methodstring
recordsDnsRecord[]
docsstring (uri)
statusstringdns_set = records written automatically. manual_setup_required = imported domain, add records at your registrar. dry_run = preview only, nothing written.
hintstring
next_stepsstring[]Provider-specific actions to complete after DNS records are set
diffobjectDry run only: what the write would do - create, replace (existing records that would be removed), already_set, keep (untouched rrsets).

Example

curl -X POST https://domani.run/api/domains/mysite.com/connect \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"target": "my-app.vercel.app"}'
GET/api/domains/{domain}/connect

List available providers for connecting a domain

Returns all supported hosting and email providers with their connection methods. Use this to discover what providers are available before calling POST.

Parameters

domain*stringDomain name you own

Response 200

domainstring
providersobject
POST/api/domains/{domain}/verify

Verify that a provider connection is working

Runs the provider's verification check to confirm DNS records have propagated and the connection is active. Use after POST /api/domains/{domain}/connect to verify.

Parameters

domain*stringDomain name you own

Request body

targetstringTarget for auto-detection
providerstringProvider name
methodstringMethod name

Response 200

domainstring
providerstring
methodstring
verifiedboolean
messagestring
hintstring
next_stepsstring[]Provider-specific troubleshooting steps (only when verified is false)

Example

curl -X POST https://domani.run/api/domains/mysite.com/verify \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"target": "my-app.vercel.app"}'

Status

GET/api/domains/{domain}/status

Check domain health: DNS, SSL, email, expiry

Checks the health of a domain via DNS-over-HTTPS: A/AAAA/CNAME/MX/TXT/NS records, SSL certificate status, email configuration, and expiry date. Supports SSE streaming via Accept: text/event-stream - closing the connection cancels in-flight DNS/SSL checks server-side. Use this after connecting to verify DNS propagation.

Parameters

domain*stringDomain name you own

Response 200

domainstring
statusstring
expiresstring (date-time)
days_until_expiryinteger
dnsobject
sslobject
emailobject

Example response

{
  "domain": "mysite.com",
  "status": "active",
  "expires": "2027-02-18T00:00:00.000Z",
  "days_until_expiry": 365,
  "dns": {
    "propagated": true,
    "a": [
      "76.76.21.21"
    ],
    "aaaa": [],
    "cname": [],
    "mx": [],
    "txt": [],
    "ns": [
      "ns1.porkbun.com",
      "ns2.porkbun.com"
    ],
    "registrar_records": [
      {
        "type": "A",
        "name": "@",
        "value": "76.76.21.21",
        "ttl": 3600
      }
    ]
  },
  "ssl": {
    "active": true
  },
  "email": {
    "configured": false,
    "mx": []
  }
}

Example

curl https://domani.run/api/domains/mysite.com/status \
  -H "Authorization: Bearer domani_sk_xxx"

Email

GET/api/domains/{domain}/email/check

Check email DNS health (MX, SPF, DMARC, DKIM)

Checks email DNS configuration for a domain: MX records, SPF, DMARC, and DKIM. Auto-detects the email provider from MX records (Google Workspace, Proton, Fastmail).

Parameters

domain*stringDomain name you own

Response 200

domainstring
providerstring | nullAuto-detected email provider (google-workspace, proton, fastmail) or null
mxobject
spfobject
dmarcobject
dkimobject

Example response

{
  "domain": "mysite.com",
  "provider": "google-workspace",
  "mx": {
    "configured": true,
    "records": [
      "1 aspmx.l.google.com",
      "5 alt1.aspmx.l.google.com"
    ]
  },
  "spf": {
    "configured": true,
    "value": "v=spf1 include:_spf.google.com ~all"
  },
  "dmarc": {
    "configured": true,
    "value": "v=DMARC1; p=quarantine; rua=mailto:dmarc@mysite.com"
  },
  "dkim": {
    "configured": true,
    "selectors": [
      "google"
    ]
  }
}

Example

curl https://domani.run/api/domains/mysite.com/email/check \
  -H "Authorization: Bearer domani_sk_xxx"
GET/api/domains/{domain}/email/deliverability

Get owner-scoped email deliverability health

Separates deterministic authentication readiness, 30-day recipient-weighted delivery outcomes, account safety state, and measured provider placement. The readiness score is not an Inbox probability, and unmeasured placement is returned as not_tested.

Parameters

domain*stringDomain name you own

Response 200

domainstring
statusstring
confidencestring
checked_atstring (date-time)
window_daysinteger
readinessobjectDeterministic DNS configuration readiness. Score is not an Inbox probability.
sending_healthobject
placementobject
actionsobject[]
caveatsstring[]

Example

curl https://domani.run/api/domains/mysite.com/email/deliverability \
  -H "Authorization: Bearer domani_sk_xxx"
POST/api/domains/{domain}/email/setup

Set up email on a domain

Pre-configure email DNS on a domain (optional). This is called automatically by create_mailbox when needed. Pass force=true to override existing email provider (Google Workspace, Fastmail, Proton).

Parameters

domain*stringDomain name

Request body

forcebooleanOverride existing email provider (Google Workspace, Fastmail, Proton). Default: false

Response 200

domainstring
statusstring
verified_atstring | null
recordsDnsRecord[]
hintstring
DELETE/api/domains/{domain}/email/setup

Remove email from a domain

Remove email from a domain, delete all mailboxes and messages, and set emailEnabled to false.

Parameters

domain*stringDomain name
GET/api/domains/{domain}/email/status

Check email DNS status

Check email DNS verification status for a domain. Returns whether the domain is verified and ready, DNS record details, and mailbox count.

Parameters

domain*stringDomain name

Response 200

domainstring
verifiedboolean
recordsDnsRecord[]
mailbox_countinteger
POST/api/email

Create a mailbox on domani.run

Create a free email address on domani.run (e.g. hello@domani.run). No domain ownership required. Limited to 1 free @domani.run mailbox per user. Many common slugs are reserved.

Request body

slug*stringLocal part of the email address (e.g. 'hello' for hello@domani.run)
namestringDisplay name (defaults to capitalized slug)

Response 200

idstring
addressstring
slugstring
domainstring
GET/api/email

List all mailboxes

List every mailbox the acting principal can access across all domains, including workspace grants.

Parameters

domainstringFilter by domain

Response 200

mailboxesobject[]
POST/api/emails

Create a mailbox

Create an email address (mailbox). Provide a full address like "hello@mysite.com". Email DNS is auto-configured on first use. Pass force=true to override existing email provider. Max 5 mailboxes per user. For free @domani.run mailboxes use POST /api/email. Pass workspace_id to create the mailbox inside a workspace you own. For a custom domain, this explicitly and atomically adopts the domain and all its unscoped sibling mailboxes into that workspace; a domain can never be split across workspaces. Pass kind="hosted" to create a real IMAP/SMTP mailbox on your own domain (connect Apple Mail, Thunderbird, or any client) instead of the default API mailbox (send/receive via API + webhooks). Hosted mailboxes return DNS records to publish, mail client settings, and a one-time app password. Requires a domain you own.

Request body

address*stringFull email address, e.g. hello@mysite.com
namestringDisplay name for outbound emails (e.g. "John Doe")
forcebooleanOverride existing MX records. Default: false
kindstring(api | hosted)"api" (default: send/receive via API + webhooks) or "hosted" (real IMAP/SMTP mailbox on your domain)
workspace_idstringWorkspace to own the mailbox. Only the workspace owner can adopt resources.

Response 201

idstring
addressstring
namestring | null
domainstring
slugstring
workspace_idstring | null
webhook_urlstring | null
forward_tostring | null
signing_secretstringHMAC secret for verifying inbound webhook payloads (shown once at creation)
GET/api/emails

List all mailboxes

List every mailbox the acting principal can access, including workspace grants. Same as GET /api/email.

Parameters

domainstringFilter by domain

Response 200

mailboxesobject[]
GET/api/emails/{address}

Get mailbox details

Get details for a specific mailbox by its full email address (URL-encoded, e.g. hello%40mysite.com).

Parameters

address*stringFull email address (URL-encoded, e.g. hello%40mysite.com)

Response 200

idstring
addressstring
slugstring
domainstring
kindstring
workspace_idstring | null
access_rolestring
webhook_urlstring | null
forward_tostring | null
signing_secretstring | nullHMAC secret for managers; null for viewers/responders
message_countinteger
storage_quota_bytesinteger | null
storage_used_bytesinteger | null
storage_measured_atstring (date-time) | null
created_atstring (date-time)
PATCH/api/emails/{address}

Update mailbox

Update a mailbox's display name, webhook URL, or forward-to email address by its full email address. Set to null to disable/clear.

Parameters

address*stringFull email address (URL-encoded)

Request body

namestring | nullDisplay name for the mailbox. Set to null to clear.
webhook_urlstring | nullHTTPS URL for inbound email webhook, or null to clear
forward_tostring | nullEmail address to forward inbound emails to, or null to clear
drop_spambooleanDrop (don't store or webhook) inbound messages flagged as spam

Response 200

idstring
addressstring
slugstring
domainstring
namestring | null
webhook_urlstring | null
forward_tostring | null
signing_secretstringHMAC secret for verifying inbound webhook payloads
DELETE/api/emails/{address}

Delete a mailbox

Delete a mailbox by its full email address. Requires confirm=true in body. Without confirm, returns a preview with message_count.

Parameters

address*stringFull email address (URL-encoded)

Request body

confirmbooleanMust be true to actually delete. Omit to preview.
POST/api/emails/{address}/send

Send an email

Send an email from a mailbox identified by its full email address. The server runs a mandatory versioned policy first: objective safety failures are denied while reputation and content-quality findings are returned as warnings. Maximum 20 unique recipients per message. Rate limit: 100 recipients per hour per mailbox.

Parameters

address*stringSender email address (URL-encoded)

Request body

to*anyRecipient email address(es)
ccanyCC recipient(s)
bccanyBCC recipient(s)
subjectstring
textstringPlain text body
htmlstringHTML body
reply_tostringReply-To address
in_reply_tostringMessage-ID of the email being replied to (for threading)
referencesstringSpace-separated Message-ID chain (for threading)
idempotency_keystringStable key that prevents duplicate sends
allow_risky_contentbooleanDeprecated compatibility field. Public HTTPS third-party auth links are allowed with a warning; objective safety blocks cannot be overridden.
attachmentsobject[]File attachments (max 10, max 40MB total). Content must be base64-encoded.

Response 200

idstring
message_idstringRFC 5322 Message-ID
fromstring
tostring
statusstring
attachments_countinteger
suppressedstring[]
deliverabilityobjectVersioned policy report applied to this send; inspect decision and categorized checks
POST/api/emails/{address}/deliverability-check

Preflight an outbound email

Run the exact versioned server-side policy used by the send endpoint without sending or consuming email quota. Public HTTPS authentication links on third-party domains are allowed with a reputation warning. Local/private URLs, non-HTTPS authentication links and executable attachments are blocked; bodies above 102 KB produce a clipping warning.

Parameters

address*stringSender email address (URL-encoded)

Request body

subjectstring
textstring
htmlstring
idempotency_keystring
allow_risky_contentbooleanDeprecated compatibility field; objective safety blocks cannot be overridden.
attachmentsobject[]

Response 200

policy_versionstring
decisionstring
safe_to_sendbooleanBackwards-compatible boolean equivalent to decision != deny
riskstring
scoreinteger
authentication_messageboolean
checksobject[]
blocked_reasonsstring[]
warningsstring[]
override_usedboolean
GET/api/emails/{address}/messages

List emails

List emails in a canonical folder or virtual view. Defaults to Inbox. Folder location is independent from message direction and spam verdict. Authentication mail (OTPs / verification codes, password resets, magic links) is tagged security: "auth". Tokens without the email:auth_secrets scope see those messages with subject and body REDACTED (redacted: true) - metadata (from, date) stays visible.

Parameters

address*stringMailbox email address (URL-encoded)
cursorstringPagination cursor from previous response
limitintegerMax messages to return
directionstring(in | out | all)Filter by direction (default: all)
folderstring(inbox | archive | sent | spam | trash)System folder. Mutually exclusive with view. Defaults to inbox.
viewstring(starred | all)Virtual view. Mutually exclusive with folder.
sincestringISO timestamp - return only messages created after this date
fromstringFilter by sender address
tostringFilter by recipient address
subjectstringFilter by subject
qstringBroad case-insensitive search across sender, recipients, subject, and plain-text body
unreadbooleanReturn unread messages only
attachmentsbooleanReturn messages that contain attachments only
spambooleanFilter by spam flag (pass false to hide spam-flagged inbound)

Response 200

messagesobject[]
next_cursorstring | null
GET/api/emails/{address}/folders

List email folders and counts

Return server-derived counts for system folders and virtual views, plus lifecycle capabilities for the mailbox backend.

Parameters

address*string

Response 200

foldersobject[]
viewsobject[]
capabilitiesobject
POST/api/emails/{address}/messages/actions

Apply a lifecycle action to messages

Move, restore, mark read, star, or permanently delete up to 100 explicit message IDs. Moving to Trash is reversible. Permanent deletion requires email:delete and only accepts messages already in Trash. Returns 207 when only part of the batch succeeds.

Parameters

address*string

Request body

message_ids*string[]
action*string(move | mark_read | star | restore | delete_permanently)
destinationstring(inbox | archive | sent | spam | trash)
readboolean
starredboolean
PATCH/api/emails/{address}/messages/read

Mark messages as read or unread

Mark one or more messages as read or unread in a mailbox identified by its full email address.

Parameters

address*stringMailbox email address (URL-encoded)

Request body

message_ids*string[]IDs of messages to mark
read*booleantrue to mark as read, false to mark as unread

Response 200

countinteger
POST/api/emails/{address}/messages/delete

Bulk delete messages

Move multiple messages to Trash. This is reversible and delegates to the canonical lifecycle action.

Parameters

address*stringMailbox email address (URL-encoded)

Request body

message_ids*string[]IDs of messages to delete

Response 200

countinteger
GET/api/emails/{address}/messages/{id}

Get a message

Fetch a single message by ID from a mailbox identified by its full email address. Messages classified as authentication mail (OTP, password reset, magic link) come back with subject/body redacted unless the token has the email:auth_secrets scope.

Parameters

address*stringMailbox email address (URL-encoded)
id*stringMessage ID

Response 200

idstring
message_idstring | null
directionstring
fromstring
tostring
ccstring | null
reply_tostring | null
subjectstring
textstring | null
htmlstring | null
in_reply_tostring | null
referencesstring | null
attachmentsobject[]
statusstring
is_readboolean
read_atstring | null
eventsobject[]
created_atstring (date-time)
DELETE/api/emails/{address}/messages/{id}

Delete a message

Move a message to Trash. This is reversible. Use the lifecycle action with delete_permanently and an email:delete token to erase a message already in Trash.

Parameters

address*stringMailbox email address (URL-encoded)
id*stringMessage ID

Response 200

idstring
deletedboolean
POST/api/emails/{address}/messages/{id}/reply

Reply to a message

Reply to a message in a mailbox identified by its full email address. Threading headers are auto-populated.

Parameters

address*stringMailbox email address (URL-encoded)
id*stringMessage ID to reply to

Request body

textstringPlain text reply body
htmlstringHTML reply body
ccanyAdditional CC recipient(s)
allbooleanReply-all: auto-populate CC from original To/CC

Response 200

idstring
message_idstring
fromstring
tostring
statusstring
POST/api/emails/{address}/messages/{id}/forward

Forward a message

Forward a message from a mailbox identified by its full email address to another recipient. Forwarding authentication mail (OTP, password reset, magic link) requires the email:auth_secrets scope - it would exfiltrate the secret.

Parameters

address*stringMailbox email address (URL-encoded)
id*stringMessage ID to forward

Request body

to*stringRecipient email address
textstringOptional note to prepend

Response 200

idstring
message_idstring
fromstring
tostring
statusstring
GET/api/emails/{address}/avatar

Get mailbox avatar info

Returns the Gravatar URL (if set) and signup link for a mailbox identified by its full email address.

Parameters

address*stringMailbox email address (URL-encoded)

Response 200

addressstring
gravatar_urlstring | null
gravatar_signupstring
has_avatarboolean
hintstring
GET/api/emails/{address}/webhook

Get mailbox webhook config

Returns the webhook URL, signing secret, and status for a mailbox.

Parameters

address*stringMailbox email address (URL-encoded)

Response 200

addressstring
webhook_urlstring | null
signing_secretstring | null
has_webhookboolean
hintstring
PUT/api/emails/{address}/webhook

Set mailbox webhook

Set or update the inbound webhook URL for a mailbox. Incoming emails will be POSTed to this URL with HMAC-SHA256 signing.

Parameters

address*stringMailbox email address (URL-encoded)

Request body

url*stringHTTPS webhook URL

Response 200

addressstring
webhook_urlstring
signing_secretstring
has_webhookboolean
hintstring
DELETE/api/emails/{address}/webhook

Remove mailbox webhook

Remove the webhook URL and signing secret from a mailbox. Inbound emails will only be stored.

Parameters

address*stringMailbox email address (URL-encoded)

Response 200

addressstring
webhook_urlstring | null
signing_secretstring | null
has_webhookboolean
hintstring
POST/api/emails/{address}/webhook/rotate

Rotate webhook signing secret

Regenerate the HMAC-SHA256 signing secret for a mailbox webhook. The old secret is immediately invalidated.

Parameters

address*stringMailbox email address (URL-encoded)

Response 200

addressstring
webhook_urlstring
signing_secretstring
has_webhookboolean
hintstring
POST/api/emails/{address}/webhook/test

Test mailbox webhook

Send a signed test payload to the mailbox webhook URL. Returns HTTP status and whether delivery succeeded.

Parameters

address*stringMailbox email address (URL-encoded)

Response 200

addressstring
webhook_urlstring
statusinteger
successboolean
hintstring
GET/api/emails/{address}/aliases

List mailbox aliases

List all alias addresses that deliver into a mailbox. Aliases let several addresses (sales@, hello@, contact@) share one inbox without using a mailbox slot.

Parameters

address*stringMailbox email address (URL-encoded)

Response 200

mailboxstring
aliasesobject[]
hintstring
POST/api/emails/{address}/aliases

Add a mailbox alias

Add an alias address that delivers into this mailbox, without using a mailbox slot. The alias must be on the same domain as the mailbox. Provide a bare slug ("sales") or a full address ("sales@mysite.com").

Parameters

address*stringMailbox email address (URL-encoded)

Request body

alias*stringAlias address - a bare slug or full slug@domain on the same domain

Response 201

addressstring
mailboxstring
created_atstring (date-time)
hintstring
DELETE/api/emails/{address}/aliases/{alias}

Remove a mailbox alias

Remove an alias from a mailbox. Email to that address stops being delivered.

Parameters

address*stringMailbox email address (URL-encoded)
alias*stringAlias address to remove (URL-encoded, or a bare slug)

Response 200

addressstring
deletedboolean
hintstring
GET/api/emails/{address}/client-settings

Get mail client settings (hosted)

Get IMAP/SMTP connection settings for a hosted mailbox, to configure Apple Mail, Thunderbird, or any mail client. Username is the full address; the password is an app password (create one with POST /api/emails/{address}/credentials). Hosted mailboxes only.

Parameters

address*stringHosted mailbox address (URL-encoded)

Response 200

mailboxstring
usernamestring
imapobject
smtpobject
hintstring
GET/api/emails/{address}/credentials

List app passwords (hosted)

List the app passwords for a hosted mailbox. Secrets are shown only once at creation; this returns labels and metadata only. Hosted mailboxes only.

Parameters

address*stringHosted mailbox address (URL-encoded)

Response 200

mailboxstring
credentialsobject[]
hintstring
POST/api/emails/{address}/credentials

Create an app password (hosted)

Create an app password for a hosted mailbox, used as the password in a mail client (IMAP/SMTP). The secret is returned once - store it now. Hosted mailboxes only.

Parameters

address*stringHosted mailbox address (URL-encoded)

Request body

labelstringLabel to identify this app password, e.g. "Laptop Mail"

Response 201

idstring
labelstring
passwordstringThe app password - shown once, store it now
client_settingsobject
hintstring
DELETE/api/emails/{address}/credentials/{id}

Revoke an app password (hosted)

Revoke an app password. Any mail client using it stops connecting immediately. Hosted mailboxes only.

Parameters

address*stringHosted mailbox address (URL-encoded)
id*stringApp password id (from the list endpoint)

Response 200

idstring
revokedboolean
hintstring
GET/api/emails/{address}/rules

List inbound mail rules

List a mailbox's inbound filtering rules, in the priority order they are applied (first match wins).

Parameters

address*stringMailbox email address (URL-encoded)

Response 200

mailboxstring
rulesMailRule[]
hintstring
POST/api/emails/{address}/rules

Add an inbound mail rule

Add a rule that runs on inbound messages matching a field (from/to/subject/body). Actions: drop, mark_read, forward (action_arg = address), webhook_only (skip forward-to), label (action_arg = label). Rules apply in priority order (lower first); first match wins.

Parameters

address*stringMailbox email address (URL-encoded)

Request body

match_field*string(from | to | subject | body)
match_op*string(contains | equals | regex)
match_value*string
action*string(drop | mark_read | forward | webhook_only | label)
action_argstringForward address (for forward) or label value (for label)
priorityintegerLower runs first (default 0)
enabledbooleanDefault true

Response 201

idstring
priorityintegerLower runs first
match_fieldstring
match_opstring
match_valuestring
actionstring
action_argstring | null
enabledboolean
created_atstring (date-time)
DELETE/api/emails/{address}/rules/{ruleId}

Remove an inbound mail rule

Remove an inbound filtering rule from a mailbox.

Parameters

address*stringMailbox email address (URL-encoded)
ruleId*stringThe rule ID

Response 200

idstring
deletedboolean
hintstring
PUT/api/domains/{domain}/email/catch-all

Set domain catch-all

Route every email sent to an unmatched address on the domain into a designated mailbox. Anything that does not match a mailbox, subaddress, or alias lands in the catch-all. The mailbox must already exist on the domain.

Parameters

domain*stringDomain name

Request body

mailbox*stringCatch-all mailbox - a bare slug or full address on this domain

Response 200

domainstring
catch_allstring | null
hintstring
DELETE/api/domains/{domain}/email/catch-all

Clear domain catch-all

Remove the catch-all on a domain. Email to unmatched addresses will be dropped again.

Parameters

domain*stringDomain name

Response 200

domainstring
catch_allstring | null
hintstring
GET/api/suppressions

List suppressed addresses

List addresses on your suppression list - hard bounces and complaints (ingested automatically from delivery events) plus manual entries. Sends to these addresses are skipped. Cursor-paginated.

Parameters

cursorstringPagination cursor from a previous response
limitintegerMax entries (default 50, max 100)

Response 200

suppressionsobject[]
next_cursorstring | null
hintstring
POST/api/suppressions

Add a suppression

Manually add an address to your suppression list so future sends skip it. Hard bounces and complaints are added automatically; use this for addresses you want to stop emailing. Idempotent.

Request body

address*stringEmail address to suppress
reasonstring(manual | hard_bounce | complaint)Reason (default: manual)

Response 201

addressstring
reasonstring
sourcestring
created_atstring (date-time)
hintstring
DELETE/api/suppressions/{address}

Remove a suppression

Remove an address from your suppression list so you can email it again (e.g. after the recipient fixed their mailbox).

Parameters

address*stringSuppressed email address (URL-encoded)

Response 200

addressstring
deletedboolean
hintstring

Verify Service

POST/api/domains/{domain}/verify-service

Add DNS records to verify domain ownership for a third-party service

Adds the correct DNS records (TXT, CNAME) to prove domain ownership to a third-party service. Supports Stripe, Google Search Console, AWS SES, Postmark, Resend, Facebook, HubSpot, and Microsoft 365. Unknown service names fall back to a generic TXT record. Records are merged with existing DNS - existing records are preserved.

Parameters

domain*stringDomain name you own

Request body

service*stringService name (e.g. stripe, google-search-console, aws-ses, postmark, resend, facebook, hubspot, microsoft-365)
token*stringVerification token provided by the service

Response 200

domainstring
servicestring
service_display_namestring
known_serviceboolean
records_addedDnsRecord[]
total_recordsinteger
docsstring (uri) | null
hintstring

Example response

{
  "domain": "mysite.com",
  "service": "stripe",
  "service_display_name": "Stripe",
  "known_service": true,
  "records_added": [
    {
      "type": "TXT",
      "name": "@",
      "value": "stripe-verification=abc123def456",
      "ttl": 3600
    }
  ],
  "total_records": 3,
  "docs": "https://docs.stripe.com/identity/verification-checks/selfie#domain-verification",
  "hint": "Stripe verification records added. Return to Stripe to complete verification."
}

Example

curl -X POST https://domani.run/api/domains/mysite.com/verify-service \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"service": "stripe", "token": "abc123def456"}'
GET/api/domains/{domain}/verify-service

List supported services for domain verification

Returns all known services with their names, descriptions, and documentation URLs. Unknown services fall back to a generic TXT record.

Parameters

domain*stringDomain name you own

Response 200

domainstring
servicesobject[]
hintstring

Discovery

GET/api/domains/whoisPublic

Look up domain registration data via RDAP

Queries the RDAP (Registration Data Access Protocol) network for domain registration data. Returns registrar, creation/expiry/update dates, days until expiry, status codes, nameservers, DNSSEC status, and contact information (registrant, admin, tech, billing, abuse). Works for any domain - no ownership required. RDAP is the modern, JSON-native replacement for WHOIS. Falls back to WHOIS (port 43) for TLDs without RDAP, and supplements RDAP with WHOIS referral data when contacts are missing (common post-GDPR). Contact information may be partially or fully redacted due to GDPR or privacy protection services. Public endpoint - no authentication required. Authenticated requests get higher rate limits (30/min vs 10/min).

Parameters

q*stringDomain to look up (e.g. example.com)

Response 200

domainstring
registeredbooleanWhether the domain is currently registered
registrarstring | nullCurrent registrar name
registrar_urlstring | nullRegistrar website URL
registrar_iana_idstring | nullRegistrar IANA ID (from RDAP publicIds)
createdstring | nullRegistration date (YYYY-MM-DD)
expiresstring | nullExpiration date (YYYY-MM-DD)
updatedstring | nullLast updated date (YYYY-MM-DD)
days_until_expiryinteger | nullDays until expiry (null if not registered)
statusstring[]EPP status codes
nameserversstring[]Authoritative nameservers
dnssecbooleanWhether DNSSEC is enabled
redactedbooleanWhether WHOIS/RDAP data is redacted for privacy (GDPR)
contactsobjectContact information by role (null when no data available, fields may be null when redacted)

Example

curl "https://domani.run/api/domains/whois?q=google.com"
GET/api/domains/{domain}/ogPublic

Get website preview metadata for a domain

Returns Open Graph metadata (title, description, image, favicon) for a domain. Data is lazily fetched and cached for 7 days. First request for a domain may take 1-5s (live fetch); subsequent requests are instant (cache hit). Returns null fields if the site is unreachable or has no OG tags. Public endpoint - no authentication required.

Parameters

domain*stringDomain to fetch metadata for (e.g. google.com)

Response 200

titlestring | nullPage title (og:title > twitter:title > <title>)
descriptionstring | nullPage description (og:description > meta description)
imagestring | nullOG image URL (absolute)
faviconstring | nullFavicon URL (absolute)

Example

curl "https://domani.run/api/domains/google.com/og"

API Tokens

GET/api/tokens

List your API tokens

Returns all API tokens for the authenticated user. Keys are masked for security (first 12 + last 4 characters visible). Use this to audit active tokens.

Response 200

tokensTokenInfo[]

Example response

{
  "tokens": [
    {
      "id": "cm5abc123",
      "name": "Default",
      "key": "domani_sk_a...wxyz",
      "lastUsed": "2025-02-18T10:30:00.000Z",
      "expiresAt": null,
      "expired": false,
      "scopes": [
        "*"
      ],
      "createdAt": "2025-01-15T10:30:00.000Z",
      "max_per_tx": null,
      "max_per_month": null,
      "spent_this_month": 0,
      "parent_token_id": null,
      "agent_identity": null
    },
    {
      "id": "cm5def456",
      "name": "CI/CD",
      "key": "domani_sk_b...vwxy",
      "lastUsed": null,
      "expiresAt": "2025-05-01T14:00:00.000Z",
      "expired": false,
      "scopes": [
        "domains:read",
        "domains:write"
      ],
      "createdAt": "2025-02-01T14:00:00.000Z"
    }
  ]
}

Example

curl https://domani.run/api/tokens \
  -H "Authorization: Bearer domani_sk_xxx"
POST/api/tokens

Create a new API token

Creates a new API token. The full key is returned only in this response - store it securely. You can create multiple tokens to separate access for different applications.

Request body

namestringHuman-readable name to identify this token
expires_inintegerToken lifetime in seconds (min 3600 = 1h, max 31536000 = 1y). Omit for no expiration.
expires_atstring (date-time)Absolute expiration date (ISO 8601). Alternative to expires_in.
scopesstring[]Permission scopes for this token. Use ['*'] for full access (default). A token can only grant scopes it already has (scope attenuation). Scopes: domains:read (GET /api/domains, GET /api/domains/{domain}, GET /api/domains/{domain}/dns, /dnssec, /status, /email/check, /auth-code, /transfer-away, /transfer-status, /analytics), domains:write (PUT /api/domains/{domain}/dns, POST/DELETE /api/domains/{domain}/dnssec, POST /connect, POST /verify, PUT /settings, PUT /parking, PUT/DELETE /api/domains/{domain}/for-sale, POST /api/domains/import, POST /import/verify), domains:transfer (POST /api/domains/buy, POST /transfer, POST /renew (involves payment, includes marketplace purchases)), tokens:read (GET /api/tokens), tokens:write (POST /api/tokens, DELETE /api/tokens/{id}), webhooks:read (GET /api/webhooks, GET /api/webhooks/{id}/deliveries), webhooks:write (POST /api/webhooks, PATCH /api/webhooks/{id}, DELETE /api/webhooks/{id}), email:read (GET /api/emails, /api/emails/{address}, /api/emails/{address}/messages, /api/emails/{address}/aliases, /api/domains/{domain}/email/status, /api/domains/{domain}/email/deliverability, /api/suppressions), email:write (POST /api/emails, POST /api/emails/{address}/send, POST /api/domains/{domain}/email/setup, aliases + catch-all, POST/DELETE /api/suppressions), email:delete (Permanently delete messages already in Trash. Moving messages to Trash only requires email:write. Grant this scope only to agents allowed to irreversibly erase email content), email:auth_secrets (Read messages classified as authentication mail (OTP / verification codes, password resets, magic links). Without it, email:read still lists them but subject and body come back redacted - so a stolen agent token can't harvest 2FA codes. Grant it only to agents that genuinely need to complete logins), account:read (GET /api/me, GET /api/agents/identity), account:write (DELETE /api/me, POST /api/billing/setup, POST /api/billing/subscribe, POST /api/billing/cancel, POST /api/me/resend-verification, POST/PATCH/DELETE /api/agents/identity), billing:read (GET /api/billing/invoices), search (GET /api/domains/search, /suggest, /whois, /dns-check, GET /api/tlds), deals:read (GET /api/deals, GET /api/deals/{id}), deals:write (POST /api/domains/sell, PATCH /api/deals/{id}), notifications:read (GET /api/notifications, GET /api/notifications/count), backorders:read (GET /api/backorders, GET /api/backorders/{id}), backorders:write (POST /api/backorders, DELETE /api/backorders/{id}).
max_per_txnumberPer-transaction spend cap in USD. Any single charge above this fails with 403 SPEND_CAP_EXCEEDED. A token can only grant caps at or below its own (CAP_ESCALATION otherwise).
max_per_monthnumberRolling calendar-month spend cap in USD across all paid operations made with this token.
agent_identity_idstringOptional id of an agent identity owned by this account. Mail actions made with the token are attributed to that agent.

Response 201

idstring
namestring
keystringFull API key - save this, it won't be shown again
expiresAtstring (date-time) | nullWhen this token expires (null = never)
scopesstring[]Permission scopes granted to this token
createdAtstring (date-time)
max_per_txnumber | nullPer-transaction spend cap in USD (null = uncapped)
max_per_monthnumber | nullMonthly spend cap in USD (null = uncapped)
agent_identityobject | null

Example response

{
  "id": "cm5ghi789",
  "name": "CI/CD Pipeline",
  "key": "domani_sk_x1Y2z3A4b5C6d7E8f9G0hIjK",
  "expiresAt": "2025-03-20T10:30:00.000Z",
  "scopes": [
    "domains:read",
    "domains:write"
  ],
  "createdAt": "2025-02-18T10:30:00.000Z",
  "max_per_tx": 25,
  "max_per_month": 100
}

Example

curl -X POST https://domani.run/api/tokens \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "CI/CD Pipeline"}'
DELETE/api/tokens/{id}

Revoke an API token

Permanently revokes an API token. Any applications using this token will immediately lose access. This action cannot be undone.

Parameters

id*stringToken ID (from GET /api/tokens response)

Response 200

successboolean

Example response

{
  "success": true
}

Example

curl -X DELETE https://domani.run/api/tokens/cm5abc123 \
  -H "Authorization: Bearer domani_sk_xxx"
GET/api/audit

List security audit events

Returns the account's security audit trail, newest first: token lifecycle (created, revoked, escalation attempts), spend-cap and max_price denials, charges, payout requests, and blocked account deletions. Use it to monitor what your agents' tokens are doing.

Parameters

limitintegerEvents per page
cursorstringPagination cursor (next_cursor from the previous page)
typestringFilter by event type (e.g. token.created, purchase.charged, purchase.denied_cap)

Response 200

eventsobject[]
next_cursorstring | null
hintstring

Example response

{
  "events": [
    {
      "id": "evt1",
      "tokenId": "cm5abc123",
      "type": "purchase.charged",
      "targetType": "domain",
      "targetId": "mysite.com",
      "metadata": {
        "amountCents": 1450,
        "operation": "buy",
        "method": "card"
      },
      "createdAt": "2026-07-11T10:00:00.000Z"
    },
    {
      "id": "evt2",
      "tokenId": "cm5abc123",
      "type": "purchase.denied_cap",
      "targetType": "domain",
      "targetId": "expensive.ai",
      "metadata": {
        "amountCents": 8470
      },
      "createdAt": "2026-07-11T09:58:00.000Z"
    }
  ],
  "next_cursor": null,
  "hint": "Security-relevant events for this account."
}

Example

curl "https://domani.run/api/audit?limit=50" \
  -H "Authorization: Bearer domani_sk_xxx"

Billing

POST/api/billing/setup

Get checkout URL for adding a payment method

Creates a checkout session for adding a credit card. Redirect the user to the returned URL. This is required before purchasing domains. After the user completes checkout, they are redirected back to the dashboard.

Request body

modestring(setup_intent | checkout)setup_intent returns a client_secret for frontend Stripe.js. checkout returns a URL to redirect the user to Stripe Checkout.

Response 200

urlstring (uri)Checkout URL (checkout mode)
client_secretstringStripe SetupIntent client secret (setup_intent mode)
hintstringNext steps guidance

Example response

{
  "url": "https://checkout.domani.run/session/cs_abc123...",
  "hint": "Open this URL in a browser to add your payment card."
}

Example

curl -X POST https://domani.run/api/billing/setup \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"mode": "checkout"}'
DELETE/api/billing/card

Remove saved payment method

Detaches the user's saved credit card from their account. After removal, a new card must be added before purchasing domains.

Response 200

removedboolean

Example response

{
  "removed": true
}

Example

curl -X DELETE https://domani.run/api/billing/card \
  -H "Authorization: Bearer domani_sk_xxx"
GET/api/billing/connect

Get marketplace payout status

Returns the seller's Stripe Connect onboarding status and available marketplace balance. payouts_enabled is true once onboarding is complete and the seller can receive bank payouts.

Response 200

onboardedbooleanSeller has submitted Connect onboarding details
payouts_enabledbooleanBank payouts are active
details_submittedboolean
balance_centsintegerAvailable marketplace balance in cents

Example response

{
  "onboarded": true,
  "payouts_enabled": true,
  "details_submitted": true,
  "balance_cents": 45000
}

Example

curl https://domani.run/api/billing/connect \
  -H "Authorization: Bearer domani_sk_xxx"
POST/api/billing/connect

Start bank payout onboarding

Creates (or resumes) Stripe Connect Express onboarding for a marketplace seller and returns a hosted onboarding URL to redirect the user to. Required before a seller can withdraw earnings to their bank.

Response 200

urlstring (uri)Stripe-hosted onboarding URL

Example response

{
  "url": "https://connect.stripe.com/setup/e/acct_123/..."
}

Example

curl -X POST https://domani.run/api/billing/connect \
  -H "Authorization: Bearer domani_sk_xxx"
POST/api/billing/connect/payout

Withdraw marketplace balance to bank

Transfers the seller's entire available marketplace balance to their connected bank account. Requires completed payout onboarding (POST /api/billing/connect). The balance is reserved before the transfer and restored if it fails, so a failure never loses funds.

Response 200

amount_centsintegerAmount transferred in cents
transfer_idstringStripe transfer id

Example response

{
  "amount_cents": 45000,
  "transfer_id": "tr_123"
}

Example

curl -X POST https://domani.run/api/billing/connect/payout \
  -H "Authorization: Bearer domani_sk_xxx"
GET/api/billing/invoices

List payment invoices

Returns a list of paid invoices with invoice number, amount, date, and links to view or download the PDF. Invoices are generated automatically for every domain purchase, renewal, and transfer.

Parameters

limitintegerMax invoices to return

Response 200

invoicesobject[]
has_moreboolean
hintstring

Example

curl https://domani.run/api/billing/invoices \
  -H "Authorization: Bearer domani_sk_xxx"

Referrals

GET/api/referrals

Get referral earnings and history

Returns your unique referral code, commission rate (20%), earnings breakdown (total, paid, pending), and a history of all referred purchases. Share your referral code with others - when they register with your code (via the ref parameter), you earn a commission on all their future domain purchases automatically.

Response 200

referral_codestringYour unique referral code to share
referral_rateintegerCommission percentage (currently 20%)
total_earned_centsintegerTotal earnings in cents
total_paid_centsintegerTotal paid out in cents
total_pending_centsintegerPending payout in cents
referralsobject[]

Example response

{
  "referral_code": "x7k9m2",
  "referral_rate": 20,
  "total_earned_cents": 400,
  "total_paid_cents": 200,
  "total_pending_cents": 200,
  "referrals": [
    {
      "id": "cm5ref001",
      "domains": [
        "friend-site.com"
      ],
      "earned_cents": 200,
      "paid_out": true,
      "created_at": "2025-01-20T10:00:00.000Z"
    },
    {
      "id": "cm5ref002",
      "domains": [
        "another.dev"
      ],
      "earned_cents": 200,
      "paid_out": false,
      "created_at": "2025-02-10T15:30:00.000Z"
    }
  ]
}

Example

curl https://domani.run/api/referrals \
  -H "Authorization: Bearer domani_sk_xxx"

Backorders

POST/api/backorders

Place a backorder on a taken domain

Watches a domain that is currently registered to someone else and automatically registers it for you when it becomes available (drops) - availability is polled every few minutes, so the catch lands within minutes of the drop. You are charged only if and when the catch succeeds - no upfront fee. A price cap protects you from a registration-price spike between now and the drop. Requires a payment method on file (card) or payment_method: "balance" so the catch can be paid when it happens. Best-effort: a contested drop may be taken by a specialized drop-catcher first.

Request body

domain*stringThe currently-registered domain to watch and catch on drop.
max_pricenumberMax USD you'll pay to catch it. Defaults to the current registration price. Must be >= the current price.
payment_methodstring(card | balance)How to pay when caught. Defaults to your preferred method.

Response 201

backorderBackorder
hintstring

Example

curl -X POST https://domani.run/api/backorders \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"domain": "taken.com"}'
GET/api/backorders

List your backorders

Returns your backorders, newest first. Filter by status (watching, caught, failed, cancelled, expired).

Parameters

statusstring(watching | caught | failed | cancelled | expired)Filter by status.

Response 200

backordersBackorder[]
GET/api/backorders/{id}

Get a backorder

Parameters

id*stringBackorder ID.

Response 200

backorderBackorder
DELETE/api/backorders/{id}

Cancel a backorder

Stops watching a domain. Only backorders in the watching state can be cancelled.

Parameters

id*stringBackorder ID.

Response 200

idstring
statusstring
hintstring

Marketplace

POST/api/domains/sell

List a domain for sale (open marketplace)

List any domain for sale on the Domani marketplace, including domains on other registrars. Domains already in your Domani account are auto-verified and listed immediately (status active). External domains require TXT ownership verification (status pending_verification). Fees: 10% platform commission on successful sales. Requires deals:write scope.

Request body

domain*stringDomain name to list for sale
price*numberSale price in USD
descriptionstringOptional listing description

Response 201

listingobject
txt_recordobject | nullTXT record to add for ownership verification (only for external domains)
hintstring
POST/api/domains/sell/verify

Verify TXT ownership for external listing

Verify that the TXT ownership record has been added for an external domain listing. On success, the listing status changes from pending_verification to active and becomes buyable. Requires deals:write scope.

Request body

domain*stringDomain name to verify

Response 200

listingobject
hintstring
GET/api/domains/sell/status

Check listing and deal status

Check the status of a domain listing and any active deal. Returns listing details, deal pipeline status, and contextual hints. Requires deals:read scope.

Parameters

domain*stringDomain name to check

Response 200

listingobject | null
dealobject | null
hintstring
GET/api/deals

List your deals

List marketplace deals where you are the buyer or seller. Filter by role or status. Requires deals:read scope.

Parameters

rolestring(buyer | seller)Filter by role
statusstring(active | escrow_held | epp_pending | transfer_started | completed | refunded | expired)Filter by status ('active' = all non-terminal)
domainstringFilter by domain name
limitintegerNumber of deals to return (default 50, max 100)
cursorstringCursor for pagination (deal ID from next_cursor)

Response 200

dealsobject[]
has_morebooleanWhether more results are available
next_cursorstring | nullCursor for the next page
POST/api/deals

Purchase an external domain (create deal)

Create an escrow deal to purchase a domain listed on the marketplace. Buyer's payment is held in escrow until the domain transfer completes. The seller is notified and has 7 days to provide the EPP/auth code. Requires domains:transfer scope.

Request body

domain*stringDomain name to purchase
payment_methodstring(card | usdc | balance)Payment method: 'card' (default), 'usdc', or 'balance' (marketplace credit).

Response 201

dealobject
hintstring
next_stepsstring[]
GET/api/deals/{id}

Get deal detail with timeline

Get full details of a deal including price breakdown, deadlines, and event timeline. Both buyer and seller can access their deals. Requires deals:read scope.

Parameters

id*stringDeal ID

Response 200

dealobject
hintstring
PATCH/api/deals/{id}

Provide EPP/auth code (seller)

Seller provides the EPP/authorization code to initiate the domain transfer. The code is encrypted at rest and used to start the registrar transfer. Max 3 attempts. Requires deals:write scope.

Parameters

id*stringDeal ID

Request body

auth_code*stringEPP/authorization code from the current registrar

Response 200

dealobject
hintstring
GET/api/deals/{id}/invoice

Get a deal receipt / statement

Returns a role-aware invoice for a marketplace deal. Buyers get a purchase receipt; sellers get a statement showing the sale, the platform commission line, and the net payout. Works for every payment method (card/USDC/balance). Requires deals:read scope.

Parameters

id*string

Response 200

invoiceDealInvoice
GET/api/negotiations

List your negotiations

Lists price negotiations where you are the buyer or seller. Counterparties are anonymized (roles only). Requires deals:read scope.

Parameters

rolestring(buyer | seller)Filter by your role
statusstringFilter by status (open|agreed|completed|declined|expired|cancelled|all)

Response 200

negotiationsNegotiation[]
POST/api/negotiations

Make an offer on a listing

Opens an anonymous price negotiation on an actively-listed domain with an initial offer. The seller can counter, accept, or decline. On acceptance the buyer finalizes payment and a deal is created at the agreed price. Requires deals:write scope.

Request body

domain*stringThe listed domain to negotiate on
offer*numberInitial offer in USD
messagestringOptional message to the seller

Response 201

negotiationNegotiation

Example

curl -X POST https://domani.run/api/negotiations \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com","offer":500}'
GET/api/negotiations/{id}

Get a negotiation thread

Returns a negotiation with its full offer thread, anonymized (offers are labelled 'you' or 'counterparty'). Requires deals:read scope.

Parameters

id*string

Response 200

negotiationNegotiation
POST/api/negotiations/{id}

Counter, accept, or decline

Respond to the current offer on the table when it's your turn. counter requires a new amount; accept locks the price at the amount currently on the table; decline ends the negotiation. Requires deals:write scope.

Parameters

id*string

Request body

action*string(counter | accept | decline)
counternumberNew amount in USD (required when action=counter)
messagestring

Response 200

negotiationNegotiation
DELETE/api/negotiations/{id}

Withdraw a negotiation

Buyer withdraws an open or agreed negotiation. Requires deals:write scope.

Parameters

id*string

Response 200

cancelledboolean
POST/api/negotiations/{id}/finalize

Finalize an agreed negotiation (pay)

Buyer pays the agreed price to create the escrow deal, then the standard transfer flow runs. Accepts the same payment methods as a marketplace purchase (card, USDC, x402). Returns 402 with payment instructions if no payment is provided. For a broker-sourced acquisition, if the agreed price is above the max_budget you set, returns 409 BUDGET_EXCEEDED - re-submit with confirm_over_budget: true to proceed. Requires domains:transfer scope.

Parameters

id*string

Request body

payment_methodstring(card | usdc | balance)
payment_signaturestring | null
payment_txstring
payment_chainstring
confirm_over_budgetbooleanFor broker deals: proceed even though the agreed price exceeds the max_budget you set.
GET/api/broker

List your broker requests

Lists your domain acquisition requests and their status. Requires deals:read scope.

Parameters

statusstringFilter by status or 'all'

Response 200

requestsBrokerRequest[]
POST/api/broker

Request acquisition of a taken domain

Ask domani to acquire a specific taken, unlisted domain on your behalf. Agents source the owner via RDAP, reach out anonymously, and negotiate - commission-only, no upfront fee. Owner interest opens an anonymous negotiation. Note: many owners are unreachable (GDPR-redacted WHOIS) - the request then ends no_contact. Requires deals:write scope.

Request body

domain*stringThe taken domain you want
max_budgetnumberYour ceiling in USD (optional)

Response 201

requestBrokerRequest

Example

curl -X POST https://domani.run/api/broker \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"domain":"dream.com","max_budget":5000}'
GET/api/broker/{id}

Get a broker request

Returns one of your broker requests. Requires deals:read scope.

Parameters

id*string

Response 200

requestBrokerRequest
DELETE/api/broker/{id}

Cancel a broker request

Cancels an active domain acquisition request. Requires deals:write scope.

Parameters

id*string

Response 200

cancelledboolean
GET/api/broker/respondPublic

Read a broker inquiry's state (owner side)

The owner-agent read path: poll the current, anonymized state of an inquiry with your token before acting. Returns the domain, the buyer's offer on the table, whose move it is, and the actions available now. Never exposes the buyer's identity or budget. Token-authorized.

Parameters

token*stringThe outreach token from your inquiry email.

Response 200

domainstring
statusstring
your_turnbooleanWhether it's the owner's move.
current_offer_usdnumber | nullThe buyer's offer on the table.
agreed_price_usdnumber | null
actionsstring[]Actions available now (subset of offer/counter/accept/decline/opt_out).

Example

curl "https://domani.run/api/broker/respond?token=<from your inbox>"
POST/api/broker/respondPublic

Respond to a broker inquiry (owner side)

The owner-agent path: respond to a broker's acquisition inquiry using the token from the outreach email delivered to your inbox - the fully autonomous alternative to replying by email or clicking the human claim link. offer/counter open or advance an anonymous negotiation at your asking price; decline and opt_out end it. Token-authorized (the token is the capability) - no account or scope required.

Request body

token*stringThe outreach token from your inquiry email (the reply-to is broker+<token>@).
action*string(offer | counter | accept | decline | opt_out)offer/counter to name a price; accept to take the buyer's current offer on the table; decline to refuse; opt_out to never be contacted again.
pricenumberYour asking price in USD (required for offer/counter).

Response 200

handledboolean
outcomestringopted_out | declined | negotiation_opened | countered | ignored_not_your_turn | unclear
statusstring
negotiation_idstring
domainstring

Example

curl -X POST https://domani.run/api/broker/respond \
  -H "Content-Type: application/json" \
  -d '{"token":"<from your inbox>","action":"offer","price":800}'

Notifications

GET/api/notifications

List notifications

List your in-app notifications with cursor-based pagination. Includes deal events, listing updates, and system alerts. Requires notifications:read scope.

Parameters

limitintegerMax notifications to return
cursorstringCursor for pagination (from previous response's next_cursor)

Response 200

notificationsobject[]
has_moreboolean
next_cursorstring | null
GET/api/notifications/count

Get unread notification count

Lightweight endpoint for polling unread notification count. Requires notifications:read scope.

Response 200

unread_countinteger

Example response

{
  "unread_count": 3
}
PATCH/api/notifications/{id}

Mark notification as read

Mark a single notification as read. Requires notifications:read scope.

Parameters

id*stringNotification ID

Request body

read*booleanSet to true to mark as read

Response 200

okboolean
POST/api/notifications/read-all

Mark all notifications as read

Mark all unread notifications as read. Requires notifications:read scope.

Response 200

okboolean
countintegerNumber of notifications marked as read
GET/api/notifications/preferences

Get notification preferences

Returns the user's notification preferences for each category and channel (in_app, email). Missing preferences default to enabled. Requires notifications:read scope.

Response 200

preferencesobject[]
expiry_alert_daysinteger[]Days-before-expiry the user receives renewal reminders (default 30/7/1). Empty = no expiry reminders.
PUT/api/notifications/preferences

Update notification preferences

Update notification preferences for specific categories and channels. Requires notifications:read scope.

Request body

preferencesobject[]
expiry_alert_daysinteger[]Days-before-expiry to receive renewal reminders. Empty array = no expiry reminders.

Response 200

okboolean

Webhooks

GET/api/webhooks

List webhooks

List all webhook endpoints configured for your account. Returns URL, subscribed events, and active status for each webhook. Secrets are never returned after creation.

Response 200

webhooksobject[]
hintstring

Example response

{
  "webhooks": [
    {
      "id": "cm5wh001",
      "url": "https://example.com/webhooks/domani",
      "events": [
        "domain.purchased",
        "dns.updated"
      ],
      "active": true,
      "created_at": "2026-02-25T10:00:00.000Z"
    }
  ],
  "hint": "1 webhook(s)."
}

Example

curl https://domani.run/api/webhooks \
  -H "Authorization: Bearer domani_sk_xxx"
POST/api/webhooks

Create webhook

Register a new webhook endpoint. The URL must use HTTPS. Choose which event types to subscribe to. The webhook secret is returned only once at creation - save it to verify incoming payloads with HMAC-SHA256. Maximum 5 webhooks per account.

Request body

url*string (uri)HTTPS URL that will receive webhook POST requests
events*string[]Event types to subscribe to. Use GET /api/webhooks/events to list available types.

Response 200

idstring
urlstring (uri)
eventsstring[]
secretstringHMAC-SHA256 signing secret (shown once)
activeboolean
created_atstring (date-time)
hintstring

Example response

{
  "id": "cm5wh001",
  "url": "https://example.com/webhooks/domani",
  "events": [
    "domain.purchased",
    "dns.updated"
  ],
  "secret": "whsec_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345",
  "active": true,
  "created_at": "2026-02-25T10:00:00.000Z",
  "hint": "Webhook created. Save the secret - it won't be shown again. Use it to verify incoming payloads with HMAC-SHA256."
}

Example

curl -X POST https://domani.run/api/webhooks \
  -H "Authorization: Bearer domani_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hook","events":["domain.purchased","dns.updated"]}'
PATCH/api/webhooks/{id}

Update webhook

Update a webhook's URL, subscribed events, or active status. Send only the fields you want to change.

Parameters

id*stringWebhook ID

Request body

urlstring (uri)New HTTPS URL
eventsstring[]New list of event types
activebooleanSet false to pause, true to resume

Response 200

idstring
urlstring (uri)
eventsstring[]
activeboolean
hintstring
DELETE/api/webhooks/{id}

Delete webhook

Delete a webhook endpoint. All pending deliveries will be cancelled. This cannot be undone.

Parameters

id*stringWebhook ID

Response 200

successboolean
hintstring
GET/api/webhooks/{id}/deliveries

List webhook deliveries

Get recent delivery attempts for a webhook. Shows event type, HTTP status, number of attempts, and any errors. Useful for debugging failed deliveries.

Parameters

id*stringWebhook ID
limitintegerMaximum number of deliveries to return

Response 200

deliveriesobject[]
hintstring

Example response

{
  "deliveries": [
    {
      "id": "cm5del001",
      "event_type": "domain.purchased",
      "status": "delivered",
      "http_status": 200,
      "attempts": 1,
      "last_error": null,
      "created_at": "2026-02-25T10:05:00.000Z"
    }
  ],
  "hint": "1 delivery(ies) for this webhook."
}
GET/api/webhooks/events

List webhook event types

List all available webhook event types with descriptions. No authentication required.

Response 200

eventsobject[]

Example response

{
  "events": [
    {
      "type": "domain.purchased",
      "description": "A new domain was successfully registered"
    },
    {
      "type": "dns.updated",
      "description": "DNS records were changed for a domain"
    },
    {
      "type": "transfer.completed",
      "description": "A domain transfer completed successfully"
    }
  ]
}

Example

curl https://domani.run/api/webhooks/events

Collaboration

POST/api/workspaces/{id}/ownership-transfer

Request a workspace ownership transfer

Owner-only. Sends a 48-hour acceptance link to an existing member. Nothing moves until that authenticated member accepts.

Parameters

id*string

Request body

membership_id*string
DELETE/api/workspaces/{id}/ownership-transfer/{transferId}

Revoke a pending ownership transfer

Parameters

id*string
transferId*string
POST/api/workspaces/ownership-transfers/accept

Accept workspace ownership

Target-only. Atomically transfers the workspace, scoped domains and mailboxes; the previous owner becomes an admin and the existing payer remains unchanged.

Request body

token*string