The full Winnr setup — buying domains, provisioning inboxes with proper SPF/DKIM/DMARC, starting warming, and pushing credentials into Instantly — is four API calls. Search domains with POST /v1/domains/search-bulk, purchase and queue setup with POST /v1/domains/purchase, enable warming with POST /v1/warming/enable, and export an Instantly-ready CSV with POST /v1/export. Total active developer time: under 15 minutes. Total wall-clock time: 2 to 5 minutes for provisioning, then a 14 to 21 day warmup before sending cold email. This guide walks through the entire pipeline with copy-paste Python and cURL.
Almost every Winnr API question we get from developers is some version of this:
"Do you have examples of how people connect the API end to end? I want to buy domains, set up inboxes, and push everything into Instantly — all from my own code."
Yes. That's exactly what the Winnr REST API is built for. The whole pipeline — from typing a list of desired domains to having warmed mailboxes loaded inside Instantly — is four API calls plus a polling loop. No DNS panel clicks, no spreadsheets of SMTP credentials, no manual CSV editing.
This guide is the runnable version of that workflow. Every code block in this article is copy-paste ready. By the end, you'll have a single Python script that takes a list of desired domain names and ends with a CSV uploaded to Instantly.
Why automate the full pipeline?
You can do all of this in the Winnr dashboard with mouse clicks. So why automate it?
Scale. Provisioning 5 domains by hand is fine. Provisioning 50 for a new agency client is a slow afternoon of clicking. Provisioning 500 for a mature agency or a SaaS rollout is impossible without automation. The same API calls that handle 5 domains handle 500 with a loop.
Reproducibility. When the setup lives in code, it's checked into git. When the setup lives in a person's memory of dashboard clicks, it's a liability. Code can be reviewed, replayed, and audited — manual setup can't.
Embedding. If you're building a product on top of Winnr — an AI SDR tool, a sequencer, an agency dashboard, an internal tool — your users never touch the Winnr dashboard at all. They click a button in your product, your backend calls four Winnr APIs, and infrastructure appears. (We covered the embed pattern in detail in How to Embed Email Infrastructure in Your App.)
Reliability. Manual workflows skip steps. Did you remember to enable warming? Did you actually export the credentials before tearing down the dashboard tab? Code doesn't forget. The pipeline either runs to completion or it errors out somewhere you can see.
The four-call architecture
Here's the entire pipeline at a glance:
POST /v1/domains/search-bulk
Check availability and pricing for up to 100 candidate domains
POST /v1/domains/purchase
Buy domains via Stripe, register them at the registrar, configure DNS, and create mailboxes — all in one call. Returns a job_id per domain.
POST /v1/warming/enable
Start the 14 to 21 day automated warmup so mailboxes build sending reputation before going live
POST /v1/export with format: "instantly"
Get a presigned CSV download URL, formatted for Instantly's bulk import — ready to upload
Plus one polling helper: GET /v1/jobs/{job_id} for tracking the async provisioning between steps 2 and 3.
That's the whole API surface for end-to-end setup. Everything else — reading inbox, sending email, deleting mailboxes, swapping domains — is for after the pipeline runs.
Step 1: Get an API token
Every call needs a Bearer token. The fastest path: log into the Winnr dashboard, go to Settings → API Tokens, and click "Create Token". Save the value — the full token is shown once.
Token format: wnr_{account_id}_{secret}. Store it in your environment, never in source code.
export WINNR_TOKEN="wnr_acct123_sk_live_..."
If you'd rather create tokens programmatically (useful for reseller integrations that mint child-account tokens), there's a token endpoint:
curl -X POST https://api.winnr.app/v1/auth/tokens \
-H "Authorization: Bearer $WINNR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "automation-pipeline", "permissions": ["read", "write"]}'
Step 2: Find available domains
Before buying, check availability and pricing in bulk. The search endpoint accepts up to 100 domains per call:
curl -X POST https://api.winnr.app/v1/domains/search-bulk \
-H "Authorization: Bearer $WINNR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"domains": [
"trywinnr-co.com",
"get-winnr.com",
"winnr-mail.com",
"winnr-outreach.io",
"winnr-direct.co"
]
}'
Response (truncated):
{
"results": [
{"domain": "trywinnr-co.com", "available": true, "price": 9.99, "tld": "com"},
{"domain": "get-winnr.com", "available": false, "price": null, "tld": "com"},
{"domain": "winnr-mail.com", "available": true, "price": 9.99, "tld": "com"},
{"domain": "winnr-outreach.io", "available": true, "price": 39.99, "tld": "io"},
{"domain": "winnr-direct.co", "available": true, "price": 24.99, "tld": "co"}
]
}
Filter the response down to available: true domains and you have your purchase list. If you want help generating candidate domains in the first place, there's also GET /v1/domains/suggest?keyword=acme which returns AI-generated suggestions based on a brand keyword.
The cheapest TLDs for cold email are .com, .net, .org, and .co (typically SEO–SEO/year). Avoid .xyz, .click, .top, and other novelty TLDs — they have terrible deliverability reputation. We covered this in detail in Best TLDs for Cold Email.
Step 3: Buy domains and queue inbox setup (one call)
This is the call that does the most work. POST /v1/domains/purchase charges your card via Stripe, registers each domain at the registrar, configures DNS (MX, SPF, DKIM, DMARC), and creates the mailboxes you specify — all in one synchronous request. The response gives you a job_id per domain to track the async provisioning that runs after the payment clears.
The trick is the users array nested inside each domain entry. List the mailboxes you want and they're created as part of the same workflow — you don't need a separate /v1/email-users/bulk call.
curl -X POST https://api.winnr.app/v1/domains/purchase \
-H "Authorization: Bearer $WINNR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"domains": [
{
"domain": "trywinnr-co.com",
"price": 9.99,
"register": true,
"setup_dns": true,
"setup_email": true,
"users": [
{"name": "Sarah Chen", "username": "sarah"},
{"name": "James Rivera", "username": "james"},
{"name": "Maria Santos", "username": "maria"}
]
},
{
"domain": "winnr-mail.com",
"price": 9.99,
"register": true,
"setup_dns": true,
"setup_email": true,
"users": [
{"name": "Alex Kim", "username": "alex"},
{"name": "David Park", "username": "david"},
{"name": "Priya Singh", "username": "priya"}
]
}
]
}'
Response:
{
"payment": {
"success": true,
"invoice_id": "in_1QrX...",
"amount_paid": 19.98
},
"domains": [
{"domain": "trywinnr-co.com", "job_id": "job_abc", "status": "queued"},
{"domain": "winnr-mail.com", "job_id": "job_def", "status": "queued"}
]
}
Two domains, six mailboxes, registered and queued for full provisioning — SEO charged, payment confirmed, jobs running. Now you wait for the jobs to finish.
If you call /v1/domains/purchase twice with the same domain list, the second call will not double-charge you. Winnr fingerprints each purchase and skips domains that already have payment_status: "paid". Safe to retry on network errors without burning money.
Already own the domains? Use register: false
If you already own the domains (BYOD — bring your own domains) and just want Winnr to host the mailboxes, set register: false. You won't be charged the registration fee, and you'll need to point nameservers (or provide a Cloudflare token) to delegate DNS.
{
"domains": [
{
"domain": "outreach-acmecorp.com",
"price": 0,
"register": false,
"setup_dns": true,
"setup_email": true,
"users": [...]
}
]
}
Step 4: Poll jobs until provisioning completes
Each domain in the purchase response comes with its own job_id. Poll GET /v1/jobs/{job_id} to track progress. Typical timing:
| Phase | Typical duration | What's happening |
|---|---|---|
| Registration | 10–30 seconds | Domain is purchased at the registrar (Dynadot) |
| DNS setup | 15–45 seconds | MX, SPF, DKIM, DMARC records published |
| Mailbox creation | 20–60 seconds | Mailcow accounts provisioned with passwords and IMAP/SMTP credentials |
| Total per domain | ~60–120 seconds | End to end, fully ready to send |
The job response shape:
curl https://api.winnr.app/v1/jobs/job_abc \
-H "Authorization: Bearer $WINNR_TOKEN"
# Response
{
"id": "job_abc",
"type": "domain_setup",
"status": "completed",
"progress": 100,
"message": "Domain trywinnr-co.com provisioned successfully",
"resource_id": "dom_xyz",
"created_at": "2026-04-20T14:00:00Z",
"completed_at": "2026-04-20T14:01:18Z"
}
A reasonable polling pattern: check every 5 seconds for the first 30 seconds, then back off to every 15 seconds. Most jobs complete in under 90 seconds.
Step 5: Enable warming
Don't skip this step. Sending cold email from a brand-new mailbox with zero sending reputation is the fastest way to land in spam — or worse, get the domain blacklisted before it's even useful. Warming sends low-volume, gradually scaling email to a peer network of real Winnr inboxes that mark messages as important, reply, and move them out of spam — building real engagement signals over 14 to 21 days.
curl -X POST https://api.winnr.app/v1/warming/enable \
-H "Authorization: Bearer $WINNR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"domains": ["trywinnr-co.com", "winnr-mail.com"]
}'
You can also pass a user_ids array to enable on specific mailboxes, or use POST /v1/warming/enable-async if you're enabling on hundreds of mailboxes at once and want to avoid hitting Lambda timeouts. The async variant returns a job_id you poll the same way as Step 4.
For background on why warming matters and how Winnr's warming pool works, read What is email warming and why does it matter and Winnr warming vs sequencer warming.
Step 6: Export to Instantly
This is the step nobody else's API gets right. Winnr produces a CSV in Instantly's exact column format — the same one Instantly's bulk import expects, including Daily Limit, Warmup Enabled, and per-mailbox SMTP/IMAP credentials.
curl -X POST https://api.winnr.app/v1/export \
-H "Authorization: Bearer $WINNR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"format": "instantly",
"domains": ["trywinnr-co.com", "winnr-mail.com"]
}'
Response:
{
"data": {
"download_url": "https://winnr-exports.s3.amazonaws.com/...?X-Amz-...",
"count": 6,
"format": "instantly"
}
}
The download_url is a presigned S3 URL valid for 7 days. Download the CSV and the columns line up with Instantly's bulk upload exactly:
Email,First Name,Last Name,IMAP Username,IMAP Password,IMAP Host,IMAP Port,
SMTP Username,SMTP Password,SMTP Host,SMTP Port,Daily Limit,
Warmup Enabled,Warmup Limit,Warmup Increment
sarah@trywinnr-co.com,Sarah,Chen,sarah@trywinnr-co.com,xY7q...,mail.winnr.app,993,
sarah@trywinnr-co.com,xY7q...,mail.winnr.app,587,30,TRUE,30,2
james@trywinnr-co.com,James,Rivera,...
Drop this file into Instantly's "Add accounts" → "Bulk upload (CSV)" flow. All accounts connect, daily limits are pre-set, and warmup is flagged enabled. Done.
The full end-to-end Python script
Here's the entire pipeline as a single, runnable Python file. Set WINNR_TOKEN in your environment, edit the DESIRED_DOMAINS and USERS_PER_DOMAIN constants, and run it.
"""
buy_provision_warm_export.py
End-to-end Winnr API pipeline:
1. Search candidate domains
2. Purchase available ones + provision mailboxes in one call
3. Poll jobs until provisioning completes
4. Enable warming
5. Export an Instantly-ready CSV
"""
import os
import time
import requests
API = "https://api.winnr.app/v1"
TOKEN = os.environ["WINNR_TOKEN"]
HEADERS = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json",
}
DESIRED_DOMAINS = [
"trywinnr-co.com",
"get-winnr.com",
"winnr-mail.com",
"winnr-outreach.io",
"winnr-direct.co",
]
USERS_PER_DOMAIN = [
{"name": "Sarah Chen", "username": "sarah"},
{"name": "James Rivera", "username": "james"},
{"name": "Maria Santos", "username": "maria"},
]
def search_available(domains):
r = requests.post(f"{API}/domains/search-bulk", headers=HEADERS,
json={"domains": domains})
r.raise_for_status()
return [d for d in r.json()["results"] if d["available"]]
def purchase_and_provision(available):
payload = {
"domains": [
{
"domain": d["domain"],
"price": d["price"],
"register": True,
"setup_dns": True,
"setup_email": True,
"users": USERS_PER_DOMAIN,
}
for d in available
]
}
r = requests.post(f"{API}/domains/purchase", headers=HEADERS, json=payload)
r.raise_for_status()
body = r.json()
print(f" Charged ${body['payment']['amount_paid']} via Stripe")
return [(d["domain"], d["job_id"]) for d in body["domains"]]
def wait_for_jobs(domain_jobs, timeout=300):
pending = dict(domain_jobs)
start = time.time()
interval = 5
while pending and time.time() - start < timeout:
for domain, job_id in list(pending.items()):
r = requests.get(f"{API}/jobs/{job_id}", headers=HEADERS)
status = r.json()["status"]
if status == "completed":
print(f" ✓ {domain} ready")
pending.pop(domain)
elif status == "failed":
raise RuntimeError(f"{domain} failed: {r.json().get('message')}")
if pending:
time.sleep(interval)
interval = min(interval + 2, 15)
if pending:
raise TimeoutError(f"Still pending: {list(pending)}")
def enable_warming(domains):
r = requests.post(f"{API}/warming/enable", headers=HEADERS,
json={"domains": domains})
r.raise_for_status()
def export_for_instantly(domains):
r = requests.post(f"{API}/export", headers=HEADERS,
json={"format": "instantly", "domains": domains})
r.raise_for_status()
data = r.json()["data"]
print(f" Exported {data['count']} mailboxes")
return data["download_url"]
if __name__ == "__main__":
print("1/5 Searching for available domains...")
available = search_available(DESIRED_DOMAINS)
print(f" {len(available)} available out of {len(DESIRED_DOMAINS)}")
print("2/5 Purchasing + provisioning...")
domain_jobs = purchase_and_provision(available)
print("3/5 Waiting for provisioning to complete...")
wait_for_jobs(domain_jobs)
domain_names = [d for d, _ in domain_jobs]
print("4/5 Enabling warming...")
enable_warming(domain_names)
print("5/5 Exporting Instantly CSV...")
csv_url = export_for_instantly(domain_names)
print(f"\nDone. Download CSV: {csv_url}")
print("Upload it via Instantly → Email Accounts → Bulk Upload.")
Run it:
$ export WINNR_TOKEN="wnr_acct123_sk_live_..."
$ python buy_provision_warm_export.py
1/5 Searching for available domains...
4 available out of 5
2/5 Purchasing + provisioning...
Charged $59.96 via Stripe
3/5 Waiting for provisioning to complete...
✓ trywinnr-co.com ready
✓ winnr-mail.com ready
✓ winnr-outreach.io ready
✓ winnr-direct.co ready
4/5 Enabling warming...
5/5 Exporting Instantly CSV...
Exported 12 mailboxes
Done. Download CSV: https://winnr-exports.s3.amazonaws.com/...
Upload it via Instantly → Email Accounts → Bulk Upload.
Active developer time after writing the script the first time: about 30 seconds (run it). Wall-clock time: 2 to 4 minutes.
Other sequencer formats (Smartlead, Reply, etc.)
The format parameter on /v1/export determines the column layout. Winnr supports 20 sequencer formats out of the box — if your sequencer is in this list, the CSV will import without any column mapping.
| Sequencer | format value |
Notes |
|---|---|---|
| Instantly | instantly | Includes Daily Limit, Warmup Enabled flags |
| Smartlead | smartlead | Default format, also matches Smartlead bulk import |
| Reply.io | reply.io | Reply-specific column names |
| Snov.io | snov | |
| Saleshandy | saleshandy | |
| EmailBison | emailbison | |
| Woodpecker | woodpecker | |
| Lemlist / Mailshake | mailshake | |
| Salesforge | salesforge | |
| ManyReach | manyreach | |
| Reachinbox | reachinbox | |
| Smartreach | smartreach | |
| Plusvibe | plusvibe | |
| Warmy | warmy | |
| Masterinbox | masterinbox | |
| Leadengine | leadengine | |
| Maillead | maillead | |
| AICloser | aicloser | |
| Sendkit | sendkit | |
| Generic CSV | default | Standard 12-column SMTP/IMAP layout |
For the full current list, call GET /v1/export/formats. Need a sequencer that isn't here? Email info@winnr.app — we add new formats on request, usually within a few days.
Real-world patterns: agencies, AI SDR, sequencer platforms
Lead-gen agencies
The most common pattern. An agency takes on a new client, runs buy_provision_warm_export.py with the client's brand keyword, and 5 minutes later has a CSV ready to drop into Instantly under the client's workspace. Multiply by 50 clients and you've replaced an FTE worth of manual provisioning work.
The variation: agencies usually want sub-account isolation per client. For that, layer the reseller API on top — create a Winnr sub-account per client with POST /v1/reseller/accounts, then run the four-call pipeline scoped to that sub-account's token.
AI SDR platforms
AI SDR products call this same pipeline when a user signs up, but they don't stop at the export — they keep the credentials inside their own database and have their AI agent send through the mailboxes via Winnr's POST /v1/email-users/{id}/inbox/send endpoint and read replies via GET /v1/email-users/{id}/inbox. Instantly never enters the picture; the agent is the sequencer. See How to build an AI SDR pipeline with Winnr for the deeper agent integration.
Sequencer platforms
Sequencers like Instantly, Smartlead, and Lemlist are increasingly building "managed infrastructure" tiers where the user clicks a button and warmed mailboxes appear. Under the hood, the same four Winnr API calls do the work — the sequencer never has to operate Mailcow servers, manage DKIM keys, or chase Google Workspace suspension support. (See Dedicated IP: Smartlead vs Winnr for the architectural comparison.)
Internal automations
Not every integration is a SaaS product. Plenty of teams use this exact script as an internal cron job: every Monday at 9am, top up the warming pool by buying N new domains for outbound campaigns scheduled to start in 3 weeks. The pipeline runs unattended, results land in Slack via webhook, and the cold email engine never runs out of warmed inventory.
Gotchas and best practices
Wait the full 14–21 days before sending cold email. The CSV is ready to import the moment provisioning completes, but the mailboxes are not ready to send. Importing into Instantly day-of is fine — just don't activate cold campaigns until warming has built reputation. Use Instantly's "warmup only" mode for the first 3 weeks. The cold email without warmup article explains why this matters.
Use register: true only when you actually want Winnr to buy the domain. If you already own the domain at a different registrar, set register: false and you won't be charged a registration fee. Winnr will just provision DNS (via nameserver delegation or your Cloudflare token) and create mailboxes.
Retry safely. The purchase endpoint is idempotent — if the same domain list is already paid for, the second call returns the existing setup without charging again. Wrap the script in a retry on 5xx errors and you'll never double-pay.
Spread mailboxes across domains. Don't put 30 mailboxes on one domain. Best practice for cold email is 3 mailboxes per domain. Use 10 domains for 30 mailboxes, not 1. The USERS_PER_DOMAIN constant in the script enforces this if you keep it at 3.
Generate realistic usernames. Use POST /v1/email-users/generate-names if you need plausible names without hardcoding them. Avoid generic patterns like user1, user2, info, contact, noreply — these are spam tells.
Poll, don't sleep blindly. Don't time.sleep(120) and assume jobs are done. Poll /v1/jobs/{id} until status is completed. Some domains finish in 30 seconds, some take 90, and the export step needs every mailbox to actually exist.
Store the Winnr IDs. Save domain_id and email user IDs in your own database. You'll need them for warming controls, sending, and eventual deletion when a customer churns.
If you're using Claude Code, Cursor, or another AI coding assistant, the same operations are exposed as MCP tools. You can ask "buy 5 cold email domains for client Acme, set up 3 mailboxes per domain, enable warming, and export to Instantly" — and the AI calls the underlying API directly. See the Winnr MCP server docs for the 37 available tools.
Frequently asked questions
How much does API access cost?
API access is included in every Winnr plan at no extra cost. You pay for domains and mailboxes at the same per-account rate as if you provisioned them through the dashboard. There are no per-call API fees, no minimum commitments, and no separate API tier. Volume discounts are available for reseller and enterprise accounts. Check the pricing page for current rates.
Can I really buy domains, set up inboxes, and export to Instantly with one script?
Yes — that's the script in the full end-to-end Python script section above. The whole pipeline is four API calls plus a polling loop. Active developer time after the first write is about 30 seconds; total wall-clock time is 2 to 5 minutes for provisioning.
How long until the mailboxes are usable in Instantly?
The mailboxes are connectable in Instantly the moment provisioning completes — usually within 2 minutes of the purchase API call. They are ready to send cold email after the 14 to 21 day warming period. Import them into Instantly immediately so warmup runs alongside Instantly's own activity, but don't activate outbound campaigns until warming completes.
Does Winnr's CSV format actually work with Instantly's bulk upload?
Yes, exactly. The CSV columns match Instantly's bulk upload schema 1:1, including Email, First Name, Last Name, IMAP/SMTP credentials, Daily Limit, Warmup Enabled, Warmup Limit, and Warmup Increment. No column mapping required. Drop the file into Instantly → Email Accounts → Bulk Upload and all accounts connect on the first try.
What if I want to use Smartlead instead of Instantly?
Pass "format": "smartlead" instead of "format": "instantly" on the export call. Same workflow, different column layout. Winnr supports 20 sequencer formats — see the sequencer formats table for the full list. There's also a default format with standard SMTP/IMAP columns that works with most tools.
Can I bring my own domains instead of buying through Winnr?
Yes. Set register: false on the purchase request and you won't be charged the registration fee. You'll either delegate nameservers to Winnr or provide a Cloudflare API token so Winnr can write DNS records into your existing Cloudflare zone. The rest of the pipeline (mailbox creation, warming, export) is identical.
What happens if the purchase API call fails partway through?
The purchase endpoint is idempotent. If Stripe charged your card but the network dropped before you got the response, calling the same endpoint again will not double-charge — Winnr fingerprints each purchase and returns the existing job IDs for already-paid domains. Safe to retry on any 5xx error or timeout.
Can I do this from Node.js / TypeScript / Go / Ruby?
Yes — the Winnr API is plain REST, so any language with an HTTP client works. The Python examples in this guide translate line-for-line to fetch in Node, net/http in Go, or Net::HTTP in Ruby. There are no language-specific SDKs you need to install.
What's the difference between this and using the Winnr MCP server?
The MCP server wraps the same REST API in a tool-calling protocol designed for AI agents (Claude, Cursor, Windsurf). If you're writing application code, use the REST API directly — it's simpler. If you're driving the workflow from an AI assistant in natural language ("buy 5 domains for client Acme and export to Instantly"), use the MCP server. Both hit the same endpoints. See winnr.app/mcp for the MCP details.
Where's the full API reference?
The complete OpenAPI spec lives at app.winnr.app/docs. Every endpoint, request schema, response shape, and error code is documented there. This guide covers the four endpoints you need for end-to-end provisioning — the docs cover the full surface (50+ endpoints including inbox, sending, warming controls, reseller, affiliate, and more).
Ready to run the pipeline? Sign up for a Winnr account, generate an API token, and the script in this article runs unmodified. The full API reference covers every endpoint if you need to go beyond the four-call setup.