Skip to content
Merged
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [5.1.0] 2026-09-12

### Added

- URL variants for the existing download methods, such as `invoices.downloadPdfUrl(id)`, `invoices.downloadXmlUrl(id)`, and `invoices.downloadZipUrl(id)`. Equivalent methods are available for cancellation receipts, receipts, and retentions. The response carries `url`, `expires_at`, `content_type`, and `filename`.

## [5.0.0] 2026-09-10

### Breaking
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ if ('pipe' in file && typeof file.pipe === 'function') {
}
```

If you would rather hand the file to someone else than receive it yourself, the
same representations are available as a short-lived URL. The URL is a bearer
credential for that one file and stops working when it expires:

```ts
const { url, filename } = await facturapi.invoices.downloadZipUrl(invoice.id);
```

The URL variants follow the existing download methods: `downloadPdfUrl`,
`downloadXmlUrl`, and `downloadZipUrl`, where each format is available. ZIP
requests similarly provide `downloadZipRequestUrl` alongside
`downloadZipRequest`.

#### Send your invoice by email

```ts
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "facturapi",
"version": "5.0.0",
"description": "SDK oficial de Facturapi para Node.js y navegadores. Integra facturación electrónica en México (CFDI) de forma simple y obtén una perspectiva fiscal completa de tu operación, con búsquedas indexadas, envío de documentos y trazabilidad.",
"version": "5.1.0",
"description": "SDK oficial de Facturapi para Node.js y navegadores. Integra facturaci\u00f3n electr\u00f3nica en M\u00e9xico (CFDI) de forma simple y obt\u00e9n una perspectiva fiscal completa de tu operaci\u00f3n, con b\u00fasquedas indexadas, env\u00edo de documentos y trazabilidad.",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -49,13 +49,13 @@
"sat-mexico",
"mexico",
"sat",
"facturación",
"facturaci\u00f3n",
"facturapi"
],
"author": "Facturación Espacial <contacto@facturapi.io>",
"author": "Facturaci\u00f3n Espacial <contacto@facturapi.io>",
"contributors": [
{
"name": "Raúl Sánchez"
"name": "Ra\u00fal S\u00e1nchez"
},
{
"name": "Javier Rosas"
Expand Down
54 changes: 54 additions & 0 deletions src/resources/invoices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
PaymentSummaryParams,
SearchResult,
SendEmailBody,
SignedDownloadUrl,
ZipRequest,
} from '../types';
import { WrapperClient } from '../wrapper';
Expand Down Expand Up @@ -118,6 +119,28 @@ export default class Invoices {
return this.client.get('/invoices/' + id + '/zip');
}

/**
* Gets a short-lived URL for downloading an invoice PDF directly.
* @param id Invoice Id
* @returns Signed download URL and its metadata
*/
downloadPdfUrl(id: string): Promise<SignedDownloadUrl> {
if (!id) return Promise.reject(new Error('id is required'));
return this.client.get('/invoices/' + id + '/download-url/pdf');
}

/** Gets a short-lived URL for downloading an invoice XML file. */
downloadXmlUrl(id: string): Promise<SignedDownloadUrl> {
if (!id) return Promise.reject(new Error('id is required'));
return this.client.get('/invoices/' + id + '/download-url/xml');
}

/** Gets a short-lived URL for downloading an invoice ZIP file. */
downloadZipUrl(id: string): Promise<SignedDownloadUrl> {
if (!id) return Promise.reject(new Error('id is required'));
return this.client.get('/invoices/' + id + '/download-url/zip');
}

/**
* Creates or retrieves a ZIP request for invoices matching the specified criteria.
* @param data ZIP request criteria
Expand Down Expand Up @@ -160,6 +183,12 @@ export default class Invoices {
return this.client.get('/invoices/zip-requests/' + id + '/zip');
}

/** Gets a short-lived URL for downloading a generated invoice ZIP request. */
downloadZipRequestUrl(id: string): Promise<SignedDownloadUrl> {
if (!id) return Promise.reject(new Error('id is required'));
return this.client.get('/invoices/zip-requests/' + id + '/download-url');
}

/**
* Downloads the cancellation receipt of a canceled invoice in XML format
* @param id Invoice Id
Expand All @@ -182,6 +211,26 @@ export default class Invoices {
return this.client.get('/invoices/' + id + '/cancellation_receipt/pdf');
}

/**
* Gets a short-lived URL for downloading a cancellation receipt PDF directly.
* @param id Invoice Id
* @returns Signed download URL and its metadata
*/
downloadCancellationReceiptPdfUrl(id: string): Promise<SignedDownloadUrl> {
if (!id) return Promise.reject(new Error('id is required'));
return this.client.get(
'/invoices/' + id + '/cancellation_receipt/download-url/pdf',
);
}

/** Gets a short-lived URL for downloading a cancellation receipt XML file. */
downloadCancellationReceiptXmlUrl(id: string): Promise<SignedDownloadUrl> {
if (!id) return Promise.reject(new Error('id is required'));
return this.client.get(
'/invoices/' + id + '/cancellation_receipt/download-url/xml',
);
}

/**
* Edits an invoice with "draft" status.
* @param id Invoice Id
Expand Down Expand Up @@ -231,4 +280,9 @@ export default class Invoices {
previewPdf(body: Record<string, any>): Promise<BinaryDownload> {
return this.client.post('/invoices/preview/pdf', { body });
}

/** Gets a short-lived URL for an invoice PDF preview. */
previewPdfUrl(body: Record<string, any>): Promise<SignedDownloadUrl> {
return this.client.post('/invoices/preview/pdf/download-url', { body });
}
}
26 changes: 25 additions & 1 deletion src/resources/receipts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Receipt,
SearchResult,
SendEmailBody,
SignedDownloadUrl,
PreviewReceiptsToInvoicePdfInput,
} from '../types'
import { WrapperClient } from '../wrapper'
Expand Down Expand Up @@ -82,7 +83,16 @@ export default class Receipts {
previewToInvoicePdf(
data: PreviewReceiptsToInvoicePdfInput,
): Promise<BinaryDownload> {
return this.client.post('/receipts/to-invoice/preview/pdf', {
return this.client.post('/receipts/to-invoice/preview', {
body: data,
})
}

/** Gets a short-lived URL for a receipts-to-invoice PDF preview. */
previewToInvoicePdfUrl(
data: PreviewReceiptsToInvoicePdfInput,
): Promise<SignedDownloadUrl> {
return this.client.post('/receipts/to-invoice/preview/download-url', {
body: data,
})
}
Expand Down Expand Up @@ -115,4 +125,18 @@ export default class Receipts {
downloadPdf(id: string): Promise<BinaryDownload> {
return this.client.get('/receipts/' + id + '/pdf')
}

/**
* Gets a short-lived URL for downloading the receipt PDF directly, instead of
* streaming the file through the SDK.
*
* A receipt is a nota de venta and is not stamped, so the PDF is the only
* representation there is to download.
* @param id Receipt Id
* @returns Signed download URL and its metadata
*/
downloadPdfUrl(id: string): Promise<SignedDownloadUrl> {
if (!id) return Promise.reject(new Error('id is required'))
return this.client.get('/receipts/' + id + '/download-url/pdf')
}
}
24 changes: 24 additions & 0 deletions src/resources/retentions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Retention,
SearchResult,
SendEmailBody,
SignedDownloadUrl,
} from '../types'
import { WrapperClient } from '../wrapper'

Expand Down Expand Up @@ -117,4 +118,27 @@ export default class Retentions {
downloadZip(id: string): Promise<BinaryDownload> {
return this.client.get('/retentions/' + id + '/zip')
}

/**
* Gets a short-lived URL for downloading the retention PDF, instead of
* streaming the file through the SDK.
* @param id Retention Id
* @returns Signed download URL and its metadata
*/
downloadPdfUrl(id: string): Promise<SignedDownloadUrl> {
if (!id) return Promise.reject(new Error('id is required'))
return this.client.get('/retentions/' + id + '/download-url/pdf')
}

/** Gets a short-lived URL for downloading a retention XML file. */
downloadXmlUrl(id: string): Promise<SignedDownloadUrl> {
if (!id) return Promise.reject(new Error('id is required'))
return this.client.get('/retentions/' + id + '/download-url/xml')
}

/** Gets a short-lived URL for downloading a retention ZIP file. */
downloadZipUrl(id: string): Promise<SignedDownloadUrl> {
if (!id) return Promise.reject(new Error('id is required'))
return this.client.get('/retentions/' + id + '/download-url/zip')
}
}
14 changes: 14 additions & 0 deletions src/types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,17 @@ export type BinaryInput =
| ArrayBuffer
| Uint8Array
| NodeLikeReadableStream;

/**
* A short-lived URL that downloads one representation of a document.
*
* The URL is a bearer credential for that file: whoever holds it can download
* it until `expires_at`. It belongs in the hands of the caller's own user, not
* in storage or logs.
*/
export interface SignedDownloadUrl {
url: string;
expires_at: string;
content_type: string;
filename: string;
}
Loading