DealHub CRM Import API

Start, track, inspect and retry asynchronous imports of buyer accounts and contacts from a tenant's connected CRM into DealHub, with per-id lookup and aggregate success/failure counts.

OpenAPI Specification

dealhub-crm-import-api-openapi.yml Raw ↑
openapi: 3.0.3
info:
  title: CRM Import API
  description: 'Public, token-authenticated services that bulk-migrate a tenant''s buyer accounts and their contacts out of
    the tenant''s connected CRM (Salesforce, HubSpot, or Microsoft Dynamics) and into DealHub.


    The caller provides a list of buyer-account CRM ids; for each id the system fetches the matching account and all of its
    contacts from the CRM and persists them. The import is asynchronous — the start call returns a `request_id` immediately
    and the work runs in the background. Progress and per-record outcomes are tracked through the status / detail / lookup
    services, and a partially failed import can be re-run with the retry service.


    Accounts that already exist in DealHub (matched by CRM id) and ids not found in the CRM are skipped, so the import is
    safe to run again.'
  version: 1.0.0
servers:
- url: https://api.dealhub.io
  description: The base URL for your DealHub instance.
  variables:
    your-dealhub-instance:
      default: app
      description: Your specific DealHub instance name (e.g., 'app', 'service-eu1').
security:
- bearerToken: []
tags:
- name: CRM Import
  description: Bulk migration of buyer accounts and contacts from the tenant's connected CRM into DealHub.
