> ## 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.

# Retrieving a user



## OpenAPI

````yaml /openapi.yaml get /users/{id}
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:
  /users/{id}:
    parameters:
      - in: path
        name: id
        required: true
        description: The user ID, e.g. `usr_01HZX`.
        schema:
          type: string
          example: usr_01HZX
    get:
      tags:
        - Users
      summary: Retrieving a user
      operationId: getUser
      responses:
        '200':
          $ref: '#/components/responses/UserResponse'
        '404':
          description: No user with that ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
components:
  responses:
    UserResponse:
      description: A user.
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/SuccessEnvelope'
              - type: object
                properties:
                  data:
                    $ref: '#/components/schemas/User'
          examples:
            user:
              summary: One user
              value:
                data:
                  id: usr_01HZX0C6Z3K4M5N6P7Q8R9S0
                  email: alex@acme.com
                  first_name: Alex
                  last_name: Rivera
                  status: active
                  sequencer:
                    provider: smartlead
                    client_id: 366903
                  created_at: '2026-05-08T12:00:00Z'
  schemas:
    ErrorEnvelope:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - invalid_request
                - unauthorized
                - forbidden
                - not_found
                - rate_limited
                - provider_not_supported
                - smartlead_connection_required
                - smartlead_client_not_found
                - order_shape_conflict
                - domain_count_mismatch
                - domain_not_imported
                - domains_not_submittable
                - premium_domain_not_supported
                - domain_not_usable_for_provider
                - domain_already_in_active_order
                - internal_error
              example: domain_count_mismatch
            message:
              type: string
              example: Submitted domain count does not match the order plan
            field:
              type: string
              example: domains
            details:
              type: object
              additionalProperties: true
              example:
                required_count: 20
                submitted_count: 18
                missing_count: 2
    SuccessEnvelope:
      type: object
      required:
        - data
      properties:
        data: {}
    User:
      type: object
      required:
        - id
        - status
        - sequencer
        - created_at
      properties:
        id:
          type: string
          example: usr_01HZX0C6Z3K4M5N6P7Q8R9S0
        email:
          type: string
          nullable: true
          example: alex@acme.com
        first_name:
          type: string
          nullable: true
          example: Alex
        last_name:
          type: string
          nullable: true
          example: Rivera
        status:
          type: string
          enum:
            - active
            - deleted
          example: active
        sequencer:
          type: object
          required:
            - provider
            - client_id
          description: Sequencer routing exposed without Peeker internal IDs.
          properties:
            provider:
              type: string
              enum:
                - smartlead
              example: smartlead
            client_id:
              type:
                - string
                - number
                - 'null'
              example: 366903
        created_at:
          type: string
          format: date-time
          example: '2026-05-08T12:00:00Z'
  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>

````