---
title: "Create Contact Export"
method: POST
path: "/v1/contacts/exports"
tags: ["Contacts"]
---

# Create Contact Export

`POST /v1/contacts/exports`

## Request

- Base URL: `https://api.migma.ai`
- URL: `https://api.migma.ai/v1/contacts/exports`
- Auth: API key in header `Authorization`

Queue a full filtered CSV export. Requires audience:read and brand ownership. Returns 202 only after enqueue. Poll exact job for download URL; no email is sent by this API. Idempotency-Key replay uses a stable queue job ID and rejects different request bodies. Jobs retained up to seven days. Filters use tag IDs. Export does not synchronize external services.

## Headers

- `Idempotency-Key` string

## Request body

- CreateContactExportRequest
  - `projectId` string, required
  - `segmentId` string
  - `filters` object: Each group combines its conditions and child groups using match: all (AND) by default, or any (OR). Values within one condition keep their existing semantics. At most 3 nested group levels below the root (root depth 0), 10 child groups per group, and 50 conditions across the entire tree. Each nonempty tags or excludeTags array, status, validationStatus, nonempty customFields key, fields entry, or activity entry counts as one condition; group containers do not count. Nested groups must contain a condition or a nonempty child group; the root may be empty. Campaign-specific activity requires all in its own group and every ancestor, but an any sibling is allowed. Send eligibility and project scope always apply outside these conditions.
    - `match` 'all' | 'any'
    - `groups` AudienceFilters[]: Child condition groups, combined with this group's conditions using its match mode. At most 3 levels below root. Each child must be nonempty.
      - `match` 'all' | 'any'
      - `groups` AudienceFilters[]: Child condition groups, combined with this group's conditions using its match mode. At most 3 levels below root. Each child must be nonempty.
      - `tags` string[]
      - `excludeTags` string[]
      - `status` 'subscribed' | 'unsubscribed' | 'non-subscribed' | 'bounced'
      - `validationStatus` 'valid' | 'invalid' | 'risky' | 'unknown'
      - `customFields` object
      - `fields` object[]
        - `key` string, required
        - `mode` 'is' | 'is_not' | 'starts_with' | 'ends_with' | 'contains' | 'not_contains' | 'date_after' | 'date_before' | 'date_on' | 'date_between' | 'number_gt' | 'number_gte' | 'number_lt' | 'number_lte' | 'number_between'
        - `values` string[], required
      - `activity` object[]
        - `action` 'sent' | 'opened' | 'clicked', required
        - `channel` 'email': Email is the only supported activity channel today.
        - `mode` 'within' | 'before' | 'never' | 'between', required: between is reserved and currently rejected.
        - `unit` 'hours' | 'days'
        - `amount` integer: Required for within/before. days: 1-365, hours: 1-8760. With minCount or maxCount, the period is at most 90 days (2160 hours).
        - `from` string, date-time
        - `to` string, date-time
        - `campaignId` string: Scopes opened or clicked to one campaign. Cannot be combined with minCount or maxCount.
        - `minCount` integer: Matches contacts who received, opened, or clicked at least this many emails in the period set by unit and amount. Only with mode within, a period of at most 90 days (2160 hours), and no campaignId. Combine with maxCount for a range; minCount cannot be greater than maxCount. Each email counts once, however often it was opened or clicked. Opened and clicked exclude bot activity, including Apple Mail Privacy Protection prefetches, and a clicked email also counts as opened.
        - `maxCount` integer: Matches contacts who received, opened, or clicked at most this many emails in the period set by unit and amount, including contacts with none in the period. Same rules and counting as minCount.
    - `tags` string[]
    - `excludeTags` string[]
    - `status` 'subscribed' | 'unsubscribed' | 'non-subscribed' | 'bounced'
    - `validationStatus` 'valid' | 'invalid' | 'risky' | 'unknown'
    - `customFields` object
    - `fields` object[]
      - `key` string, required
      - `mode` 'is' | 'is_not' | 'starts_with' | 'ends_with' | 'contains' | 'not_contains' | 'date_after' | 'date_before' | 'date_on' | 'date_between' | 'number_gt' | 'number_gte' | 'number_lt' | 'number_lte' | 'number_between'
      - `values` string[], required
    - `activity` object[]
      - `action` 'sent' | 'opened' | 'clicked', required
      - `channel` 'email': Email is the only supported activity channel today.
      - `mode` 'within' | 'before' | 'never' | 'between', required: between is reserved and currently rejected.
      - `unit` 'hours' | 'days'
      - `amount` integer: Required for within/before. days: 1-365, hours: 1-8760. With minCount or maxCount, the period is at most 90 days (2160 hours).
      - `from` string, date-time
      - `to` string, date-time
      - `campaignId` string: Scopes opened or clicked to one campaign. Cannot be combined with minCount or maxCount.
      - `minCount` integer: Matches contacts who received, opened, or clicked at least this many emails in the period set by unit and amount. Only with mode within, a period of at most 90 days (2160 hours), and no campaignId. Combine with maxCount for a range; minCount cannot be greater than maxCount. Each email counts once, however often it was opened or clicked. Opened and clicked exclude bot activity, including Apple Mail Privacy Protection prefetches, and a clicked email also counts as opened.
      - `maxCount` integer: Matches contacts who received, opened, or clicked at most this many emails in the period set by unit and amount, including contacts with none in the period. Same rules and counting as minCount.
    - `search` string

## Response `202`

Export queued (or existing replay job returned)

- ApiResponseContactExportStatus
  - `success` boolean, required
  - `data` object, nullable
    - `jobId` string, required
    - `projectId` string, required
    - `status` 'pending' | 'processing' | 'completed' | 'failed', required
    - `contactCount` integer, required: Estimated count until completed; actual exported rows on completion.
    - `fileName` string, required
    - `createdAt` string, date-time, required
    - `completedAt` string, date-time, nullable, required
    - `downloadUrl` string, uri: Private signed URL present only when completed; expires in one hour.
    - `downloadExpiresAt` string, date-time
    - `error` string
  - `error` string, nullable

## Other responses

- `400`: Bad request
- `401`: Unauthorized
- `403`: Forbidden - Missing required permissions or access denied
- `404`: Resource not found
- `409`: Replay key already used for different body
- `503`: Queue temporarily unavailable

## Changes

- **2026-10-22** `7d6c766ccd54`: 2 info
  - added the new optional request property `filters/allOf[#/components/schemas/AudienceFilters]/activity/items/maxCount`
  - added the new optional request property `filters/allOf[#/components/schemas/AudienceFilters]/activity/items/minCount`
- **2026-10-01** `9697a55a9fec`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/migma/apis/migma-ai-api-v1/changes/v1/contacts/exports/post.md)

---

[API](https://skmtc.dev/migma/apis/migma-ai-api-v1.md) - [All operations](https://skmtc.dev/migma/apis/migma-ai-api-v1/llms.txt) - [OpenAPI document](https://skmtc.dev/migma/apis/migma-ai-api-v1/revisions/2ce0e1e4d856?raw)
