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;