Sign in Create account
API reference

CSV exports API

CSV files with emails, domains, logs, keys, suppressions and contacts.

Generate CSV files with emails, domains, logs, API keys, suppressions, contacts (properties go in one JSON column), the submissions of a form or the visits to a form. The export runs in the background: create it, poll until status is completed and download it from download_url. Files stay available for 7 days and the download link is valid for 1 hour (fetch the export again to generate another one). Exporting is part of the plan (exports): without it, creating one answers 403 plan_limit_reached.

#List exports

GET/exports

Full-access key

Accepts limit, after and before.

GET /exports

cURL

curl https://api.postal.so/exports \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/exports', {
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
  },
});

const data = await response.json();

Python

import os
import requests

response = requests.get(
    "https://api.postal.so/exports",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->get('exports', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);

$data = json_decode($response->getBody(), true);
200Response
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "export",
      "id": "019a1c10-7e8f-7a90-8b1c-2d3e4f5a6b7c",
      "type": "emails",
      "filters": {
        "status": "bounced"
      },
      "status": "completed",
      "rows": 312,
      "total_rows": 312,
      "error": null,
      "created_at": "2026-10-24T13:20:00.000000Z",
      "completed_at": "2026-10-24T13:20:04.000000Z",
      "expires_at": "2026-10-31T13:20:00.000000Z",
      "filename": "postal-emails-2026-10-24-132000.csv",
      "download_url": "https://api.postal.so/exports/019a1c10-7e8f-7a90-8b1c-2d3e4f5a6b7c/download?expires=1761315600&signature=7b1d…"
    }
  ]
}

#Create an export

POST/exports

Full-access key

The filters are the same as those of the corresponding list: emails, domains, logs, API keys and contacts (suppressions have no filters). For form_submissions and form_visits, the form_id filter is required (one form per export) and the form must exist (404 if not). If an identical export is still running, it is returned (status 200) instead of creating another. Each team can have up to 3 exports running at a time. For form_submissions, an answer typed in the “Other” option comes out as Other: text or Outro: texto, in the language of the request that created the export (Accept-Language).

Request body

  • typestringrequired
    emails, domains, logs, api_keys, suppressions, contacts, form_submissions or form_visits.
  • filtersobject
    List filters, such as {"status": "bounced", "start_date": "2026-10-01"}. For form_submissions, only form_id. For form_visits: form_id and, optionally, from and to (a date or a date-time; a date alone in to covers the whole day).

Errors

  • 403plan_limit_reached
    The plan does not include exports.
  • 404not_found
    The form in form_id does not exist.
  • 422validation_error
    Invalid type or filter, or a missing form_id.
  • 429rate_limit_exceeded
    There are already 3 exports running.
POST /exports

cURL

curl -X POST https://api.postal.so/exports \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "emails",
    "filters": {
      "status": "bounced"
    }
  }'

Node.js

const response = await fetch('https://api.postal.so/exports', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'emails',
    filters: {
      status: 'bounced',
    },
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/exports",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    json={
        "type": "emails",
        "filters": {
            "status": "bounced",
        },
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('exports', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'json' => [
        'type' => 'emails',
        'filters' => [
            'status' => 'bounced',
        ],
    ],
]);

$data = json_decode($response->getBody(), true);
201Response
{
  "object": "export",
  "id": "019a1c10-7e8f-7a90-8b1c-2d3e4f5a6b7c",
  "type": "emails",
  "filters": {
    "status": "bounced"
  },
  "status": "pending",
  "rows": 0,
  "total_rows": null,
  "error": null,
  "created_at": "2026-10-24T13:20:00.000000Z",
  "completed_at": null,
  "expires_at": "2026-10-31T13:20:00.000000Z",
  "filename": "postal-emails-2026-10-24-132000.csv",
  "download_url": null
}

#Get an export

GET/exports/{id}

Full-access key

Shows the progress (rows out of total_rows) and, when ready, the download link. status: pending, processing, completed or failed (with error).

Path parameters

  • idstringrequired
    Export ID.

Errors

  • 404not_found
    The resource does not exist or belongs to another team.
GET /exports/{id}

cURL

curl https://api.postal.so/exports/019a1c10-7e8f-7a90-8b1c-2d3e4f5a6b7c \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/exports/019a1c10-7e8f-7a90-8b1c-2d3e4f5a6b7c', {
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
  },
});

const data = await response.json();

Python

import os
import requests

response = requests.get(
    "https://api.postal.so/exports/019a1c10-7e8f-7a90-8b1c-2d3e4f5a6b7c",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->get('exports/019a1c10-7e8f-7a90-8b1c-2d3e4f5a6b7c', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);

$data = json_decode($response->getBody(), true);
200Response
{
  "object": "export",
  "id": "019a1c10-7e8f-7a90-8b1c-2d3e4f5a6b7c",
  "type": "emails",
  "filters": {
    "status": "bounced"
  },
  "status": "completed",
  "rows": 312,
  "total_rows": 312,
  "error": null,
  "created_at": "2026-10-24T13:20:00.000000Z",
  "completed_at": "2026-10-24T13:20:04.000000Z",
  "expires_at": "2026-10-31T13:20:00.000000Z",
  "filename": "postal-emails-2026-10-24-132000.csv",
  "download_url": "https://api.postal.so/exports/019a1c10-7e8f-7a90-8b1c-2d3e4f5a6b7c/download?expires=1761315600&signature=7b1d…"
}