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

# Scan another tool

> Check a key for another outreach tool and scan that account, before anything is imported. Opens an import in status `scanned` that holds the scan; `POST /api/integrations/migrations/{id}` starts it.

Supported tools and the credentials each takes:

| `provider` | `credentials` |
| --- | --- |
| `instantly` | `api_key` (an API V2 key) |
| `smartlead` | `api_key` |
| `lemlist` | `api_key` |
| `apollo` | `api_key` (a master key, for sequences to come over) |
| `snovio` | `client_id`, `client_secret` |
| `saleshandy` | `api_key` |

Only the fields a tool takes are kept. The key is stored encrypted on the import and erased when the import finishes or is cancelled. It is never returned.

To import contacts from a CRM or support tool already connected under **Integrations** (HubSpot, Pipedrive, Attio, Close, Zendesk, Intercom, Freshdesk), send `connection_id` instead. The connection's stored credentials are used and nothing is pasted.

Requires the `write` permission.



## OpenAPI

````yaml POST /api/integrations/migrations
openapi: 3.1.0
info:
  title: ManyPI API
  description: >-
    The REST API behind ManyPI — AI lead generation, email validation,
    cold-email outreach, agent runs and web scraping. Every endpoint is
    authenticated with a ManyPI API key (`mpi_...`) or an OAuth 2.1 access
    token, and honours the permissions granted to that credential.
  version: 2.0.0
  contact:
    name: ManyPI Support
    email: support@manypi.com
    url: https://manypi.com
servers:
  - url: https://app.manypi.com
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Leads
    description: Find, store, enrich and manage leads.
  - name: Email validation
    description: Verify that lead addresses can receive mail.
  - name: Outreach
    description: Inboxes, sequences, campaigns, sending and replies.
  - name: Agent
    description: Agent runs, replies and skills.
  - name: Scraping
    description: Scrapers, runs and extracted data.
  - name: Endpoints
    description: Publish scrapers as typed REST endpoints.
  - name: Account
    description: Key validation and account information.
  - name: Imports
    description: >-
      Bring campaigns, leads and the do-not-contact list over from another
      outreach tool.
