{"openapi":"3.0.0","info":{"title":"Cheap Inboxes API","version":"1.0.0","description":"Programmatic access to your Cheap Inboxes organization. Search and register domains, provision Google Workspace and Microsoft 365 mailboxes, manage DNS, connect sending-platform integrations, and handle billing — all through a single REST API.\n\n## Authentication\n\nEvery request must include your API key as a Bearer token:\n\n```\nAuthorization: Bearer ci_live_...\n```\n\nAPI keys are scoped to a single organization. Create and manage them from the Cheap Inboxes dashboard under **Integrations → API**. Treat keys like passwords — store them in environment variables, never commit them to source control.\n\n## Credential handling\n\nSome endpoints exchange secrets — sending-platform API keys and mailbox passwords go in on the way through, mailbox credentials and TOTP secrets come back out. Every authenticated request is audit-logged, but credential-bearing fields are masked in the request body, response body and query string before that log is written, so those secrets are never stored in plaintext.\n\nThe request path itself is logged verbatim, so never put a secret in a URL — pass it in the JSON body. Send over HTTPS only, and rotate anything you believe has been exposed.\n\n## Rate Limits\n\nAll endpoints are rate-limited to **120 requests per minute** per API key. Response headers include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` so you can throttle proactively.\n\n## Errors\n\nErrors return a consistent JSON envelope:\n\n```json\n{ \"error\": { \"code\": \"VALIDATION_ERROR\", \"message\": \"...\" } }\n```\n\nCommon codes: `VALIDATION_ERROR` (400), `UNAUTHORIZED` (401), `NOT_FOUND` (404), `RATE_LIMIT_EXCEEDED` (429), `INTERNAL_ERROR` (500).\n\n## Webhooks\n\nSubscribe to lifecycle events (domain provisioned, mailbox active, credentials ready, etc.) via the Webhooks endpoints. Each delivery is signed with HMAC-SHA256 so you can verify authenticity."},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"Use your API key as a Bearer token: `Authorization: Bearer ci_live_...`"}},"schemas":{}},"paths":{"/v1/org":{"get":{"summary":"Get organization details","tags":["Organization"],"description":"Returns your organization's profile including name, plan, member count, and configuration settings. Use this to verify your API key is working or to display org-level information in your integration.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"organization":{"id":"550e8400-e29b-41d4-a716-446655440000","name":"Acme Outreach","billing_email":"billing@acme.com","stripe_customer_id":"cus_abc123","status":"active","timezone":"UTC","currency":"usd","created_at":"2025-06-15T10:00:00.000Z","billing_name":"Acme Inc","billing_company":"Acme Inc","billing_address_line1":"123 Main Street","billing_city":"New York","billing_state":"NY","billing_country":"US","billing_zip":"10001"},"role":"owner"}}}}}}},"patch":{"summary":"Update organization settings","tags":["Organization"],"description":"Update organization-level settings such as display name. Only organization admins can modify these settings.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"New organization display name."}},"additionalProperties":false}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"organization":{"id":"550e8400-e29b-41d4-a716-446655440000","name":"Acme Outreach","billing_email":"billing@acme.com","stripe_customer_id":"cus_abc123","status":"active","timezone":"UTC","currency":"usd","created_at":"2025-06-15T10:00:00.000Z","billing_name":"Acme Inc","billing_company":"Acme Inc","billing_address_line1":"123 Main Street","billing_city":"New York","billing_state":"NY","billing_country":"US","billing_zip":"10001"},"role":"owner"}}}}}}}},"/v1/domains":{"get":{"summary":"List all domains","tags":["Domains"],"description":"Returns all domains in your organization with their current status (provisioning, active, error), provider (Google/Microsoft), DNS configuration state, forwarding URLs, and tags. Supports query parameters for filtering by status, tags, and pagination. Pass `tags` as a comma-separated list to match domains carrying ANY of them.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"domains":[{"id":"d_abc123","domain":"acmeoutreach.com","status":"active","source_provider":"cheapinboxes","dns_mode":"nameservers","infra_provider":"google","auto_renew":true,"forwarding_url":"https://acme.com","tags":["campaign-1"],"created_at":"2025-06-15T10:00:00.000Z"},{"id":"d_def456","domain":"acmeimported.com","status":"pending_manual_setup","source_provider":"external","dns_mode":"cloudflare_connect","cloudflare_connected":false,"infra_provider":"google","created_at":"2025-06-16T10:00:00.000Z"}],"pagination":{"total":42,"limit":25,"offset":0}}}}}}}}},"/v1/domains/tags":{"get":{"summary":"List domain tags","tags":["Domains"],"description":"Returns every tag currently in use on your domains. Useful for building a filter UI or reconciling your own tag taxonomy before a bulk update.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"tags":["campaign-1","client-acme","retired"]}}}}}}}},"/v1/domains/bulk-tags":{"post":{"summary":"Bulk update domain tags","tags":["Domains"],"description":"Add or remove tags across multiple domains in a single call. Tags are useful for grouping domains by campaign, client, or lifecycle state — for example marking retired domains so they stay out of your filters. Pass `domain_ids` (array of UUIDs) and at least one of `add_tags` or `remove_tags` (arrays of tag strings). All array fields must be real JSON arrays, not stringified ones. The add/remove is merged server-side per domain, so existing tags are preserved. A single call covers at most 5,000 domains — larger requests are rejected with `BULK_TAG_CAP_EXCEEDED` rather than partially applied, so page through them yourself. The response reports `updated` (domains whose tag list actually changed), `matched` (ids that resolved to a visible, non-archived domain) and `failed`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["domain_ids"],"properties":{"domain_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Domains to update."},"add_tags":{"type":"array","items":{"type":"string"},"description":"Tags to add to each domain (deduped)."},"remove_tags":{"type":"array","items":{"type":"string"},"description":"Tags to remove from each domain."}},"additionalProperties":false}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"updated":64,"failed":0,"matched":64}}}}}}}},"/v1/domains/bulk-forwarding":{"post":{"summary":"Bulk update forwarding URLs","tags":["Domains"],"description":"Set the website forwarding URL for multiple domains at once. When someone visits your domain in a browser, they'll be redirected to this URL. Pass `domain_ids` as a real JSON array of UUIDs, plus `forwarding_url` (or null to clear) and optional `permanent` flag for 301 vs 302 redirects.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["domain_ids"],"properties":{"domain_ids":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":200,"description":"Domain UUIDs to update. Pass as a real JSON array, not a stringified one."},"forwarding_url":{"type":"string","nullable":true,"description":"Target URL to redirect to. Pass null to clear forwarding."},"permanent":{"type":"boolean","description":"If true, use a 301 (permanent) redirect; otherwise 302 (temporary)."}},"additionalProperties":false}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"updated":5}}}}}}}},"/v1/domains/{id}":{"get":{"summary":"Get domain by ID","tags":["Domains"],"description":"Returns full details for a single domain including DNS configuration, DKIM/SPF verification status, mailbox count, and provisioning history. Use this to check if a domain is fully configured and ready for sending.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"domain":{"id":"d_abc123","domain":"acmeoutreach.com","status":"active","setup_state":null,"provisioning_error":null,"source_provider":"cheapinboxes","dns_mode":"nameservers","infra_provider":"google","auto_renew":true,"forwarding_url":"https://acme.com","forwarding_permanent":true,"forwarding_status":"active","created_at":"2025-06-15T10:00:00.000Z"}}}}}}}},"patch":{"summary":"Update domain settings","tags":["Domains"],"description":"Update domain-level settings: `tags` and `auto_renew`. Tags are useful for grouping domains by campaign, client, or lifecycle state, and can be filtered on via `GET /v1/domains?tags=`. Passing `tags` replaces the full list — use `POST /v1/domains/bulk-tags` to add or remove without replacing.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"},"description":"Replace the full tag list. Pass as a real JSON array, not a stringified one. Tags are trimmed, lowercased and deduped."},"auto_renew":{"type":"boolean","description":"Enable or disable automatic renewal at the registrar."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"domain":{"id":"d_abc123","domain":"acmeoutreach.com","status":"active","tags":["campaign-2"]}}}}}}}}},"/v1/domains/{id}/runs":{"get":{"summary":"Provisioning run history","tags":["Domains"],"description":"Returns the automation run history for a domain — each provisioning step (DNS setup, DKIM verification, mailbox creation) with timestamps, status, and error details. Use this to debug stuck or failed provisioning.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"runs":[{"id":"run_001","domain_id":"d_abc123","status":"completed","step":"dns_setup","started_at":"2025-06-15T10:05:00.000Z","completed_at":"2025-06-15T10:06:00.000Z","error":null}]}}}}}}}},"/v1/domains/{id}/forwarding":{"patch":{"summary":"Update forwarding URL","tags":["Domains"],"description":"Change where visitors are redirected when they navigate to this domain in a browser. The domain must be fully provisioned before forwarding can be configured.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"forwarding_url":{"type":"string","nullable":true,"description":"Target URL to redirect to. Pass null to clear forwarding."},"permanent":{"type":"boolean","description":"If true, use a 301 (permanent) redirect; otherwise 302 (temporary)."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"domain":{"id":"d_abc123","forwarding_url":"https://acme.com","forwarding_status":"active"}}}}}}}}},"/v1/domains/{id}/dns-records":{"get":{"summary":"List DNS records","tags":["Domains"],"description":"Returns all DNS records configured for this domain, including MX, TXT (SPF/DKIM/DMARC), and CNAME records. The domain must be fully provisioned. Use this to audit DNS configuration or verify deliverability settings.","parameters":[{"schema":{"type":"string","enum":["A","AAAA","CNAME","TXT","MX","NS","SRV"]},"in":"query","name":"type","required":false,"description":"Optional filter by DNS record type."},{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"records":[{"id":"rec_001","type":"MX","name":"acmeoutreach.com","content":"aspmx.l.google.com","priority":1,"ttl":3600,"proxied":false},{"id":"rec_002","type":"TXT","name":"acmeoutreach.com","content":"v=spf1 include:_spf.google.com ~all","ttl":3600,"proxied":false}]}}}}}}},"post":{"summary":"Create DNS record","tags":["Domains"],"description":"Add a custom DNS record to this domain. Useful for adding tracking CNAME records, custom TXT records, or additional MX entries. Records that conflict with system-managed patterns (default MX, SPF, DKIM, etc.) are rejected.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["type","name","content"],"properties":{"type":{"type":"string","enum":["A","AAAA","CNAME","TXT","MX","NS","SRV"],"description":"DNS record type."},"name":{"type":"string","minLength":1,"description":"Record name (subdomain or @ for apex)."},"content":{"type":"string","minLength":1,"description":"Record value (IP, hostname, text, etc.)."},"ttl":{"type":"number","description":"TTL in seconds. Default 3600."},"priority":{"type":"number","description":"Priority for MX/SRV records."},"proxied":{"type":"boolean","description":"Whether the record is proxied by the DNS provider."}},"additionalProperties":false}}},"required":true},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"record":{"id":"rec_003","type":"TXT","name":"acmeoutreach.com","content":"v=verify123","ttl":3600}}}}}}}}},"/v1/domains/{id}/dns-records/{recordId}":{"patch":{"summary":"Update DNS record","tags":["Domains"],"description":"Modify an existing DNS record. Use this to update record values, TTL, or proxy status. Get the record ID from the List DNS Records endpoint. System-managed records (default MX, SPF, DKIM, etc.) cannot be updated.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["A","AAAA","CNAME","TXT","MX","NS","SRV"],"description":"DNS record type."},"name":{"type":"string","minLength":1,"description":"Record name (subdomain or @ for apex)."},"content":{"type":"string","minLength":1,"description":"Record value (IP, hostname, text, etc.)."},"ttl":{"type":"number","description":"TTL in seconds."},"priority":{"type":"number","description":"Priority for MX/SRV records."},"proxied":{"type":"boolean","description":"Whether the record is proxied by the DNS provider."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"},{"schema":{"type":"string"},"in":"path","name":"recordId","required":true,"description":"DNS record ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"record":{"id":"rec_003","type":"TXT","name":"acmeoutreach.com","content":"updated-value","ttl":3600}}}}}}}},"delete":{"summary":"Delete DNS record","tags":["Domains"],"description":"Remove a DNS record from this domain. System-managed records (MX, SPF, DKIM) cannot be deleted via this endpoint to protect email delivery.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"},{"schema":{"type":"string"},"in":"path","name":"recordId","required":true,"description":"DNS record ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"success":true}}}}}}}},"/v1/domains/{id}/dmarc":{"get":{"summary":"Get DMARC config","tags":["Domains"],"description":"Returns the current DMARC policy for this domain. DMARC controls how receiving mail servers handle messages that fail SPF/DKIM checks. The domain must be fully provisioned.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"dmarc":{"policy":"none","rua":"mailto:dmarc@acme.com","pct":100,"record":"v=DMARC1; p=none; rua=mailto:dmarc@acme.com"}}}}}}}},"patch":{"summary":"Update DMARC","tags":["Domains"],"description":"Update the DMARC policy for this domain. Recommended to start with `p: \"none\"` (monitoring only) and move to `quarantine` or `reject` once deliverability is confirmed. The DMARC TXT record is created automatically if it does not exist.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["p"],"properties":{"p":{"type":"string","enum":["none","quarantine","reject"],"description":"Main DMARC policy."},"sp":{"type":"string","enum":["none","quarantine","reject"],"description":"Subdomain DMARC policy."},"rua":{"type":"string","description":"Aggregate report URI (e.g. mailto:dmarc@example.com)."},"ruf":{"type":"string","description":"Forensic report URI."},"adkim":{"type":"string","enum":["r","s"],"description":"DKIM alignment mode (r=relaxed, s=strict)."},"aspf":{"type":"string","enum":["r","s"],"description":"SPF alignment mode (r=relaxed, s=strict)."},"pct":{"type":"number","minimum":0,"maximum":100,"description":"Percent of mail to which the policy applies."},"fo":{"type":"string","description":"Failure-reporting options."},"ri":{"type":"number","minimum":0,"description":"Reporting interval in seconds."}},"additionalProperties":false}}},"required":true},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"dmarc":{"policy":"quarantine","pct":100}}}}}}}}},"/v1/domains/{id}/transfer-out":{"post":{"summary":"Initiate domain transfer","tags":["Domains"],"description":"Begin the process of transferring this domain to another registrar. Returns the EPP authorization code needed by the receiving registrar. This is irreversible once the transfer is accepted by the new registrar. No request body required.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"transfer":{"domain":"acmeoutreach.com","epp_code":"aBc123XyZ","status":"initiated"}}}}}}}}},"/v1/domains/{id}/setup/nameservers":{"post":{"summary":"Get nameservers for imported domain","tags":["Domain Imports"],"description":"Assigns and returns nameserver records for you to set at your domain registrar (Namecheap, Porkbun, GoDaddy, etc.). Once you update the nameservers at your registrar and they propagate, provisioning begins automatically. Only available for imported domains. Safe to retry. No request body required.\n\n**Not for Cloudflare Connect domains.** A domain staged with `source_provider: \"cloudflare\"` keeps its zone in your own Cloudflare account and has no nameserver setup to assign — this route returns `CLOUDFLARE_CONNECT_DOMAIN` (400) for those. Call `POST /v1/domains/:id/cloudflare/connect` instead.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"domain":{"id":"dom_abc","domain":"example.com","status":"pending_ns_verification"},"nameservers":["ns1.cheapinboxes.com","ns2.cheapinboxes.com"],"cleanup_due_at":"2025-07-20T12:00:00.000Z"}}}}}}}},"/v1/domains/{id}/cloudflare/connect":{"post":{"summary":"Connect Cloudflare DNS","tags":["Domains"],"description":"Finish setup for a domain staged with `source_provider: \"cloudflare\"` — the path where the zone stays in **your own** Cloudflare account and the nameservers never change.\n\nSupply a scoped Cloudflare API token for the zone. We look the zone up, store the token encrypted, and start provisioning: the MX, SPF, DKIM and DMARC records are written into your existing zone. The domain moves from `pending_manual_setup` to `provisioning`.\n\n**Token permissions.** The token needs **Zone → Read** and **DNS → Edit** on the zone — plus **Page Rules → Edit** only if you also use `PATCH /v1/domains/:id/forwarding`, which is implemented with page rules. Zone Resources may be narrowed to just the zones in play. This is a narrower token than the account-wide one used when adding a Cloudflare account.\n\n**The zone must already exist in your Cloudflare account.** This endpoint connects an existing zone; it does not create one. If the domain was delegated to our nameservers instead, there is nothing on your side to connect and you should stay on the nameserver path.\n\nTo connect every domain on an order in one call, use `POST /v1/orders/:id/cloudflare/connect` instead.\n\nErrors: `DOMAIN_NOT_FOUND` (404), `CHECKOUT_REQUIRED` (400, domain not purchased yet), `INVALID_DOMAIN_SOURCE` (400, domain was bought from us so it is already on our DNS), `INVALID_API_TOKEN` (400), `CLOUDFLARE_AUTH_FAILED` (400, token invalid or missing zone permissions), `CLOUDFLARE_TOKEN_VALIDATION_FAILED` (400, Cloudflare could not be reached or rejected the lookup — retryable), `CLOUDFLARE_ZONE_NOT_FOUND` (400, no zone for this domain in the token's account), `CLOUDFLARE_LINKAGE_CONFLICT` (409).","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["api_token"],"properties":{"api_token":{"type":"string","minLength":10,"description":"Scoped Cloudflare API token with Zone:Read and DNS:Edit on this domain's zone. Stored encrypted; redacted from audit logs."}},"additionalProperties":false}}},"required":true},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"domain":{"id":"dom_abc","domain":"acme.com","status":"provisioning","source_provider":"external","dns_mode":"cloudflare_connect","cloudflare_connected":true},"zone":{"zoneId":"0123456789abcdef0123456789abcdef","nameservers":["gina.ns.cloudflare.com","rick.ns.cloudflare.com"],"status":"active"},"run_id":"run_abc123"}}}}}}}},"/v1/domains/{id}/request-dfy":{"post":{"summary":"Request Done-For-You DNS setup","tags":["Domain Imports"],"description":"Request our team to handle DNS configuration for this imported domain. Use this when you prefer not to manage nameservers yourself. Our team will handle the setup and notify you when complete. No request body required.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Domain ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"success":true,"message":"DFY setup request submitted. Our team will handle configuration.","domain":{"id":"dom_abc","domain":"example.com","status":"pending_manual_setup"}}}}}}}}},"/v1/mailboxes":{"get":{"summary":"List all mailboxes","tags":["Mailboxes"],"description":"Returns all mailboxes in your organization with their status, email address, assigned domain, persona details, tags, and integration sync state. Supports query parameters for filtering by status, domain, tags, and pagination.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"mailboxes":[{"id":"mb_abc123","domain_id":"d_abc123","full_email":"emma.johnson@acmeoutreach.com","first_name":"Emma","last_name":"Johnson","profile_picture_url":null,"status":"active","source_provider":"google","daily_limit":50,"tags":["campaign-1"],"target_integration_id":"int_abc123","created_at":"2025-06-15T10:00:00.000Z","activated_at":"2025-06-15T12:00:00.000Z"}],"pagination":{"total":120,"limit":25,"offset":0}}}}}}}}},"/v1/mailboxes/bulk-tags":{"post":{"summary":"Bulk update tags","tags":["Mailboxes"],"description":"Add or remove tags from multiple mailboxes in a single call. Tags are useful for organizing mailboxes into campaigns, client groups, or warmup cohorts. Pass `mailbox_ids` (array of UUIDs), and at least one of `add_tags` or `remove_tags` (arrays of tag strings). All array fields must be real JSON arrays, not stringified ones. The add/remove is merged server-side per mailbox, so existing tags are preserved. A single call covers at most 5,000 mailboxes — larger requests are rejected with `BULK_TAG_CAP_EXCEEDED` rather than partially applied. Tag sync to connected sending platforms runs in the background after the response returns. The response reports `updated` — mailboxes whose tag list actually changed, so re-sending an already-applied tag returns 0 — plus `matched` (ids that resolved to a visible mailbox) and `failed`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["mailbox_ids"],"properties":{"mailbox_ids":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"description":"Mailbox UUIDs to update."},"add_tags":{"type":"array","items":{"type":"string","minLength":1},"description":"Tags to add to each mailbox (deduped)."},"remove_tags":{"type":"array","items":{"type":"string","minLength":1},"description":"Tags to remove from each mailbox."}},"additionalProperties":false}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"updated":5}}}}}}}},"/v1/domains/{domainId}/mailboxes":{"post":{"summary":"Create mailboxes on domain","tags":["Mailboxes"],"description":"Add new mailboxes to an existing, fully provisioned domain. The domain must be active with a completed Google Workspace or Microsoft 365 setup. Pass `count` (how many mailboxes to create, 1-100) and optional `first_name_pattern` / `last_name_pattern` to control the persona names.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["count"],"properties":{"count":{"type":"number","minimum":1,"maximum":100,"description":"Number of mailboxes to create on this domain."},"first_name_pattern":{"type":"string","description":"Optional pattern/seed for generated first names."},"last_name_pattern":{"type":"string","description":"Optional pattern/seed for generated last names."}},"additionalProperties":false}}},"required":true},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"domainId","required":true,"description":"Domain ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"mailboxes":[{"id":"mb_new001","full_email":"olivia.martinez@acmeoutreach.com","first_name":"Olivia","last_name":"Martinez","status":"provisioning"}]}}}}}}}},"/v1/mailboxes/{id}/credentials":{"get":{"summary":"Get mailbox credentials","tags":["Mailboxes"],"description":"Retrieve the login credentials (email and password) for a mailbox. Credentials are only available once the mailbox has been fully provisioned and is in `active` status. Use this to connect mailboxes to sending platforms or for manual login.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Mailbox ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"credentials":{"email":"emma.johnson@acmeoutreach.com","password":"SecureP@ss123!","app_password":"abcd efgh ijkl mnop","imap_host":"imap.gmail.com","imap_port":993,"smtp_host":"smtp.gmail.com","smtp_port":587}}}}}}}}},"/v1/mailboxes/{id}/totp":{"get":{"summary":"Get TOTP codes","tags":["Mailboxes"],"description":"Generate current TOTP (Time-based One-Time Password) codes for a mailbox that has 2FA enabled. Returns the current code, time remaining, and the TOTP secret. Useful for automated logins to Google Workspace or Microsoft 365 admin consoles.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Mailbox ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"totp":{"code":"482901","remaining_seconds":18,"secret":"JBSWY3DPEHPK3PXP"}}}}}}}}},"/v1/mailboxes/{id}/cancel/undo":{"post":{"summary":"Undo cancel","tags":["Mailboxes"],"description":"Reverse a pending cancellation for a mailbox. Only works if the cancellation hasn't been finalized yet (i.e., the mailbox is scheduled for cancellation at the end of the billing period but hasn't been deleted). Restores the mailbox to active billing. No request body required.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Mailbox ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"success":true,"mailbox_id":"mb_abc123","status":"active"}}}}}}}},"/v1/mailboxes/{id}/cancel":{"post":{"summary":"Cancel mailbox","tags":["Mailboxes"],"description":"Schedule a mailbox for cancellation at the end of the current billing period. The mailbox remains active until the period ends, then is deprovisioned. To cancel immediately, use the billing cancel endpoint instead. Cancellation can be undone before the period ends.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","enum":["user_request","non_payment","abuse","other"],"description":"Reason code for cancellation."},"reason_detail":{"type":"string","description":"Free-form detail about the cancellation reason."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Mailbox ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"success":true,"mailbox_id":"mb_abc123","cancellation_effective_at":"2025-08-01T00:00:00.000Z"}}}}}}}},"/v1/mailboxes/{id}/cancellation":{"get":{"summary":"Get cancellation details","tags":["Mailboxes"],"description":"Check if a mailbox has a pending or completed cancellation. Returns the cancellation date, effective date, reason, and whether it can still be undone. Returns empty if the mailbox is not cancelled.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Mailbox ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"cancellation":{"mailbox_id":"mb_abc123","status":"pending","requested_at":"2025-07-15T10:00:00.000Z","effective_at":"2025-08-01T00:00:00.000Z","reason":"No longer needed"}}}}}}}}},"/v1/mailboxes/{id}":{"get":{"summary":"Get mailbox by ID","tags":["Mailboxes"],"description":"Returns full details for a single mailbox including email address, persona (first/last name, profile picture), status, assigned domain, tags, integration sync state, provisioning dates, and credential availability.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Mailbox ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"mailbox":{"id":"mb_abc123","domain_id":"d_abc123","full_email":"emma.johnson@acmeoutreach.com","first_name":"Emma","last_name":"Johnson","profile_picture_url":null,"has_profile_picture":false,"status":"active","source_provider":"google","daily_limit":50,"tags":["campaign-1"],"target_integration_id":"int_abc123","created_at":"2025-06-15T10:00:00.000Z","activated_at":"2025-06-15T12:00:00.000Z","warmup_filtered":false}}}}}}}},"patch":{"summary":"Update a mailbox","tags":["Mailboxes"],"description":"Update a mailbox's mutable fields: status (active/paused), persona details (first name, last name, profile picture URL), tags, or even the email prefix via `full_email`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["active","paused"],"description":"Pause or resume sending for this mailbox."},"profile_picture_url":{"type":"string","nullable":true,"description":"URL to a profile picture image (or null to clear)."},"first_name":{"type":"string","description":"First name for the mailbox persona."},"last_name":{"type":"string","description":"Last name for the mailbox persona."},"full_email":{"type":"string","format":"email","description":"Rename the mailbox by changing the prefix portion (domain stays the same)."},"tags":{"type":"array","items":{"type":"string","minLength":1},"description":"Replace the full tag list. Pass as a real JSON array, not a stringified one."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Mailbox ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"mailbox":{"id":"mb_abc123","first_name":"Emma","last_name":"Johnson","tags":["campaign-2"]}}}}}}}}},"/v1/orders":{"get":{"summary":"List all orders","tags":["Orders"],"description":"Returns all orders for your organization with their status, creation date, cart contents, and pricing breakdown. Orders progress through statuses: `provisioning` → `completed` (or `failed`).","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"orders":[{"id":"ord_abc123","name":"Acme Corp - Q3 batch","status":"completed","created_at":"2025-06-15T10:00:00.000Z","total_cents":4197}]}}}}}}}},"/v1/orders/quote":{"post":{"summary":"Get pricing quote","tags":["Orders"],"description":"Calculate the exact cost for a planned order before committing. Returns an itemized breakdown with domain registration fees, mailbox costs (with volume discounts applied), and the total. Your organization's custom pricing rules and any promo codes are automatically applied.\n\nAccepts the **same domain shape as `POST /v1/orders/checkout`** so you can pass an identical payload to both endpoints. To quote an imported domain at $0, pass `staged_import_id` (or `isExisting: true`) on the domain.\n\nThe response `promo_code_status` field tells you whether a submitted promo code actually applied (`applied`, `not_eligible`, `expired`, or `unknown_code`); when not applied, `promo_code_message` explains why.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"cart":{"type":"object","required":["domains"],"properties":{"domains":{"type":"array","items":{"type":"object","required":["domain","tld","mailboxes"],"properties":{"domain":{"type":"string","description":"Domain name (e.g. \"acmeoutreach.com\")"},"tld":{"type":"string","description":"Top-level domain (e.g. \"com\", \"shop\")"},"isExisting":{"type":"boolean","description":"Set true if you already own the domain (priced at $0)"},"staged_import_id":{"type":"string","format":"uuid","description":"Staged import ID from POST /v1/domain-imports/bulk. Implies isExisting:true."},"mailboxes":{"type":"object","required":["provider","count"],"properties":{"provider":{"type":"string","enum":["google","microsoft"]},"count":{"type":"number"}}}},"additionalProperties":true}}},"additionalProperties":true},"promo_code":{"type":["string","null"],"description":"Promo code to apply. The response will tell you if it actually applied."}},"additionalProperties":true}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"quote":{"promo_code":"BLS250CHPNBX","promo_code_status":"applied","promo_code_message":"Promo code applied.","items":[{"code":"domain_com","description":"Domain: acmeoutreach.com (1 year)","quantity":1,"base_unit_price_cents":999,"final_unit_price_cents":999,"total_cents":999},{"code":"mailbox_monthly_google","description":"Google Workspace Mailbox (First Month)","quantity":3,"base_unit_price_cents":350,"discount_amount_cents":100,"final_unit_price_cents":250,"total_cents":750}],"totals":{"domain_total_cents":999,"mailbox_total_cents":750,"total_today_cents":2049,"mailbox_monthly_cents":750,"mailbox_unit_price_cents":250,"total_google_mailboxes":3,"total_microsoft_mailboxes":0,"total_mailboxes_in_cart":3,"existing_mailboxes_in_org":120,"total_mailboxes_for_pricing":123},"applied_rule_ids":["37ef7bb7-076d-43b7-ac5c-7ef1aafefcb9"]}}}}}}}}},"/v1/orders/checkout":{"post":{"summary":"Place an order","tags":["Orders"],"description":"Purchase domains and/or mailboxes in one call. Supports both **new domain registration** and **imported domains** (domains you already own). For new domains, validates availability, registers via our registrar, and starts provisioning. For imported domains, pass `staged_import_id` (from the Domain Imports endpoints) — the domain is linked to the order without registration and nameserver setup is started automatically when needed. After checkout, poll `GET /v1/domains?order_id=<order_id>` until each imported domain has `nameservers`, then show those nameservers to the customer so they can update their registrar. Charges your default payment method on file. You can mix new and imported domains in a single order.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["domains"],"properties":{"domains":{"type":"array","items":{"type":"object","required":["domain","mailboxes"],"properties":{"domain":{"type":"string","description":"Domain name (e.g. \"acmeoutreach.com\")"},"tld":{"type":"string","description":"Top-level domain (e.g. \"com\", \"shop\"). Auto-detected from domain if omitted."},"staged_import_id":{"type":"string","format":"uuid","description":"Staged import ID (from POST /v1/domain-imports/bulk). When provided, the domain is treated as an import — no registration, pending DNS setup after checkout."},"mailboxes":{"type":"object","required":["provider","accounts"],"properties":{"provider":{"type":"string","enum":["google","microsoft"],"description":"Email provider"},"accounts":{"type":"array","items":{"type":"object","required":["first_name","last_name","email_prefix"],"properties":{"first_name":{"type":"string","description":"First name for the mailbox persona"},"last_name":{"type":"string","description":"Last name for the mailbox persona"},"email_prefix":{"type":"string","description":"Email prefix (before the @). e.g. \"emma.johnson\""},"profile_picture_url":{"type":"string","description":"URL for the profile picture (optional)","nullable":true},"tags":{"type":"array","items":{"type":"string"},"description":"Tags to apply to this mailbox"},"preferred_password":{"type":"string","nullable":true,"description":"Optional per-mailbox password override. Must be 8-100 characters and include at least one lowercase letter, one uppercase letter, and one number. Overrides the order-level preferred_mailbox_password for this account only."}},"additionalProperties":true}}},"additionalProperties":true}},"additionalProperties":true}},"promo_code":{"type":"string","description":"Promo code to apply for discounts","nullable":true},"integration_id":{"type":"string","format":"uuid","description":"Integration ID to auto-connect mailboxes to a sending platform"},"preferred_mailbox_password":{"type":"string","nullable":true,"description":"Optional order-wide default password. Must be 8-100 characters and include at least one lowercase letter, one uppercase letter, and one number. Applied to every mailbox without its own preferred_password override (see mailboxes.accounts[].preferred_password). When neither is set, each mailbox gets an auto-generated secure password. Passwords are stored encrypted and shown in the dashboard once provisioning completes."},"name":{"type":"string","nullable":true,"maxLength":240,"description":"Optional label for this order (max 240 characters), shown wherever the order appears: the customer dashboard, the order list, and the Stripe receipt. Use it to identify a batch — e.g. a client name or campaign. Omit it and the order is labelled \"Order #N\" by its position in your organization."}},"additionalProperties":true}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"order_id":"ord_abc123","name":"Acme Corp - Q3 batch","status":"provisioning","total_charged_cents":2274,"payment_intent_id":"pi_abc123","domains":[{"id":"d_new001","domain":"acmeoutreach.com","status":"provisioning","source_provider":"cheapinboxes","dns_mode":"nameservers"},{"id":"d_imp001","domain":"myexisting.com","status":"pending_ns_verification","source_provider":"porkbun","dns_mode":"nameservers","nameserver_setup":"triggered"},{"id":"d_imp002","domain":"mycloudflarezone.com","status":"pending_manual_setup","source_provider":"external","dns_mode":"cloudflare_connect","cloudflare_connected":false,"nameserver_setup":"manual_cloudflare_required"}],"mailboxes":[{"id":"mb_new001","full_email":"emma.johnson@acmeoutreach.com","first_name":"Emma","last_name":"Johnson","status":"provisioning"},{"id":"mb_imp001","full_email":"sarah.wilson@myexisting.com","first_name":"Sarah","last_name":"Wilson","status":"pending"}],"quote":{"totals":{"total_today_cents":2274,"mailbox_unit_price_cents":325}},"imported_domains_pending_setup":["d_imp001","d_imp002"],"nameserver_setup":{"status":"triggered","domain_ids":["d_imp001"],"message":"Nameserver assignment has been started automatically for imported domains that use the nameserver flow. Poll list_domains with this order_id until each imported domain has nameservers.","poll":"GET /v1/domains?order_id=ord_abc123&limit=100"},"cloudflare_connect_setup":{"status":"awaiting_api_token","domain_ids":["d_imp002"],"message":"These domains keep their DNS zone in your own Cloudflare account and will never be assigned nameservers. Supply a scoped Cloudflare API token to continue.","connect":"POST /v1/orders/ord_abc123/cloudflare/connect"},"setup_options":{"nameservers":"Already triggered automatically for imported domains that use the nameserver flow. Poll GET /v1/domains?order_id=:order_id until nameservers are present.","cloudflare_connect":"POST /v1/domains/:id/cloudflare/connect — Provide your Cloudflare API token and we manage DNS in your account.","dfy":"POST /v1/domains/:id/request-dfy — Request our team to handle Cloudflare setup for you."}}}}}}}}},"/v1/orders/{id}":{"get":{"summary":"Get order by ID","tags":["Orders"],"description":"Returns full details for an order including its current status, cart snapshot, pricing quote, associated domains and mailboxes, payment information, and provisioning progress. Use this to poll order status after checkout.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true,"description":"Order ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"order":{"id":"ord_abc123","name":"Acme Corp - Q3 batch","status":"completed","created_at":"2025-06-15T10:00:00.000Z","cart_snapshot":{}},"domains":[{"id":"d_abc123","domain":"acmeoutreach.com","status":"active","source_provider":"cheapinboxes","dns_mode":"nameservers"},{"id":"d_def456","domain":"acmeimported.com","status":"pending_manual_setup","source_provider":"external","dns_mode":"cloudflare_connect","cloudflare_connected":false}],"mailboxes":[{"id":"mb_abc123","full_email":"emma.johnson@acmeoutreach.com","first_name":"Emma","last_name":"Johnson","status":"active","source_provider":"google"}]}}}}}}},"patch":{"summary":"Rename an order","tags":["Orders"],"description":"Change an order's label after it has been placed. The name is what identifies the order in the customer dashboard and on the Stripe receipt for future charges; orders placed without one are labelled \"Order #N\" by their position in your organization.\n\nUse this to name orders that already exist — including any placed before `name` was accepted at checkout. To set the label at purchase time instead, pass `name` to `POST /v1/orders/checkout`.\n\nRenaming is metadata only: it never re-charges, re-provisions, or otherwise touches the order's domains and mailboxes.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":240,"description":"The new label (1-240 characters). Leading and trailing whitespace is trimmed. Unlike checkout, this endpoint requires a name — there is no way to clear one back to null over the API."}},"additionalProperties":false}}},"required":true},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Order ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"order":{"id":"ord_abc123","organization_id":"org_abc123","name":"Acme Corp - Q3 batch","status":"completed","created_at":"2025-06-15T10:00:00.000Z"}}}}}}}}},"/v1/orders/{id}/cloudflare/connect":{"post":{"summary":"Connect Cloudflare DNS for order","tags":["Orders"],"description":"Connect every domain on this order that was staged with `source_provider: \"cloudflare\"` and is still waiting on a token — the bulk form of `POST /v1/domains/:id/cloudflare/connect`. Use this after checkout when the whole order sits in one Cloudflare account.\n\n**Validation is up-front; connection is per-domain.** Every pending domain's zone is looked up before anything is written — if any one cannot be reached, nothing is connected at all and the call fails with `CLOUDFLARE_ZONES_PARTIAL`, whose `error.details.domains` lists every pending domain with a `status` — `ok` for those that resolved fine, and `zone_not_found`, `auth_failed` or `token_error` for those that did not. Once validation passes, domains are connected one at a time; a failure part-way through leaves the earlier ones connected and they will not appear in any response. **Re-calling is always safe** — already-connected domains are skipped, so just call again until you get a `200`.\n\n**Token permissions.** Each token needs **Zone → Read** and **DNS → Edit** on the zones it covers, plus **Page Rules → Edit** only if you also use domain forwarding.\n\n**All domains must live in the same Cloudflare account** — a token that can see zones across several accounts is rejected with `CLOUDFLARE_ACCOUNT_MISMATCH`. For a domain in a different account, pass a per-domain override in `domain_api_tokens`.\n\nDomains already connected, and those awaiting Done-For-You setup, are skipped. If nothing on the order is pending you get `NO_DOMAINS_PENDING_CONNECT` (400).\n\nErrors: `ORDER_NOT_FOUND` (404), `NO_DOMAINS_PENDING_CONNECT` (400), `INVALID_API_TOKEN` (400), `CHECKOUT_REQUIRED` (400), `CLOUDFLARE_ZONES_PARTIAL` (400), `CLOUDFLARE_ACCOUNT_MISMATCH` (400), `CLOUDFLARE_LINKAGE_CONFLICT` (409).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"api_token":{"type":"string","minLength":10,"description":"Scoped Cloudflare API token (Zone:Read + DNS:Edit) used for every pending domain on the order, unless overridden in domain_api_tokens. Stored encrypted; redacted from audit logs."},"domain_api_tokens":{"type":"object","additionalProperties":{"type":"string","minLength":10},"description":"Optional per-domain token overrides for domains in a different Cloudflare account. **Keys are domain IDs (UUIDs), not domain names** — take them from `GET /v1/domains?order_id=…`. Any domain without an override falls back to api_token."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Order ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"results":[{"domain_id":"dom_abc","domain":"acme.com","run_id":"run_abc123"},{"domain_id":"dom_def","domain":"example.com","run_id":"run_def456"}]}}}}}}}},"/v1/billing/summary":{"get":{"summary":"Get billing summary","tags":["Billing"],"description":"Returns a high-level billing overview for your organization: current monthly spend, next invoice date, outstanding balance, active mailbox count, and subscription status. Use this for dashboard widgets or billing alerts.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"subscriptions":[{"id":"sub_001","domain":"10 domains","mailboxCount":30,"monthlyAmountCents":9750,"nextBillingDate":"2025-08-01","provider":"google"}],"totalMonthlyCents":9750,"totalMailboxes":30}}}}}}}},"/v1/billing/usage":{"get":{"summary":"Usage summary","tags":["Billing"],"description":"Returns a detailed usage breakdown — active mailboxes by provider, domains by status, and monthly cost projections. More granular than the billing summary, useful for cost analysis and capacity planning.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"usage":{"period_start":"2025-07-01T00:00:00.000Z","period_end":"2025-07-31T23:59:59.000Z","items":[],"total_cents":0}}}}}}}}},"/v1/billing/invoices":{"get":{"summary":"List invoices","tags":["Billing"],"description":"List all invoices for your organization, ordered by date. Each invoice includes line items, amounts, payment status, and links to PDF receipts. Supports pagination for organizations with long billing histories.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"invoices":[{"id":"inv_001","order_id":"ord_abc123","period_start":"2025-07-01","period_end":"2025-07-31","total_cents":9750,"status":"paid","receipt_url":"https://pay.stripe.com/receipts/...","created_at":"2025-07-01T00:00:00.000Z"}]}}}}}}}},"/v1/billing/orders":{"get":{"summary":"List billing orders","tags":["Billing"],"description":"List billing-related order records showing what was purchased, when, and how much was charged. Distinct from the Orders endpoints — this view is optimized for financial reconciliation and accounting.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"orders":[{"id":"ord_abc123","status":"completed","created_at":"2025-06-15T10:00:00.000Z","total_cents":4197,"invoice_id":"inv_001","line_items":[{"type":"domain","description":"Domain: acmeoutreach.com (1 year)","quantity":1,"unit_price_cents":999,"total_cents":999},{"type":"mailbox","description":"Google Workspace Mailbox (First Month)","provider":"google","quantity":3,"unit_price_cents":325,"total_cents":975}]}]}}}}}}}},"/v1/billing/payments":{"get":{"summary":"List payments","tags":["Billing"],"description":"Returns a chronological list of all payments made by your organization, including successful charges, failed attempts, and refunds. Each record includes the Stripe payment intent ID for cross-referencing with your own Stripe dashboard.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"payments":[{"id":"pay_001","invoice_id":"inv_001","amount_cents":9750,"currency":"usd","status":"succeeded","receipt_url":"https://pay.stripe.com/receipts/...","processed_at":"2025-07-01T00:05:00.000Z"}]}}}}}}}},"/v1/billing/invoices/{id}":{"get":{"summary":"Get invoice details","tags":["Billing"],"description":"Returns the full details of a single invoice including all line items with per-unit pricing, discounts applied, taxes, and the total. Also includes the payment status and receipt URL.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Invoice ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"invoice":{"id":"inv_001","order_id":"ord_abc123","period_start":"2025-07-01","period_end":"2025-07-31","total_cents":9750,"status":"paid","line_items":[{"code":"mailbox_monthly_google","description":"Google Workspace Mailbox","quantity":30,"unit_price_cents":325,"total_cents":9750}]}}}}}}}}},"/v1/billing/payment-methods":{"get":{"summary":"List payment methods","tags":["Billing"],"description":"List all payment methods (credit/debit cards) on file for your organization. Shows card brand, last four digits, expiration date, and which card is set as the default for automatic billing.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"payment_methods":[{"id":"pm_abc123","type":"card","is_default":true,"card":{"brand":"visa","last4":"4242","exp_month":12,"exp_year":2028},"created":1718000000}]}}}}}}}},"/v1/billing/payment-methods/{id}":{"delete":{"summary":"Remove payment method","tags":["Billing"],"description":"Remove a payment method from your organization. Cannot remove the default payment method if you have active mailboxes — set a different card as default first. The payment method ID comes from the List Payment Methods endpoint.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true,"description":"Stripe payment method ID (e.g. pm_xxx)."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"success":true}}}}}}}},"/v1/billing/payment-methods/{id}/default":{"post":{"summary":"Set default payment method","tags":["Billing"],"description":"Set a payment method as the default for automatic billing. All future recurring charges and API checkout orders will use this card. You must have at least one payment method on file. No request body required.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true,"description":"Stripe payment method ID (e.g. pm_xxx)."}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"success":true,"payment_method_id":"pm_abc123"}}}}}}}},"/v1/billing/pay-now":{"post":{"summary":"Pay overdue invoices","tags":["Billing"],"description":"Immediately attempt to pay all overdue invoices using your default payment method. Use this if automatic billing failed (e.g., expired card) and you've updated your payment method. Returns the payment result for each invoice. No request body required.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"invoices_attempted":1,"invoices_paid":1,"invoices_failed":0,"total_paid_cents":9750}}}}}}}},"/v1/billing/cancel/undo":{"post":{"summary":"Undo cancellations","tags":["Billing"],"description":"Reverse pending cancellations for mailboxes and/or domains. Only works for cancellations that haven't been finalized yet (scheduled but not yet executed at period end). Pass `mailboxIds` and/or `domainIds` as real JSON arrays of UUIDs.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"mailboxIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Mailbox UUIDs whose pending cancellation should be reversed."},"domainIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Domain UUIDs whose auto-renew should be re-enabled."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"mailboxes":["mb_abc123"],"domains":[],"errors":[]}}}}}}}},"/v1/billing/cancel":{"post":{"summary":"Cancel mailboxes or domains","tags":["Billing"],"description":"Schedule mailboxes and/or domains for cancellation at the end of their current billing period. Resources remain active until the period ends, then are deprovisioned. Use the undo endpoint to reverse before the period ends. Pass `mailboxIds` and/or `domainIds` as real JSON arrays of UUIDs (camelCase).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"mailboxIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Mailbox UUIDs to schedule for cancellation."},"domainIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Domain UUIDs to disable auto-renew on (so they expire at term end)."},"reason":{"type":"string","description":"Reason code for cancellation (free-form)."},"reason_detail":{"type":"string","description":"Free-form detail about the cancellation reason."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"mailboxes":[{"mailboxId":"mb_abc123","effectiveAt":"2025-08-01"}],"domains":[]}}}}}}}},"/v1/integrations":{"get":{"summary":"List integrations","tags":["Integrations"],"description":"List all sending-platform integrations configured for your organization. Returns each integration's provider (Instantly, Smartlead, EmailBison, PlusVibe, MasterInbox, or custom), connection status, display name, and sync state. Use this to see which platforms are connected.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"integrations":[{"id":"int_001","type":"sending","provider":"instantly","status":"active","display_name":"Main Instantly","has_login_credentials":false,"created_at":"2025-06-15T10:00:00.000Z"},{"id":"int_002","type":"sending","provider":"emailbison","status":"active","display_name":"Sales Team","has_login_credentials":true,"created_at":"2025-06-20T10:00:00.000Z"}]}}}}}}},"post":{"summary":"Create an integration","tags":["Integrations"],"description":"Connect a new sending-platform integration. Provide the provider type and API credentials. Supported providers: `instantly`, `smartlead`, `emailbison`, `plusvibe`. After creation, use the sync endpoint to push mailboxes to the platform.\n\n**`sequencer_login` MUST be a JSON object** like `{ \"email\": \"you@x.com\", \"password\": \"...\" }` — NOT a JSON-encoded string like `\"{\\\"email\\\":\\\"...\\\"}\"` and NOT a plain email string.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["provider","api_key","sequencer_login"],"properties":{"provider":{"type":"string","enum":["instantly","smartlead","emailbison","plusvibe"],"description":"Sending-platform provider."},"api_key":{"type":"string","minLength":1,"description":"API key from the sending platform."},"base_url":{"type":"string","description":"Optional custom base URL (mainly for self-hosted EmailBison)."},"display_name":{"type":"string","description":"Friendly name shown in the dashboard."},"sequencer_login":{"type":"object","required":["email","password"],"description":"Login credentials for the sending platform UI. Required for browser-side automation. Pass as an actual object, not a stringified JSON.","properties":{"email":{"type":"string","format":"email","description":"Email used to log into the sequencer."},"password":{"type":"string","minLength":1,"description":"Password for the sequencer login."},"workspace_name":{"type":"string","description":"Optional workspace name when the platform supports multiple workspaces."}},"additionalProperties":false}},"additionalProperties":false}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"integration":{"id":"int_new001","type":"sending","provider":"instantly","status":"active","display_name":"New Integration"}}}}}}}}},"/v1/integrations/masterinbox/connect":{"post":{"summary":"Connect MasterInbox","tags":["Integrations"],"description":"Validate a MasterInbox `pk_` API key and connect its workspace. The key must expose exactly one workspace because MasterInbox custom-provider writes do not accept a workspace parameter.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["api_key"],"properties":{"api_key":{"type":"string","minLength":1,"description":"MasterInbox partner API key."}},"additionalProperties":false}}},"required":true},"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/v1/integrations/{id}":{"get":{"summary":"Get integration details","tags":["Integrations"],"description":"Returns full details for a single integration including provider, status, display name, metadata, and creation date. Use this to check the health of a specific integration or get its ID for sync operations.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true,"description":"Integration ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"integration":{"id":"int_001","type":"sending","provider":"instantly","status":"active","display_name":"Main Instantly","metadata":{},"created_at":"2025-06-15T10:00:00.000Z"}}}}}}}},"patch":{"summary":"Update integration","tags":["Integrations"],"description":"Update an integration's API key, base URL, or status (active/disabled). Use this to rotate API keys or temporarily disable syncing without deleting the integration.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"api_key":{"type":"string","minLength":1,"description":"New API key (rotation)."},"base_url":{"type":"string","description":"Optional custom base URL."},"status":{"type":"string","enum":["active","disabled"],"description":"Toggle integration on/off without deleting it."},"display_name":{"type":"string","description":"Friendly name shown in the dashboard."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Integration ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"integration":{"id":"int_001","status":"active","display_name":"Updated Name"}}}}}}}},"delete":{"summary":"Delete an integration","tags":["Integrations"],"description":"Remove a sending-platform integration. This disconnects all linked mailboxes from the platform but does not delete the mailboxes themselves or their accounts on the sending platform.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true,"description":"Integration ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"success":true}}}}}}}},"/v1/integrations/custom":{"post":{"summary":"Create custom integration","tags":["Integrations"],"description":"Request a custom integration setup for platforms without built-in 1-click support. Provide the platform name, login credentials, and optional workspace name. The Cheap Inboxes team will configure the integration manually and update its status to active.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["platformName","email","password"],"properties":{"platformName":{"type":"string","minLength":1,"description":"Display name of the platform (e.g. \"MySender Pro\")."},"platformUrl":{"type":"string","description":"Login URL for the platform."},"email":{"type":"string","format":"email","description":"Login email for the platform."},"password":{"type":"string","minLength":1,"description":"Login password for the platform."},"workspace":{"type":"string","description":"Optional workspace/account name."}},"additionalProperties":false}}},"required":true},"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"integration":{"id":"int_custom001","type":"other","provider":"custom_platform","status":"manual","display_name":"Custom Platform"}}}}}}}}},"/v1/integrations/{id}/sync":{"post":{"summary":"Sync all mailboxes","tags":["Integrations"],"description":"Trigger a full sync of all eligible mailboxes to this integration's sending platform. Creates accounts on the platform for any mailboxes that aren't already synced. This can take a few minutes for large mailbox counts.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"mailbox_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Optional subset of mailbox IDs to sync. If omitted, syncs all eligible mailboxes for this org."},"auth_method":{"type":"string","enum":["imap_smtp","oauth"],"description":"MasterInbox connection method. Defaults to IMAP/SMTP."}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Integration ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"synced":15,"failed":0,"skipped":3}}}}}}}},"/v1/integrations/{id}/link-mailboxes":{"post":{"summary":"Link mailboxes","tags":["Integrations"],"description":"Link specific mailboxes to this integration for syncing. Pass a `mailbox_ids` array in the body. Linked mailboxes will be included in future sync operations. Use this to selectively control which mailboxes appear on which sending platform.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["mailbox_ids"],"properties":{"mailbox_ids":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"description":"Mailbox IDs to link. Pass as a real JSON array, not a stringified one."}},"additionalProperties":false}}},"required":true},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Integration ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"linked":5}}}}}}}},"/v1/integrations/{id}/pull":{"post":{"summary":"Pull from sequencer","tags":["Integrations"],"description":"Pull the latest state from the sending platform back into Cheap Inboxes. Updates sync status, warmup state, and deliverability metrics for all mailboxes connected to this integration. Useful for getting up-to-date platform data. No request body required.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Integration ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"pulled":15,"updated":8}}}}}}}},"/v1/mailboxes/{id}/sync":{"post":{"summary":"Sync single mailbox","tags":["Integrations"],"description":"Push a single mailbox to a specific sending-platform integration. Creates the mailbox account on the platform if it doesn't exist, or updates it if it does. Use this for targeted syncing instead of a full integration-wide sync. You must specify which integration to push to via `integration_id`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["integration_id"],"properties":{"integration_id":{"type":"string","format":"uuid","description":"UUID of the sending-platform integration to push this mailbox to."}},"additionalProperties":false}}},"required":true},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Mailbox ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"success":true,"mailbox_id":"mb_abc123","platform_id":"ext_12345"}}}}}}},"delete":{"summary":"Disconnect mailbox","tags":["Integrations"],"description":"Disconnect a single mailbox from its sending platform. Removes the link between the Cheap Inboxes mailbox and the platform account. The platform account itself is not deleted.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["integration_id"],"properties":{"integration_id":{"type":"string","format":"uuid","description":"Integration to disconnect from"}},"additionalProperties":false}}},"required":true},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true,"description":"Mailbox ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"success":true,"mailbox_id":"mb_abc123"}}}}}}}},"/v1/mailboxes/{id}/check-status":{"post":{"summary":"Check connection status","tags":["Integrations"],"description":"Check the real-time connection status of a mailbox on its sending platform. Verifies that IMAP/SMTP credentials are working and the mailbox is reachable. Returns the current status and any error details.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Mailbox ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"mailbox_id":"mb_abc123","connected":true,"imap_status":"ok","smtp_status":"ok","last_checked":"2025-07-15T10:00:00.000Z"}}}}}}}},"/v1/mailboxes/{id}/fix-connection":{"post":{"summary":"Fix broken connection","tags":["Integrations"],"description":"Attempt to automatically repair a broken connection between a mailbox and its sending platform. Re-pushes credentials and reconfigures the platform account. Use this when check-status reports a connection failure.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true,"description":"Mailbox ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"success":true,"mailbox_id":"mb_abc123","action_taken":"credentials_refreshed"}}}}}}}},"/v1/usage":{"get":{"summary":"Get usage statistics","tags":["Usage"],"description":"Returns aggregate usage statistics for your organization — total domains, active mailboxes, mailboxes by provider (Google vs Microsoft), and historical usage trends. Useful for building internal dashboards or monitoring growth.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"usage":{"period_start":"2025-07-01T00:00:00.000Z","period_end":"2025-07-31T23:59:59.000Z","items":[],"total_cents":0}}}}}}}}},"/v1/org/members":{"get":{"summary":"List organization members","tags":["Organization"],"description":"List all team members in your organization with their roles (owner, admin, member) and join dates. Useful for auditing team access or building user-management features on top of Cheap Inboxes.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"members":[{"id":"mem_001","user_id":"usr_abc123","role":"owner","created_at":"2025-06-15T10:00:00.000Z","user":{"id":"usr_abc123","email":"nick@acme.com","name":"Nick Smith","avatar_url":"https://example.com/avatar.jpg"}},{"id":"mem_002","user_id":"usr_def456","role":"member","created_at":"2025-07-01T12:00:00.000Z","user":{"id":"usr_def456","email":"sam@acme.com","name":"Sam Jones","avatar_url":null}}]}}}}}}}},"/v1/webhooks":{"post":{"summary":"Create a webhook subscription","tags":["Webhooks"],"description":"Register a URL to receive real-time event notifications. You must specify which events to subscribe to. The webhook secret is returned only in this response — store it securely to verify delivery signatures. Supported events: `domain.provisioned`, `domain.dns_configured`, `mailbox.active`, `mailbox.credentials_ready`, `order.completed`, `order.failed`, `billing.invoice_paid`, `billing.invoice_failed`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["domain.provisioned","domain.dns_configured","mailbox.active","mailbox.credentials_ready","order.completed","order.failed","billing.invoice_paid","billing.invoice_failed"]},"minItems":1}}}}},"required":true},"responses":{"201":{"description":"Created webhook with secret (shown once)","content":{"application/json":{"schema":{"description":"Created webhook with secret (shown once)","type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"secret":{"type":"string"},"created_at":{"type":"string"}},"example":{"id":"wh_abc123","url":"https://yourapp.com/webhooks/cheapinboxes","events":["domain.provisioned","mailbox.active","mailbox.credentials_ready"],"secret":"whsec_a1b2c3d4e5f6...store_this_securely","created_at":"2025-07-15T10:00:00.000Z"}}}}}}},"get":{"summary":"List webhook subscriptions","tags":["Webhooks"],"description":"Returns all active webhook subscriptions for your organization. Secrets are not included in list responses for security — they are only shown once during creation.","responses":{"200":{"description":"List of webhooks (secrets are not included)","content":{"application/json":{"schema":{"description":"List of webhooks (secrets are not included)","type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string"}}},"example":[{"id":"wh_abc123","url":"https://yourapp.com/webhooks/cheapinboxes","events":["domain.provisioned","mailbox.active"],"created_at":"2025-07-15T10:00:00.000Z"}]}}}}}}},"/v1/webhooks/{id}":{"delete":{"summary":"Delete a webhook subscription","tags":["Webhooks"],"description":"Permanently remove a webhook subscription. The endpoint will stop receiving event deliveries immediately. This action cannot be undone — you'll need to create a new webhook to resume notifications.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true,"description":"Webhook ID"}],"responses":{"200":{"description":"Webhook deleted","content":{"application/json":{"schema":{"description":"Webhook deleted","type":"object","properties":{"success":{"type":"boolean"}},"example":{"success":true}}}}}}}},"/v1/webhooks/{id}/test":{"post":{"summary":"Send a test event to a webhook","tags":["Webhooks"],"description":"Send a test event to a webhook endpoint to verify it's reachable and correctly processing deliveries. Sends a `webhook.test` event with an HMAC-SHA256 signature. Returns the HTTP status code and response body from your endpoint.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true,"description":"Webhook ID"}],"responses":{"200":{"description":"Test delivery result","content":{"application/json":{"schema":{"description":"Test delivery result","type":"object","properties":{"success":{"type":"boolean"},"status_code":{"type":"integer"},"response_body":{"type":"string"}},"example":{"success":true,"status_code":200,"response_body":"{\"received\": true}"}}}}}}}},"/v1/discovery/tlds":{"get":{"summary":"List available TLDs and their prices","tags":["Discovery"],"description":"Returns all supported top-level domains (e.g. .com, .io, .shop) with their annual registration prices. Call this before domain search to show your users which TLDs are available and how much they cost.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"tlds":[{"tld":"com","label":".com","price":9.99},{"tld":"io","label":".io","price":39.99},{"tld":"shop","label":".shop","price":3},{"tld":"online","label":".online","price":3}]}}}}}}}},"/v1/discovery/domains/search":{"post":{"summary":"Search for available domains","tags":["Discovery"],"description":"Search for domain availability by keyword. Returns an exact-match result plus creative suggestions across the requested TLDs. This is the first step of the domain purchase flow — use it to let users find and pick domains before placing an order.","requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"keyword":{"type":"string","description":"The domain name keyword to search (without TLD), e.g. \"acmeoutreach\""},"tlds":{"type":"array","items":{"type":"string"},"description":"TLDs to search, without leading dots, e.g. [\"com\", \"shop\", \"co\"]. Omit to search all available TLDs."}},"required":["keyword"]}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"exact":[{"domain":"acmeoutreach.com","available":true,"status":"available","price":9.99,"currency":"USD"}],"suggestions":[{"domain":"acmeoutreach.io","available":true,"status":"available","price":39.99,"currency":"USD"},{"domain":"acmeleads.com","available":true,"status":"available","price":9.99,"currency":"USD"}]}}}}}}}},"/v1/discovery/pricing/mailboxes":{"get":{"summary":"Get mailbox pricing tiers","tags":["Discovery"],"description":"Returns your organization's current per-mailbox pricing, including any volume discounts that apply at your scale. Prices are calculated based on your total active mailbox count. Use this to display accurate pricing to your users before checkout.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"basePriceCents":350,"tiers":[{"minQty":0,"maxQty":99,"priceCents":350,"priceFormatted":"$3.50"},{"minQty":100,"maxQty":249,"priceCents":325,"priceFormatted":"$3.25"},{"minQty":250,"maxQty":999,"priceCents":300,"priceFormatted":"$3.00"},{"minQty":1000,"maxQty":null,"priceCents":275,"priceFormatted":"$2.75"}]}}}}}}}},"/v1/domain-imports/bulk":{"post":{"summary":"Bulk stage domains for import","tags":["Domain Imports"],"description":"Stage multiple domains you already own for import into Cheap Inboxes. Pass an array of domain names to validate and queue for import. Staged domains are not imported immediately — review them with the list endpoint, then include them in an order to complete the import.\n\n**`domains` MUST be a JSON array of strings**, e.g. `[\"acme.com\", \"example.shop\"]` — NOT a JSON-encoded string like `\"[\\\"acme.com\\\"]\"`. Each entry must be a plain domain name (lowercase letters, digits, hyphens, dots only).","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["domains","source_provider"],"properties":{"domains":{"type":"array","description":"Array of domain names to stage. Pass as a real JSON array, not a stringified one.","items":{"type":"string","minLength":3,"maxLength":253,"pattern":"^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$","description":"A single domain name like \"acme.com\" (no protocol, no slashes, no quotes, no brackets)."},"minItems":1},"source_provider":{"type":"string","enum":["manual","namecheap","godaddy","porkbun","cloudflare","DIY_import","DFY_import","migration","external"],"description":"Where the domains came from, and — for `cloudflare` — how DNS will be run.\n\nUse `manual` if you registered them yourself, or the matching registrar name. These domains take the **nameserver** path: checkout creates a zone in our Cloudflare and returns nameservers for you to point the domain at.\n\nUse `cloudflare` when the domain already has a zone in **your own** Cloudflare account and you want to keep it there. These domains take the **Cloudflare Connect** path: no zone is created on our side and no nameservers change. After checkout the domain sits in `pending_manual_setup` until you supply a scoped Cloudflare API token, and we then write the MX/SPF/DKIM/DMARC records into your existing zone.\n\n**Reading it back:** for historical reasons `source_provider` is reported as `external` on `GET /v1/domains`, not `cloudflare`. Key off **`dns_mode`** instead — it is `cloudflare_connect` for these domains and `nameservers` for every other path, and `cloudflare_connected` tells you whether the token has been supplied yet."},"metadata":{"type":"object","additionalProperties":true,"description":"Optional free-form metadata to attach to the staged domains."}},"additionalProperties":false}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"entries":[{"id":"imp_001","domain_name_normalized":"myexisting.com","state":"staged","source_provider":"DIY_import"}],"errors":[]}}}}}}}},"/v1/domain-imports":{"get":{"summary":"List staged domain imports","tags":["Domain Imports"],"description":"Returns all domains currently staged for import, including their validation status and any detected issues (e.g., nameserver conflicts, existing registrations). Review this list before completing an import order.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"entries":[{"id":"imp_001","domain_name_normalized":"myexisting.com","source_provider":"DIY_import","state":"staged","created_by_user_id":"usr_abc123","selected_at":"2025-06-15T10:00:00.000Z","cancelled_at":null,"purchased_at":null,"linked_domain_id":null}]}}}}}}}},"/v1/domain-imports/{id}":{"delete":{"summary":"Remove a staged domain import","tags":["Domain Imports"],"description":"Remove a domain from the import staging area. Use this if you no longer want to import a specific domain, or if it was staged by mistake.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true,"description":"Staged import ID"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"example":{"success":true}}}}}}}}},"security":[{"bearerAuth":[]}],"tags":[{"name":"Discovery","description":"Search for available domains and get live pricing before placing an order."},{"name":"Orders","description":"Get pricing quotes, place orders, and track order status through provisioning."},{"name":"Domains","description":"Manage your registered domains — DNS records, forwarding, DMARC, and transfers."},{"name":"Mailboxes","description":"View and manage individual mailboxes — credentials, TOTP, tags, and cancellations."},{"name":"Integrations","description":"Connect and sync mailboxes with sending platforms like Instantly, Smartlead, EmailBison, and PlusVibe."},{"name":"Domain Imports","description":"Import domains you already own from other registrars into Cheap Inboxes."},{"name":"Billing","description":"View invoices, payment history, manage payment methods, and handle overdue balances."},{"name":"Organization","description":"View and update your organization profile and team members."},{"name":"Webhooks","description":"Subscribe to real-time event notifications for provisioning milestones and billing events."},{"name":"Usage","description":"View aggregate usage statistics for your organization."}]}