# Domain Purchase Buy an external domain (e.g. `yourapp.com`) on the tenant's behalf. Cohesivity is the legal registrant of record; the tenant is the beneficial owner. Cloudflare Registrar backs the v1 implementation — at-cost wholesale, Cohesivity adds a 10% markup, the user pays in INR via Razorpay. Unlike the rest of the offerings catalog, this is **not** provisioned through `/api/resources`. It's a paid one-shot: agents quote candidates, the human pays through a Razorpay-hosted checkout, and Cohesivity registers the domain server-side after payment captures. ## Lifecycle 1. `POST /api/domains/check` — agent submits up to 20 candidate names; Cohesivity returns availability + price + a 10-minute signed quote token for each available one. 2. `POST /api/domains/purchase` — agent picks one (passing the quote token); Cohesivity mints a Razorpay Payment Link, returns a `cohesivity.ai/t/` checkout URL. 3. Human pays via the URL (no Cohesivity sign-in required to pay). 4. Razorpay webhook → Cohesivity re-checks availability, calls Cloudflare Registrar to register, inserts a row in `tenant_domains`. If registration fails or the domain was taken in between, the payment is automatically refunded. 5. `GET /api/domains` / `GET /api/domains/` — agent inspects what's on file, including the registrar record id, expiry, and renewal quote. ## Pricing - Wholesale comes from Cloudflare Registrar's `domain-check` endpoint (USD, at-cost). - Cohesivity applies a flat **10% markup** on the wholesale USD cost, then converts to INR paise at the configured FX rate (`DOMAIN_USD_INR_RATE_PAISE_PER_CENT`, default 83 paise per USD cent ≈ ₹83/$1). - The user-visible INR price is shown in the `/api/domains/check` response and embedded in the signed quote token. The token expires 10 minutes after issue — agents must re-check before purchase if the human takes longer than that to decide. - Renewals (~12 months out) ride on `auto_renew=true` for v1: Cloudflare auto-charges Cohesivity's card on file, so users do not pay year 2 today. The user-billed renewal flow is on the roadmap. ## Supported TLDs Cloudflare Registrar's API beta covers ~390 TLDs including `.com`, `.dev`, `.app`, `.net`, `.org`, `.xyz`, `.studio`, `.co`. **Not supported in this beta:** `.ai`, `.io`, `.uk`, `.in`. Check responses surface unsupported TLDs as `reason: "tld_unavailable"` so agents can offer the human a different TLD instead of failing silently. Cloudflare is expanding the beta; the catalog inherits new TLDs as they open up — no Cohesivity code change required. ## Endpoint reference ### Check availability curl -s -X POST https://cohesivity.ai/api/domains/check \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"domains":["yourapp.com","yourapp.dev","yourapp.app"]}' Returns one entry per requested name: { "registrar": "cloudflare", "currency": "INR", "markup_percent": 10, "quote_validity_ms": 600000, "domains": [ { "name": "yourapp.com", "available": true, "tier": "standard", "purchase_inr_paise": 95700, "renewal_inr_paise": 95700, "currency": "INR", "quote_token": "eyJh...HS256.eyJ...exp", "quote_valid_until_ms": 1715543700000 }, { "name": "yourapp.dev", "available": false, "reason": "taken" }, { "name": "yourapp.app", "available": true, ... } ] } `reason` values when unavailable: `taken`, `tld_unavailable`, `invalid_format`, `unknown`. ### Purchase curl -s -X POST https://cohesivity.ai/api/domains/purchase \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"quote_token":"eyJh...HS256.eyJ...exp"}' Returns a Razorpay-backed checkout link: { "provider": "razorpay", "kind": "payment_link", "payment_link_id": "plink_xxxxxxx", "domain": "yourapp.com", "purchase_inr_paise": 95700, "renewal_inr_paise": 95700, "currency": "INR", "status": "created", "checkout_url": "https://cohesivity.ai/t/plink_xxxxxxx", "next_steps": ["Hand the human this URL: ...", "..."] } Hand the `checkout_url` to the human; no Cohesivity sign-in is required to pay. Cohesivity registers the domain automatically on receipt of the Razorpay `payment_link.paid` webhook. ### List domains owned by this tenant curl -s https://cohesivity.ai/api/domains \ -H "Authorization: Bearer " Returns every `tenant_domains` row keyed to the calling tenant, newest-first. ### Get a single domain curl -s https://cohesivity.ai/api/domains/yourapp.com \ -H "Authorization: Bearer " Returns 404 if the domain is not owned by this tenant (whether it doesn't exist or belongs to someone else — the response shape is intentionally the same). ## Common Mistakes - **Holding a quote token longer than 10 minutes.** The signed token carries an `exp`; `/api/domains/purchase` returns `410 quote_expired` past it. Re-call `/api/domains/check` to get a fresh quote. - **Trying to register `.ai` / `.io` / `.uk` / `.in`.** These TLDs surface as `tld_unavailable` because Cloudflare Registrar's API beta doesn't register them yet. Surface a different TLD to the human; don't loop on the unsupported ones. - **Re-trying purchase after webhook success.** If `GET /api/domains/` returns `200`, the domain is registered. A second `POST /api/domains/purchase` with the same quote token will reject with `409 already_registered`. The webhook is the authoritative settlement path. - **Expecting renewal billing to round-trip through the user this year.** Year 1 only: `auto_renew=true` means Cloudflare auto-charges Cohesivity's card at expiry. The user-billed renewal flow is not yet shipped; surface this in your UX (e.g. "domain is renewed automatically for the first year"). - **Treating `transferred_out` as available again.** Once a row exists in `tenant_domains`, no other tenant on this Cohesivity deploy can re-purchase the name through the same flow (UNIQUE on `domain_name`). Transfers are a future endpoint, not a re-purchase loop. ## Limitations (v1) - TLD coverage limited to the Cloudflare Registrar API beta's ~390 TLDs. - Renewals via the Cloudflare API are not yet open — we ride on `auto_renew=true`. - Transfers (in and out) are not yet open via the Cloudflare API. - The "release / let it expire" flow is dashboard-only at Cloudflare; deletion of the `tenant_domains` row is not exposed in the API. - DNS records on a purchased domain are managed at Cloudflare directly (the domain auto-lands as a zone in Cohesivity's Cloudflare account). A Cohesivity DNS-record API for purchased domains will follow in a later iteration. ## Auth All four endpoints require a `coh_management_key` (`Authorization: Bearer ...`). **Lifecycle gate**: `/api/domains/check` is open to ephemeral tenants — browsing pricing is harmless and useful as a "should I claim before I commit?" signal. The other three endpoints (`purchase`, list, get) require a **claimed** tenant. Ephemeral tenants auto-terminate at the 72-hour claim window and the `tenant_domains` row would CASCADE-delete on termination, but the Cloudflare registration would survive — the user would have paid for a domain we no longer have a record of. Gating purchase + management to claimed tenants closes that hole. If a call returns `403 tenant_must_be_claimed`, claim the tenant first using the `claim_url` in the response.