---
title: "Create a bulk PDF export"
method: POST
path: "/v1/erp/payouts/payables/pdf_exports"
tags: ["Payable"]
---

# Create a bulk PDF export

`POST /v1/erp/payouts/payables/pdf_exports`

## Request

- Base URL: `https://api.cardda.com`
- URL: `https://api.cardda.com/v1/erp/payouts/payables/pdf_exports`
- Auth: HTTP bearer

Starts an **asynchronous** bulk download of the PDFs of the selected payables
("cuentas por pagar"). The response is `202 Accepted` with a `PdfExport` whose `status`
starts as `fetching`; poll `GET /v1/erp/payouts/payables/pdf_exports/{id}` until it is
`completed` (or `failed`/`expired`) and then download the ZIP files in `files[].url`.

**Selecting payables**

- The payable filters go in the **querystring**, exactly as in
  `GET /v1/erp/payouts/payables` (List all payables) (e.g. `amount={"$lt": 11000}`). See
  [How to filter results](ref:filters).
- The control parameters (`company_id`, `scope`, `ids`, `excluded_ids`,
  `include_attachments`) can go in the JSON body or in the querystring. `company_id` and
  `scope` are **required** in one of the two places (`422`, `payables/pdf_export_scope`, if
  missing). Exports belong to the user, not to a company: these endpoints do not use the
  `company-id` header, and it does not replace `company_id`.
- `scope=all` exports every payable matching the filters minus `excluded_ids`;
  `scope=partial` exports only `ids` (ids from another company or not visible to the caller
  are ignored).
- Only payables of companies where the caller can list payables are included.

**Limits**

- At most **2.000** payables per export (`422`, `payables/pdf_export_limit`). Narrow the
  filters or split the export.
- **One export in flight per user** (`fetching` or `zipping`). A second request returns `409`
  with the `pdf_export_id` of the running export, so you can resume polling it.
- The ZIP files are kept for **7 days** after completion; then the export becomes `expired`.

**Result**

One ZIP per company (`<company name>_<date_from>_<date_to>.zip`) with one folder per document
type that has documents (folders such as `Facturas/`, `Boletas de honorarios/`, `Reembolsos/`,
`Anticipos/` or `Otros/`), and an `indice.csv` (UTF-8 with BOM, `;` separator) with the columns
`Empresa;Fecha emisión;Razón social emisor;RUT emisor;Tipo;Folio;Monto;Estado;Motivo;Archivo`.
`Estado` is `incluido` or `falló`; failed documents are listed with their `Motivo`, and are
summarized in `failures_by_reason`. When the export finishes, the user is also notified in-app
and by email, according to their notification preferences.

**Polling example**

```bash
# 1. Create the export (filters in the querystring, control params in the body)
created=$(curl -s -X POST 'https://api.cardda.com/v1/erp/payouts/payables/pdf_exports?amount=%7B%22%24lt%22%3A11000%7D' \
  -H 'Authorization: Bearer <api_key>' \
  -H 'Content-Type: application/json' \
  -d '{"company_id": "550e8400-e29b-41d4-a716-446655440000", "scope": "all"}')
# 202 {"id": "0d6f8a52-...", "status": "fetching", ...}: keep its id.
# 409 {"error_code": "payables/pdf_export_in_progress", "pdf_export_id": "..."}: resume that one.
export_id=$(echo "$created" | jq -r '.id // .pdf_export_id // empty')
if [ -z "$export_id" ]; then echo "$created" >&2; exit 1; fi  # 422: see error_code

# 2. Poll every few seconds until status is "completed", "failed" or "expired"
while true; do
  export=$(curl -s "https://api.cardda.com/v1/erp/payouts/payables/pdf_exports/$export_id" \
    -H 'Authorization: Bearer <api_key>')
  status=$(echo "$export" | jq -r .status)
  echo "$status $(echo "$export" | jq -c .progress)"
  case "$status" in completed|failed|expired) break ;; esac
  sleep 5
done

# 3. Download each ZIP. The URLs are signed for 1 hour and re-signed on every GET:
#    if a link expired, GET the export again to get a fresh one.
echo "$export" | jq -r '.files[] | "\(.url)\t\(.filename)"' |
  while IFS=$'\t' read -r url name; do curl -s -o "$name" "$url"; done
```

## Query parameters

- `company_id` string
- `scope` 'all' | 'partial'
- `ids[]` string[]
- `excluded_ids[]` string[]
- `include_attachments` boolean

## Parameters

- `#/paths/~1v1~1erp~1payouts~1reviews/get/parameters/5` (unresolved $ref)

## Request body

