---
title: "Purchase one or more domains"
method: POST
path: "/api/domains/buy"
tags: ["Domains"]
---

# Purchase one or more domains

`POST /api/domains/buy`

## Request

- Base URL: `https://domani.run`
- URL: `https://domani.run/api/domains/buy`
- Auth: HTTP bearer

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.

## Headers

- `PAYMENT-SIGNATURE` string

## Request body

- object
  - `domain` string: Single domain to purchase (e.g. mysite.com)
  - `domains` string[]: Array of domains to purchase (max 10, requires card on file)
  - `payment_method` 'card' | 'usdc' | 'balance': Payment method: 'card' (default), 'usdc' (x402 - first call returns 402 with payment instructions), or 'balance' (spend marketplace credit).
  - `payment_tx` string: Transaction hash of a USDC payment already sent on-chain. Required on the second call of the two-step USDC flow.
  - `payment_chain` 'base' | 'ethereum': Chain the USDC payment was sent on (e.g. 'base' or 'ethereum').
  - `years` integer: Number of years to register (1-10, default 1). Price is multiplied by years.
  - `max_price` number: Ceiling 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. Without max_price the ceiling is the price search listed: when only a registrar billing more can take the name, the buy fails the same way. The error's `price` is the total to accept, with `listed_price` and `renewal_price` beside it; confirm them with the user before retrying with that max_price.
  - `ref` string: Referral code (optional) - the referrer earns a commission

## Response `201`

Domain purchased and registered successfully

- union
  - BuyDomainItem
    - `domain` string, required
    - `status` 'active' | 'provisioning', required
    - `expires` string, date-time, nullable
    - `price` number, required
    - `currency` string, required
    - `years` integer, required
    - `payment_method` 'card' | 'balance' | 'x402' | 'crypto', required
    - `payment_tx` string
    - `auto_renew` boolean, required
    - `renewal_price` number: Registrations only: what a year of renewal costs, in USD, at the registrar that registered the domain. Absent when unknown.
    - `marketplace_purchase` boolean
  - BuyDomainBulkResult
    - `results` BuyDomainItem[], required
      - `domain` string, required
      - `status` 'active' | 'provisioning', required
      - `expires` string, date-time, nullable
      - `price` number, required
      - `currency` string, required
      - `years` integer, required
      - `payment_method` 'card' | 'balance' | 'x402' | 'crypto', required
      - `payment_tx` string
      - `auto_renew` boolean, required
      - `renewal_price` number: Registrations only: what a year of renewal costs, in USD, at the registrar that registered the domain. Absent when unknown.
      - `marketplace_purchase` boolean
    - `errors` BuyDomainFailure[], required
      - `domain` string, required
      - `error` string, required
      - `failure_code` 'registration_race' | 'payment' | 'registrar' | 'unexpected' | 'marketplace_listing', required
      - `code` 'REGISTRAR_CREDIT': Set when no registrar had the prepaid credit for this name: nothing was charged for it; retry later.
      - `min_years` integer: Set when at least one registrar in the chain refused only on the requested term (not the name, not an outage): a year count that registrar said it would accept. Retry with years set to this.
    - `summary` object, required
      - `total` integer, required
      - `succeeded` integer, required
      - `failed` integer, required
      - `charged` number, required
      - `refunded` number, required
    - `hint` string, required
    - `next_steps` string[], required

## Other responses

- `202`: Domain purchase accepted and provisioning is still in progress
- `400`: Invalid request parameters, or PRICE_ABOVE_MAX: the name costs more than max_price, or (without max_price) more than search listed because the registrar with the listed price cannot take it. Nothing was charged; show `price` and `renewal_price` to the user and retry with max_price of at least `price` to accept.
- `401`: Missing or invalid API key
- `402`: Payment required - includes x402 payment requirements for USDC on Base
- `409`: Domain is not available for registration
- `429`: Rate limit exceeded - wait and retry
- `500`: Registration failed after the purchase attempt
- `503`: Two honest, retryable cases, nothing ever charged. REGISTRAR_CREDIT (RegistrarCreditError): no registrar that sells the name has the prepaid credit for this registration right now and none could be topped up - retry later. Any other registrar failure before payment (low balance read stale, a rate limit, an outage): BuyDomainRegistrationError with charged: false and a Retry-After header (seconds).

---

[API](https://skmtc.dev/domani/apis/domani-run-api.md) · [All operations](https://skmtc.dev/domani/apis/domani-run-api/llms.txt) · [OpenAPI document](https://skmtc.dev/domani/apis/domani-run-api/revisions/7679f8159533?raw)
