Skip to main content
Use this flow when the customer already owns the domains at another registrar. Peeker imports the domains into Cloudflare, provisions inboxes, and pushes them into Smartlead.

Full workflow

Save this as a sandbox script to verify the chain end-to-end before you ship.
cURL · full workflow
BASE="https://api.peeker.ai/partner/v1"
AUTH="Authorization: Bearer pk_test_<your-key>"

# Step 1: Submit the import job for domains the customer already owns.
curl -X POST "$BASE/domains/import" \
  -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{
    "domains":    ["acme-mail.com", "team-acme.com"]
  }'

# Step 2: Poll until the import completes (or listen for the webhook).
curl -X GET "$BASE/domains/import/imp_01HZX0IM…" -H "$AUTH"

# For bulk async imports up to 500 domains, submit a job instead.
curl -X POST "$BASE/domains/import/jobs" \
  -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{
    "domains":    ["acme-mail.com", "team-acme.com"]
  }'

# Step 3: Submit the order - provisions every inbox, routes to Smartlead,
#         runs forwarding, queues the webhook stream.
curl -X POST "$BASE/orders" \
  -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{
    "user": "alex@acme.com",
    "bundle_id":          "bun_01HZX0BU…",
    "sequencer": {
      "provider": "smartlead",
      "client_id": 366903,
      "login_email": "ops@acme.com",
      "login_password": "their-smartlead-password"
    },
    "forwarding_url":     "https://acme.com",
    "domains":            ["acme-mail.com", "team-acme.com"],
    "personas": [
      { "first_name": "Alex", "last_name": "Rivera", "profile_picture_url": "https://cdn.acme.com/alex.jpg" },
      { "first_name": "Sam",  "last_name": "Lee",    "profile_picture_url": "https://cdn.acme.com/sam.jpg"  }
    ]
  }'

Steps in detail

1. Submit the import job

Call POST /domains/import with domains the customer already owns. The response includes the nameservers your customer must set at their registrar. The domains attach to the customer later, when you place the order.Submit root domains only, such as acme-mail.com, not subdomains like mail.acme.com. Peeker validates import eligibility before returning the job; invalid or non-root domains can come back as failed job rows and may take longer than the normal fast enqueue path.
200 OK · nameserver groups
{
	"data": {
		"id": "imp_01HZX0IM1A2B3C4D5E6F7G8H",
		"status": "completed",
		"submitted_count": 2,
		"completed_count": 2,
		"failed_count": 0,
		"nameserver_groups": [
			{
				"nameserver_one": "helena.ns.cloudflare.com",
				"nameserver_two": "idris.ns.cloudflare.com",
				"domains": ["acme-mail.com", "team-acme.com"]
			}
		],
		"failed": [],
		"created_at": "2026-05-08T12:00:00Z",
		"completed_at": "2026-05-08T12:00:04Z"
	}
}
Send the customer those nameservers in your UI:
Open your registrar’s DNS settings for acme-mail.com and team-acme.com. Replace the existing nameservers with helena/idris. Most registrars apply the change within a few hours.
For larger batches, call POST /domains/import/jobs with up to 500 domains. It returns 202 Accepted with an in_progress job immediately; then poll GET /domains/import/jobs/{id} or listen for domain_import.* webhooks. Async job creation uses the normal Partner API rate limit and does not fail just because Cloudflare accounts are currently busy. Retrying the same normalized domain set returns the existing import job, so there is no idempotency header to generate.
Poll GET /domains/import/{id} or GET /domains/import/jobs/{id} every 60 seconds, or subscribe to the domain_import.completed webhook. If you no longer have the imp_... ID, call GET /domains/import?domains=acme-mail.com,team-acme.com; the domain query is required because this endpoint is a lookup, not a general import-history list. Async job status moves from in_progresscompleted once nameservers are ready.If you get domain_import.action_required, surface “Acme - waiting on registrar” in your UI and re-show the same nameservers. Peeker keeps re-checking; you’ll get domain_import.completed automatically when the customer flips DNS.
{
	"id": "evt_01HZX0EVBA2B3C4D5E6F7G8H",
	"type": "domain_import.completed",
	"created_at": "2026-05-08T12:08:43Z",
	"data": {
		"id": "imp_01HZX0IM1A2B3C4D5E6F7G8H",
		"status": "completed",
		"submitted_count": 2,
		"completed_count": 2,
		"failed_count": 0,
		"nameserver_groups": [
			{
				"nameserver_one": "helena.ns.cloudflare.com",
				"nameserver_two": "idris.ns.cloudflare.com",
				"domains": ["acme-mail.com", "team-acme.com"]
			}
		],
		"failed": [],
		"created_at": "2026-05-08T12:00:00Z",
		"completed_at": "2026-05-08T12:08:43Z"
	}
}
One call. Pass a bundle_id, Smartlead route and login credentials, imported domains, and personas.Smartlead orders require both sequencer.login_email and sequencer.login_password. A Smartlead client ID or API key alone is not enough for Peeker to submit the provider work.If the user email already exists, Peeker reuses that user and updates their saved Smartlead routing for future orders. Re-sending the same body returns the original order - orders dedupe for 24 hours.
200 OK · order
{
	"data": {
		"id": "ord_01HZX0OR1A2B3C4D5E6F7G8H",
		"user_id": "usr_01HZX0C6Z3K4M5N6P7Q8R9S0",
		"email": "alex@acme.com",
		"bundle_id": "bun_01HZX0BU…",
		"status": "in_progress",
		"domain_count": 2,
		"costs": {
			"total_cents": 6800,
			"currency": "usd"
		},
		"created_at": "2026-05-08T12:00:00Z"
	}
}
Use GET /orders/{id} or order webhooks when you need the full detail response with imported vs registered domains, line items, and personas.
The progression for a Smartlead-backed order:
order.in_progress
  → domain.connected (×N)   (one per domain as DNS + provider land)
  → order.completed         (every inbox is live in Smartlead)
