---
title: "Set up branded lookalike domains and mailboxes; start warmup."
method: POST
path: "/setup-infrastructure"
tags: ["infrastructure"]
---

# Set up branded lookalike domains and mailboxes; start warmup.

`POST /setup-infrastructure`

## Request

- Base URL: `https://api.coldrig.dev`
- URL: `https://api.coldrig.dev/setup-infrastructure`
- Auth: HTTP bearer

Domain purchase + DNS + mailbox setup + warmup run BEFORE the response, not in the background — a large request can take a while to answer. Returns 202 for every call that sets up, finished or not: a `provisioning` field names the state when work is still owed, and a 202 without it means the call finished (200 is returned only for a `quoteOnly: true` preview, which sets up nothing); poll GET /infrastructure-status or re-call this endpoint to progress a `provisioning` outcome. In demo/free plans this runs entirely against test mode — no real domain purchase or mailbox setup occurs.

`domains` covers ordinals 0..domains-1; each ordinal fills to its own mailbox count via `inboxesEach` (uniform) or `distribution` (per-ordinal). This is the infrastructure you want to HAVE, not an amount to add — a repeat call at the same `domains` sets up nothing new once every ordinal is satisfied, so reaching ordinal 1 needs `domains: 2`, not a second `domains: 1` call; raising `inboxesEach`/`distribution` tops existing domains up to the new per-domain count. Mailbox addresses are DETERMINISTIC — derived from `persona` + ordinal + slot — so keep `persona` unchanged across resumes and retries.

