> ## Documentation Index
> Fetch the complete documentation index at: https://docs.peeker.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Checking domain availability

> Submit up to 50 domains. The response indicates which are free to
register and which are taken or premium. Premium, invalid, and
errored domains return `available: false` with a `reason`.




## OpenAPI

````yaml /openapi.yaml post /domains/availability
openapi: 3.1.0
info:
  title: Peeker Partner API
  version: 1.0.0
  description: >
    White-label Peeker's email infrastructure — Google Workspace and Microsoft

    365 inboxes, deliverability monitoring, and self-healing domain swaps —

    from your own app. Add a user, order licenses with imported or

    Peeker-managed domains, and Peeker's backend handles provisioning,

    DNS, forwarding, and lifecycle webhooks.


    The API is opinionated. Most of the heavy logic lives on Peeker's side,

    so a single `POST /orders` provisions everything end-to-end.


    ## Read first


    - **[Authentication](/authentication)** — bearer keys, live vs. sandbox,
    request IDs.

    - **[Rate limits](/rate-limits)** — flat 600/min per key, 429 backoff.

    - **[Webhooks](/webhooks)** — signed event delivery for every async job.

    - **[Best practices](/best-practices)** — errors, paging, idempotency,
    pending actions, webhook hygiene.


    ## Workflow guides


    - **[Quickstart](/guides/quickstart)** — first end-to-end call.

    - **[Buying domains & ordering](/guides/buying-domains)** — the default
    flow: bundle, availability, order, webhooks.

    - **[Importing domains](/guides/importing-domains)** — bring in existing
    customer domains.

    - **[Forwarding](/guides/forwarding)** — point a domain at a marketing site.

    - **[How to implement domain swaps](/guides/domain-swaps)** — standard vs
    premium swaps.

    - **[Changing user names on active orders](/guides/user-name-swaps)** —
    rename inboxes on a warm domain.

    - **[Domain swaps & renames](/guides/domain-swaps)** — standard vs premium
    swaps and user-name swaps.


    ## Public ID prefixes


    | Prefix | Resource |

    | --- | --- |

    | `usr_` | User |

    | `bun_` | Bundle |

    | `ord_` | Order |

    | `dom_` | Domain |

    | `imp_` | Domain import job |

    | `fwj_` | Domain forwarding job |

    | `swp_` | Domain swap |

    | `act_` | Pending action |

    | `evt_` | Webhook event |
servers:
  - url: https://api.peeker.ai/partner/v1
    description: >-
      Live and sandbox share one base URL — the `pk_live_…` vs `pk_test_…` key
      prefix routes the request.
security:
  - BearerAuth: []