When order.completed fires, the inboxes are already pushed into Smartlead. They can start warming and sending. No additional API call from your side.
{
	"id": "evt_01HZX0EV7A2B3C4D5E6F7G8H",
	"type": "domain.connected",
	"created_at": "2026-05-08T12:08:00Z",
	"data": {
		"id": "dom_01HZX0D01A2B3C4D5E6F7G8H",
		"domain": "acme-mail.com",
		"category": "connected",
		"status": "active",
		"provider": "google",
		"user_id": "usr_01HZX0C6Z3K4M5N6P7Q8R9S0",
		"order_id": "ord_01HZX0OR1A2B3C4D5E6F7G8H",
		"usable_for": ["google", "microsoft"],
		"forwarding_url": "https://acme.com",
		"created_at": "2026-05-08T12:00:00Z"
	}
}

Things to handle in production

A few real-world cases that show up once you’re past sandbox:
  • Customer hasn’t flipped nameservers yet. You’ll see domain_import.action_required after ~30 minutes. Re-show the nameservers in your UI and surface “Acme - waiting on registrar.” Peeker keeps re-checking; you’ll get domain_import.completed when they propagate.
  • Profile picture URL is private/invalid. You’ll see order.action_required; call GET /orders/pending?order_id=... and look for reason: "profile_picture_not_accessible". Get a fresh public URL from the customer and place a corrected order.
  • Domain needs partner action. GET /orders/pending returns update_nameservers or replace_domain only when there is a concrete customer/partner action to take.

What’s next

Buying domains from your registrar

Same flow, but Peeker buys the domains and runs DNS - no nameserver step for the customer.

Change forwarding URLs

Repoint up to 25 domains at a new forwarding URL after the order is live.

How to implement domain swaps

Replace a degrading domain mid-flight without losing warmup.

Best practices

Errors, paging, idempotent retries, pending actions, webhook hygiene.
Last modified on June 29, 2026