---
title: "Register a domain (fail-closed; requires Idempotency-Key)"
method: POST
path: "/registrar/purchase"
tags: ["Registrar"]
---

# Register a domain (fail-closed; requires Idempotency-Key)

`POST /registrar/purchase`

## Request

- Base URL: `https://api.customdomain.ai/v1`
- URL: `https://api.customdomain.ai/v1/registrar/purchase`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.customdomain.ai/oauth/agent/authorize`, token URL `https://api.customdomain.ai/oauth/agent/token`, scopes: `domains:purchase`)

Off on the hosted service. This route registers a domain without taking a payment, so it needs both `SELL_PURCHASE_ENABLED` and its own switch `SELL_DIRECT_PURCHASE_ENABLED`; buy through checkout instead. A required `Idempotency-Key` header dedupes retries so a registration is never charged twice.

A delegated agent token needs `domains:purchase`; the grant's monthly spend cap is charged first, and a purchase that would pass it answers `409`. A tenant API key is refused with `401`. A workspace that billing refuses answers `402`.

## Headers

- `Idempotency-Key` string, required

## Request body

- object
  - `domain` string, required
  - `buyer` object, required
    - `name` string
    - `email` string, required
    - `country` string

## Response `200`

Order (idempotent replay)

## Other responses

- `201`: Order
- `400`: Error envelope: {code, title, details}.
- `401`: Error envelope: {code, title, details}.
- `402`: Refused because of the workspace's billing. `billing_required`: the workspace has no active or trialing subscription (a new workspace that has not started its trial, a Free workspace past its grace period, a comp that ended, or a subscription Stripe marked unpaid or canceled). It stops a new connection, the start of a domain purchase and the first live credential (an application, an API key or a rotated client secret). Domains already connected keep working, and an owner or admin sets up billing in the console; `details` says where. A new workspace's first domain is the exception: while it has fewer pending, propagating or live connections than its free domains (ten, unless an operator set another number: `free_domains`), a connect and the start of a purchase go through (`first_domain_free`), and the next one is refused with a `title` saying a card is needed to connect more domains. Credentials still need the trial. `quota_exceeded` (connections only): a plan that is refused past its quota (Starter, and the retired Free) is at it and the deployment enforces quotas. `connections_unavailable`: what a connect-scoped widget token, the tenant's own end user, gets instead of either, with neutral wording, so the tenant's customers are never told about the tenant's billing. A delegated agent token gets the full wording, and `GET /billing/standing` tells it beforehand whether a new connection would be refused.
- `403`: Error envelope: {code, title, details}.
- `503`: Error envelope: {code, title, details}.

---

[API](https://skmtc.dev/customdomain/apis/customdomain-control-plane-api.md) · [All operations](https://skmtc.dev/customdomain/apis/customdomain-control-plane-api/llms.txt) · [OpenAPI document](https://skmtc.dev/customdomain/apis/customdomain-control-plane-api/revisions/34d89cb7a7cd?raw)
