# domani.run > Internet identity for AI agents. One API for domains, email, DNS, and payments. ## Quick Start Register for an API key (no credit card required). Pass a `ref` code to earn referral commissions on all future purchases by this user: ```bash curl -X POST https://domani.run/api/auth/register \ -H "Content-Type: application/json" \ -d '{"email": "you@example.com", "ref": "YOUR_REFERRAL_CODE"}' ``` Response: ```json { "api_key": "domani_sk_a1B2c3D4e5F6g7H8i9J0kLmN", "referral_code": "x7k9m2", "has_payment_method": false, "user": { "id": "cm5abc123", "email": "you@example.com" } } ``` ## Agent Integration Three ways to give your AI agent an internet identity: ### 1. Agent Skill (Claude, OpenClaw) Install the domani skill so your agent knows how to search, buy, and manage domains automatically. - **Claude.ai**: Download [domani-skill.zip](https://domani.run/domani-skill.zip) → Settings > Features > Upload skill - **Claude Code**: Copy [skill.md](https://domani.run/skill.md) to `.claude/skills/domani/skill.md` in your project - **Claude API**: Upload via `POST /v1/skills` with the zip contents - **OpenClaw**: `clawhub install domani` The skill teaches the agent the full workflow: register, search, compare pricing, buy, and configure DNS. ### 2. MCP (Model Context Protocol) Connect any MCP-compatible agent (Claude Code, Cursor, Windsurf, etc.) to the Streamable HTTP endpoint: ```json { "mcpServers": { "domani": { "type": "streamable-http", "url": "https://domani.run/mcp", "headers": { "Authorization": "Bearer domani_sk_xxx" } } } } ``` This exposes 71 tools (search, suggest_domains, buy_domain, list_domains, get_domain_info, domain_status, set_auto_renew, set_whois_privacy, renew_domain, set_contact, resend_email_verification, get_domain_preview, set_parking, set_listing_price, get_parking_analytics, sell_domain, unsell_domain, transfer_domain, check_transfer_eligibility, check_transfer_status, get_transfer_away, get_auth_code, watch_transfer, set_security_lock, import_domain, verify_import, list_providers, get_dns, set_dns, snapshot_dns, restore_dns, get_nameservers, set_nameservers, connect_domain, dns_check, verify_connection, verify_service, list_services, setup_domain_email, get_domain_email_status, create_mailbox, list_mailboxes, send_email, list_email_messages, get_message, set_email_webhook, rotate_email_webhook_secret, test_email_webhook, set_email_forward, delete_mailbox, mark_messages_read, delete_message, delete_messages, forward_message, reply_to_message, check_email, list_webhooks, create_webhook, update_webhook, delete_webhook, list_webhook_deliveries, get_account, list_tokens, create_token, revoke_token, setup_billing, upgrade_plan, cancel_plan, list_invoices, list_tlds, whois_lookup) as native callable tools with typed parameters. Discovery manifest: [mcp.json](https://domani.run/.well-known/mcp.json) ### 3. Direct API Use the REST API directly with `curl`, `fetch`, or any HTTP client. See the [OpenAPI spec](https://domani.run/.well-known/openapi.json) for the full schema, or the [interactive docs](https://domani.run/docs) to try endpoints in the browser. ## Authentication Most API endpoints require a Bearer token: ``` Authorization: Bearer domani_sk_xxx ``` API keys use the prefix `domani_sk_` followed by 32 random characters. You can create multiple tokens via `POST /api/tokens` for different applications. Tokens can optionally have an expiration date - pass `expires_in` (seconds, min 3600 = 1h, max 31536000 = 1y) or `expires_at` (ISO 8601 datetime). Expired tokens return 401 with code `EXPIRED_API_KEY`. Tokens support scoped permissions. Default: `*` (full access). A token can only create child tokens with equal or fewer scopes (scope attenuation). Error code: `INSUFFICIENT_SCOPE` (403). Scope reference: - `domains:read` → GET /api/domains, GET /api/domains/{domain}, GET /api/domains/{domain}/dns, /status, /email/check, /auth-code, /transfer-away, /transfer-status, /analytics - `domains:write` → PUT /api/domains/{domain}/dns, 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/domains/{domain}/email/status - `email:write` → POST /api/emails, POST /api/emails/{address}/send, POST /api/domains/{domain}/email/setup - `account:read` → GET /api/me - `account:write` → DELETE /api/me, POST /api/billing/setup, POST /api/me/resend-verification - `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 **Public endpoints** (no authentication required): - `GET /api/domains/search` - Check availability and pricing (single via `?q=`, multi via `?domains=`, SSE via `Accept: text/event-stream`). SSE is cancellable - closing the connection aborts in-flight RDAP lookups. - `GET /api/tlds` - List all TLDs with pricing - `GET /api/domains/whois` - WHOIS/RDAP lookup - `GET /api/domains/{domain}/og` - Website preview metadata (title, description, image, favicon) - `GET /api/domains/suggest` - AI domain name suggestions. Optional: `tlds` (comma-separated preferred TLDs), `style` (single|creative|short|brandable|keyword), `lang` (japanese|spanish|french|italian|latin|nordic|arabic|sanskrit). SSE is cancellable - closing the connection aborts in-flight LLM generation and RDAP lookups. These endpoints use IP-based rate limiting. Authenticated requests get higher rate limits. ## Agent Workflow Recommended sequence for finding and buying a domain: 1. **Check account**: `GET /api/me` - verify authentication, check `has_payment_method` and `has_contact` 1b. **Set contact info** (one-time): If `has_contact` is false, set WHOIS registrant contact before buying: `PUT /api/me/contact` with `{"first_name":"...","last_name":"...","address1":"...","city":"...","state":"...","postal_code":"...","country":"US","phone":"+1.5551234567","email":"..."}`. Required by ICANN for domain registration. When updated, the new contact is automatically propagated to all active domains at supported registrars. 2. **Get name ideas**: `GET /api/domains/suggest?prompt=AI+coding+assistant&tlds=com,dev&style=short` - AI-generated domain names with availability (10/min). Optional filters: `tlds` (preferred TLDs), `style` (single|creative|short|brandable|keyword), `lang` (japanese|spanish|french|...) 3. **Browse TLDs**: `GET /api/tlds?max_price=20&sort=price` - list available TLDs with pricing 4. **Search domains**: `GET /api/domains/search?domains=myproject.com,myproject.dev,myproject.ai` - check availability and pricing across TLDs via RDAP (max 200 domains, 20/min). For a single domain: `GET /api/domains/search?q=myproject.dev`. For real-time streaming: add `Accept: text/event-stream` header. 5. **Purchase**: `POST /api/domains/buy` with `{"domain": "myproject.dev", "payment_method": "card"}` for single or `{"domains": ["myproject.dev", "myproject.com"]}` for bulk (max 10). Each domain is processed independently - failures don't block other purchases. Optional `years` (1-10, default 1) to register for multiple years - price is multiplied by years. Optional `payment_method`: `"card"` (charge card) or `"usdc"` (returns 402 with payment instructions). For USDC: first call returns 402 with `payment_options.crypto` (wallet, amount, chains), send USDC, then retry with `payment_tx` (tx hash) and `payment_chain` ("base" or "ethereum"). If omitted, uses the user's `preferred_payment_method` (set via `PUT /api/me`), or auto-selects (card if available, else 402). Bulk purchases require card. 6. **Connect**: `POST /api/domains/myproject.dev/connect` with `{"target": "my-app.vercel.app"}` - auto-detects provider and sets DNS records. Or use `{"provider": "google-workspace"}` for email. For imported domains (registrar=external), returns the records as instructions with `status: "manual_setup_required"` instead of writing them - the user adds records at their registrar manually. 7. **Set up email**: `POST /api/domains/myproject.dev/connect` with `{"provider": "google"}` - shorthand for email setup. Sets MX, SPF, DMARC records. Shorthand names: `google` (Google Workspace), `proton` (Proton Mail), `fastmail` (Fastmail). 8. **Check email health**: `GET /api/domains/myproject.dev/email/check` - verifies MX propagation, SPF, DMARC, DKIM. Auto-detects the email provider from MX records. 9. **Verify**: `POST /api/domains/myproject.dev/verify` with `{"target": "my-app.vercel.app"}` - confirms DNS propagation 10. **Verify service**: `POST /api/domains/myproject.dev/verify-service` with `{"service": "stripe", "token": "xxx"}` - adds verification DNS records for Stripe, Google Search Console, AWS SES, etc. 11. **Check status**: `GET /api/domains/myproject.dev/status` - DNS, SSL, email, and expiry health check 12. **WHOIS / RDAP lookup**: `GET /api/domains/whois?q=example.com` - look up full registration data for any domain: registrar (name, URL, IANA ID), dates (created, expires, updated, days_until_expiry), status codes, nameservers, DNSSEC, privacy redaction status, and contacts (registrant, admin, tech, billing, abuse - each with name, organization, email, phone, address). Uses RDAP with automatic WHOIS port 43 fallback. Cached 1h (registered) / 5min (not-found). Rate limit: 30/min 13. **Domain info**: `GET /api/domains/myproject.dev` - get detailed info about a domain you own: status, auto-renew, parking_enabled, listing_price, purchase/expiry dates, days until expiry, payment method, and registrar-side data (security lock, WHOIS privacy, auto-renew status, creation/expiration dates). Rate limit: 60/min 14. **Domain settings**: `PUT /api/domains/myproject.dev/settings` with `{"auto_renew": false}`, `{"whois_privacy": true}`, and/or `{"security_lock": false}` - toggle auto-renew, WHOIS privacy, and security lock. Applied at registrar first, then synced to local DB. WHOIS privacy and security lock are enabled by default on new registrations. Rate limit: 60/min 14a. **DNS snapshot**: `POST /api/domains/myproject.dev/dns/snapshot` - capture all DNS records via public DNS lookups (Cloudflare DoH). Discovers subdomains from Certificate Transparency logs, SPF records, and common names. Returns `domain`, `records[]`, `subdomains[]`, `sources[]`, `capturedAt`. Also stores a server-side backup. Optional body: `{"extra_subdomains": ["api", "cdn"]}`. Rate limit: 10/min 14b. **DNS restore**: `POST /api/domains/myproject.dev/dns/restore` - apply DNS records from a snapshot to the current registrar. Uses diff algorithm to avoid duplicating existing records. If no body, restores from server backup. Optional body: `{"snapshot": {...}}` with a snapshot object. Returns `applied`, `skipped`, `errors[]`. Rate limit: 10/min 14c. **Nameservers**: `GET /api/domains/myproject.dev/nameservers` - get authoritative nameservers. `PUT /api/domains/myproject.dev/nameservers` with `{"nameservers": ["ns1.systemdns.com", "ns2.systemdns.com", "ns3.systemdns.com"]}` - replace nameservers (2-13 required). If a domain has no nameservers, DNS operations (parking, email, connect) will fail. Rate limit: 60/min (GET), 30/min (PUT) 14d. **Get auth code** (transfer away): `GET /api/domains/myproject.dev/auth-code` - get the EPP auth code to transfer a domain to another registrar. Automatically unlocks the domain if locked. Returns `auth_code`, `was_unlocked`, and `next_steps`. Give the auth code to the new registrar to initiate the transfer. Rate limit: 10/min 14e. **Transfer away status**: `GET /api/domains/myproject.dev/transfer-away` - check status of an outbound domain transfer. Returns `status` (none|pending|approved|completed|rejected|expired), `gaining_registrar`, `request_date`. Rate limit: 30/min 14f. **Parking**: `PUT /api/domains/myproject.dev/parking` with `{"enabled": true}` - enable or disable the parking page. When enabled, visitors see a branded parking page. If the domain has existing DNS records, the API returns 409 with `requires_confirmation: true` and `existing_dns` listing the records that will be overwritten - call again with `{"enabled": true, "confirm": true}` to proceed. Newly purchased domains are parked by default. 14g. **Parking analytics**: `GET /api/domains/myproject.dev/analytics` - get visitor analytics for a parked domain: `views_7d`, `views_30d`, `daily_views` (30-day breakdown by date). Rate limit: 60/min 14i. **Webhooks**: Register HTTPS endpoints to receive real-time event notifications. `POST /api/webhooks` with `{"url":"https://example.com/hook","events":["domain.purchased","dns.updated"]}` - returns `id`, `url`, `events`, `secret` (shown once), `active`. Verify payloads with the `X-Domani-Signature` header (`t=timestamp,v1=hmac-sha256`). Events: `domain.purchased`, `domain.renewed`, `domain.expiring`, `dns.updated`, `transfer.initiated`, `transfer.completed`, `transfer.failed`, `transfer.eligible`, `inquiry.received`, `parking.updated`, `listing.created`, `listing.verified`, `listing.sold`, `listing.cancelled`, `listing.updated`, `deal.created`, `deal.epp_required`, `deal.transfer_started`, `deal.completed`, `deal.expired`, `deal.refunded`, `email.verified`, `email.received`, `email.queued`, `email.sent`, `email.delivered`, `email.bounced`, `email.complained`, `email.failed`, `email.delayed`, `email.suppressed`, `email.deleted`. Max 5 per account, HTTPS only. Failed deliveries retried 3x with backoff. Manage: `GET /api/webhooks` (list), `PATCH /api/webhooks/{id}` (update), `DELETE /api/webhooks/{id}` (delete), `GET /api/webhooks/{id}/deliveries` (delivery history), `GET /api/webhooks/events` (list event types, public). Rate limit: 60/min 14j. **Programmatic Email**: Send and receive emails from any domain you own. Create mailboxes, send/receive messages, and forward inbound emails to webhooks. Email DNS (MX/SPF/DKIM/DMARC) is auto-configured on first mailbox creation. - **Create mailbox**: `POST /api/emails` with `{"address": "hello@myproject.dev"}` - creates `hello@myproject.dev`. Email DNS is auto-configured on first use - no separate setup step needed. On `domani.run`: `POST /api/email` auto-generates a random handle. Max 5 mailboxes per user total. Only 1 free @domani.run mailbox per user (buy a domain for unlimited custom-domain mailboxes). Many common slugs are reserved on @domani.run (admin, support, hello, info, ceo, etc.). Pass `{"force": true}` to override existing email provider (Google Workspace, Fastmail, Proton) MX records. - **Check status**: `GET /api/domains/myproject.dev/email/status` - check DNS verification and readiness. - **Send email**: `POST /api/emails/hello%40myproject.dev/send` with `{"to": "user@example.com", "subject": "Hello", "text": "Hi there"}`. Supports `cc`, `bcc`, `reply_to`. Returns RFC 5322 `message_id`. Rate limit: 100/hr per mailbox. - **Reply / threading**: Include `in_reply_to` (parent Message-ID) and `references` (space-separated Message-ID chain) to thread emails. Example: `{"to": "user@example.com", "subject": "Re: Hello", "text": "Thanks!", "in_reply_to": "", "references": ""}`. - **List messages**: `GET /api/emails/hello%40myproject.dev/messages` - cursor-based pagination (`cursor`, `limit`, `direction=in|out|all`). Use `since` (ISO timestamp) to fetch only messages created after a given date (useful for polling). Each message includes `message_id`, `cc`, `reply_to`, `in_reply_to`, `references` for RFC 5322 compliance and threading. The `status` field is the current delivery state: `queued` (accepted by API), `sent` (provider confirmed sending), `delivered` (recipient accepted), `delayed` (temporary issue), `bounced` (permanent rejection), `failed` (provider error), `complained` (marked as spam), `suppressed` (suppression list). The `events` array is the full delivery lifecycle log - each entry has `status`, optional `detail` (bounce/complaint metadata), and `created_at` timestamp. Example: `[{"status":"queued","created_at":"..."}, {"status":"sent","created_at":"..."}, {"status":"delivered","created_at":"..."}]`. - **Inbound webhooks**: `PUT /api/emails/hello%40myproject.dev/webhook` with `{"url": "https://my-app.com/hook"}` - forward incoming emails to your URL as signed JSON. `DELETE /api/emails/hello%40myproject.dev/webhook` to remove. `POST /api/emails/hello%40myproject.dev/webhook/rotate` to regenerate the HMAC signing secret. `POST /api/emails/hello%40myproject.dev/webhook/test` to send a test payload. - **Email forwarding**: `PATCH /api/emails/hello%40myproject.dev` with `{"forward_to": "me@gmail.com"}` - forward a copy of every inbound email to a personal address. The original is still stored. Reply-To is set to the original sender. Set to `null` to remove. Can be used alongside webhook. - **Get single message**: `GET /api/emails/hello%40myproject.dev/messages/{messageId}` - fetch a single message by ID with full headers and body. - **Mark messages read**: `PATCH /api/emails/hello%40myproject.dev/messages/read` with `{"message_ids": ["id1", "id2"], "read": true}` - batch mark messages as read/unread. - **Delete message**: `DELETE /api/emails/hello%40myproject.dev/messages/{messageId}` - soft-delete a single message. - **Delete messages**: `POST /api/emails/hello%40myproject.dev/messages/delete` with `{"message_ids": ["id1", "id2"]}` - batch delete messages. - **Forward message**: `POST /api/emails/hello%40myproject.dev/messages/{messageId}/forward` with `{"to": "user@example.com"}` - forward a message to another address. - **Reply to message**: `POST /api/emails/hello%40myproject.dev/messages/{messageId}/reply` with `{"text": "Thanks!"}` - reply to a message (auto-sets threading headers). - **List all mailboxes**: `GET /api/emails` (or `GET /api/email`) - all mailboxes across all domains. - **Pre-configure email DNS** (optional): `POST /api/domains/myproject.dev/email/setup` - explicitly set up email DNS before creating mailboxes. Called automatically by create mailbox when needed. Pass `{"force": true}` to override existing MX records. - **Remove email**: `DELETE /api/domains/myproject.dev/email/setup` - removes email, deletes all mailboxes and messages. - Webhook events: `email.verified` (DNS verified, ready to send/receive), `email.received`, `email.queued`, `email.sent`, `email.delivered` (outbound delivered), `email.bounced` (outbound bounced), `email.complained` (recipient marked as spam), `email.failed` (outbound failed), `email.delayed` (temporary delivery issue, retrying), `email.suppressed` (address on suppression list), `email.deleted`. - After first mailbox creation, DNS verification is automatic (polled every 2 minutes). Subscribe to the `email.verified` webhook to be notified when the domain is ready - no manual polling needed. 15. **Renew domain**: `POST /api/domains/myproject.dev/renew` with `{"years": 1}` - extend registration by 1-10 years. Optional `payment_method`: `"card"` or `"usdc"`. Payment charged upfront. Returns new expiry date. Rate limit: 10/min **Bring an existing domain**: Two options - **import** (free, read-only monitoring, domain stays at current registrar) or **transfer** (paid - includes 1 year renewal, full migration with EPP code, domain moves to domani.run). Always check eligibility and price first via `GET /api/domains/transfer-check?domain=example.com` and confirm with the user before initiating. 16. **Import an existing domain** (free): `POST /api/domains/import` with `{"domain": "mysite.com"}` → returns a TXT record to add at your DNS provider. After adding the record, call `POST /api/domains/import/verify` with `{"domain": "mysite.com"}` to complete. Then use `POST /api/domains/mysite.com/connect` with `{"target": "my-app.vercel.app"}` to get the exact DNS records to add at your registrar (`status: "manual_setup_required"`). Verify with `POST /api/domains/mysite.com/verify`. No lock-in - domain stays at your current registrar. Rate limit: 30/min (initiate), 10/min (verify) 17. **Check transfer eligibility**: `GET /api/domains/transfer-check?domain=example.com` - pre-check whether a domain can be transferred. Returns `eligible` (boolean), `price` (transfer cost in USD), `code` (UNSUPPORTED_TLD, TRANSFER_NOT_ELIGIBLE, ALREADY_REGISTERED), `eligible_at` (ICANN waiting period date), and `hint`. Always call this before transfer_domain. Rate limit: 30/min 17a. **Transfer domain** (full migration, paid): `POST /api/domains/transfer` with `{"domain": "example.com", "auth_code": "EPP-CODE", "payment_method": "card"}` - transfer a domain from another registrar. **Paid** - the transfer price includes 1 year of renewal (check eligibility and price first via `GET /api/domains/transfer-check?domain=example.com`). Always show the price and get user confirmation before calling. Requires authorization/EPP code from your current registrar. Domain moves to domani.run with full DNS, renewal, and auto-renew control. **DNS auto-migration**: existing DNS records are automatically snapshotted before the transfer (via CT logs, SPF, MX/DKIM inference, common wordlist) and restored when it completes - pass `extra_subdomains` for custom subdomains we might miss. Returns 202 (async, takes 1-5 days). Rate limit: 5/min **Before transferring**: The user must (1) unlock the domain at their current registrar, (2) get the EPP/auth code, and (3) optionally disable WHOIS privacy. Common registrar paths: GoDaddy (Domain Settings → Transfer away), Namecheap (Sharing & Transfer → Transfer Out → Unlock → Auth Code), Cloudflare (Configuration → Authorization Code), Squarespace (Settings → Transfer away → Get auth code). If the domain was registered or transferred less than 60 days ago, ICANN rules prevent transfer. **If the domain is not yet eligible**, the API returns the eligibility date and we automatically email the user when it becomes eligible - they'll need a fresh EPP code at that time. No payment is charged until the transfer actually goes through. 17b. **Watch for transfer eligibility**: `POST /api/domains/transfer-watch` with `{"domain": "example.com"}` - uses RDAP to check EPP status codes and ICANN 60-day lock windows. If not yet eligible, sets up a notification (email + `transfer.eligible` webhook) for when the domain becomes transferable. If already eligible, returns immediately. Rate limit: 30/min 17c. **Check transfer status**: `GET /api/domains/example.com/transfer-status` - check inbound transfer progress. Returns detailed status (`pending_owner`, `pending_admin`, `pending_registry`, `completed`, `cancelled`) with actionable hints. Rate limit: 30/min ### Example: Search and Buy ```bash # Step 1: Compare pricing across TLDs (no auth needed) curl "https://domani.run/api/domains/search?domains=myproject.com,myproject.dev,myproject.xyz,myproject.ai&max_price=20" # Step 2: Buy the cheapest available option curl -X POST https://domani.run/api/domains/buy \ -H "Authorization: Bearer domani_sk_xxx" \ -H "Content-Type: application/json" \ -d '{"domain": "myproject.xyz"}' # Step 3: Connect to your hosting provider (auto-detects Vercel) # The response includes a next_steps array with provider-specific actions # (e.g., add the domain in Vercel dashboard). Always present these to the user. # Some providers (cloudflare-pages, github-pages, railway, fly) require a target # parameter - use GET /api/domains/{domain}/connect to check requires_target first. curl -X POST https://domani.run/api/domains/myproject.xyz/connect \ -H "Authorization: Bearer domani_sk_xxx" \ -H "Content-Type: application/json" \ -d '{"target": "my-app.vercel.app"}' # Step 4: Verify the connection is live curl -X POST https://domani.run/api/domains/myproject.xyz/verify \ -H "Authorization: Bearer domani_sk_xxx" \ -H "Content-Type: application/json" \ -d '{"target": "my-app.vercel.app"}' # Step 5: Verify domain for Stripe curl -X POST https://domani.run/api/domains/myproject.xyz/verify-service \ -H "Authorization: Bearer domani_sk_xxx" \ -H "Content-Type: application/json" \ -d '{"service": "stripe", "token": "abc123def456"}' # Step 6: Check overall domain health curl https://domani.run/api/domains/myproject.xyz/status \ -H "Authorization: Bearer domani_sk_xxx" ``` ### Example: Bulk Buy ```bash # Buy multiple domains at once (requires card on file) curl -X POST https://domani.run/api/domains/buy \ -H "Authorization: Bearer domani_sk_xxx" \ -H "Content-Type: application/json" \ -d '{"domains": ["myproject.com", "myproject.dev", "myproject.ai"]}' ``` Response (partial success): ```json { "results": [ {"domain": "myproject.com", "status": "active", "price": 16.50, "currency": "USD", "payment_method": "card"}, {"domain": "myproject.dev", "status": "active", "price": 19.00, "currency": "USD", "payment_method": "card"} ], "errors": [ {"domain": "myproject.ai", "error": "Domain is not available", "code": "DOMAIN_UNAVAILABLE"} ], "summary": {"total": 3, "succeeded": 2, "failed": 1} } ``` ## Endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/api/auth/register` | Create a new account | | `POST` | `/api/auth/login` | Send a magic link sign-in email | | `GET` | `/api/auth/verify` | Verify a magic link token | | `GET` | `/api/me` | Get current account details | | `PUT` | `/api/me` | Update account preferences | | `DELETE` | `/api/me` | Delete account and all associated data | | `GET` | `/api/me/contact` | Get registrant contact info | | `PUT` | `/api/me/contact` | Set registrant contact info | | `POST` | `/api/me/resend-verification` | Resend contact email verification | | `GET` | `/api/tlds` | List all available TLDs with pricing | | `GET` | `/api/domains/search` | Check availability and pricing for domains | | `GET` | `/api/domains/suggest` | AI-powered domain suggestions with availability | | `GET` | `/api/domains` | List all your registered domains | | `POST` | `/api/domains/buy` | Purchase one or more domains | | `GET` | `/api/domains/{domain}/dns` | Get DNS records for a domain you own | | `PUT` | `/api/domains/{domain}/dns` | Set DNS records for a domain you own | | `POST` | `/api/domains/{domain}/dns/snapshot` | Capture a DNS snapshot | | `POST` | `/api/domains/{domain}/dns/restore` | Restore DNS from a snapshot | | `GET` | `/api/domains/{domain}/nameservers` | Get nameservers for a domain | | `PUT` | `/api/domains/{domain}/nameservers` | Set nameservers for a domain | | `POST` | `/api/domains/{domain}/connect` | Connect a domain to a hosting or email provider | | `GET` | `/api/domains/{domain}/connect` | List available providers for connecting a domain | | `GET` | `/api/domains/{domain}/status` | Check domain health: DNS, SSL, email, expiry | | `GET` | `/api/domains/{domain}/email/check` | Check email DNS health (MX, SPF, DMARC, DKIM) | | `POST` | `/api/domains/{domain}/verify` | Verify that a provider connection is working | | `POST` | `/api/domains/{domain}/verify-service` | Add DNS records to verify domain ownership for a third-party service | | `GET` | `/api/domains/{domain}/verify-service` | List supported services for domain verification | | `GET` | `/api/domains/{domain}` | Get detailed information about a domain you own | | `PUT` | `/api/domains/{domain}/settings` | Update domain settings | | `PUT` | `/api/domains/{domain}/parking` | Update parking settings | | `GET` | `/api/notifications` | List notifications | | `GET` | `/api/notifications/count` | Get unread notification count | | `PATCH` | `/api/notifications/{id}` | Mark notification as read | | `POST` | `/api/notifications/read-all` | Mark all notifications as read | | `GET` | `/api/notifications/preferences` | Get notification preferences | | `PUT` | `/api/notifications/preferences` | Update notification preferences | | `GET` | `/api/domains/{domain}/analytics` | Get parking analytics | | `GET` | `/api/domains/{domain}/auth-code` | Get EPP auth code | | `GET` | `/api/domains/{domain}/transfer-away` | Get outbound transfer status | | `GET` | `/api/domains/{domain}/transfer-status` | Check inbound transfer status | | `POST` | `/api/domains/import` | Import an external domain | | `POST` | `/api/domains/import/verify` | Verify and complete domain import | | `GET` | `/api/domains/transfer-check` | Pre-check transfer eligibility | | `POST` | `/api/domains/transfer-watch` | Watch a domain for transfer eligibility | | `POST` | `/api/domains/transfer` | Transfer domain from another provider | | `POST` | `/api/domains/{domain}/renew` | Renew a domain | | `GET` | `/api/domains/whois` | Look up domain registration data via RDAP | | `GET` | `/api/domains/{domain}/og` | Get website preview metadata for a domain | | `GET` | `/api/domains/dns-check` | Fast DNS-based domain existence check | | `GET` | `/api/tokens` | List your API tokens | | `POST` | `/api/tokens` | Create a new API token | | `DELETE` | `/api/tokens/{id}` | Revoke an API token | | `POST` | `/api/billing/setup` | Get checkout URL for adding a payment method | | `DELETE` | `/api/billing/card` | Remove saved payment method | | `GET` | `/api/billing/invoices` | List payment invoices | | `GET` | `/api/referrals` | Get referral earnings and history | | `GET` | `/api/webhooks` | List webhooks | | `POST` | `/api/webhooks` | Create webhook | | `PATCH` | `/api/webhooks/{id}` | Update webhook | | `DELETE` | `/api/webhooks/{id}` | Delete webhook | | `GET` | `/api/webhooks/{id}/deliveries` | List webhook deliveries | | `GET` | `/api/webhooks/events` | List webhook event types | | `POST` | `/api/domains/{domain}/email/setup` | Set up email on a domain | | `DELETE` | `/api/domains/{domain}/email/setup` | Remove email from a domain | | `GET` | `/api/domains/{domain}/email/status` | Check email DNS status | | `POST` | `/api/email` | Create a mailbox on domani.run | | `GET` | `/api/email` | List all mailboxes | | `POST` | `/api/emails` | Create a mailbox | | `GET` | `/api/emails` | List all mailboxes | | `GET` | `/api/emails/{address}` | Get mailbox details | | `PATCH` | `/api/emails/{address}` | Update mailbox | | `DELETE` | `/api/emails/{address}` | Delete a mailbox | | `POST` | `/api/emails/{address}/send` | Send an email | | `GET` | `/api/emails/{address}/messages` | List emails | | `PATCH` | `/api/emails/{address}/messages/read` | Mark messages as read or unread | | `POST` | `/api/emails/{address}/messages/delete` | Bulk delete messages | | `GET` | `/api/emails/{address}/messages/{id}` | Get a message | | `DELETE` | `/api/emails/{address}/messages/{id}` | Delete a message | | `POST` | `/api/emails/{address}/messages/{id}/reply` | Reply to a message | | `POST` | `/api/emails/{address}/messages/{id}/forward` | Forward a message | | `GET` | `/api/emails/{address}/avatar` | Get mailbox avatar info | | `GET` | `/api/emails/{address}/webhook` | Get mailbox webhook config | | `PUT` | `/api/emails/{address}/webhook` | Set mailbox webhook | | `DELETE` | `/api/emails/{address}/webhook` | Remove mailbox webhook | | `POST` | `/api/emails/{address}/webhook/rotate` | Rotate webhook signing secret | | `POST` | `/api/emails/{address}/webhook/test` | Test mailbox webhook | ## Rate Limits All responses include rate limit headers: - `X-RateLimit-Limit` - Maximum requests allowed per window - `X-RateLimit-Remaining` - Requests remaining in current window - `X-RateLimit-Reset` - Unix timestamp when the window resets | Endpoint | Public Limit | Auth Limit | Notes | |----------|-------------|------------|-------| | `GET /api/domains/search` | 20/min (IP) | 60/min (user) | Availability + pricing (single, multi, SSE) | | `GET /api/tlds` | 30/min (IP) | 60/min (user) | TLD listing | | `GET /api/domains/suggest` | 10/min (IP) | 10/min (IP) | AI-generated domain names | | `GET /api/domains/whois` | 10/min (IP) | 30/min (user) | Cached 1h registered, 5min not-found | | `GET /api/domains/{domain}/og` | 30/min (IP) | 30/min (IP) | Cached 7 days, CDN 24h | | `GET /api/domains/{domain}` | - | 60/min (user) | Auth required | | `PUT /api/domains/{domain}/settings` | - | 60/min (user) | Auth required | | `POST /api/domains/buy` | - | 10/min (user) | Auth required | | `POST /api/domains/import` | - | 30/min (user) | Auth required | | `POST /api/domains/import/verify` | - | 10/min (user) | Auth required, DNS lookup | | `POST /api/domains/transfer` | - | 5/min (user) | Auth required, async (1-5 days) | | `GET /api/domains/{domain}/transfer-status` | - | 30/min (user) | Auth required, inbound transfer status | | `GET /api/domains/{domain}/auth-code` | - | 10/min (user) | Auth required, auto-unlocks domain | | `GET /api/domains/{domain}/transfer-away` | - | 30/min (user) | Auth required | | `POST /api/domains/{domain}/renew` | - | 10/min (user) | Auth required | | `POST /api/auth/register` | 5/min (IP) | - | | | `POST /api/auth/login` | 10/min (IP) | - | | | All other authenticated | - | 60/min (user) | Per endpoint | When rate limited (HTTP 429), the response includes a `Retry-After` header with seconds to wait. Recommended strategy: exponential backoff starting at the `Retry-After` value. ## Error Format All errors return a consistent JSON envelope: ```json { "error": "Human-readable message", "code": "MACHINE_CODE", "hint": "Actionable recovery guidance", "documentation_url": "https://domani.run/llms.txt" } ``` ### Error Codes | Code | HTTP | Meaning | Recovery | |------|------|---------|----------| | `MISSING_API_KEY` | 401 | No Authorization header | Add `Authorization: Bearer domani_sk_xxx` header | | `INVALID_API_KEY` | 401 | Key not found or revoked | Check key or create a new one via `POST /api/tokens` | | `EXPIRED_API_KEY` | 401 | Token has expired | Create a new token via `POST /api/tokens` or run `domani login` | | `INSUFFICIENT_SCOPE` | 403 | Token lacks required scope | Create a new token with the needed scopes via `POST /api/tokens` | | `MISSING_PARAMETER` | 400 | Required field missing | Check `hint` field for the missing parameter | | `INVALID_DOMAIN` | 400 | Bad domain format | Use format `name.tld` (e.g. `mysite.com`) | | `PAYMENT_REQUIRED` | 402 | No payment method | Pay with USDC via x402 (automatic) or add a card via `POST /api/billing/setup` | | `DOMAIN_UNAVAILABLE` | 409 | Domain is taken | Try a different domain or TLD | | `RATE_LIMIT_EXCEEDED` | 429 | Too many requests | Wait `retry_after` seconds, then retry | | `USER_EXISTS` | 409 | Email already registered | Use `POST /api/auth/login` instead | | `NOT_FOUND` | 404 | Resource not found | Verify the ID/domain exists and belongs to you | | `NOT_AVAILABLE` | 501 | Feature not available | The requested feature is not supported by the current registrar (Preview - coming soon) | ### CLI Error Format (`--json` mode) When using the CLI with `--json`, errors include a `fix_command` field for auto-recovery: ```json { "error": "Not logged in", "code": "auth_required", "hint": "Run 'domani login' or set DOMANI_API_KEY", "fix_command": "domani login" } ``` | CLI Code | fix_command | When | |----------|-------------|------| | `auth_required` | `domani login` | 401/403 or missing token | | `payment_required` | `domani billing` | 402 or `setup_url` present | | `contact_required` | `domani contact set` | Contact info missing | | `validation_error` | - | Invalid input (422) | | `not_found` | - | Resource not found (404) | | `rate_limited` | - | Too many requests (429) | | `conflict` | - | Conflict (409) | ### CLI Flags for Agents The CLI (`npx domani`) supports flags designed for agent integration: - `--json` - Machine-readable JSON output on all commands - `--fields ` - Filter JSON output to specific fields (comma-separated, e.g. `--fields domain,status`) - `--dry-run` - Preview mutations without executing (buy, dns set, connect, etc.) - `--yes` - Skip confirmation prompts (for automated workflows) - `$DOMANI_API_KEY` - Set as environment variable to authenticate without `domani login` **TTY auto-detect**: When stdout is piped (not a terminal), the CLI automatically outputs JSON and skips confirmations - no `--json` or `--yes` needed. **Input hardening**: Domain and TLD inputs are validated against path traversal (`../`), control characters, query strings (`?`/`#`), and percent-encoding (`%2e`). Invalid input returns `code: "invalid_input"` with a hint. ### Schema Introspection Discover CLI command schemas programmatically: - All commands: `GET https://domani.run/api/schema` - Single command: `GET https://domani.run/api/schema?command=buy` - CLI: `domani schema [command] --json` Returns parameters, types, constraints, enums, and error codes for each command. ## Referral Program Agents and integrations can earn commissions on domain purchases: 1. **Register** with `POST /api/auth/register` to get your `referral_code` 2. **Link users** by passing your code as `ref` when registering new users: `{"email": "...", "ref": "YOUR_CODE"}` 3. **Earn automatically** - every domain purchased by a user you referred earns you a 20% commission on the markup, permanently The referral link is set once at registration and applies to all future purchases. No need to pass the code on every buy request. Check your earnings with `GET /api/referrals`. ## Paying with USDC No credit card needed. Two-step flow: 1. `POST /api/domains/buy` with `{"domain": "mysite.com", "payment_method": "usdc"}` - returns 402 with wallet address, USDC amount, and supported chains (Base or Ethereum) in `payment_options.crypto`. 2. Send USDC to the provided address, then retry: `POST /api/domains/buy` with `{"domain": "mysite.com", "payment_tx": "0xabc...", "payment_chain": "base"}`. The server verifies the transaction on-chain and completes registration. Each tx hash can only be used once. Works for single domain purchases. **Supported chains**: Base (chain_id 8453), Ethereum Mainnet (chain_id 1). **Asset**: USDC. ### Automatic payments with x402 For agents with their own crypto wallet, the API supports the [x402 protocol](https://x402.org) for fully automatic USDC payments - no manual transfer needed. Setup: `npm install @x402/fetch`, create a wallet funded with USDC on Base, then wrap your fetch: ```typescript import { wrapFetch } from "@x402/fetch"; const x402Fetch = wrapFetch(fetch, walletClient); // walletClient = viem wallet with USDC on Base const res = await x402Fetch("https://domani.run/api/domains/buy", { method: "POST", headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ domain: "mysite.com" }), }); ``` Every API call that requires payment is handled automatically via `PAYMENT-REQUIRED` / `PAYMENT-SIGNATURE` / `PAYMENT-RESPONSE` headers. ## Pagination The `GET /api/tlds` endpoint supports pagination via `limit` and `offset` query parameters. When more results exist, the response includes a `Link` header: ``` Link: ; rel="next" ```