Retry contract: repeating a call NEVER buys a second time, whatever you do with the Idempotency-Key — resend the same one, send a different one, or send none at all. The key governs response replay only (a replay of a completed keyed call returns that call's recorded result without re-running it); it has no bearing on what is purchased. This is a change from earlier behavior, in which a NEW key opened a second purchase — retrying with a fresh key bought a second lookalike domain and a duplicate set of billable mailboxes.

`registerDomains` (real domain purchases on the platform's own account — the customer's bill is unchanged, mailbox-count-based only): omitting it leaves any previously-given consent UNCHANGED; `true` opts in (`registrant` required only when none is already on file — see `Registrant`); an explicit `false` revokes it. There is no default. A buy-bearing call that omits it (or sends `false`) is refused before any spend: 400 `registrar_optin_missing` once this account has consented before ("registerDomains was not set on this request" — set it only after the account owner has approved the quoted purchase), or 503 `registrar_unarmed` if domain registration is not enabled for the platform at all. A test-mode account needs neither `registerDomains` nor `registrant`: they apply only to a real domain purchase.

If fewer available, not-already-owned lookalike domains exist than the call still has to BUY, it fails with 400 naming how many were found — never a silent partial setup or a repeat purchase of one domain. A call that needs to buy nothing (a pure retry) does not need any candidate to be available and cannot fail this way.

A big order in a paying, monthly-billed account's first billing month can be asked up to two questions, each only when the `quoteOnly: true` preview shows it: `billing.domainEnding` (a call naming more than 10 mailboxes: its new domains end in .co; send `dotComDomains: true` to keep .com for the one-time fee it shows per domain, added to the next bill) and `billing.prepay` (an order making more than 4 new domains in your first billing month: month 2 is paid up front with the order; send `prepaySecondMonth: true` to accept it, otherwise the call is refused 400 `prepay_required` before anything is bought). Ask the account owner before sending either. On an order whose preview shows neither, both fields do nothing.

A domain's DNS setup may still be completing when this call returns — the domain is recorded either way and is never lost. There is no autonomous background retry: completing it is the caller's job. Repeat the same call to converge onto that domain and finish its DNS setup. There is no per-domain DNS field to poll: GET /infrastructure-status reports a domain count, not per-domain detail, but its `nextSteps` names the account's own next action.

## Headers

- `Idempotency-Key` string

## Request body

- SetupInfrastructureInput: Exactly one of `inboxesEach` (uniform per-domain count) or `distribution` (per-ordinal counts) is required — supplying neither is rejected naming `inboxesEach`, and `distribution`'s length must equal `domains`.
  - `brand` string, required: One line of plain text: a line break, tab, control character or invisible text-direction mark is refused (400) naming this field.
  - `primaryDomain` string, required
  - `domains` integer, required: Number of lookalike domains to set up.
  - `inboxesEach` integer: Mailboxes to set up per domain, UNIFORM across every ordinal. 3 to 5 mailboxes per domain (3 by default, 5 when you order more than 10 in your first month); on a paying account a new domain needs 3 to 5. Putting 5 on a domain means fewer domains, but if that domain gets burned, 5 mailboxes stop sending instead of 3. Optional only when `distribution` is supplied.
  - `distribution` integer[]: The per-ordinal mailbox count directly — one entry per domain ordinal (e.g. `[4, 3]` for 4 mailboxes on domain 0 and 3 on domain 1), for a shape `inboxesEach`'s single uniform number cannot express. Its length must equal `domains`. Alternative to `inboxesEach`, not additive with it.
  - `persona` string, required
  - `physicalAddress` string, required: CAN-SPAM-required physical postal address, injected into the footer of every campaign email (replies sent with the reply tool or the dashboard carry no footer).
  - `senderIdentity` string, required: Legal sender identity captured at setup, injected into the footer of every campaign email (replies sent with the reply tool or the dashboard carry no footer).
  - `quoteOnly` boolean: Quote-before-add. true previews the new mailbox count + projected monthly (returns 200 { quoteOnly, billing }) WITHOUT setup. false (default) sets up and returns { jobId, billing }.
  - `registerDomains` boolean: This account's opt-in consent to real domain purchases made on the platform's own account (the customer's bill is unchanged, mailbox-count-based only). Omitting this field entirely leaves any previously-given consent UNCHANGED; an explicit `false` revokes it. There is no default — an absent field is never treated as `false`. `true` opts in; `registrant` is required only when no complete registrant is already on file from a prior call (see `Registrant`) — a call omitting `registerDomains` (or sending `false`) on a request that needs to buy a domain is refused before any spend: 400 `registrar_optin_missing` once this account has consented at least once, or 503 `registrar_unarmed` if domain registration is not enabled for the platform at all. The 400 is cleared by sending `registerDomains: true`, and only after the account owner has approved the quoted purchase; a request cannot clear the 503.
  - `registrant` Registrant: The domain registrant-of-record. Optional even when `registerDomains` is true, whenever a complete registrant is already on file from a prior call — supplying one makes it authoritative for THIS call; omitting it re-uses the persisted one. This platform never invents a domain registrant's legal identity, so a `registerDomains:true` call with no registrant anywhere (neither supplied nor persisted) is rejected (400 `incomplete_registrant`) naming the missing fields, before any purchase.
    - `firstName` string, required
    - `lastName` string, required
    - `email` string, email, required
    - `phone` string, required
    - `addressLine1` string, required
    - `city` string, required
    - `state` string, required
    - `country` string, required
    - `postalCode` string, required
    - `organization` string: Optional; defaults to the account's own brand if omitted.
  - `dotComDomains` boolean: Only when the quote's billing.domainEnding is set: true keeps those new domains on .com for the one-time fee it shows per domain, added to the next bill. Leave it out for .co. Ask the account owner first.
  - `prepaySecondMonth` boolean: Only when the quote's billing.prepay is set (an order making more than 4 new domains in your first billing month): true accepts paying month 2 up front with this order (the amount it shows, charged right after the mailboxes are set up; the next renewal moves one month later; a cancel at any time ends the plan on that date; the prepaid month is not refunded). Ask the account owner first.

## Response `202`

Accepted: returned by every call that sets up, finished or not. Returns { jobId, billing } where `jobId` is a correlation id only, not a trackable job handle — there is no job store and no endpoint that accepts it, so do not poll for it or treat its presence as meaningful. `billing` (MailboxBilling) is the billing projection for the real post-setup mailbox count. A quoteOnly:true request instead returns 200 with { quoteOnly: true, billing } and sets up nothing.

A `provisioning` field is present only when the call returned OWING work, and names which state: "pending" (the last domain's DNS registration is still completing — `pendingDomain` names it) or "capacity_pending" (setup is paused while Coldrig support checks recent purchases on this account; nothing failed and nothing was charged; once support finishes, repeating your last call with the SAME idempotency key completes it) or "payment_pending" (a large order: it would bring the account more than 20 mailboxes past what its last confirmed payment covered, so nothing was bought; a person with the card pays this billing period's share at `payment.url`, which is taken off the next bill, and repeating the call with the SAME idempotency key then completes it — see LargeOrderPayment). An outcome carrying `provisioning`, and a quoteOnly preview, are NOT recorded against your Idempotency-Key — retrying with the same key re-runs the call rather than replaying the unfinished answer.

`nextSteps` (design §7.2) names what this account should do next — the SAME derivation `GET /infrastructure-status` embeds.

- object
  - `nextSteps` NextSteps: What this account should do next, derived fresh on every response that carries it (design §7.2) — the one endpoint-agnostic self-guiding contract shared by setup_infrastructure and infrastructure_status.
    - `status` 'owed' | 'none_owed', required: EXPLICIT discriminator — never inferred from `steps` being empty. `none_owed` is routinely emitted WITH a non-empty `steps` array (an `available` step is not owed).
    - `steps` NextStep[], required
      - `reason` 'paid_seats_unprovisioned' | 'seat_headroom_free' | 'domain_dns_incomplete' | 'ordinal_incomplete' | 'ordinal_slot_shortfall' | 'ordinal_domain_retired' | 'setup_operator_blocked' | 'setup_capacity_held' | 'large_order_payment_pending' | 'billed_quantity_drift' | 'account_frozen' | 'message_action_required' | 'mailbox_credentials_pending' | 'ready_to_launch', required: Closed union (design §7.2). paid_seats_unprovisioned = paying, nothing set up (owed). seat_headroom_free = at the account's mailbox minimum with fewer set up, the remainder cost $0 (NEVER owed). domain_dns_incomplete = a live domain's mail DNS has not come up yet. ordinal_incomplete = a requested domain ordinal never completed. ordinal_slot_shortfall = the domain landed and its mail DNS is up, but it holds fewer mailboxes than the call that created it asked for — mailbox slots complete independently, so repeating the setup call creates exactly the missing ones. ordinal_domain_retired = a committed ordinal's domain was burned by the platform itself and has not yet been automatically replaced; no self-serve call brings it back — contact Coldrig support. setup_operator_blocked = the platform stopped; only someone at Coldrig can restart it. setup_capacity_held = setup is paused while Coldrig support checks recent purchases on this account — nothing failed and nothing was charged. large_order_payment_pending = a large order waits on its payment page (owed until it is paid or expires): a person with the card pays it, then the same call repeats; nothing was bought. billed_quantity_drift = our Stripe push did not land, ours to fix. account_frozen = lifecycle freeze. With no subscription still running, re-checkout (POST /checkout) is the way out. With one still running, a second checkout is refused (it would charge twice), and the way out is the billing portal (POST /billing/portal): update the card when behind on payment, or renew the subscription when a cancellation is scheduled. An open or lost dispute (or a `disputed` billing state) bars checkout: contact Coldrig support. message_action_required = an unacked message the account will not progress past. mailbox_credentials_pending = a set-up mailbox's credentials have not reached the sending engine yet. ready_to_launch = infrastructure is up, no campaign exists yet.
      - `kind` 'owed' | 'available', required: owed = the account will not progress until this happens. available = nothing is blocked; this is the next thing worth doing.
      - `why` string, required: One customer-safe sentence. Vendor-blind: composed prose, never a raw error message.
      - `action` NextStepAction, required: A DISCRIMINATED UNION on `via` — always present, so "no action is possible" is a stated value rather than an empty object.
        - `via` 'mcp_tool' | 'http' | 'none', required
        - `tool` 'setup_infrastructure' | 'launch_campaign' | 'contact_operator' | 'ack_message' | 'configure_byo_domain': Present when via=mcp_tool. A real MCP tool name.
        - `params` object: Present when via=mcp_tool. Literal call arguments, complete EXCEPT the names listed in paramsToSupply.
        - `paramsToSupply` string[]: Present when via=mcp_tool. Fields the platform cannot know; the caller must add them. Empty = send params verbatim.
        - `idempotencyKey` unknown
        - `method` string: Present when via=http.
        - `path` string: Present when via=http.
        - `note` string: Present when via=http or via=none — who acts / what unblocks it.
      - `waitingOn` unknown, required
      - `notBeforeMs` integer, required: Do not repeat this call before notBeforeMs from now — the retry cadence as a machine field. 0 = now.
      - `effect` MailboxBilling, required: Billing projection returned on every mailbox add/remove (no silent capacity addition). `provisionedAfter` is the live mailbox count AFTER the call — reality, not the ask (a capacity-limited partial or a quoteOnly preview both report the true resulting count).
        - `provisionedAfter` integer, required: Live set-up mailbox count after this operation.
        - `projectedMonthlyCents` integer, required: Projected monthly: $49 a month covers the first 3 mailboxes, then $5 a month for each extra mailbox (3-mailbox minimum) — folding any checkout discount.
        - `formula` string, required
        - `domainEnding` object: Only on a quoteOnly preview of an order over 10 mailboxes in the account's first billing month that buys new domains, when the platform's .co switch is on: they will end in .co unless the real call sends `dotComDomains: true`.
          - `ending` 'co', required
          - `newDomains` integer, required: New domains this order buys.
          - `feeCentsEach` integer, required: The one-time fee per .com domain bought, in cents, added to the next bill.
          - `text` string, required: The same
        - `prepay` object: Only on a quoteOnly preview of an order making more than 4 new domains in your first billing month, when the platform's prepaid-month switch is on: the real call must send `prepaySecondMonth: true` (else 400 `prepay_required`, nothing bought); month 2 is then charged right after the mailboxes are set up, at most `cents`.
          - `months` 2, required: The month paid up front is the second one.
          - `cents` integer, required: Month 2 at this order's mailbox count.
          - `text` string, required: The same
      - `sinceMs` unknown, required
    - `computedAt` integer, required
  - `payment` LargeOrderPayment: The one hosted card payment page a large order waits on. Its amount is this billing period's share of the mailboxes past what the last confirmed payment covered, and is taken off the next bill. The cardholder's bank may ask for a code. Repeating the order returns the same page until it is paid or expires.
    - `url` string, required: Open in a browser; a person with the card pays here.
    - `amountCents` integer, required
    - `mailboxes` integer, required: How many mailboxes past the last confirmed payment this page pays for.
    - `coveredAfter` integer, required: The mailbox count the account's confirmed payments cover once this page is paid.
    - `expiresAt` integer, required: Unix milliseconds.
    - `text` string, required

## Other responses

- `400`: Request body failed schema validation.
- `401`: Missing, invalid, or inactive bearer token.
- `409`: The work this request asks for is already running — either a call with this Idempotency-Key has not finished yet, or (on /remove-mailboxes, where only one release may run at a time per account) an unkeyed release is in flight, or (on /setup-infrastructure, only where Coldrig has switched on background completion) that completion is running right now. Retryable by design: retry once the first call has recorded its result. After a refused release, re-read your live mailbox count before deciding to retry — the call that is running may already be doing what you asked.
- `502`: A real vendor call failed (domain registrar or mailbox provider) — materially different from a bug in this service. `step` names the vendor operation that failed when derivable; `retryable` is the adapter's own grade, so a caller can back off instead of guessing. THE GRADE IS TWO INDEPENDENT QUESTIONS. `retryable` says whether an immediate re-attempt can succeed on its own. `operatorActionable` says whether Coldrig's team can clear the refusal — funding a provider account, rotating a credential, un-suspending a workspace — after which your SAME request, with the same idempotency key, completes. When it is true the `code` is `vendor_operator_blocked`: do not change your inputs and do not give up, because nothing about the request is wrong. When both are false the refusal is yours to fix or to escalate.
- `503`: A required vendor integration is not yet enabled for this account. Nothing was charged. This condition has no automatic notification to Coldrig's team behind it, so the response says so and points at contact_operator rather than implying a human is already on it.

## Changes

- **2026-10-19** `242a462d82c4`: 1 warning, 5 info
  - added the new `large_order_payment_pending` enum value to the `nextSteps/steps/items/reason` response property for the response status `202`
  - added the new optional request property `dotComDomains`
  - added the new optional request property `prepaySecondMonth`
  - added the optional property `nextSteps/steps/items/effect/oneOf[#/components/schemas/MailboxBilling]/domainEnding` to the response with the `202` status
  - …2 more

[Operation history](https://skmtc.dev/coldrig/apis/agent-cold-email-api/changes/setup-infrastructure/post.md)

---

[API](https://skmtc.dev/coldrig/apis/agent-cold-email-api.md) · [All operations](https://skmtc.dev/coldrig/apis/agent-cold-email-api/llms.txt) · [OpenAPI document](https://skmtc.dev/coldrig/apis/agent-cold-email-api/revisions/242a462d82c4?raw)