paths:
  /api/v1/accounts/crm-import:
    post:
      tags:
      - CRM Import
      summary: Start CRM Import
      description: Starts (or joins) a CRM import for the authenticated tenant. Accepts the list of buyer-account CRM ids
        to migrate; duplicate ids are ignored. If an import is already running for the tenant, its `request_id` is returned
        instead of starting a new one.
      operationId: startCrmImport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StartCrmImportRequest'
            examples:
              startCrmImportExample:
                summary: Start an import for two buyer accounts
                value:
                  buyer_account_crm_ids:
                  - 0011t00000ABCDE
                  - 0011t00000FGHIJ
      responses:
        '200':
          description: Import started (or joined). The work runs in the background.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CrmImportRequestResponse'
              examples:
                startedExample:
                  summary: Import started
                  value:
                    request_id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
        '400':
          description: Invalid payload, missing ids, or the tenant's CRM integration is not supported.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                invalidFormat:
                  summary: Invalid payload format
                  value:
                    message: 'Invalid payload format. Supported format: JSON'
                missingIds:
                  summary: No ids provided
                  value:
                    message: No buyer_account_crm_ids provided.
                unsupportedIntegration:
                  summary: Unsupported CRM integration
                  value:
                    message: 'Unsupported integration: <TYPE>'
        '403':
          description: Unauthenticated. The bearer token is missing or invalid.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                unauthenticated:
                  value:
                    message: Unauthenticated
        '500':
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                serverError:
                  value:
                    message: Internal server error
  /api/v1/accounts/crm-import/status:
    get:
      tags:
      - CRM Import
      summary: Get CRM Import Status
      description: 'Retrieves the summary of an import: aggregate counts plus a flat list of per-id failures with reasons.
        This is the service to poll. When `request_id` is omitted, the tenant''s most recent import is used.'
      operationId: getCrmImportStatus
      parameters:
      - name: request_id
        in: query
        required: false
        description: ID of the import request. If omitted, the tenant's most-recent import is returned.
        schema:
          type: string
          maxLength: 64
      responses:
        '200':
          description: Import status summary.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CrmImportStatusResponse'
              examples:
                runningExample:
                  summary: Import in progress with some failures
                  value:
                    request_id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
                    status: RUNNING
                    total: 1200
                    processed: 850
                    succeeded: 800
                    failed: 12
                    skipped: 38
                    failures:
                    - crm_id: 0011t00000XYZAB
                      reason: HubSpot rate limit (429)
        '403':
          description: Unauthenticated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No import found for the tenant (or the given request_id).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                notFound:
                  value:
                    message: No CRM import job found.
        '500':
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/accounts/crm-import/detail:
    get:
      tags:
      - CRM Import
      summary: Get CRM Import Detail
      description: 'Retrieves the full picture of an import: the same summary as the status service plus every buyer account
        and each of its contacts, each with its own state and (if failed) reason. Use this to see exactly which contacts within
        an account did or did not migrate. When `request_id` is omitted, the tenant''s most recent import is used.'
      operationId: getCrmImportDetail
      parameters:
      - name: request_id
        in: query
        required: false
        description: ID of the import request. If omitted, the tenant's most-recent import is returned.
        schema:
          type: string
          maxLength: 64
      responses:
        '200':
          description: Full per-account, per-contact import detail.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CrmImportDetailResponse'
              examples:
                detailExample:
                  summary: One succeeded account with a failed contact, one skipped account
                  value:
                    request_id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
                    status: COMPLETED
                    total: 2
                    processed: 2
                    succeeded: 1
                    failed: 0
                    skipped: 1
                    accounts:
                    - crm_id: 0011t00000ABCDE
                      state: SUCCEEDED
                      reason: null
                      contacts:
                      - crm_id: 0031t00000AAA11
                        email: a@example.com
                        state: SUCCEEDED
                        reason: null
                      - crm_id: null
                        email: b@example.com
                        state: FAILED
                        reason: duplicate email
                    - crm_id: 0011t00000FGHIJ
                      state: SKIPPED
                      reason: null
                      contacts: []
        '403':
          description: Unauthenticated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No import found for the tenant (or the given request_id).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                notFound:
                  value:
                    message: No CRM import job found.
        '500':
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/accounts/crm-import/lookup:
    get:
      tags:
      - CRM Import
      summary: Lookup CRM Import Id
      description: 'Resolves a single CRM id within an import: whether it is a buyer account or a contact (or not part of
        the import), its state, and the failure reason if any. For a contact match, the buyer-account CRM id it belongs to
        is also returned.'
      operationId: lookupCrmImportId
      parameters:
      - name: crm_id
        in: query
        required: true
        description: The CRM id to resolve (account or contact).
        schema:
          type: string
      - name: request_id
        in: query
        required: false
        description: ID of the import request. If omitted, the tenant's most-recent import is used.
        schema:
          type: string
          maxLength: 64
      responses:
        '200':
          description: Lookup result for the given CRM id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CrmImportLookupResponse'
              examples:
                contactMatch:
                  summary: Id resolves to a contact
                  value:
                    request_id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
                    crm_id: 0031t00000AAA11
                    type: CONTACT
                    account_crm_id: 0011t00000ABCDE
                    state: SUCCEEDED
                    reason: null
        '400':
          description: Missing required crm_id query parameter.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                missingCrmId:
                  value:
                    message: Query parameter 'crm_id' is required.
        '403':
          description: Unauthenticated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No import found for the tenant (or the given request_id).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                notFound:
                  value:
                    message: No CRM import job found.
        '500':
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/accounts/crm-import/retry:
    post:
      tags:
      - CRM Import
      summary: Retry CRM Import
      description: 'Re-runs the failed ids of an import (plus any unfinished ids from an interruption) under the same `request_id`.
        Already-imported accounts are skipped, so retrying is safe. No-op when there is nothing to reprocess. Like Start CRM
        Import, retry is asynchronous: it re-enqueues the failed ids and returns the `request_id` immediately — poll Get CRM
        Import Status (same `request_id`) for the outcome as the re-run progresses.'
      operationId: retryCrmImport
      parameters:
      - name: request_id
        in: query
        required: false
        description: ID of the import request to retry. If omitted, the tenant's most-recent import is retried.
        schema:
          type: string
          maxLength: 64
      responses:
        '200':
          description: Retry started (or nothing to reprocess). Same response shape as Start CRM Import.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CrmImportRequestResponse'
              examples:
                retryExample:
                  value:
                    request_id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
        '400':
          description: The tenant's CRM integration is not supported for import.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                unsupportedIntegration:
                  value:
                    message: 'Unsupported integration: <TYPE>'
        '403':
          description: Unauthenticated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No import found for the tenant (or the given request_id).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                notFound:
                  value:
                    message: No CRM import job found.
        '500':
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  securitySchemes:
    bearerToken:
      type: http
      scheme: bearer
      description: DealHub authentication token generated by the CPQ administrator and shared with the consuming application
        in advance. The tenant is resolved from the token — it is never passed in the request.
  schemas:
    StartCrmImportRequest:
      type: object
      properties:
        buyer_account_crm_ids:
          type: array
          description: List of buyer-account CRM ids to import.
          items:
            type: string
          minItems: 1
      required:
      - buyer_account_crm_ids
    CrmImportRequestResponse:
      type: object
      properties:
        request_id:
          type: string
          description: ID of the asynchronous import request. Use it for the status / detail / lookup / retry services, or
            wait for the `crmImport` completion webhook (same request_id).
    CrmImportFailure:
      type: object
      properties:
        crm_id:
          type: string
          description: The buyer-account CRM id that failed.
        reason:
          type: string
          description: Human-readable failure reason.
    CrmImportStatusResponse:
      type: object
      properties:
        request_id:
          type: string
          description: ID of the import request.
        status:
          type: string
          enum:
          - RUNNING
          - COMPLETED
          - INTERRUPTED
          description: COMPLETED does not imply zero failures — check the failed count.
        total:
          type: integer
          description: Number of (de-duplicated) ids in the import.
        processed:
          type: integer
          description: Number of ids that reached a terminal state (succeeded + failed + skipped).
        succeeded:
          type: integer
          description: Number of accounts imported.
        failed:
          type: integer
          description: Number of ids that errored. See failures for reasons.
        skipped:
          type: integer
          description: Number of ids skipped — already in DealHub, or not found in the CRM.
        failures:
          type: array
          description: Per-id failures. Empty when nothing failed.
          items:
            $ref: '#/components/schemas/CrmImportFailure'
    CrmImportContactResult:
      type: object
      properties:
        crm_id:
          type: string
          nullable: true
          description: Contact CRM id. Null when the contact has no CRM id (identified by email).
        email:
          type: string
          format: email
          description: Contact email.
        state:
          type: string
          enum:
          - SUCCEEDED
          - FAILED
        reason:
          type: string
          nullable: true
          description: Failure reason (contact level). Present only when state = FAILED.
    CrmImportAccountResult:
      type: object
      properties:
        crm_id:
          type: string
          description: Buyer-account CRM id.
        state:
          type: string
          enum:
          - SUCCEEDED
          - FAILED
          - SKIPPED
          - PENDING
        reason:
          type: string
          nullable: true
          description: Failure reason (account level). Present only when state = FAILED.
        contacts:
          type: array
          description: Per-contact results for the account.
          items:
            $ref: '#/components/schemas/CrmImportContactResult'
    CrmImportDetailResponse:
      allOf:
      - $ref: '#/components/schemas/CrmImportStatusResponse'
      - type: object
        properties:
          accounts:
            type: array
            description: Per buyer-account results.
            items:
              $ref: '#/components/schemas/CrmImportAccountResult'
    CrmImportLookupResponse:
      type: object
      properties:
        request_id:
          type: string
          description: ID of the import request.
        crm_id:
          type: string
          description: The looked-up CRM id.
        type:
          type: string
          enum:
          - ACCOUNT
          - CONTACT
          - NOT_FOUND
          description: What the id resolved to.
        account_crm_id:
          type: string
          nullable: true
          description: For a contact match, the buyer-account CRM id it belongs to. Present only when type = CONTACT.
        state:
          type: string
          nullable: true
          enum:
          - SUCCEEDED
          - FAILED
          - SKIPPED
          - PENDING
          - null
          description: Empty when type = NOT_FOUND.
        reason:
          type: string
          nullable: true
          description: Failure reason, if any.
    ErrorResponse:
      type: object
      description: A generic error response. The specific message will vary based on the context.
      properties:
        message:
          type: string
          example: Unauthenticated
x-readme:
  explorer-enabled: true
  proxy-enabled: true