tags:
  - name: Account
    x-group: Account
    description: |
      Confirm an API key is valid and inspect the partner record it belongs
      to. Use `GET /me` to check the key, see which environment it targets
      (live or sandbox), and read the partner profile.
  - name: Users
    x-group: Users
    description: |
      A user is one real person you're buying email sending for. Create a
      user record once, or let `POST /orders` create/reuse it from email.
      Every order, domain, and swap they buy from you afterwards links back
      to it. IDs look like `usr_01HZX`.

      **Status values**

      | Value | What it means |
      | --- | --- |
      | `active` | The user is in good standing. |
      | `deleted` | You marked them as deleted. Their order history stays. |

      **Things to know**

      - A user's email is unique inside your account. Adding the same
        email twice gives you back the existing user in the error
        response so you can match on `id`.
        - `sequencer.client_id` controls Smartlead routing for future orders.
          Send a Smartlead client ID for client routing, or `null`/`""` to
          use the configured default/admin route.
      - Deleting a user is reversible from the portal. The API doesn't
        hard-delete — past orders, domains, and swaps stay attached.
      - Users don't log in — they're identity-only records. If you want
        them to log in, send them an invite from the portal.
  - name: Bundles
    x-group: Bundles
    description: |
      A bundle is a saved order template. Set it up once with a monthly
      sending volume (and optionally a Google/Microsoft split), then re-use
      it on every order. IDs look like `bun_01HZX`.

      **Status values**

      | Value | What it means |
      | --- | --- |
      | `active` | Selectable for new orders. |
      | `archived` | Read-only. Old orders still show it. |

      **How the split works**

      If you set both `google_percent` and `microsoft_percent`, they have to
      add up to 100. If you leave them both empty, Peeker picks the
      recommended split for you. Either way, the response shows you the
      resolved values in the `effective` block.

      The recommended values aren't stored on the bundle row. When Peeker
      adjusts the recommended split, bundles inheriting it pick up the new
      values automatically.

      **Things to know**

      - Bundles are sized in monthly sending volume. By default Peeker does
        not send on weekends, so monthly volume is spread across 22 sending
        days. Set `send_on_weekends` to `true` to spread the same monthly
        volume across 30 days.
      - Google domains use `google_inboxes_per_domain` as the density. Allowed
        values are `2` and `3`; the default is `2`. Each Google inbox sends
        20 emails/day, so Google capacity is 40 or 60 emails/domain/day.
      - Microsoft capacity is 150 emails/domain/day.
      - Deleting a bundle that no order ever used removes it. Otherwise
        it's archived so old orders stay linked correctly.
  - name: Domains
    x-group: Domains
    description: >
      A domain is a website your inboxes will send from. Domains live in

      your partner account whether or not they're attached to an order, so

      the same `dom_01HZX` ID stays with the domain forever.


      **Status values**


      | Value | What it means |

      | --- | --- |

      | `available` | The domain is in inventory but not on an order yet. |

      | `in_progress` | We're setting it up. |

      | `active` | Live and warming or healthy. |

      | `failed` | Something broke that we can't fix automatically. |

      | `action_required` | We need your help (e.g. update nameservers). |


      **Inventory vs connected**


      - `category=inventory` — yours but not on any order. Inventory rows
        don't include `provider`.
      - `category=connected` — on an active order. The row carries the link
        fields `provider`, `user_id`, `order_id`, `usable_for`, and
        `forwarding_url`.

      **`usable_for` matrix**


      When you check a domain's availability:


      | Who's on the domain | `usable_for` |

      | --- | --- |

      | Nobody, or just Google | `["google", "microsoft"]` |

      | Microsoft (or both) | `["google"]` |

      | Premium / invalid / errored | not orderable; `available: false` with a
      `reason` |


      **Imports and forwarding are jobs**


      Both **import** and **forwarding** are async jobs. Posting to those

      endpoints starts the job and gives you back a row with `status:

      "in_progress"`. Poll the job's GET endpoint to see when it finishes:


      - Import jobs: prefix `imp_…`. Includes the `nameserver_groups` your
        customer needs to set at their registrar.
        - Forwarding jobs: prefix `fwj_…`. Already-correct domains are
          returned in `completed` with newly updated domains.
  - name: Orders
    x-group: Orders
    description: >
      An order is one purchase: domains plus Google or Microsoft licenses for

      one customer. Each license becomes one or more inboxes once

      provisioning finishes. Pick either a saved bundle OR custom

      Google/Microsoft license counts plus inboxes-per-domain — not both.

      IDs look like `ord_01HZX`.


      **Status values**


      | Value | What it means |

      | --- | --- |

      | `in_progress` | Accepted; we're setting it up. |

      | `completed` | All domains and inboxes are live. |

      | `failed` | Something broke that we can't fix automatically. |

      | `action_required` | We need your help to finish (see "Pending actions"
      below). |

      | `cancel_scheduled` | Cancellation accepted; not finished yet. |

      | `cancelled` | Cancelled. The domains are free to use on a new order. |


      **How many domains you need**


      - Microsoft: 1 license → 1 domain. Custom orders must send
        `microsoft_inboxes_per_domain` as `25`, `50`, `75`, or `99`.
      - Google: 1 license → 1 inbox. Custom orders must send
        `google_inboxes_per_domain` as `2` or `3`.

      So total domains needed equals

      `microsoft_licenses + ceil(google_licenses / google_inboxes_per_domain)`.

      If your count is off, you'll get back `domain_count_mismatch` with the

      gap in `details`.


      Partners import owned domains first through `/domains/import`. Domains

      that have not been imported are treated as registration candidates and

      are checked cache-first before the order is accepted. Unavailable,

      premium, unknown, or provider-error domains fail before wallet debit

      with `domains_not_submittable`.


      **Re-sending the same order**


      Re-sending the exact same order body — same customer, same domains,

      same everything — returns the original order. No second charge, no

      duplicate provisioning, no extra webhook.


      If you send a different order body but it overlaps with a domain

      that's on another active order (or one scheduled to cancel), the

      whole new order is rejected with `domain_already_in_active_order`.

      Cancel the other order first, or pick different domains.


      **Pending actions**


      A pending action is a public blocker that needs partner/customer input.

      Open actions make the order read as `action_required`. Use

      `GET /orders/pending` to list them. Allowed reasons:

      `update_nameservers`, `replace_domain`, and

      `profile_picture_not_accessible`.


      **Cancellations**


      Call `POST /orders/{id}/cancel` with no body to cancel the whole

      order. Send `domains` only when you want to cancel specific domains.

      The order's status moves to `cancel_scheduled` immediately and

      finalizes after a short grace window.


      Cancelling an order schedules the inboxes to be deleted at the end of

      the renewal period. Cancelling an individual Google domain cancels the

      2-3 licenses on that domain and only reduces that part of the order

      amount.
  - name: Domain swaps
    x-group: Domain swaps
    description: |
      A domain swap replaces a domain inside an existing order without
      losing the order. IDs look like `swp_01HZX`.

      **Status values**

      | Value | What it means |
      | --- | --- |
      | `created` | We've accepted the swap. |
      | `in_progress` | We're working on it. |
      | `completed` | The new domain is up; the old one is detached. |
      | `failed` | Something broke that we can't fix automatically. |
      | `action_required` | We need your help to finish. |

      **Standard vs premium**

      - **Standard.** The old domain is retired right away.
      - **Premium.** We keep the old domain warm for 14 days alongside the
        new one before retiring it. Use this when you want a smoother
        handoff. Premium swaps are **Microsoft domains only**, capped per
        billing tier (Base 20% / Premium 30% / VIP 40% / VIP+ 60% of your
        active Microsoft inboxes), and each domain has a **30-day cooldown**
        between premium swaps.

      A swap we reject as a duplicate of one already in flight is reported
      as `failed`.

      **Rename inboxes on a domain**

      The user-names swap is a different kind of swap: the domain stays, but
      the people whose names we use to generate inboxes change. Use it when
      a customer wants different names on the inboxes of an already-running
      domain.
  - name: Webhooks
    x-group: Webhooks
    description: >
      Peeker POSTs signed JSON events to a URL you configure when async work

      changes state — orders, domains, imports, and swaps. Register

      the URL and shared secret in the [Partner
      portal](https://app.peeker.com/partner/webhooks).

      Event IDs look like `evt_01HZX`.


      Delivery is at-least-once with up to 5 retries over 24 hours. Verify

      the `Peeker-Signature` header (`v1=<hex hmac-sha256>`) against the

      string `<timestamp>.<event_id>.<body>` before trusting any payload,

      and dedupe on `Peeker-Event-Id` for at least 24 hours.


      Full event catalog, signature verification snippets, and retry policy

      live on the [Webhooks](/webhooks) page.
paths:
  /domains/availability:
    post:
      tags:
        - Domains
      summary: Checking domain availability
      description: |
        Submit up to 50 domains. The response indicates which are free to
        register and which are taken or premium. Premium, invalid, and
        errored domains return `available: false` with a `reason`.
      operationId: checkDomainAvailability
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - domains
              properties:
                domains:
                  type: array
                  maxItems: 50
                  description: Domain names to check (e.g. `acme.com`). Up to 50 per call.
                  items:
                    type: string
            examples:
              three_domains:
                summary: Check three candidate domains
                value:
                  domains:
                    - acme-mail.com
                    - team-acme.com
                    - premium-domain.com
      responses:
        '200':
          description: One row per domain you sent.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          domains:
                            type: array
                            items:
                              $ref: '#/components/schemas/AvailabilityRow'
              examples:
                all_available:
                  summary: All three domains free to register
                  value:
                    data:
                      domains:
                        - domain: acme-mail.com
                          available: true
                          price_cents: 1300
                          renewal_price_cents: 1500
                          currency: usd
                          usable_for:
                            - google
                            - microsoft
                        - domain: team-acme.com
                          available: true
                          price_cents: 1300
                          renewal_price_cents: 1500
                          currency: usd
                          usable_for:
                            - google
                            - microsoft
                        - domain: news-acme.com
                          available: true
                          price_cents: 1300
                          renewal_price_cents: 1500
                          currency: usd
                          usable_for:
                            - google
                            - microsoft
                mixed:
                  summary: One available, one premium, one unsupported TLD
                  value:
                    data:
                      domains:
                        - domain: acme-mail.com
                          available: true
                          price_cents: 1300
                          renewal_price_cents: 1500
                          currency: usd
                          usable_for:
                            - google
                            - microsoft
                        - domain: premium-acme.com
                          available: false
                          premium: true
                          reason: premium
                        - domain: acme.unsupported
                          available: false
                          reason: unsupported_tld
                all_taken:
                  summary: Every domain already registered
                  value:
                    data:
                      domains:
                        - domain: acme.com
                          available: false
                        - domain: amazon.com
                          available: false
components:
  schemas:
    SuccessEnvelope:
      type: object
      required:
        - data
      properties:
        data: {}
    AvailabilityRow:
      type: object
      required:
        - domain
        - available
      properties:
        domain:
          type: string
          example: acme-mail.com
        available:
          type: boolean
          example: true
        price_cents:
          type: integer
          description: >-
            Registration cost in cents using your partner default currency.
            Present on every `available: true` row. If Peeker does not support
            the TLD, the row returns `available: false` with `reason:
            unsupported_tld`.
          example: 1300
        renewal_price_cents:
          type: integer
          description: >-
            Renewal cost in cents for the next yearly renewal. Present on every
            `available: true` row.
          example: 1500
        currency:
          type: string
          description: 'Present on every `available: true` row.'
          example: usd
        usable_for:
          type: array
          items:
            type: string
            enum:
              - google
              - microsoft
          example:
            - google
            - microsoft
        premium:
          type: boolean
          description: >-
            Present and `true` only when the domain is sold at a premium tier by
            the registry. Omitted otherwise.
          example: true
        reason:
          type: string
          enum:
            - unsupported_tld
            - premium
            - stale
            - provider_error
            - unknown
          description: >-
            Present on `available: false` rows when Peeker can attribute a
            reason: `unsupported_tld` (Peeker does not register the TLD),
            `premium` (registry sells the domain at a premium tier), `stale` /
            `provider_error` / `unknown` (lookup did not return a fresh
            registrability answer). When a domain is simply already registered
            to someone else, the row returns `available: false` with no
            `reason`.
          example: unsupported_tld
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        Your partner API key. Use `pk_live_…` against the live system or
        `pk_test_…` against the sandbox.
      x-default: Bearer pk_test_<your-test-key>

````