---
title: "Download an import report (Beta)"
method: GET
path: "/migrations/{id}/report"
tags: ["Migrations"]
---

# Download an import report (Beta)

`GET /migrations/{id}/report`

## Request

- Base URL: `https://api.groundmail.dev`
- URL: `https://api.groundmail.dev/migrations/{id}/report`
- Auth: HTTP bearer

The import report as a file. CSV (the default) has the columns `section`, `item`, `status` and `detail`: one row per domain (with the DNS changes still to make), webhook and template, the suppression totals and each skip reason, resources that could not be read, next steps and notes. `format=json` returns the same report as `GET /migrations/{id}`.

## Path parameters

- `id` string, uuid, required

## Query parameters

- `format` 'csv' | 'json'

## Response `200`

The report, as an attachment

- MigrationResponse
  - `migration` Migration, required
    - `id` string, uuid, required: Shared by every request of one import
    - `provider` 'sendgrid' | 'postmark' | 'mailgun', required
    - `dry_run` boolean, required
    - `status` 'in_progress' | 'completed' | 'partial' | 'failed', required: `partial` when some items or resources could not be imported (each is listed); `failed` when nothing could be read.
    - `started_at` string, date-time, required
    - `resources` string[], required
    - `has_more` boolean, required
    - `next_cursor` string, nullable, required
    - `retry_after_seconds` integer, nullable: Wait this long before continuing (the provider rate-limited the import).
    - `domains` object
      - `totals` MigrationTotals: Running totals for the whole import. Keys: `found`, `imported`, `skipped`, `failed`; templates add `needs_attention`; suppressions add `already_suppressed`, `bounce`, `complaint`, `unsubscribe`, `manual` and `product_opt_out` (opt-outs that only covered some of your mail at the old provider; they stop product email only). In a dry run `imported` counts what would be imported.
      - `items` object[]: Domains handled by this request
        - `name` string
        - `status` 'imported' | 'would_import' | 'needs_attention' | 'skipped' | 'failed'
        - `domain_id` string, nullable
        - `verified_at_source` boolean, nullable
        - `records` object[]: DNS records to publish (also available from GET /domains/{id})
          - `type` string
          - `host` string
          - `value` string
          - `purpose` string
        - `dns_plan` MigrationDnsPlan
          - `checked` boolean, required: False when DNS could not be read; every record is then listed as `add`.
          - `changes` MigrationDnsChange[], required
            - `action` 'add' | 'update' | 'keep' | 'done', required: `add` a new record; `update` the record at this name to `value` (for SPF, our include added to your existing record, never a second SPF record); `keep` yours as it is (for example your DMARC record); `done` when it is already published.
            - `type` string, required
            - `host` string, required
            - `value` string, required: The value to publish (for `keep`, what is there now)
            - `purpose` string, required
            - `current` string, nullable, required: What this name holds today, when it holds something relevant
            - `note` string, nullable, required
          - `keep_old_records` string, required: Which of the old provider's records to keep while you switch, and when to remove them
        - `dashboard_url` string, nullable
        - `message` string, nullable
    - `webhooks` object
      - `totals` MigrationTotals: Running totals for the whole import. Keys: `found`, `imported`, `skipped`, `failed`; templates add `needs_attention`; suppressions add `already_suppressed`, `bounce`, `complaint`, `unsubscribe`, `manual` and `product_opt_out` (opt-outs that only covered some of your mail at the old provider; they stop product email only). In a dry run `imported` counts what would be imported.
      - `items` object[]
        - `url` string
        - `status` 'imported' | 'would_import' | 'needs_attention' | 'skipped' | 'failed'
        - `webhook_id` string, nullable
        - `secret` string, nullable: Signing secret of a webhook this request created. Shown once.
        - `enabled` boolean: Imported webhooks start disabled until your handler accepts Groundmail events.
        - `source_events` string[]
        - `events` string[]
        - `unmapped_events` string[]
        - `message` string, nullable
    - `templates` object
      - `totals` MigrationTotals: Running totals for the whole import. Keys: `found`, `imported`, `skipped`, `failed`; templates add `needs_attention`; suppressions add `already_suppressed`, `bounce`, `complaint`, `unsubscribe`, `manual` and `product_opt_out` (opt-outs that only covered some of your mail at the old provider; they stop product email only). In a dry run `imported` counts what would be imported.
      - `items` object[]
        - `source_id` string
        - `name` string
        - `status` 'imported' | 'would_import' | 'needs_attention' | 'skipped' | 'failed'
        - `template_id` string, nullable
        - `alias` string, nullable
        - `published` boolean
        - `variables` string[]
        - `notes` string[]: What changed in conversion, and anything that did not convert
        - `message` string, nullable
    - `suppressions` object
      - `totals` MigrationTotals: Running totals for the whole import. Keys: `found`, `imported`, `skipped`, `failed`; templates add `needs_attention`; suppressions add `already_suppressed`, `bounce`, `complaint`, `unsubscribe`, `manual` and `product_opt_out` (opt-outs that only covered some of your mail at the old provider; they stop product email only). In a dry run `imported` counts what would be imported.
      - `skipped_reasons` object
      - `estimated` boolean
    - `errors` object[], required: Resources that could not be read at all (for example, a key without that permission)
      - `resource` string
      - `code` string
      - `message` string
    - `notes` string[], required
    - `attention` string[], required: What you still need to do (publish DNS, enable webhooks, review drafts)
    - `template_map` object[], required: Provider template id → Groundmail alias, for updating your send calls
      - `source_id` string
      - `alias` string
    - `code_changes` object[], nullable: When the import has finished, the send call before and after, per stack
      - `stack` 'node' | 'python' | 'curl'
      - `label` string
      - `diff` string
    - `agent_prompt` string, nullable: When the import has finished, a prompt for your coding agent to make the code change

## Other responses

- `400`: `format` is not `csv` or `json`
- `401`: Missing or invalid API key
- `404`: No import with this id in your workspace

---

[API](https://skmtc.dev/groundmail/apis/groundmail-api.md) · [All operations](https://skmtc.dev/groundmail/apis/groundmail-api/llms.txt) · [OpenAPI document](https://skmtc.dev/groundmail/apis/groundmail-api/revisions/479821c9fecf?raw)
