diff --git a/website/docs/guides/invoices/cancelaciones.mdx b/website/docs/guides/invoices/cancelaciones.mdx
index 5da3717bb..6d55017d7 100644
--- a/website/docs/guides/invoices/cancelaciones.mdx
+++ b/website/docs/guides/invoices/cancelaciones.mdx
@@ -158,7 +158,7 @@ Facturapi consulta periódicamente el SAT y actualiza `cancellation_status`. Pue
## Acuse de cancelación y verificación
-Cuando solicitas la cancelación mediante Facturapi, puedes descargar el acuse emitido por el SAT en XML o PDF desde el endpoint de [Cancellation receipt](/api/#tag/invoice/operation/downloadCancellationReceiptXml). El acuse demuestra el resultado inmediato de la solicitud, pero no necesariamente que el CFDI ya esté cancelado: confirma el desenlace con `status`, `cancellation_status` o `verification_url`.
+Cuando solicitas la cancelación mediante Facturapi, puedes descargar el acuse emitido por el SAT en XML o PDF desde el endpoint de [Cancellation receipt](/api/#tag/invoice/operation/downloadCancellationReceipt). El acuse demuestra el resultado inmediato de la solicitud, pero no necesariamente que el CFDI ya esté cancelado: confirma el desenlace con `status`, `cancellation_status` o `verification_url`.
Facturapi sólo puede recuperar acuses de solicitudes enviadas mediante su servicio. Para cancelaciones realizadas externamente, valida el estado actual del CFDI con `verification_url`.
diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/guides/invoices/cancelaciones.mdx b/website/i18n/en/docusaurus-plugin-content-docs/current/guides/invoices/cancelaciones.mdx
index 53cf66e3f..790112f84 100644
--- a/website/i18n/en/docusaurus-plugin-content-docs/current/guides/invoices/cancelaciones.mdx
+++ b/website/i18n/en/docusaurus-plugin-content-docs/current/guides/invoices/cancelaciones.mdx
@@ -144,7 +144,7 @@ Facturapi periodically checks the SAT and updates `cancellation_status`. You can
## Cancellation receipt and verification
-When you submit the cancellation through Facturapi, you can download the SAT receipt in XML or PDF from the [Cancellation receipt](/api/#tag/invoice/operation/downloadCancellationReceiptXml) endpoint. The receipt proves the immediate result of the request, but does not necessarily mean that the CFDI is already canceled: confirm the outcome using `status`, `cancellation_status`, or `verification_url`.
+When you submit the cancellation through Facturapi, you can download the SAT receipt in XML or PDF from the [Cancellation receipt](/api/#tag/invoice/operation/downloadCancellationReceipt) endpoint. The receipt proves the immediate result of the request, but does not necessarily mean that the CFDI is already canceled: confirm the outcome using `status`, `cancellation_status`, or `verification_url`.
Facturapi can only retrieve receipts for requests submitted through its service. For cancellations performed externally, validate the current CFDI status using `verification_url`.
diff --git a/website/openapi_v2.en.yaml b/website/openapi_v2.en.yaml
index bb95cb952..24e107210 100644
--- a/website/openapi_v2.en.yaml
+++ b/website/openapi_v2.en.yaml
@@ -4096,7 +4096,9 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/Invoice"
+ anyOf:
+ - $ref: "#/components/schemas/Invoice"
+ - $ref: "#/components/schemas/InvoiceDraft"
"400":
$ref: "#/components/responses/BadRequest"
"401":
@@ -4916,7 +4918,7 @@ paths:
$ref: "#/components/responses/UnexpectedError"
/invoices/{invoice_id}/cancellation_receipt/{format}:
get:
- operationId: downloadCancellationReceiptXml
+ operationId: downloadCancellationReceipt
tags:
- invoice
summary: Cancellation receipt
@@ -6628,10 +6630,10 @@ paths:
schema:
type: string
format: binary
- "204":
- description: No eligible receipts found for the provided keys
"400":
$ref: "#/components/responses/BadRequest"
+ "404":
+ $ref: "#/components/responses/NotFound"
"401":
$ref: "#/components/responses/Unauthenticated"
"500":
@@ -10029,12 +10031,9 @@ paths:
content:
application/json:
schema:
- type: object
- properties:
- data:
- type: array
- items:
- $ref: "#/components/schemas/OrganizationSeriesGroup"
+ type: array
+ items:
+ $ref: "#/components/schemas/OrganizationSeriesGroup"
"400":
$ref: "#/components/responses/BadRequest"
@@ -10136,7 +10135,9 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/OrganizationSeriesGroup"
+ type: array
+ items:
+ $ref: "#/components/schemas/OrganizationSeriesGroup"
"400":
$ref: "#/components/responses/BadRequest"
"401":
@@ -10235,7 +10236,7 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/OkResponse"
+ $ref: "#/components/schemas/Organization"
"400":
$ref: "#/components/responses/BadRequest"
"401":
@@ -10338,7 +10339,9 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/OrganizationSeriesGroup"
+ type: array
+ items:
+ $ref: "#/components/schemas/OrganizationSeriesGroup"
"400":
$ref: "#/components/responses/BadRequest"
"401":
@@ -10421,7 +10424,9 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/OrganizationSeriesGroup"
+ type: array
+ items:
+ $ref: "#/components/schemas/OrganizationSeriesDeletion"
"400":
$ref: "#/components/responses/BadRequest"
"401":
@@ -13586,7 +13591,7 @@ components:
example: 0.1
description: Tax rate in decimal format.
base:
- type: number
+ type: [number, 'null']
description: "Tax base. If omitted, the full subtotal of the line item is used."
type:
type: string
@@ -13617,8 +13622,12 @@ components:
type: number
example: 0.16
description: Tax rate in decimal format.
- base:
+ amount:
type: number
+ readOnly: true
+ description: Calculated tax amount.
+ base:
+ type: [number, 'null']
description: "Tax base. If omitted, it is calculated from the line item subtotal and tax factor. For the Cuota factor, the number of units is used."
type:
type: string
@@ -13687,6 +13696,12 @@ components:
type: string
description: "SAT FechaTimbrado: local date and time without a timezone offset. Preserved as text."
example: "2026-09-17T06:59:16"
+ complement_string:
+ type: [string, 'null']
+ description: Original string of the fiscal stamp.
+ rfc_provider_cert:
+ type: string
+ description: Tax ID of the provider that certified the document.
sat_cert_number:
type: string
description: SAT certificate serial number used for stamping.
@@ -13720,12 +13735,20 @@ components:
items:
type: string
description: Customs entry numbers associated with this invoice item. Omitted when there are none.
+ third_party:
+ $ref: '#/components/schemas/ThirdParty'
+ complement:
+ anyOf:
+ - type: string
+ format: xml
+ - $ref: '#/components/schemas/HidroYPetroComplement'
+ - $ref: '#/components/schemas/IeduComplement'
parts:
type: array
items:
$ref: "#/components/schemas/Parts"
description: Object with information about the parts conforming this item or product.
- ThirdPartyInput:
+ ThirdParty:
type: object
description: |
Object with information about the third-party contributor, on behalf of whom the operation is carried out.
@@ -13750,6 +13773,9 @@ components:
type: string
description: Postal code of the third party.
example: "01234"
+ ThirdPartyInput:
+ allOf:
+ - $ref: "#/components/schemas/ThirdParty"
InvoiceItemInput:
title: InvoiceItem
required:
@@ -13914,13 +13940,9 @@ components:
type: string
description: Postal code of the third party.
example: "01234"
- HidroYPetroComplementInput:
+ HidroYPetroComplement:
title: HidroYPetroComplement
type: object
- required:
- - tipo_permiso
- - numero_permiso
- - sub_producto_hyp
properties:
tipo_permiso:
type: string
@@ -13995,18 +14017,20 @@ components:
- Diesel
- Combustibles para barco
- IeduComplementInput:
+ HidroYPetroComplementInput:
+ allOf:
+ - $ref: "#/components/schemas/HidroYPetroComplement"
+ required:
+ - tipo_permiso
+ - numero_permiso
+ - sub_producto_hyp
+ IeduComplement:
title: IeduComplement
type: object
description: |
Private Education Institutions concept-level complement version 1.0.
Include it at the concept level in `items[].complement`.
- required:
- - nombreAlumno
- - CURP
- - nivelEducativo
- - autRVOE
properties:
nombreAlumno:
type: string
@@ -14040,6 +14064,14 @@ components:
description: RFC of the person making the payment when different from the person receiving the service.
example: PELJ800101ABC
+ IeduComplementInput:
+ allOf:
+ - $ref: "#/components/schemas/IeduComplement"
+ required:
+ - nombreAlumno
+ - CURP
+ - nivelEducativo
+ - autRVOE
CustomComplementData:
title: string
type: string
@@ -15246,7 +15278,7 @@ components:
CartaPorteDataInput:
allOf:
- $ref: "#/components/schemas/CartaPorteDataProperties"
- ComercioExteriorDataProperties:
+ ComercioExteriorCommonProperties:
type: object
title: ComercioExterior
description: Foreign Trade complement version 2.0.
@@ -15312,10 +15344,6 @@ components:
oneOf:
- $ref: "#/components/schemas/ComercioExteriorPropietario"
- $ref: "#/components/schemas/CustomerComercioExterior"
- Receptor:
- oneOf:
- - $ref: "#/components/schemas/ComercioExteriorReceptor"
- - $ref: "#/components/schemas/CustomerComercioExterior"
Destinatario:
type: array
items:
@@ -15324,9 +15352,25 @@ components:
- $ref: "#/components/schemas/CustomerComercioExterior"
Mercancias:
$ref: "#/components/schemas/ComercioExteriorMercancias"
+ ComercioExteriorDataProperties:
+ allOf:
+ - $ref: '#/components/schemas/ComercioExteriorCommonProperties'
+ properties:
+ Receptor:
+ anyOf:
+ - $ref: "#/components/schemas/ComercioExteriorReceptor"
+ - $ref: "#/components/schemas/CustomerComercioExterior"
ComercioExteriorDataInput:
allOf:
- - $ref: "#/components/schemas/ComercioExteriorDataProperties"
+ - $ref: '#/components/schemas/ComercioExteriorCommonProperties'
+ properties:
+ Receptor:
+ anyOf:
+ - $ref: "#/components/schemas/ComercioExteriorReceptor"
+ - $ref: "#/components/schemas/CustomerComercioExterior"
+ - type: string
+ pattern: '^[a-fA-F0-9]{24}$'
+ description: ID of an existing customer.
ComercioExteriorDomicilio:
type: object
required:
@@ -16030,6 +16074,37 @@ components:
format: url
example: http://www.sat.gob.mx/sitio_interet/cfd/iedu/iedu.xsd
description: URL of the XSD validation schema.
+ Address:
+ allOf:
+ - $ref: '#/components/schemas/CommonAddressProperties'
+ properties:
+ state:
+ type: string
+ country:
+ type: string
+ minLength: 3
+ maxLength: 3
+ CustomerAddress:
+ type: [object, 'null']
+ properties:
+ street:
+ type: [string, 'null']
+ exterior:
+ type: [string, 'null']
+ interior:
+ type: [string, 'null']
+ neighborhood:
+ type: [string, 'null']
+ city:
+ type: [string, 'null']
+ municipality:
+ type: [string, 'null']
+ zip:
+ type: [string, 'null']
+ state:
+ type: [string, 'null']
+ country:
+ type: [string, 'null']
CommonAddressProperties:
type: object
properties:
@@ -16240,25 +16315,12 @@ components:
- type: object
properties:
address:
- allOf:
- - $ref: "#/components/schemas/CommonAddressProperties"
- - type: object
- description: Fiscal address.
- properties:
- state:
- type: string
- description: If the country is Mexico ("MEX"), it contains the name of the State or Federative Entity. For foreigners, it contains the State code according to the standard [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2), which you can consult in our [State Catalog](https://dashboard.facturapi.io/catalogs/state).
- example: Sonora
- country:
- type: string
- description: Country code according to the standard [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3), from the [Country Catalog](https://dashboard.facturapi.io/catalogs/country).
- example: MEX
- default: MEX
+ $ref: '#/components/schemas/CustomerAddress'
CustomerCommonProperties:
type: object
properties:
legal_name:
- type: string
+ type: [string, 'null']
description: |
Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.).
example: Dunder Mifflin
@@ -16279,7 +16341,7 @@ components:
description: |
Required for national clients. Key of the customer's tax regime, from the [Tax Regime Catalog](#tax-regime).
email:
- type: string
+ type: [string, 'null']
format: email
description: Email address to which to send the generated invoices.
example: email@example.com
@@ -16293,6 +16355,15 @@ components:
type: string
description: Default CFDI use for the customer.
example: G01
+ CustomerEmailInput: &customer_email_input
+ type: [string, 'null']
+ format: email
+ CustomerEmailUpdateInput:
+ anyOf:
+ - $ref: '#/components/schemas/CustomerEmailInput'
+ - type: string
+ pattern: '^\s*$'
+ description: On update, null, an empty string, or whitespace-only text clears the email address.
CustomerEditableInput:
type: object
properties:
@@ -16302,10 +16373,8 @@ components:
Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.).
example: Dunder Mifflin
tax_id: *customer_tax_id
- email: &customer_email_input
- type: [string, 'null']
- format: email
- description: Customer email address. On update, null clears the email address.
+ email:
+ $ref: '#/components/schemas/CustomerEmailUpdateInput'
phone: *customer_phone
external_id: &customer_external_id_input
type: [string, 'null']
@@ -16343,6 +16412,7 @@ components:
example: Huatabampo
zip:
type: string
+ minLength: 1
description: Postal code
example: '86500'
state: &customer_address_state_input
@@ -16352,7 +16422,7 @@ components:
default: MEX
description: ISO 3166-1 alpha-3.
CustomerUpdateInput:
- description: On update without createEditLink=true, omitted fields retain their value. A null value clears fields that accept null; an optional field can be omitted without necessarily accepting null. Null is not allowed for legal_name, tax_id, tax_system, or address.
+ description: On update without createEditLink=true, omitted fields retain their value. A null value clears fields that accept null; an optional field can be omitted without necessarily accepting null. Null is not allowed for legal_name, tax_id, tax_system, or address. For email, phone, and the optional address fields street, exterior, interior, neighborhood, city, municipality, and state, empty or whitespace-only strings normalize to null. Empty postal codes are not accepted.
allOf:
- $ref: '#/components/schemas/CustomerEditableInput'
- type: object
@@ -16373,12 +16443,13 @@ components:
country:
type: string
CustomerUpdateWithEditLinkInput:
- description: With createEditLink=true, omitted fields retain their value and null clears the existing value, including fiscal fields. Non-null values must be valid.
+ description: With createEditLink=true, omitted fields retain their value and null clears the existing value, including fiscal fields. Non-null values must be valid. For email, phone, and the optional address fields street, exterior, interior, neighborhood, city, municipality, and state, empty or whitespace-only strings normalize to null. Empty postal codes are not accepted.
type: object
properties:
legal_name: *customer_legal_name_input
tax_id: *customer_tax_id
- email: *customer_email_input
+ email:
+ $ref: '#/components/schemas/CustomerEmailUpdateInput'
phone: *customer_phone
external_id: *customer_external_id_input
default_invoice_use: *customer_default_invoice_use_input
@@ -16396,6 +16467,7 @@ components:
country: *customer_address_country_input
zip:
type: [string, 'null']
+ minLength: 1
description: Postal code
example: '86500'
CancellationQueryInput:
@@ -16488,8 +16560,8 @@ components:
state:
type: string
country:
- type: string
- const: MEX
+ type: [string, 'null']
+ enum: [MEX, '', null]
default: MEX
required:
- zip
@@ -16712,6 +16784,11 @@ components:
items:
$ref: "#/components/schemas/Product"
ProductCreateInput:
+ properties:
+ unit_name:
+ type: string
+ maxLength: 20
+ description: Word representing the unit of measure of your product. It must be related to the unit key `unit_key`.
allOf:
- $ref: "#/components/schemas/ProductCommonProperties"
required: [description, product_key, price]
@@ -16719,7 +16796,17 @@ components:
allOf:
- $ref: "#/components/schemas/ProductCommonProperties"
required: [description, product_key, price]
+ properties:
+ unit_name:
+ type: [string, 'null']
+ default: Elemento
+ description: Name of the unit of measure used to express the quantity. It must be related to the unit key `unit_key`.
ProductUpdateInput:
+ properties:
+ unit_name:
+ type: string
+ maxLength: 20
+ description: Word representing the unit of measure of your product. It must be related to the unit key `unit_key`.
allOf:
- $ref: "#/components/schemas/ProductCommonProperties"
ProductCommonProperties:
@@ -16801,10 +16888,6 @@ components:
Key of the unit of measure, from the SAT catalog. The default value `"H87"` (element) is the key to represent a piece or sales unit (pencil, notebook, television, etc).
If the unit of your product is kilograms, liters, hours, or any other unit, we provide a convenient way to find the key using our [key search tool](https://dashboard.facturapi.io/catalogs/unit).
- unit_name:
- type: string
- default: Elemento
- description: Name of the unit of measure used to express the quantity. It must be related to the unit key `unit_key`.
sku:
type: string
description: Internal identifier designated by the company. It can have any value.
@@ -17090,25 +17173,19 @@ components:
description: ID of the `customer` object related to the invoice, in case it has not been deleted.
example: 58e93bd8e86eb318b0197456
legal_name:
- type: string
+ type: [string, 'null']
description: |
Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.).
example: Dunder Mifflin
tax_id:
- type: string
+ type: [string, 'null']
description: |
In Mexican clients, it contains the customer's RFC. For foreigners, it is optional and represents the tax identification number, that is, the equivalent to the RFC in the customer's country.
example: ABC101010111
address:
- type: object
- properties:
- country:
- type: string
- format: ISO 3166-1 alpha-3
- description: Country code according to the standard [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3).
- example: MEX
- zip:
- type: string
+ $ref: '#/components/schemas/Address'
+ phone:
+ type: [string, 'null']
tax_system:
type:
- string
@@ -17124,7 +17201,6 @@ components:
RelatedDocumentInput:
required:
- relationship
- - related
allOf:
- $ref: '#/components/schemas/RelatedDocument'
RelatedDocument:
@@ -17299,21 +17375,71 @@ components:
items:
$ref: "#/components/schemas/InvoiceZipRequest"
Invoice:
- title: Invoice object
+ title: Invoice
+ oneOf:
+ - $ref: '#/components/schemas/InvoiceStamped'
+ - $ref: '#/components/schemas/InvoiceDraft'
+ - $ref: '#/components/schemas/InvoicePending'
+ - $ref: '#/components/schemas/InvoiceFailed'
+ InvoiceStamped:
+ title: Stamped
allOf:
- - $ref: "#/components/schemas/ResourceAutoGeneratedProps"
- - $ref: "#/components/schemas/InvoiceProperties"
+ - $ref: '#/components/schemas/ResourceAutoGeneratedProps'
+ - $ref: '#/components/schemas/InvoiceCommonProperties'
+ - type: object
+ required: [date, status, uuid, stamp]
+ properties:
+ status:
+ type: string
+ enum: [valid, canceled]
+ date:
+ type: string
+ format: date-time
+ uuid:
+ type: string
+ format: uuid
+ stamp:
+ allOf:
+ - $ref: '#/components/schemas/Stamp'
+ required: [date, sat_signature, sat_cert_number, signature, complement_string]
+ properties:
+ complement_string:
+ type: string
+ InvoicePending:
+ title: Pending
+ allOf:
+ - $ref: '#/components/schemas/ResourceAutoGeneratedProps'
+ - $ref: '#/components/schemas/InvoiceProperties'
+ - type: object
+ properties:
+ status:
+ type: string
+ const: pending
+ InvoiceFailed:
+ title: Failed
+ allOf:
+ - $ref: '#/components/schemas/ResourceAutoGeneratedProps'
+ - $ref: '#/components/schemas/InvoiceProperties'
+ - type: object
+ properties:
+ status:
+ type: string
+ const: failed
InvoiceCancellation:
type: object
description: Information about the invoice cancellation request.
required: [status]
properties:
status:
- $ref: '#/components/schemas/InvoiceProperties/properties/cancellation_status'
+ $ref: '#/components/schemas/InvoiceCommonProperties/properties/cancellation_status'
requested_at:
type: string
format: date-time
description: Date and time when cancellation was requested, if applicable.
+ last_checked:
+ type: string
+ format: date-time
+ description: Date and time when the last cancellation status check started. Omitted if no check has been started. It does not guarantee that the check completed successfully.
canceled_at:
type: string
format: date-time
@@ -17342,8 +17468,29 @@ components:
items:
$ref: "#/components/schemas/Invoice"
InvoiceProperties:
+ allOf:
+ - $ref: '#/components/schemas/InvoiceCommonProperties'
+ - type: object
+ required: [date, status]
+ properties:
+ date:
+ type:
+ - string
+ - 'null'
+ format: date-time
+ description: Issue date in ISO8601 format. May be null for drafts.
+ uuid:
+ type: [string, "null"]
+ format: uuid
+ description: Fiscal folio of the invoice, assigned by the SAT. For unstamped drafts it can be null or omitted.
+ example: 39c85a3f-275b-4341-b259-e8971d9f8a94
+ stamp:
+ anyOf:
+ - $ref: '#/components/schemas/Stamp'
+ - type: 'null'
+ InvoiceCommonProperties:
type: object
- required: [date, status, cancellation_status, type, total, currency, exchange, items, amount_due]
+ required: [status, cancellation_status, type, total, currency, exchange, items, amount_due]
properties:
status:
type: string
@@ -17381,15 +17528,9 @@ components:
format: uri
description: URL to verify the status of the CFDI on the SAT portal. This link is the same as the one that appears in the QR code on the invoice PDF.
example: https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?id=45BEC0CA-5F1E-491E-9417-698EA48C382A&re=AAA010101AAA&rr=ABC101010111&tt=345.600000&fe=bWApPw==
- date:
- type:
- - string
- - 'null'
- format: date-time
- description: Issue date in ISO8601 format. May be null for drafts.
address:
allOf:
- - $ref: '#/components/schemas/CommonAddressProperties'
+ - $ref: '#/components/schemas/Address'
- type: object
description: Address where the invoice was issued.
properties:
@@ -17415,11 +17556,6 @@ components:
type: number
description: Total amount invoiced.
example: 10944.82
- uuid:
- type: [string, "null"]
- format: uuid
- description: Fiscal folio of the invoice, assigned by the SAT. For unstamped drafts it can be null or omitted.
- example: 39c85a3f-275b-4341-b259-e8971d9f8a94
folio_number:
type: integer
description: Autoincremental folio number for internal control and without fiscal relevance.
@@ -17521,10 +17657,6 @@ components:
description: Namespaces to insert in the root node of the invoice. Required for `addenda`.
items:
$ref: '#/components/schemas/NamespaceProperties'
- stamp:
- anyOf:
- - $ref: '#/components/schemas/Stamp'
- - type: 'null'
organization:
description: "ID of the organization this resource belongs to."
type:
@@ -17574,7 +17706,23 @@ components:
type: integer
InvoiceDraftProperties:
allOf:
- - $ref: '#/components/schemas/InvoiceProperties'
+ - $ref: '#/components/schemas/InvoiceCommonProperties'
+ - type: object
+ required: [date, status]
+ properties:
+ status:
+ type: string
+ const: draft
+ date:
+ type:
+ - string
+ - 'null'
+ format: date-time
+ description: Issue date in ISO8601 format. May be null for drafts.
+ uuid:
+ type: 'null'
+ stamp:
+ type: 'null'
InvoiceableCommonInput:
type: object
properties:
@@ -18698,6 +18846,12 @@ components:
- items
- key
properties:
+ address:
+ $ref: '#/components/schemas/Address'
+ api_version:
+ type: integer
+ readOnly: true
+ description: API version used to create the receipt.
date:
type: string
format: date-time
@@ -18734,7 +18888,7 @@ components:
example: 356.78
description: Total amount of the operation
invoice:
- type: string
+ type: [string, 'null']
example: 614496b471d422de4b6cfcc4
description: ID of the associated invoice, if invoiced.
customer:
@@ -19163,7 +19317,7 @@ components:
Current status of the document.
example: valid
verification_url:
- type: string
+ type: [string, 'null']
format: uri
description: URL to verify the status of the retention on the SAT portal. This link is the same as the one that appears in the QR code on the retention PDF.
example: https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?id=45BEC0CA-5F1E-491E-9417-698EA48C382A&re=AAA010101AAA&rr=ABC101010111&tt=345.600000&fe=bWApPw==
@@ -19174,7 +19328,7 @@ components:
example: Retención
description: Type of document.
uuid:
- type: string
+ type: [string, 'null']
format: uuid
description: |
Fiscal folio of the retention, assigned by the SAT.
@@ -19199,7 +19353,7 @@ components:
- fecha_exp
properties:
cve_retenc:
- type: string
+ type: [string, 'null']
example: "01"
description: |
Key of the retention or payment information according to the SAT catalog.
@@ -19218,7 +19372,7 @@ components:
type: string
description: Alphanumeric identifier for internal control of the company and without fiscal relevance.
periodo:
- type: object
+ type: [object, 'null']
description: Information about the retention period.
properties:
mes_ini:
@@ -19235,7 +19389,7 @@ components:
type: integer
description: Fiscal year in which the retention was made.
totales:
- type: object
+ type: [object, 'null']
description: Information about the total of retentions made in the corresponding period.
properties:
monto_tot_operacion:
@@ -19279,6 +19433,9 @@ components:
type: number
minimum: 0
description: Amount of the tax withheld.
+ pago_provisional:
+ type: boolean
+ readOnly: true
tipo_pago_ret:
type: string
enum:
@@ -19294,11 +19451,11 @@ components:
- `03`: Definitive ISR Platforms payment
- `04`: Provisional ISR payment
external_id:
- type: string
+ type: [string, 'null']
description: |
Optional identifier that you can use to relate this retention to your records and later search by this number. Facturapi does not validate that this field is unique.
idempotency_key:
- type: string
+ type: [string, 'null']
description: |
Unique identifier that you can use to avoid duplicates when retrying a request. It can be any text string, as long as it is unique for each document.
@@ -19316,9 +19473,11 @@ components:
description: |
In case you need to include more information in the PDF, this field allows you to insert HTML code with your own content.
addenda:
- type: string
- format: xml
- description: XML code with the Addenda that needs to be added to the invoice.
+ type: array
+ items:
+ type: string
+ format: xml
+ description: List of XML fragments to include as addendas in the retention document.
namespaces:
type: array
description: Namespaces to insert in the root node of the invoice. Required for `addenda`.
@@ -19528,9 +19687,11 @@ components:
description: |
In case you need to include more information in the PDF, this field allows you to insert HTML code with your own content.
addenda:
- type: string
- format: xml
- description: XML code with the Addenda that needs to be added to the invoice.
+ type: array
+ items:
+ type: string
+ format: xml
+ description: List of XML fragments to include as addendas in the retention document.
namespaces:
type: array
description: Namespaces to insert in the root node of the invoice. Required for `addenda`.
@@ -19543,6 +19704,10 @@ components:
- type: object
description: Fiscal address of the issuing organization.
properties:
+ country:
+ type: string
+ minLength: 3
+ maxLength: 3
state:
type: string
description: Name of the State or Federative Entity.
@@ -19617,6 +19782,13 @@ components:
type: string
description: |
Fiscal or Legal Name of the organization, *without* the corporate regime (e.g.: S.A. de C.V.).
+ tax_id:
+ type: string
+ support_email:
+ type: string
+ format: email
+ curp:
+ type: string
tax_system:
type: string
example: '601'
@@ -19639,6 +19811,19 @@ components:
description: |
Organization customization settings, which will be used to reflect the branding and PDF preferences of the organization. This data can be updated at any time.
properties:
+ default_series:
+ type: object
+ properties:
+ I:
+ type: string
+ E:
+ type: string
+ P:
+ type: string
+ N:
+ type: string
+ T:
+ type: string
has_logo:
type: boolean
description: Indicates if the organization already has a logo uploaded.
@@ -20203,18 +20388,18 @@ components:
type: object
required:
- series
- - next_folio
- - next_folio_test
properties:
series:
type: string
- maxLength: 55
+ maxLength: 25
description: Series name
next_folio:
type: integer
+ default: 1
description: Folio number that will be assigned to the next invoice in the Live environment (and will automatically increase with each new invoice).
next_folio_test:
type: integer
+ default: 1
description: Folio number that will be assigned to the next invoice in the Test environment (and will automatically increase with each new invoice).
OrganizationSeriesUpdateInput:
type: object
@@ -20247,6 +20432,21 @@ components:
minLength: 1
maxLength: 25
description: Series name.
+ OrganizationSeriesDeletion:
+ type: [object, 'null']
+ properties:
+ series:
+ type: string
+ livemode:
+ type: boolean
+ next_folio:
+ type: integer
+ created_at:
+ type: string
+ format: date-time
+ updated_at:
+ type: string
+ format: date-time
OrganizationSeriesGroup:
title: Objeto Series
type: object
diff --git a/website/openapi_v2.yaml b/website/openapi_v2.yaml
index d63000f68..7e0dc190c 100644
--- a/website/openapi_v2.yaml
+++ b/website/openapi_v2.yaml
@@ -4070,7 +4070,9 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/Invoice"
+ anyOf:
+ - $ref: "#/components/schemas/Invoice"
+ - $ref: "#/components/schemas/InvoiceDraft"
"400":
$ref: "#/components/responses/BadRequest"
"401":
@@ -4888,7 +4890,7 @@ paths:
$ref: "#/components/responses/UnexpectedError"
/invoices/{invoice_id}/cancellation_receipt/{format}:
get:
- operationId: downloadCancellationReceiptXml
+ operationId: downloadCancellationReceipt
tags:
- invoice
summary: Descargar acuse de cancelación
@@ -6591,10 +6593,10 @@ paths:
schema:
type: string
format: binary
- "204":
- description: No se encontraron recibos elegibles para las keys enviadas
"400":
$ref: "#/components/responses/BadRequest"
+ "404":
+ $ref: "#/components/responses/NotFound"
"401":
$ref: "#/components/responses/Unauthenticated"
"500":
@@ -9987,12 +9989,9 @@ paths:
content:
application/json:
schema:
- type: object
- properties:
- data:
- type: array
- items:
- $ref: "#/components/schemas/OrganizationSeriesGroup"
+ type: array
+ items:
+ $ref: "#/components/schemas/OrganizationSeriesGroup"
"400":
$ref: "#/components/responses/BadRequest"
@@ -10094,7 +10093,9 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/OrganizationSeriesGroup"
+ type: array
+ items:
+ $ref: "#/components/schemas/OrganizationSeriesGroup"
"400":
$ref: "#/components/responses/BadRequest"
"401":
@@ -10193,7 +10194,7 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/OkResponse"
+ $ref: "#/components/schemas/Organization"
"400":
$ref: "#/components/responses/BadRequest"
"401":
@@ -10296,7 +10297,9 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/OrganizationSeriesGroup"
+ type: array
+ items:
+ $ref: "#/components/schemas/OrganizationSeriesGroup"
"400":
$ref: "#/components/responses/BadRequest"
"401":
@@ -10379,7 +10382,9 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/OrganizationSeriesGroup"
+ type: array
+ items:
+ $ref: "#/components/schemas/OrganizationSeriesDeletion"
"400":
$ref: "#/components/responses/BadRequest"
"401":
@@ -13538,7 +13543,7 @@ components:
example: 0.1
description: Tasa del impuesto en fracción decimal.
base:
- type: number
+ type: [number, 'null']
description: "Base del impuesto. Si se omite, se utiliza el subtotal completo del concepto."
type:
type: string
@@ -13569,8 +13574,12 @@ components:
type: number
example: 0.16
description: Tasa del impuesto en fracción decimal.
- base:
+ amount:
type: number
+ readOnly: true
+ description: Importe calculado del impuesto.
+ base:
+ type: [number, 'null']
description: "Base del impuesto. Si se omite, se calcula a partir del subtotal del concepto y el factor del impuesto. Para el factor Cuota, se utiliza la cantidad de unidades."
type:
type: string
@@ -13639,6 +13648,12 @@ components:
type: string
description: "FechaTimbrado del SAT: fecha y hora local sin offset de zona horaria. Se conserva como texto."
example: "2026-09-17T06:59:16"
+ complement_string:
+ type: [string, 'null']
+ description: Cadena original del timbre fiscal digital.
+ rfc_provider_cert:
+ type: string
+ description: RFC del proveedor que certificó el comprobante.
sat_cert_number:
type: string
description: Número de serie del certificado del SAT usado para timbrar.
@@ -13671,12 +13686,20 @@ components:
items:
type: string
description: Números de pedimento asociados a este concepto. Se omite cuando no existen.
+ third_party:
+ $ref: '#/components/schemas/ThirdParty'
+ complement:
+ anyOf:
+ - type: string
+ format: xml
+ - $ref: '#/components/schemas/HidroYPetroComplement'
+ - $ref: '#/components/schemas/IeduComplement'
parts:
type: array
items:
$ref: "#/components/schemas/Parts"
description: Objeto con información de las partes de la factura.
- ThirdPartyInput:
+ ThirdParty:
type: object
description: |
Objeto con información del contribuyente tercero, a cuenta del que se realiza la operación.
@@ -13701,6 +13724,9 @@ components:
type: string
description: Código postal del tercero.
example: "01234"
+ ThirdPartyInput:
+ allOf:
+ - $ref: "#/components/schemas/ThirdParty"
InvoiceItemInput:
title: InvoiceItem
required:
@@ -13865,13 +13891,9 @@ components:
type: string
description: Código postal del tercero.
example: "01234"
- HidroYPetroComplementInput:
+ HidroYPetroComplement:
title: HidroYPetroComplement
type: object
- required:
- - tipo_permiso
- - numero_permiso
- - sub_producto_hyp
properties:
tipo_permiso:
type: string
@@ -13946,18 +13968,20 @@ components:
- Diesel
- Combustibles para barco
- IeduComplementInput:
+ HidroYPetroComplementInput:
+ allOf:
+ - $ref: "#/components/schemas/HidroYPetroComplement"
+ required:
+ - tipo_permiso
+ - numero_permiso
+ - sub_producto_hyp
+ IeduComplement:
title: IeduComplement
type: object
description: |
Complemento concepto de Instituciones Educativas Privadas versión 1.0.
Se incluye a nivel concepto dentro de `items[].complement`.
- required:
- - nombreAlumno
- - CURP
- - nivelEducativo
- - autRVOE
properties:
nombreAlumno:
type: string
@@ -13991,6 +14015,14 @@ components:
description: RFC de quien realiza el pago cuando sea diferente a quien recibe el servicio.
example: PELJ800101ABC
+ IeduComplementInput:
+ allOf:
+ - $ref: "#/components/schemas/IeduComplement"
+ required:
+ - nombreAlumno
+ - CURP
+ - nivelEducativo
+ - autRVOE
CustomComplementData:
title: string
type: string
@@ -15189,7 +15221,7 @@ components:
CartaPorteDataInput:
allOf:
- $ref: "#/components/schemas/CartaPorteDataProperties"
- ComercioExteriorDataProperties:
+ ComercioExteriorCommonProperties:
type: object
title: ComercioExterior
description: Complemento Comercio Exterior versión 2.0.
@@ -15255,10 +15287,6 @@ components:
oneOf:
- $ref: "#/components/schemas/ComercioExteriorPropietario"
- $ref: "#/components/schemas/CustomerComercioExterior"
- Receptor:
- oneOf:
- - $ref: "#/components/schemas/ComercioExteriorReceptor"
- - $ref: "#/components/schemas/CustomerComercioExterior"
Destinatario:
type: array
items:
@@ -15267,9 +15295,25 @@ components:
- $ref: "#/components/schemas/CustomerComercioExterior"
Mercancias:
$ref: "#/components/schemas/ComercioExteriorMercancias"
+ ComercioExteriorDataProperties:
+ allOf:
+ - $ref: '#/components/schemas/ComercioExteriorCommonProperties'
+ properties:
+ Receptor:
+ anyOf:
+ - $ref: "#/components/schemas/ComercioExteriorReceptor"
+ - $ref: "#/components/schemas/CustomerComercioExterior"
ComercioExteriorDataInput:
allOf:
- - $ref: "#/components/schemas/ComercioExteriorDataProperties"
+ - $ref: '#/components/schemas/ComercioExteriorCommonProperties'
+ properties:
+ Receptor:
+ anyOf:
+ - $ref: "#/components/schemas/ComercioExteriorReceptor"
+ - $ref: "#/components/schemas/CustomerComercioExterior"
+ - type: string
+ pattern: '^[a-fA-F0-9]{24}$'
+ description: ID de un cliente existente.
ComercioExteriorDomicilio:
type: object
required:
@@ -16027,6 +16071,37 @@ components:
example: http://www.sat.gob.mx/sitio_interet/cfd/iedu/iedu.xsd
description: Dirección URL del esquema de validación XSD.
+ Address:
+ allOf:
+ - $ref: '#/components/schemas/CommonAddressProperties'
+ properties:
+ state:
+ type: string
+ country:
+ type: string
+ minLength: 3
+ maxLength: 3
+ CustomerAddress:
+ type: [object, 'null']
+ properties:
+ street:
+ type: [string, 'null']
+ exterior:
+ type: [string, 'null']
+ interior:
+ type: [string, 'null']
+ neighborhood:
+ type: [string, 'null']
+ city:
+ type: [string, 'null']
+ municipality:
+ type: [string, 'null']
+ zip:
+ type: [string, 'null']
+ state:
+ type: [string, 'null']
+ country:
+ type: [string, 'null']
CommonAddressProperties:
type: object
properties:
@@ -16242,25 +16317,12 @@ components:
- type: object
properties:
address:
- allOf:
- - $ref: "#/components/schemas/CommonAddressProperties"
- - type: object
- description: Domicilio fiscal.
- properties:
- state:
- type: string
- description: Si el país es México ("MEX"), contiene el nombre del Estado o Entidad Federativa. Para extranjeros contiene el código de Estado de acuerdo al estándar [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2), que puedes consultar en nuestro [Catálogo de Estados](https://dashboard.facturapi.io/catalogs/state).
- example: Sonora
- country:
- type: string
- description: Código de país acorde al estándar [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3), del [Catálogo de Países](https://dashboard.facturapi.io/catalogs/country).
- example: MEX
- default: MEX
+ $ref: '#/components/schemas/CustomerAddress'
CustomerCommonProperties:
type: object
properties:
legal_name:
- type: string
+ type: [string, 'null']
description: |
Nombre Fiscal o Razón Social del cliente. *sin* el régimen societario (ej.: S.A. de C.V.).
example: Dunder Mifflin
@@ -16279,7 +16341,7 @@ components:
minLength: 3
description: Requerido para clientes nacionales. Clave del régimen fiscal del cliente, del catálogo de [Regímenes Fiscales](#r%C3%A9gimen-fiscal).
email:
- type: string
+ type: [string, 'null']
format: email
description: Dirección de correo electrónico al cual enviar las facturas generadas.
example: email@example.com
@@ -16293,6 +16355,15 @@ components:
type: string
description: Uso de CFDI por defecto.
example: G01
+ CustomerEmailInput: &customer_email_input
+ type: [string, 'null']
+ format: email
+ CustomerEmailUpdateInput:
+ anyOf:
+ - $ref: '#/components/schemas/CustomerEmailInput'
+ - type: string
+ pattern: '^\s*$'
+ description: En una actualización, null, una cadena vacía o sólo espacios eliminan el correo.
CustomerEditableInput:
type: object
properties:
@@ -16302,10 +16373,8 @@ components:
Nombre Fiscal o Razón Social del cliente. *sin* el régimen societario (ej.: S.A. de C.V.).
example: Dunder Mifflin
tax_id: *customer_tax_id
- email: &customer_email_input
- type: [string, 'null']
- format: email
- description: Correo electrónico del cliente. En una actualización, null elimina el correo.
+ email:
+ $ref: '#/components/schemas/CustomerEmailUpdateInput'
phone: *customer_phone
external_id: &customer_external_id_input
type: [string, 'null']
@@ -16343,6 +16412,7 @@ components:
example: Huatabampo
zip:
type: string
+ minLength: 1
description: Código postal
example: '86500'
state: &customer_address_state_input
@@ -16352,7 +16422,7 @@ components:
default: MEX
description: ISO 3166-1 alpha-3.
CustomerUpdateInput:
- description: En una actualización sin createEditLink=true, los campos omitidos conservan su valor. Un valor null borra los campos que admiten null; un campo opcional puede omitirse sin que necesariamente admita null. No se permite null en legal_name, tax_id, tax_system ni address.
+ description: En una actualización sin createEditLink=true, los campos omitidos conservan su valor. Un valor null borra los campos que admiten null; un campo opcional puede omitirse sin que necesariamente admita null. No se permite null en legal_name, tax_id, tax_system ni address. En email, phone y los campos opcionales street, exterior, interior, neighborhood, city, municipality y state del domicilio, una cadena vacía o sólo espacios se normaliza a null. El código postal vacío no se acepta.
allOf:
- $ref: '#/components/schemas/CustomerEditableInput'
- type: object
@@ -16373,12 +16443,13 @@ components:
country:
type: string
CustomerUpdateWithEditLinkInput:
- description: Con createEditLink=true, los campos omitidos conservan su valor y null borra el valor existente, incluidos los datos fiscales. Los valores no nulos deben ser válidos.
+ description: Con createEditLink=true, los campos omitidos conservan su valor y null borra el valor existente, incluidos los datos fiscales. Los valores no nulos deben ser válidos. En email, phone y los campos opcionales street, exterior, interior, neighborhood, city, municipality y state del domicilio, una cadena vacía o sólo espacios se normaliza a null. El código postal vacío no se acepta.
type: object
properties:
legal_name: *customer_legal_name_input
tax_id: *customer_tax_id
- email: *customer_email_input
+ email:
+ $ref: '#/components/schemas/CustomerEmailUpdateInput'
phone: *customer_phone
external_id: *customer_external_id_input
default_invoice_use: *customer_default_invoice_use_input
@@ -16396,6 +16467,7 @@ components:
country: *customer_address_country_input
zip:
type: [string, 'null']
+ minLength: 1
description: Código postal
example: '86500'
CancellationQueryInput:
@@ -16488,8 +16560,8 @@ components:
state:
type: string
country:
- type: string
- const: MEX
+ type: [string, 'null']
+ enum: [MEX, '', null]
default: MEX
required:
- zip
@@ -16707,6 +16779,11 @@ components:
items:
$ref: "#/components/schemas/Product"
ProductCreateInput:
+ properties:
+ unit_name:
+ type: string
+ maxLength: 20
+ description: Palabra que representa la unidad de medida de tu producto. Debe estar relacionada con la clave de unidad `unit_key`.
allOf:
- $ref: "#/components/schemas/ProductCommonProperties"
required: [description, product_key, price]
@@ -16714,7 +16791,17 @@ components:
allOf:
- $ref: "#/components/schemas/ProductCommonProperties"
required: [description, product_key, price]
+ properties:
+ unit_name:
+ type: [string, 'null']
+ default: Elemento
+ description: Palabra que representa la unidad de medida de tu producto. Debe estar relacionada con la clave de unidad `unit_key`.
ProductUpdateInput:
+ properties:
+ unit_name:
+ type: string
+ maxLength: 20
+ description: Palabra que representa la unidad de medida de tu producto. Debe estar relacionada con la clave de unidad `unit_key`.
allOf:
- $ref: "#/components/schemas/ProductCommonProperties"
ProductCommonProperties:
@@ -16794,10 +16881,6 @@ components:
description: |
Clave de unidad de medida, del catálogo del SAT. El valor por default `"H87"` (elemento) es la clave para representar una pieza o unidad de venta (lápiz, cuaderno, televisión, etc).
Si la unidad de tu producto es kilogramos, litros, horas u otra unidad, puedes encontrar la clave utilizando nuestra [herramienta de búsqueda de claves](https://dashboard.facturapi.io/catalogs/unit).
- unit_name:
- type: string
- default: Elemento
- description: Palabra que representa la unidad de medida de tu producto. Debe estar relacionada con la clave de unidad `unit_key`.
sku:
type: string
description: Identificador de uso interno designado por la empresa. Puede tener cualquier valor.
@@ -17046,24 +17129,18 @@ components:
description: ID del objeto `customer` relacionado a la factura, en caso de no haber sido eliminado
example: 58e93bd8e86eb318b0197456
legal_name:
- type: string
+ type: [string, 'null']
description: |
Nombre Fiscal o Razón Social del cliente, *sin* incluir el régimen societario (ej.: S.A. de C.V.).
example: Dunder Mifflin
tax_id:
- type: string
+ type: [string, 'null']
description: RFC del cliente.
example: ABC101010111
address:
- type: object
- properties:
- country:
- type: string
- format: ISO 3166-1 alpha-3
- description: Código de País acorde al estándar ISO 3166-1 alpha-3, del Catálogo de Países.
- example: MEX
- zip:
- type: string
+ $ref: '#/components/schemas/Address'
+ phone:
+ type: [string, 'null']
tax_system:
type:
- string
@@ -17079,7 +17156,6 @@ components:
RelatedDocumentInput:
required:
- relationship
- - related
allOf:
- $ref: '#/components/schemas/RelatedDocument'
RelatedDocument:
@@ -17253,21 +17329,71 @@ components:
items:
$ref: "#/components/schemas/InvoiceZipRequest"
Invoice:
- title: Objeto Invoice
+ title: Invoice
+ oneOf:
+ - $ref: '#/components/schemas/InvoiceStamped'
+ - $ref: '#/components/schemas/InvoiceDraft'
+ - $ref: '#/components/schemas/InvoicePending'
+ - $ref: '#/components/schemas/InvoiceFailed'
+ InvoiceStamped:
+ title: Timbrada
allOf:
- - $ref: "#/components/schemas/ResourceAutoGeneratedProps"
- - $ref: "#/components/schemas/InvoiceProperties"
+ - $ref: '#/components/schemas/ResourceAutoGeneratedProps'
+ - $ref: '#/components/schemas/InvoiceCommonProperties'
+ - type: object
+ required: [date, status, uuid, stamp]
+ properties:
+ status:
+ type: string
+ enum: [valid, canceled]
+ date:
+ type: string
+ format: date-time
+ uuid:
+ type: string
+ format: uuid
+ stamp:
+ allOf:
+ - $ref: '#/components/schemas/Stamp'
+ required: [date, sat_signature, sat_cert_number, signature, complement_string]
+ properties:
+ complement_string:
+ type: string
+ InvoicePending:
+ title: Pendiente
+ allOf:
+ - $ref: '#/components/schemas/ResourceAutoGeneratedProps'
+ - $ref: '#/components/schemas/InvoiceProperties'
+ - type: object
+ properties:
+ status:
+ type: string
+ const: pending
+ InvoiceFailed:
+ title: Fallida
+ allOf:
+ - $ref: '#/components/schemas/ResourceAutoGeneratedProps'
+ - $ref: '#/components/schemas/InvoiceProperties'
+ - type: object
+ properties:
+ status:
+ type: string
+ const: failed
InvoiceCancellation:
type: object
description: Información de la solicitud de cancelación de la factura.
required: [status]
properties:
status:
- $ref: '#/components/schemas/InvoiceProperties/properties/cancellation_status'
+ $ref: '#/components/schemas/InvoiceCommonProperties/properties/cancellation_status'
requested_at:
type: string
format: date-time
description: Fecha y hora en que se solicitó la cancelación, cuando aplica.
+ last_checked:
+ type: string
+ format: date-time
+ description: Fecha y hora de inicio de la última consulta del estado de cancelación. Se omite si nunca se ha iniciado una consulta. No garantiza que la consulta haya terminado correctamente.
canceled_at:
type: string
format: date-time
@@ -17296,8 +17422,29 @@ components:
items:
$ref: "#/components/schemas/Invoice"
InvoiceProperties:
+ allOf:
+ - $ref: '#/components/schemas/InvoiceCommonProperties'
+ - type: object
+ required: [date, status]
+ properties:
+ date:
+ type:
+ - string
+ - 'null'
+ format: date-time
+ description: Fecha de expedición en formato ISO8601. Puede ser null en borradores.
+ uuid:
+ type: [string, "null"]
+ format: uuid
+ description: Folio fiscal de la factura, asignado por el SAT. En borradores sin timbrar puede ser null o estar omitido.
+ example: 39c85a3f-275b-4341-b259-e8971d9f8a94
+ stamp:
+ anyOf:
+ - $ref: '#/components/schemas/Stamp'
+ - type: 'null'
+ InvoiceCommonProperties:
type: object
- required: [date, status, cancellation_status, type, total, currency, exchange, items, amount_due]
+ required: [status, cancellation_status, type, total, currency, exchange, items, amount_due]
properties:
status:
type: string
@@ -17335,15 +17482,9 @@ components:
format: uri
description: Dirección URL para verificar el estado del CFDI en el portal del SAT. Este link es el mismo que aparece en el código QR, en el PDF de la factura.
example: https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?id=45BEC0CA-5F1E-491E-9417-698EA48C382A&re=AAA010101AAA&rr=ABC101010111&tt=345.600000&fe=bWApPw==
- date:
- type:
- - string
- - 'null'
- format: date-time
- description: Fecha de expedición en formato ISO8601. Puede ser null en borradores.
address:
allOf:
- - $ref: '#/components/schemas/CommonAddressProperties'
+ - $ref: '#/components/schemas/Address'
- type: object
description: Domicilio de expedición de la factura.
properties:
@@ -17369,11 +17510,6 @@ components:
type: number
description: Monto total facturado.
example: 10944.82
- uuid:
- type: [string, "null"]
- format: uuid
- description: Folio fiscal de la factura, asignado por el SAT. En borradores sin timbrar puede ser null o estar omitido.
- example: 39c85a3f-275b-4341-b259-e8971d9f8a94
folio_number:
type: integer
description: Número de folio autoincremental para control interno y sin validez fiscal.
@@ -17475,10 +17611,6 @@ components:
description: Namespaces a insertar en el nodo raíz de la factura. Requerido en `addenda`.
items:
$ref: '#/components/schemas/NamespaceProperties'
- stamp:
- anyOf:
- - $ref: '#/components/schemas/Stamp'
- - type: 'null'
organization:
description: "ID de la organización a la que pertenece este recurso."
type:
@@ -17528,7 +17660,23 @@ components:
type: integer
InvoiceDraftProperties:
allOf:
- - $ref: '#/components/schemas/InvoiceProperties'
+ - $ref: '#/components/schemas/InvoiceCommonProperties'
+ - type: object
+ required: [date, status]
+ properties:
+ status:
+ type: string
+ const: draft
+ date:
+ type:
+ - string
+ - 'null'
+ format: date-time
+ description: Fecha de expedición en formato ISO8601. Puede ser null en borradores.
+ uuid:
+ type: 'null'
+ stamp:
+ type: 'null'
InvoiceableCommonInput:
type: object
properties:
@@ -18634,6 +18782,12 @@ components:
- items
- key
properties:
+ address:
+ $ref: '#/components/schemas/Address'
+ api_version:
+ type: integer
+ readOnly: true
+ description: Versión de la API usada para crear el recibo.
date:
type: string
format: date-time
@@ -18667,7 +18821,7 @@ components:
example: 356.78
description: Monto total de la operación
invoice:
- type: string
+ type: [string, 'null']
example: 614496b471d422de4b6cfcc4
description: ID de la factura asociada, en caso de estar facturado.
customer:
@@ -19082,7 +19236,7 @@ components:
Estado actual de la retención.
example: valid
verification_url:
- type: string
+ type: [string, 'null']
format: uri
description: Dirección URL para verificar el estado de la retención en el portal del SAT. Este link es el mismo que aparece en el código QR, en el PDF de la retención.
example: https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?id=45BEC0CA-5F1E-491E-9417-698EA48C382A&re=AAA010101AAA&rr=ABC101010111&tt=345.600000&fe=bWApPw==
@@ -19093,7 +19247,7 @@ components:
example: Retención
description: Tipo de comprobante.
uuid:
- type: string
+ type: [string, 'null']
format: uuid
description: Folio fiscal de la retención, asignado por el SAT.
example: 39c85a3f-275b-4341-b259-e8971d9f8a94
@@ -19117,7 +19271,7 @@ components:
- fecha_exp
properties:
cve_retenc:
- type: string
+ type: [string, 'null']
example: "01"
description: Clave de la retención o información de pagos de acuerdo al catálogo del SAT.
fecha_exp:
@@ -19134,7 +19288,7 @@ components:
type: string
description: Identificador alfanumérico para control interno de la empresa y sin relevancia fiscal.
periodo:
- type: object
+ type: [object, 'null']
description: Información sobre el periodo de la retención.
properties:
mes_ini:
@@ -19151,7 +19305,7 @@ components:
type: integer
description: Año o ejercicio fiscal en que se realizó la retención.
totales:
- type: object
+ type: [object, 'null']
description: Información sobre el total de retenciones efectuadas en el periodo correspondiente.
properties:
monto_tot_operacion:
@@ -19191,6 +19345,9 @@ components:
type: number
minimum: 0
description: Importe del impuesto retenido
+ pago_provisional:
+ type: boolean
+ readOnly: true
tipo_pago_ret:
type: string
enum:
@@ -19204,10 +19361,10 @@ components:
- `03`: Pago definitivo ISR Plataformas
- `04`: Pago provisional ISR
external_id:
- type: string
+ type: [string, 'null']
description: Identificador opcional que puedes usar para relacionar esta retención con tus registros y poder hacer búsquedas usando este identificador. Facturapi no valida que este campo sea único.
idempotency_key:
- type: string
+ type: [string, 'null']
description: |
Identificador único que puedes usar para evitar duplicados al reintentar una petición. Puede ser cualquier cadena de texto, mientras sea única para cada documento.
Si se deja en blanco, no se tomará en cuenta.
@@ -19224,9 +19381,11 @@ components:
format: html
description: En caso de que necesites incluir más información en el PDF, este campo te permite insertar código HTML con tu propio contenido.
addenda:
- type: string
- format: xml
- description: Código XML con la Addenda que se necesite agregar a la factura.
+ type: array
+ items:
+ type: string
+ format: xml
+ description: Lista de fragmentos XML que se incluirán como addendas en la retención.
namespaces:
type: array
description: Namespaces a insertar en el nodo raíz de la factura. Requerido en `addenda`.
@@ -19429,9 +19588,11 @@ components:
format: html
description: En caso de que necesites incluir más información en el PDF, este campo te permite insertar código HTML con tu propio contenido.
addenda:
- type: string
- format: xml
- description: Código XML con la Addenda que se necesite agregar a la factura.
+ type: array
+ items:
+ type: string
+ format: xml
+ description: Lista de fragmentos XML que se incluirán como addendas en la retención.
namespaces:
type: array
description: Namespaces a insertar en el nodo raíz de la factura. Requerido en `addenda`.
@@ -19444,6 +19605,10 @@ components:
- type: object
description: Domicilio fiscal de la organización emisora.
properties:
+ country:
+ type: string
+ minLength: 3
+ maxLength: 3
state:
type: string
description: Nombre del Estado o Entidad Federativa.
@@ -19517,6 +19682,13 @@ components:
type: string
description: |
Nombre Fiscal o Razón Social de la organización, *sin* el régimen societario (ej.: S.A. de C.V.).
+ tax_id:
+ type: string
+ support_email:
+ type: string
+ format: email
+ curp:
+ type: string
tax_system:
type: string
example: '601'
@@ -19540,6 +19712,19 @@ components:
Configuración de personalización de la organización, que se utilizarán para reflejar el branding y
las preferencias de PDFs de la organización. Estos datos se pueden actualizar en cualquier momento.
properties:
+ default_series:
+ type: object
+ properties:
+ I:
+ type: string
+ E:
+ type: string
+ P:
+ type: string
+ N:
+ type: string
+ T:
+ type: string
has_logo:
type: boolean
description: Indica si la organización ya tiene un logotipo cargado.
@@ -20101,18 +20286,18 @@ components:
type: object
required:
- series
- - next_folio
- - next_folio_test
properties:
series:
type: string
- maxLength: 55
+ maxLength: 25
description: Nombre de la serie.
next_folio:
type: integer
+ default: 1
description: Número de folio que se asignará a la siguiente factura en ambiente Live (y que se incrementará automáticamente por cada nueva factura).
next_folio_test:
type: integer
+ default: 1
description: Número de folio que se asignará a la siguiente factura en ambiente Test (y que se incrementará automáticamente por cada nueva factura).
OrganizationSeriesUpdateInput:
type: object
@@ -20145,6 +20330,21 @@ components:
minLength: 1
maxLength: 25
description: Nombre de la serie.
+ OrganizationSeriesDeletion:
+ type: [object, 'null']
+ properties:
+ series:
+ type: string
+ livemode:
+ type: boolean
+ next_folio:
+ type: integer
+ created_at:
+ type: string
+ format: date-time
+ updated_at:
+ type: string
+ format: date-time
OrganizationSeriesGroup:
title: Objeto Series
type: object
diff --git a/website/package.json b/website/package.json
index 17ed999b3..f793076ba 100644
--- a/website/package.json
+++ b/website/package.json
@@ -36,6 +36,7 @@
"@types/react": "^19.1.8",
"@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3",
+ "ajv": "^8.20.0",
"js-yaml": "5.4.2",
"openapi-typescript": "7.13.0",
"typescript": "^5.8.3"
diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml
index 34b9c04de..d3e5f98c9 100644
--- a/website/pnpm-lock.yaml
+++ b/website/pnpm-lock.yaml
@@ -66,6 +66,9 @@ importers:
'@types/react-router-dom':
specifier: ^5.3.3
version: 5.3.3
+ ajv:
+ specifier: ^8.20.0
+ version: 8.20.0
js-yaml:
specifier: 5.4.2
version: 5.4.2
diff --git a/website/scripts/check-openapi.mjs b/website/scripts/check-openapi.mjs
index 7983d3d0a..398840d22 100644
--- a/website/scripts/check-openapi.mjs
+++ b/website/scripts/check-openapi.mjs
@@ -7,6 +7,7 @@ import { execFileSync } from "node:child_process";
import * as yaml from "js-yaml";
import openapiTS, { astToString } from "openapi-typescript";
import ts from "typescript";
+import Ajv2020 from "ajv/dist/2020.js";
const root = new URL("../", import.meta.url);
const contracts = [];
@@ -39,16 +40,106 @@ function contract(value, key) {
for (const filename of ["openapi_v2.yaml", "openapi_v2.en.yaml"]) {
// Match the generator parser so unquoted numeric SAT codes fail validation.
const spec = yaml.load(await readFile(new URL(filename, root), "utf8"));
+ const ancestors = new WeakSet();
+ const visited = new WeakSet();
+ function rejectAnchorCycles(value) {
+ if (!value || typeof value !== "object") return;
+ assert(!ancestors.has(value), "Cyclic YAML anchors are not supported");
+ if (visited.has(value)) return;
+ ancestors.add(value);
+ for (const child of Object.values(value)) rejectAnchorCycles(child);
+ ancestors.delete(value);
+ visited.add(value);
+ }
+ rejectAnchorCycles(spec);
+ const validateItemComplement = new Ajv2020({
+ strict: false,
+ validateFormats: false,
+ }).compile({
+ $ref: "#/components/schemas/InvoiceItem/properties/complement",
+ components: spec.components,
+ });
+ for (const complement of [
+ "",
+ { tipo_permiso: "PER01", numero_permiso: "123", sub_producto_hyp: "Diesel" },
+ {
+ nombreAlumno: "Alumno",
+ CURP: "FULJ790823MWSWSZ23",
+ nivelEducativo: "Primaria",
+ autRVOE: "123",
+ },
+ ]) {
+ assert(
+ validateItemComplement(complement),
+ `Valid invoice item complement rejected: ${JSON.stringify(validateItemComplement.errors)}`,
+ );
+ }
+ for (const complement of [null, 1, []])
+ assert(
+ !validateItemComplement(complement),
+ "Invalid invoice item complement accepted",
+ );
+ const validateForeignTradeReceiver = new Ajv2020({
+ strict: false,
+ validateFormats: false,
+ }).compile({
+ $ref: "#/components/schemas/ComercioExteriorDataProperties/properties/Receptor",
+ components: spec.components,
+ });
+ assert(
+ validateForeignTradeReceiver({ NumRegIdTrib: "123456" }),
+ "Foreign trade receiver object must not be rejected by overlapping branches",
+ );
+ // The sibling components object supplies the local $ref namespace to AJV.
+ for (const [schema, value, expected] of [
+ ["BaseTax", { type: "IVA", rate: 0.16, base: null }, true],
+ ["LocalTax", { type: "ISH", rate: 0.03, base: null }, true],
+ ["CustomerInfo", { legal_name: null, tax_id: null, phone: null }, true],
+ ["ProductProperties", { description: "Example", product_key: "60131324", price: 100, unit_name: null }, true],
+ ["ProductCreateInput", { description: "Example", product_key: "60131324", price: 100, unit_name: null }, false],
+ ["CustomerNationalAddressInput", { zip: "83200", country: null }, true],
+ ["RelatedDocumentInput", { relationship: "01", documents: ["39c85a3f-275b-4341-b259-e8971d9f8a94"] }, true],
+ ["OrganizationSeriesCreateInput", { series: "A" }, true],
+ ["CustomerProperties", { legal_name: null, address: null }, true],
+ ["RetentionProperties", { fecha_exp: null, cve_retenc: null, periodo: null, totales: null, addenda: [] }, true],
+ ["RetentionUpdateInput", { addenda: [""] }, true],
+ ]) {
+ const validate = new Ajv2020({ strict: false, validateFormats: false }).compile({
+ $ref: `#/components/schemas/${schema}`,
+ components: spec.components,
+ });
+ assert.equal(validate(value), expected, `Unexpected contract validation for ${schema}`);
+ }
for (const name of Object.keys(spec.components.schemas))
assert(!/(InputProperties|RequiredProperties)$/.test(name),
`Use an Input schema with its own required fields: ${name}`);
for (const [name, body] of Object.entries(spec.components.requestBodies)) {
- const reference = body.content?.["application/json"]?.schema?.$ref;
- if (reference) assert(reference.endsWith("Input"),
- `Request body schemas must end in Input: ${name}`);
+ for (const media of Object.values(body.content || {})) {
+ for (const schema of [media.schema, ...(media.schema?.oneOf || []), ...(media.schema?.anyOf || [])]) {
+ if (schema?.$ref) assert(schema.$ref.endsWith("Input"),
+ `Named request body schemas must end in Input: ${name}`);
+ }
+ }
}
function walk(value, path = [], references) {
if (!value || typeof value !== "object") return;
+ if (path[0] === "components" && path[1] === "schemas" && value.oneOf) {
+ const validate = new Ajv2020({
+ strict: false,
+ validateFormats: false,
+ }).compile({
+ ...value,
+ components: spec.components,
+ });
+ // This bounded check catches empty-object overlap, not every possible overlap.
+ validate({});
+ assert(
+ !validate.errors?.some(
+ error => error.keyword === "oneOf" && error.params.passingSchemas?.length > 1,
+ ),
+ `Overlapping oneOf branches accept the same empty object: ${path.join(".")}`,
+ );
+ }
assert(
!Object.hasOwn(value, "<<"),
`YAML merge keys are not supported: ${path.join(".")}`,
diff --git a/website/test/openapi-types.fixture.txt b/website/test/openapi-types.fixture.txt
index cbb221ea5..21d386c4f 100644
--- a/website/test/openapi-types.fixture.txt
+++ b/website/test/openapi-types.fixture.txt
@@ -166,6 +166,17 @@ const createdInvoiceDate: string | null | undefined = createdInvoiceResponse.dat
const createdInvoiceStamp: components["schemas"]["Stamp"] | null | undefined = createdInvoiceResponse.stamp;
void createdInvoiceDate;
void createdInvoiceStamp;
+if (createdInvoiceResponse.status === 'valid' || createdInvoiceResponse.status === 'canceled') {
+ const stampedFields: [string, string, string, string, string, string] = [
+ createdInvoiceResponse.uuid,
+ createdInvoiceResponse.stamp.date,
+ createdInvoiceResponse.stamp.signature,
+ createdInvoiceResponse.stamp.sat_signature,
+ createdInvoiceResponse.stamp.sat_cert_number,
+ createdInvoiceResponse.stamp.complement_string,
+ ];
+ void stampedFields;
+}
// A payment summary can be reused directly in a payment complement.
declare const paymentSummary: operations["getInvoicePaymentSummary"]["responses"][200]["content"]["application/json"];
@@ -248,3 +259,45 @@ void retentionTaxResponse;
const nullUpdatedTaxSystem: components['schemas']['CustomerUpdateInput'] = { tax_system: null };
const clearedCustomerWithEditLink: components['schemas']['CustomerUpdateWithEditLinkInput'] = { legal_name: null, tax_id: null, tax_system: null, address: null };
+
+const organizationSeriesList: operations['getSeriesGroup']['responses'][200]['content']['application/json'] = [{ series: 'A', next_folio: 1 }];
+// @ts-expect-error Series listing returns an array, without a data envelope.
+const organizationSeriesEnvelope: operations['getSeriesGroup']['responses'][200]['content']['application/json'] = { data: [] };
+
+const cancellationLastChecked: components['schemas']['InvoiceCancellation'] = { status: "none", last_checked: "2026-10-14T12:00:00Z" };
+
+const sharedThirdParty: components['schemas']['ThirdParty'] = { tax_id: 'MIC920101HN7' };
+const invoiceThirdParty: components['schemas']['InvoiceItem']['third_party'] = sharedThirdParty;
+// @ts-expect-error educational complement requests retain required student fields
+const incompleteIeduInput: components['schemas']['IeduComplementInput'] = {};
+// @ts-expect-error hydrocarbon complement requests retain required permit fields
+const incompleteHydrocarbonInput: components['schemas']['HidroYPetroComplementInput'] = {};
+void [invoiceThirdParty, incompleteIeduInput, incompleteHydrocarbonInput];
+
+const nullTaxBase: components['schemas']['BaseTax'] = { type: 'IVA', rate: 0.16, base: null };
+const nullLocalTaxBase: components['schemas']['LocalTax'] = { type: 'ISH', rate: 0.03, base: null };
+const incompleteInvoiceCustomer: components['schemas']['CustomerInfo'] = { legal_name: null, tax_id: null, phone: null };
+const uninvoicedReceipt: components['schemas']['ReceiptProperties']['invoice'] = null;
+const responseProductWithNullUnit: components['schemas']['ProductProperties'] = { description: 'Example', product_key: '60131324', price: 100, unit_name: null };
+// @ts-expect-error product creation still requires a non-null unit name when supplied
+const inputProductWithNullUnit: components['schemas']['ProductCreateInput'] = { description: 'Example', product_key: '60131324', price: 100, unit_name: null };
+const completeResponseAddress: components['schemas']['Address'] = { state: 'Sonora', country: 'MEX' };
+void [nullTaxBase, nullLocalTaxBase, incompleteInvoiceCustomer, uninvoicedReceipt, responseProductWithNullUnit, inputProductWithNullUnit, completeResponseAddress];
+const nationalAddressWithDefaultCountry: components['schemas']['CustomerNationalAddressInput'] = { zip: '83200', country: null };
+const relatedInvoiceByUuid: components['schemas']['RelatedDocumentInput'] = { relationship: '01', documents: ['39c85a3f-275b-4341-b259-e8971d9f8a94'] };
+const seriesWithDefaultFolios: components['schemas']['OrganizationSeriesCreateInput'] = { series: 'A' };
+const receiptAddress: components['schemas']['ReceiptProperties']['address'] = { country: 'MEX', state: 'Sonora' };
+const invoiceCustomerAddress: components['schemas']['CustomerInfo']['address'] = { city: 'Huatabampo', street: 'Example' };
+void [nationalAddressWithDefaultCountry, relatedInvoiceByUuid, seriesWithDefaultFolios, receiptAddress, invoiceCustomerAddress];
+const clearedCustomerResponse: components['schemas']['CustomerProperties'] = { email: null, legal_name: null, address: null };
+const emptyRetentionResponse: components['schemas']['RetentionProperties'] = { fecha_exp: null, cve_retenc: null, periodo: null, totales: null, addenda: [] };
+const retentionWithAddenda: components['schemas']['RetentionUpdateInput'] = { addenda: [''] };
+const createdSeriesResponse: operations['createSeriesGroup']['responses'][200]['content']['application/json'] = [{ series: 'A', next_folio: 1, next_folio_test: 1 }];
+const updatedSeriesResponse: operations['updateSeriesGroup']['responses'][200]['content']['application/json'] = createdSeriesResponse;
+const deletedSeriesResponse: operations['deleteSeriesGroup']['responses'][200]['content']['application/json'] = [{ series: 'A', livemode: true, next_folio: 1 }, null];
+void [clearedCustomerResponse, emptyRetentionResponse, retentionWithAddenda, createdSeriesResponse, updatedSeriesResponse, deletedSeriesResponse];
+const blankCustomerUpdate: components['schemas']['CustomerUpdateInput'] = { email: ' ', phone: '', address: { street: ' ', zip: '77535' } };
+void blankCustomerUpdate;
+
+const foreignTradeCustomerId: NonNullable = '507f1f77bcf86cd799439011';
+void foreignTradeCustomerId;