paths:
  /api/integrations/migrations:
    post:
      tags:
        - Imports
      summary: Scan another tool
      description: >-
        Check a key for another outreach tool and scan that account, before
        anything is imported. Opens an import in status `scanned` that holds the
        scan; `POST /api/integrations/migrations/{id}` starts it.


        Supported tools and the credentials each takes:


        | `provider` | `credentials` |

        | --- | --- |

        | `instantly` | `api_key` (an API V2 key) |

        | `smartlead` | `api_key` |

        | `lemlist` | `api_key` |

        | `apollo` | `api_key` (a master key, for sequences to come over) |

        | `snovio` | `client_id`, `client_secret` |

        | `saleshandy` | `api_key` |


        Only the fields a tool takes are kept. The key is stored encrypted on
        the import and erased when the import finishes or is cancelled. It is
        never returned.


        To import contacts from a CRM or support tool already connected under
        **Integrations** (HubSpot, Pipedrive, Attio, Close, Zendesk, Intercom,
        Freshdesk), send `connection_id` instead. The connection's stored
        credentials are used and nothing is pasted.


        Requires the `write` permission.
      operationId: scanImport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - title: From another outreach tool
                  type: object
                  required:
                    - provider
                    - credentials
                  properties:
                    provider:
                      type: string
                      enum:
                        - instantly
                        - smartlead
                        - lemlist
                        - apollo
                        - snovio
                        - saleshandy
                    credentials:
                      type: object
                      description: The tool's credential fields, see the table above.
                      additionalProperties:
                        type: string
                        maxLength: 4000
                - title: From a connected CRM or support tool
                  type: object
                  required:
                    - connection_id
                  properties:
                    connection_id:
                      type: string
                      format: uuid
            example:
              provider: instantly
              credentials:
                api_key: your-instantly-v2-key
      responses:
        '201':
          description: The import, in status `scanned`, with what the scan found.
          content:
            application/json:
              schema:
                type: object
                properties:
                  job:
                    $ref: '#/components/schemas/MigrationJob'
        '400':
          description: >-
            The body failed validation, the tool is not supported, a required
            credential field is empty, or the other tool rejected the key
            (`code: provider_auth`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: The workspace is locked for billing (past due or ended plan).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: >-
            The other tool refused the key the access it needs (`code:
            provider_forbidden`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: >-
            No such connection in this workspace (`connection_id`), or the other
            tool answered not found (`code: provider_not_found`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: >-
            The other tool is rate limiting (`code: provider_rate_limited`).
            Retry shortly.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '502':
          description: >-
            The other tool's API failed (`code: provider_unavailable`). Retry
            shortly.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: Imports are not switched on for this deployment yet.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    MigrationJob:
      type: object
      properties:
        id:
          type: string
          format: uuid
        provider:
          type: string
          description: The tool imported from, e.g. `instantly` or `hubspot`.
        account_label:
          type: string
          nullable: true
          description: The account as the other tool names it.
        status:
          type: string
          enum:
            - scanned
            - running
            - done
            - failed
            - cancelled
        scan:
          type: object
          description: What the scan found.
          properties:
            label:
              type: string
            campaigns:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  status:
                    type: string
                    description: The other tool's own word for it.
                  emailSteps:
                    type: integer
                    nullable: true
                  leads:
                    type: integer
                    nullable: true
            leadsTotal:
              type: integer
              nullable: true
              description: Null when the tool cannot count cheaply.
            blocklistTotal:
              type: integer
              nullable: true
            accounts:
              type: array
              description: Sending mailboxes in the other tool. They are listed, not moved.
              items:
                type: object
                properties:
                  email:
                    type: string
                  provider:
                    type: string
                    enum:
                      - google
                      - microsoft
                      - smtp
                      - other
                  name:
                    type: string
                    nullable: true
            notes:
              type: array
              items:
                type: string
              description: >-
                Worth knowing before starting, e.g. a key that cannot read
                sequences.
        options:
          $ref: '#/components/schemas/MigrationOptions'
        progress:
          type: object
          nullable: true
          properties:
            phase:
              type: string
              enum:
                - blocklist
                - campaigns
                - leads
                - done
            campaignIndex:
              type: integer
            campaignMap:
              type: object
              additionalProperties:
                type: string
              description: Other tool's campaign id to ManyPI campaign id.
            counts:
              type: object
              properties:
                suppressed:
                  type: integer
                  description: Addresses and domains added to do-not-contact.
                campaigns:
                  type: integer
                  description: Draft campaigns created.
                steps:
                  type: integer
                  description: Email steps recreated across them.
                created:
                  type: integer
                  description: New leads.
                updated:
                  type: integer
                  description: Existing leads merged into.
                skipped:
                  type: integer
                  description: >-
                    Rows not written: duplicates, rows with no email or other
                    identity, and conflicts.
                overLimit:
                  type: integer
                  description: Leads left out because the plan's lead allowance was full.
                failed:
                  type: integer
                enrolled:
                  type: integer
                  description: Leads queued in their new draft campaign.
                customFields:
                  type: integer
                  description: Custom variables created as lead fields.
            warnings:
              type: array
              items:
                type: string
            skippedSteps:
              type: object
              additionalProperties:
                type: array
                items:
                  type: string
              description: >-
                Per ManyPI campaign, steps that could not come over, e.g. `2
                LinkedIn steps`.
            leadsSeen:
              type: integer
        result:
          type: object
          nullable: true
          description: >-
            Set when the import ends: `counts`, `campaignMap`, `warnings` and
            `skippedSteps`, as in `progress`.
        error:
          type: string
          nullable: true
          description: Why a `failed` import stopped. What came over before it is kept.
        created_at:
          type: string
          format: date-time
        started_at:
          type: string
          format: date-time
          nullable: true
        finished_at:
          type: string
          format: date-time
          nullable: true
    Error:
      type: object
      properties:
        error:
          description: >-
            Human-readable message. A few validation failures (creating or
            updating an endpoint, bulk lead updates and deletes) send a list of
            the individual problems here instead of one string.
          oneOf:
            - type: string
            - type: array
              items:
                type: object
                properties:
                  path:
                    type: array
                    items:
                      type:
                        - string
                        - integer
                  message:
                    type: string
        code:
          type: string
          description: >-
            A machine-readable code on gated failures: `lead_limit`,
            `outreach_locked`, `validation_credits_exhausted`.
        details:
          description: >-
            Extra detail on some failures, such as the list of parameters that
            failed an endpoint's `param_schema`.
    MigrationOptions:
      type: object
      properties:
        campaigns:
          type: array
          items:
            type: string
          maxItems: 500
          default: []
          description: >-
            Campaign ids from `scan.campaigns`. Leads are read through these
            campaigns, so for lemlist and Smartlead a lead in no chosen campaign
            stays behind; Instantly also brings leads that sit in lists, and
            Apollo, Snov.io and Saleshandy bring every contact.
        recreateCampaigns:
          type: boolean
          default: true
          description: Recreate the chosen campaigns as drafts with their email steps.
        importLeads:
          type: boolean
          default: true
        doNotContact:
          type: boolean
          default: true
          description: Bring the block list, plus leads that bounced or unsubscribed there.
        enrollNotContacted:
          type: boolean
          default: true
          description: >-
            Queue leads the old tool never emailed into their new draft
            campaign. Needs `recreateCampaigns` and a paid plan.
        duplicates:
          type: string
          enum:
            - merge
            - overwrite
            - skip
          default: merge
          description: >-
            A lead that already exists: `merge` fills in only its empty fields,
            `overwrite` replaces its fields, `skip` leaves it alone.
        asCustomers:
          type: boolean
          default: false
          description: >-
            Contact imports: also put every imported address on do-not-contact
            (existing customers).
  responses:
    Unauthorized:
      description: >-
        Missing, invalid or expired credential — or a key that lacks the
        permission this route requires.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        A ManyPI API key (`mpi_...`) from the profile menu → API Access in the
        dashboard, or an OAuth 2.1 access token obtained through the MCP consent
        flow.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.