- object: Control parameters of a payable PDF export. They can be sent in this JSON body or in the querystring. The payable filters themselves always go in the querystring (see the endpoint description). `company_id` and `scope` are required, but they can be sent either here or in the querystring, so this body does not require them on its own.
  - `company_id` union: **Required** (here or in the querystring); the `company-id` header does not replace it. Company (or companies) whose payables are exported. In the querystring, send several as a JSON array, e.g. `company_id=["uuid1","uuid2"]`, as in the payables list. Only companies where the caller can list payables are considered.
    - string, uuid
    - string[]
  - `scope` 'all' | 'partial': - `all`: every payable matching the querystring filters, minus `excluded_ids`. - `partial`: only the payables in `ids`.
  - `ids` string[]: With `scope=partial`, the payables to export. Ids from another company or not visible to the caller are ignored.
  - `excluded_ids` string[]: With `scope=all`, payables to leave out (e.g. rows the user unchecked).
  - `include_attachments` boolean: Also include the attachments (user documents) of payables that have a PDF. Manual payables without a PDF but with attachments are **always** included with their attachments, regardless of this flag: they count as `included` (`indice.csv`: `Estado = incluido`, `Motivo = "Sin PDF; se incluyó el adjunto."`). Only a manual payable with neither a PDF nor attachments fails, with reason `manual_without_pdf`.

## Response `202`

Export accepted. Poll `GET /v1/erp/payouts/payables/pdf_exports/{id}` for its progress.

- PdfExport: An asynchronous bulk download of payable PDFs. Created with `POST /v1/erp/payouts/payables/pdf_exports` and polled with `GET /v1/erp/payouts/payables/pdf_exports/{id}` until `status` is `completed`, `failed` or `expired` (after `expired`, create a new export to get the files again). Each completed export produces **one ZIP file per company** (`files[]`). Every ZIP contains one folder per document type that has documents (folders such as `Facturas/`, `Boletas de honorarios/`, `Reembolsos/`, `Anticipos/` or `Otros/`), plus an `indice.csv` index (UTF-8 with BOM, `;` separator) with the columns `Empresa;Fecha emisión;Razón social emisor;RUT emisor;Tipo;Folio;Monto;Estado;Motivo;Archivo` where `Estado` is `incluido` or `falló`, and `Motivo` explains why a document failed (or, for a manual payable without a PDF, that its attachment was included instead).
  - `id` string, uuid, required
  - `status` 'fetching' | 'zipping' | 'completed' | 'failed' | 'expired', required: - `fetching`: the PDFs are being retrieved (stored documents, attachments and, when needed, the SII). - `zipping`: the ZIP files are being built. - `completed`: the ZIP files are ready in `files`. - `failed`: the export could not be completed. - `expired`: the ZIP files were purged (7 days after completion); `files` is empty. Create a new export.
  - `progress` object, required: Counters of the payables in the export. All four are always present.
    - `total` integer, required: Number of payables in the export.
    - `processed` integer, required: Payables already handled (included + failed).
    - `included` integer, required: Payables whose document made it into the ZIP. Includes manual payables without a PDF whose attachments were included instead.
    - `failed` integer, required: Payables whose document could not be obtained. They are still listed in `indice.csv` with `Estado = falló`.
  - `failures_by_reason` object[]: Failed payables grouped by reason, sorted by `count` descending. `manual_without_pdf` is a manual payable with neither a PDF nor attachments; one that has attachments is included (not failed) with its attachments. `stored_file_unreadable` is a PDF stored in Cardda that could not be read; `interrupted` marks the documents still pending when the export failed.
    - `reason` 'manual_without_pdf' | 'missing_admin_key' | 'missing_company_key' | 'missing_bar_code' | 'sii_not_found' | 'sii_timeout' | 'sii_unavailable' | 'sii_session_limit' | 'not_retrieved' | 'fetch_error' | 'stored_file_unreadable' | 'interrupted'
    - `count` integer
    - `message` string: Human-readable explanation (Spanish). Starts in lowercase because it is meant to follow the count, e.g. `137: la empresa no tiene…`.
  - `include_attachments` boolean: Whether the attachments of payables that do have a PDF were included.
  - `company_ids` string[]: Companies covered by the export.
  - `date_from` string, date, nullable: Start of the issue-date range of the exported payables (used in the ZIP filename).
  - `date_to` string, date, nullable: End of the issue-date range of the exported payables (used in the ZIP filename).
  - `created_at` string, date-time
  - `completed_at` string, date-time, nullable
  - `expires_at` string, date-time, nullable: When the ZIP files will be purged (7 days after `completed_at`). `null` until completed.
  - `files` object[], required: One ZIP per company. Empty until `status` is `completed`. Each `url` is a signed link valid for **1 hour** and is re-signed on every `GET`, so fetch the export again to get a fresh link.
    - `company_id` string, uuid
    - `company_name` string
    - `filename` string, required
    - `byte_size` integer
    - `url` string, uri, required: Signed download URL, valid for 1 hour.

## Other responses

- `401`: Unauthorized
- `409`: The user already has an export in flight (`fetching` or `zipping`).
- `422`: The export cannot be created. `error_code` is one of: - `payables/pdf_export_limit`: more than 2.000 payables match; `limit` carries the cap. - `payables/pdf_export_empty`: no payables match the selection. - `payables/pdf_export_scope`: `scope` or `company_id` is missing or invalid.

---

[API](https://skmtc.dev/cardda/apis/erp-api.md) - [All operations](https://skmtc.dev/cardda/apis/erp-api/llms.txt) - [OpenAPI document](https://skmtc.dev/cardda/apis/erp-api/revisions/0730ed26bc3c?raw)
