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

# Connect a workspace

> Connect a provider once, then use the returned `workspace_id` in later
orders and pool attachments. You do not need an `Idempotency-Key` for
this request. An exact reconnect reuses the existing workspace.

For Smartlead, connect the main workspace with its main API key and a
label. Do not send a `client_id` or customer object here. Add
`client_id` to an order only when that order belongs to a Smartlead
client. The Smartlead login email and password are optional, but they
must be sent together. They are saved on the workspace and never sent
with an order.

If an EmailBison target already belongs to a normal Peeker workspace,
this request returns `workspace_promotion_required`. Repeat it with
`promote_existing: true` to let the Partner API use that same workspace.

With a sandbox key, Peeker checks credential shape and stores the
submitted placeholder values, but creates a local provider identity
without calling the sequencer. Never submit production provider keys to
sandbox.




## OpenAPI

````yaml /openapi.yaml post /workspaces
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. Create a provider workspace target, order licenses with
    imported or

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

    DNS, forwarding, and lifecycle webhooks.


    Create a provider workspace target with `POST /workspaces`, then send its

    `wrk_...` ID to `POST /orders`. For Smartlead, create one main workspace,

    then add `client_id` to an order only when it should go to a client

    workspace. Omit it to order into the main workspace.


    ## Read first


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

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

    - **[Webhooks](/webhooks)**: signed events and delivery rules.

    - **[Async jobs](/concepts/async-jobs)**: what to poll vs subscribe to.

    - **[Best practices](/best-practices)**: errors, paging, idempotency.


    ## Concepts


    - **[Orders](/concepts/orders)**: statuses, sizing, pending actions, cancel.

    - **[Domains](/concepts/domains)**: inventory, `usable_for`, import,
    forwarding.

    - **[Bundles](/concepts/bundles)**: saved templates and capacity.

    - **[Swaps](/concepts/swaps)**: standard vs premium, renames.


    ## Workflow guides


    - **[Provider ordering](/guides/provider-ordering)** — create a provider
    workspace target and order through it.

    - **[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.

    - **[Renaming inbox senders](/guides/user-name-swaps)** — change mailbox
    personas on a warm domain.


    ## Public ID prefixes


    | Prefix | Resource |

    | --- | --- |

    | `cus_` | Customer tracking record |

    | `wrk_` | Provider workspace |

    | `bun_` | Bundle |

    | `ord_` | Order |

    | `dom_` | Domain |

    | `pcl_` | Pool reservation |

    | `prp_` | Pool replacement |

    | `prl_` | Pool release |

    | `imp_` | Domain import job |

    | `fwj_` | Domain forwarding job |

    | `brj_` | Burned-domain replacement job |

    | `swp_` | Domain swap |

    | `act_` | Pending action |

    | `evt_` | Webhook event |
servers:
  - url: https://api.peeker.ai/partner/v1
    description: >-
      Same base URL for live and sandbox. The key prefix (`pk_live_…` /
      `pk_test_…`) picks the environment.
security:
  - BearerAuth: []
tags:
  - name: Account
    x-group: Account
    description: >
      Confirm an API key with `GET /me`. Returns environment (live or sandbox),

      permission preset, and the partner profile. See
      [Authentication](/authentication).
  - name: Provider workspaces
    x-group: Provider workspaces
    description: |
      A provider workspace is one reusable provider connection. Create it once,
      keep the returned `wrk_...` ID, and use it in orders and pool attach.
      Optional customer tracking stays on the target. Provider credentials stay
      internal and are never echoed.

      EmailBison requires a workspace API-user key. Super-admin keys are
      rejected. Instantly and Smartlead connections require `api_key`.
      Smartlead workspace creation takes a main API key and a label. Do not send
      `client_id` or customer data. Its login email and password are optional,
      but must be sent together. Google orders do not need the pair, and orders
      never accept it. Smartlead Microsoft orders and pool attach still use the
      workspace-saved pair. Add
      `client_id` to the order destination when the order belongs to a client
      workspace; omit it for the main workspace.
      PlusVibe setup requires `api_key`, remote `workspace_id`, and its login
      email/password pair so orders and pool attach work immediately. Instantly
      login credentials belong on an order destination.
  - name: Bundles
    x-group: Bundles
    description: |
      Saved order template sized by monthly sending volume (`bun_…`). Pass
      `bundle_id` on an order instead of custom license counts.

      Lifecycle: [How bundles work](/concepts/bundles).
  - name: Domains
    x-group: Domains
    description: >
      Sending domains in your partner account (`dom_…`). Same ID whether or not

      the domain is on an order.


      **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`, `customer_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: Domain pool
    x-group: Domain pool
    description: |
      Assign an exact quantity from pre-warmed Google inventory. Each domain
      needs at least one active Google inbox on a paid Google order (commonly
      two), complete provider mailbox identity, and no explicit burned provider
      result. Attach is all-or-nothing: full quantity or
      `insufficient_inventory` with no pool change. Attach and release are
      async. Poll the resource or subscribe to `pool.*` webhooks.

      Guide: [Pre-warmed pool](/guides/pre-warmed-pool).
  - name: Orders
    x-group: Orders
    description: |
      One purchase of domains plus Google and/or Microsoft licenses for one
      customer (`ord_…`). Use a saved `bundle_id` **or** custom license counts,
      never both. Requires `Idempotency-Key`.

      Lifecycle: [How orders work](/concepts/orders).
  - name: Domain swaps
    x-group: Domain swaps
    description: |
      Replace a domain inside an existing order without losing the order
      (`swp_…`). For persona-only changes use `POST /swaps/user_names`.

      Statuses and standard vs premium: [How swaps work](/concepts/swaps).
  - name: Webhooks
    x-group: Webhooks
    description: |
      Signed JSON POSTs for orders, domains, imports, forwarding, pool, and
      swaps. Register the URL and secret in the
      [Partner portal](https://app.peeker.ai/partner/webhooks). Event IDs look
      like `evt_01HZX`.

      Delivery is at least once (up to 5 attempts). Verify `Peeker-Signature`
      and dedupe `Peeker-Event-Id` for at least 24 hours.

      Full catalog: [Webhooks](/webhooks).
paths:
  /workspaces:
    post:
      tags:
        - Provider workspaces
      summary: Connect a workspace
      description: |
        Connect a provider once, then use the returned `workspace_id` in later
        orders and pool attachments. You do not need an `Idempotency-Key` for
        this request. An exact reconnect reuses the existing workspace.

        For Smartlead, connect the main workspace with its main API key and a
        label. Do not send a `client_id` or customer object here. Add
        `client_id` to an order only when that order belongs to a Smartlead
        client. The Smartlead login email and password are optional, but they
        must be sent together. They are saved on the workspace and never sent
        with an order.

        If an EmailBison target already belongs to a normal Peeker workspace,
        this request returns `workspace_promotion_required`. Repeat it with
        `promote_existing: true` to let the Partner API use that same workspace.

        With a sandbox key, Peeker checks credential shape and stores the
        submitted placeholder values, but creates a local provider identity
        without calling the sequencer. Never submit production provider keys to
        sandbox.
      operationId: createProviderWorkspaceV1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkspaceCreateRequest'
            examples:
              emailbison:
                summary: EmailBison
                value:
                  provider: emailbison
                  promote_existing: false
                  credentials:
                    api_url: https://dedi.emailbison.com
                    api_key: <emailbison-workspace-api-key>
              instantly:
                summary: Instantly
                value:
                  provider: instantly
                  credentials:
                    api_key: <instantly-api-key>
              smartlead:
                summary: Smartlead main workspace
                value:
                  provider: smartlead
                  label: Agency Smartlead
                  credentials:
                    api_key: <smartlead-main-api-key>
                    login_email: automation@agency.com
                    login_password: <smartlead-login-password>
              plusvibe:
                summary: PlusVibe
                value:
                  provider: plusvibe
                  credentials:
                    api_key: <plusvibe-api-key>
                    workspace_id: 692577ed8543f416f83ad252
                    login_email: automation@agency.com
                    login_password: <plusvibe-login-password>
      responses:
        '200':
          description: Exact existing workspace target reused.
          headers:
            Peeker-Request-Id:
              $ref: '#/components/headers/PeekerRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceEnvelope'
        '201':
          description: Workspace connected, including an approved EmailBison promotion.
          headers:
            Peeker-Request-Id:
              $ref: '#/components/headers/PeekerRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceEnvelope'
              examples:
                plusvibe:
                  value:
                    data:
                      workspace_id: wrk_01HZX0PV1A2B3C4D5E6F7G8H
                      name: Acme PlusVibe
                      provider: plusvibe
                      provider_workspace_id: 692577ed8543f416f83ad252
                      status: active
                      created_at: '2026-07-09T12:00:00.000Z'
                      updated_at: '2026-07-09T12:00:00.000Z'
        '400':
          $ref: '#/components/responses/InvalidRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/ProviderCredentialsRejected'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ProviderUnavailable'
components:
  schemas:
    WorkspaceCreateRequest:
      oneOf:
        - $ref: '#/components/schemas/EmailBisonWorkspaceCreateRequest'
        - $ref: '#/components/schemas/InstantlyWorkspaceCreateRequest'
        - $ref: '#/components/schemas/SmartleadWorkspaceCreateRequest'
        - $ref: '#/components/schemas/PlusVibeWorkspaceCreateRequest'
      discriminator:
        propertyName: provider
        mapping:
          emailbison:
            $ref: '#/components/schemas/EmailBisonWorkspaceCreateRequest'
          instantly:
            $ref: '#/components/schemas/InstantlyWorkspaceCreateRequest'
          smartlead:
            $ref: '#/components/schemas/SmartleadWorkspaceCreateRequest'
          plusvibe:
            $ref: '#/components/schemas/PlusVibeWorkspaceCreateRequest'
    WorkspaceEnvelope:
      type: object
      additionalProperties: false
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/Workspace'
      example:
        data:
          workspace_id: wrk_01HZX0PV1A2B3C4D5E6F7G8H
          name: Acme PlusVibe
          provider: plusvibe
          provider_workspace_id: 692577ed8543f416f83ad252
          status: active
          created_at: '2026-07-09T12:00:00.000Z'
          updated_at: '2026-07-09T12:00:00.000Z'
    EmailBisonWorkspaceCreateRequest:
      title: EmailBison
      type: object
      additionalProperties: false
      required:
        - provider
        - credentials
      properties:
        provider:
          type: string
          enum:
            - emailbison
        promote_existing:
          type: boolean
          default: false
          description: >-
            Set `true` to reuse an existing Peeker EmailBison workspace with
            Partner API (not copy it).
        credentials:
          $ref: '#/components/schemas/EmailBisonCredentials'
        customer:
          $ref: '#/components/schemas/WorkspaceCustomerTracking'
    InstantlyWorkspaceCreateRequest:
      title: Instantly
      type: object
      additionalProperties: false
      required:
        - provider
        - credentials
      properties:
        provider:
          type: string
          enum:
            - instantly
        credentials:
          $ref: '#/components/schemas/InstantlyCredentials'
        customer:
          $ref: '#/components/schemas/WorkspaceCustomerTracking'
    SmartleadWorkspaceCreateRequest:
      title: Smartlead
      type: object
      additionalProperties: false
      required:
        - provider
        - label
        - credentials
      properties:
        provider:
          type: string
          enum:
            - smartlead
        label:
          type: string
          minLength: 1
          maxLength: 256
          description: Partner-facing name for this Smartlead connection.
          example: Agency Smartlead
        credentials:
          $ref: '#/components/schemas/SmartleadWorkspaceCredentials'
    PlusVibeWorkspaceCreateRequest:
      title: PlusVibe
      type: object
      additionalProperties: false
      required:
        - provider
        - credentials
      properties:
        provider:
          type: string
          enum:
            - plusvibe
        credentials:
          $ref: '#/components/schemas/PlusVibeSetupCredentials'
        customer:
          $ref: '#/components/schemas/WorkspaceCustomerTracking'
    Workspace:
      type: object
      required:
        - workspace_id
        - name
        - provider
        - provider_workspace_id
        - status
        - created_at
        - updated_at
      properties:
        workspace_id:
          type: string
          pattern: ^wrk_
          description: Peeker workspace target ID (`wrk_…`) for orders and pool attach.
          example: wrk_01HZX0WK1A2B3C4D5E6F7G8H
        name:
          type: string
          example: Acme PlusVibe
        provider:
          type: string
          enum:
            - emailbison
            - instantly
            - smartlead
            - plusvibe
          example: plusvibe
        provider_workspace_id:
          type: string
          description: Remote workspace ID from the provider (not Peeker’s `wrk_…`).
          example: 692577ed8543f416f83ad252
        client_id:
          type: string
          pattern: ^[1-9][0-9]*$
          description: Present when this target routes to a Smartlead client.
          example: '366903'
        label:
          type: string
          description: >-
            Optional label owned by this workspace target. Omitted when no label
            was supplied.
          example: Agency Smartlead
        customer:
          $ref: '#/components/schemas/WorkspaceCustomer'
        status:
          type: string
          enum:
            - active
            - disconnected
          example: active
        created_at:
          type: string
          format: date-time
          example: '2026-07-09T12:00:00.000Z'
        updated_at:
          type: string
          format: date-time
          example: '2026-07-09T12:00:00.000Z'
    ErrorEnvelope:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          type: object
          additionalProperties: false
          required:
            - code
            - message
            - request_id
          properties:
            code:
              type: string
              description: Stable machine-readable code. Branch on this field.
              enum:
                - invalid_request
                - unauthorized
                - forbidden
                - not_found
                - rate_limited
                - provider_not_supported
                - provider_mismatch
                - provider_credentials_invalid
                - provider_login_credentials_required
                - provider_unavailable
                - emailbison_super_admin_key
                - smartlead_connection_required
                - smartlead_client_id_required
                - smartlead_client_not_found
                - idempotency_key_required
                - idempotency_conflict
                - idempotency_in_progress
                - workspace_promotion_required
                - workspace_credentials_conflict
                - order_shape_conflict
                - domain_count_mismatch
                - insufficient_inventory
                - domain_not_imported
                - domains_not_submittable
                - premium_domain_not_supported
                - domain_not_usable_for_provider
                - domain_already_in_active_order
                - internal_error
              example: emailbison_super_admin_key
            message:
              type: string
              description: Human-readable explanation. Wording may change.
              example: >-
                Use an EmailBison workspace/user API key; super-admin keys are
                not accepted.
            request_id:
              type: string
              description: >-
                Stable request identifier, identical to the `Peeker-Request-Id`
                response header.
              example: 550e8400-e29b-41d4-a716-446655440000
            field:
              type: string
              description: Dot path to the invalid request property, when applicable.
              example: credentials.api_key
            details:
              type: object
              additionalProperties: true
              description: Structured context specific to `error.code`.
    EmailBisonCredentials:
      type: object
      additionalProperties: false
      required:
        - api_url
        - api_key
      properties:
        api_url:
          type: string
          minLength: 1
          maxLength: 2048
          description: >-
            Public EmailBison URL (stored as HTTPS). Private or local addresses
            are rejected.
          example: https://dedi.emailbison.com
        api_key:
          type: string
          format: password
          writeOnly: true
          minLength: 1
          maxLength: 4096
          description: EmailBison workspace/user API key (not a super-admin key).
          example: <emailbison-workspace-api-key>
    WorkspaceCustomerTracking:
      type: object
      additionalProperties: false
      required:
        - reference
      properties:
        reference:
          type: string
          maxLength: 256
          description: Partner-owned customer or account identifier.
        name:
          type: string
          maxLength: 256
        email:
          type: string
          format: email
          maxLength: 320
      description: >-
        Optional partner-owned tracking data. It does not create a Peeker user
        or affect routing.
    InstantlyCredentials:
      type: object
      additionalProperties: false
      required:
        - api_key
      properties:
        api_key:
          type: string
          format: password
          writeOnly: true
          minLength: 1
          maxLength: 4096
          description: Instantly API key for the workspace you want to connect.
          example: <instantly-api-key>
    SmartleadWorkspaceCredentials:
      type: object
      additionalProperties: false
      required:
        - api_key
      dependentRequired:
        login_email:
          - login_password
        login_password:
          - login_email
      properties:
        api_key:
          type: string
          format: password
          writeOnly: true
          minLength: 1
          maxLength: 4096
          description: Smartlead main API key.
          example: <smartlead-api-key>
        login_email:
          type: string
          format: email
          writeOnly: true
          maxLength: 320
          description: Optional Smartlead login. Send it together with login_password.
          example: automation@agency.com
        login_password:
          type: string
          format: password
          writeOnly: true
          minLength: 1
          maxLength: 4096
          description: Optional Smartlead password. Send it together with login_email.
          example: <smartlead-login-password>
    PlusVibeSetupCredentials:
      type: object
      additionalProperties: false
      required:
        - api_key
        - workspace_id
        - login_email
        - login_password
      properties:
        api_key:
          type: string
          format: password
          writeOnly: true
          minLength: 1
          maxLength: 4096
          description: PlusVibe API key that can access the selected PlusVibe workspace.
          example: <plusvibe-api-key>
        workspace_id:
          type: string
          minLength: 1
          maxLength: 256
          description: Remote PlusVibe workspace ID. This is not a Peeker `wrk_...` ID.
          example: 692577ed8543f416f83ad252
        login_email:
          type: string
          format: email
          writeOnly: true
          maxLength: 320
          description: >-
            PlusVibe login saved to the execution workspace for orders and pool
            attach.
          example: automation@agency.com
        login_password:
          type: string
          format: password
          writeOnly: true
          minLength: 1
          maxLength: 4096
          description: PlusVibe login password saved to the execution workspace.
          example: <plusvibe-login-password>
    WorkspaceCustomer:
      type: object
      additionalProperties: false
      required:
        - customer_id
        - reference
        - status
      properties:
        customer_id:
          type: string
          pattern: ^cus_
          description: >-
            Stable Peeker ID for filtering orders, domains, pending actions, and
            swaps.
          example: cus_01HZX0C6Z3K4M5N6P7Q8R9S0
        reference:
          type: string
          description: Partner-owned customer or account identifier.
          example: customer-42
        name:
          type: string
          example: Acme
        email:
          type: string
          format: email
          example: ops@acme.com
        status:
          type: string
          enum:
            - active
            - archived
          example: active
  headers:
    PeekerRequestId:
      description: Stable request identifier. Log it with the status and response body.
      schema:
        type: string
        example: 550e8400-e29b-41d4-a716-446655440000
  responses:
    InvalidRequest:
      description: Bad request body, or fields that don’t belong to this provider.
      headers:
        Peeker-Request-Id:
          $ref: '#/components/headers/PeekerRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Unauthorized:
      description: Missing, invalid, or revoked API key.
      headers:
        Peeker-Request-Id:
          $ref: '#/components/headers/PeekerRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Forbidden:
      description: Valid key, but it doesn’t allow this action.
      headers:
        Peeker-Request-Id:
          $ref: '#/components/headers/PeekerRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Conflict:
      description: Conflicts with an existing resource or idempotency key.
      headers:
        Peeker-Request-Id:
          $ref: '#/components/headers/PeekerRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    ProviderCredentialsRejected:
      description: >-
        Provider rejected the credentials (or an EmailBison super-admin key was
        used).
      headers:
        Peeker-Request-Id:
          $ref: '#/components/headers/PeekerRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          examples:
            invalid_credentials:
              value:
                error:
                  code: provider_credentials_invalid
                  message: Provider credentials were rejected.
                  request_id: 550e8400-e29b-41d4-a716-446655440000
                  field: credentials
            super_admin_key:
              value:
                error:
                  code: emailbison_super_admin_key
                  message: >-
                    Use an EmailBison workspace/user API key; super-admin keys
                    are not accepted.
                  request_id: 550e8400-e29b-41d4-a716-446655440000
                  field: credentials.api_key
    RateLimited:
      description: Too many requests. Wait `error.details.retry_after_seconds`, then retry.
      headers:
        Peeker-Request-Id:
          $ref: '#/components/headers/PeekerRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    InternalError:
      description: Unexpected server error. Retry with backoff and keep the request ID.
      headers:
        Peeker-Request-Id:
          $ref: '#/components/headers/PeekerRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    ProviderUnavailable:
      description: Provider is temporarily unavailable. Retry with backoff.
      headers:
        Peeker-Request-Id:
          $ref: '#/components/headers/PeekerRequestId'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        Partner API key (`pk_live_…` or `pk_test_…`).
      x-default: Bearer pk_test_<your-test-key>

````