Skip to main content
Bulk-retrieve the CFDIs (or their metadata) that an RFC issued or received in a date range, straight from the SAT’s descarga masiva service. It is asynchronous and hemmed in by hard SAT limits — read the limits section before you design around it.

Prerequisites

  • A FIEL (e.firma) registered on the fiscal profile — descarga masiva authorizes with the FIEL, not the CSD. Without one, POST /v1/downloads returns 422 download.fiel_required.
    # add a FIEL to an existing profile via PATCH (or include `fiel` at creation)
    # fiel = { cer_base64, key_base64, password } — same shape as csd
    
  • No sandbox exists for descarga masiva. Test mode uses the simulated driver; real downloads only work in production against a live FIEL.

Request a download

curl -s -X POST localhost:3000/v1/downloads \
  -H "authorization: Bearer $API_KEY" -H 'content-type: application/json' \
  -d '{
    "fiscal_profile_id": "fp_...",
    "side": "issued",          // issued = you are the emisor; received = you are the receptor
    "type": "cfdi",            // cfdi = ZIP of XMLs; metadata = ZIP of metadata rows
    "date_from": "2026-01-01",
    "date_to":   "2026-01-31"
  }' | jq
date_from/date_to accept YYYY-MM-DD or a full ISO datetime; a bare date widens to cover the whole day. Response is a download object with status: "requested" and a metadata.warnings array carrying any SAT-limit advisories. 201 for a new request, 200 if it deduped to an identical prior request (no new SAT slot consumed).

Lifecycle

requested ─▶ processing ─▶ ready ──▶ (packages downloaded & stored)
                     └────▶ no_data     (terminal, empty — the range simply had no CFDIs)
                     └────▶ failed
The API fires the SAT request, then a poll job checks status and, the moment SAT marks it ready, downloads the ZIP packages immediately (their URLs expire in ~5 minutes) and stores them.
curl -s localhost:3000/v1/downloads/$DL_ID -H "authorization: Bearer $API_KEY" | jq '.status,.package_count'
curl -s localhost:3000/v1/downloads/$DL_ID/files -H "authorization: Bearer $API_KEY" | jq
curl -s localhost:3000/v1/downloads/$DL_ID/files/0 -H "authorization: Bearer $API_KEY" -o package-0.zip
Subscribe to download.ready / download.failed webhooks instead of polling.

SAT limits — design around these

The SAT (not this API) enforces the following. They are the reason descarga masiva must be planned, not fired ad hoc. Codes below map to sat.* in ../errors.md.
LimitCodeWhat it meansWhat to do
2 identical lifetime requests5002 sat.download_request_limitYou may request the exact same params at most twice — ever.Never retry identical params. Vary/fragment the date range so each request is distinct.
200,000 XML per request (1M metadata)5003 sat.download_too_largeThe range produced too many documents.Fragment the date range (month → weeks → days) and issue separate requests.
Package URLs expire ~5 min5007 sat.download_package_not_foundThe signed S3 URL went stale before download.Re-poll status to re-mint URLs and download at once (the poll job already does this).
No data5004 sat.download_no_dataTerminal success, empty result — the range has no CFDIs.Do not retry; treat as done. Surfaces as status: "no_data".
Third party not authorized5001 sat.download_unauthorizedThe FIEL does not match the requested RFC/side.Ensure the FIEL belongs to the RFC and side matches its role.

Fragmentation strategy

Because of both the 200k cap and the 2-identical-lifetime rule, prefer narrow, non-repeating windows:
  1. Start monthly. If a month returns 5003 (too large), split it into weeks, then days.
  2. Make every request’s date_from/date_to unique — do not resubmit a window you already requested twice. If you must reprocess, reuse the stored packages via GET /v1/downloads/:id/files rather than issuing a new SAT request.
  3. For ongoing sync, advance the window forward (yesterday, the day before…) so params never repeat.
  4. Metadata requests are cheaper (1M cap) — pull type: "metadata" first to size a range before pulling full type: "cfdi".

Notes

  • status_filter (active/cancelled) is recorded for request differentiation but is advisory — it is not currently forwarded to the SAT request.
  • The FIEL PFX is the only key material sent to the PAC, and only for descarga masiva. CSD private keys never leave this server.