diff --git a/website/docs/getting-started/errors.mdx b/website/docs/getting-started/errors.mdx index 6a5b9bbd3..70e57fec3 100644 --- a/website/docs/getting-started/errors.mdx +++ b/website/docs/getting-started/errors.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 7 +sidebar_position: 8 --- import Tabs from '@theme/Tabs'; @@ -196,7 +196,7 @@ Esta lista es la referencia documentada de códigos raíz públicos de la API. P | Código | Descripción | | --- | --- | -| `idempotency_key_in_use` | La idempotency key ya está siendo usada. | +| `idempotency_key_in_use` | La `idempotency_key` está siendo procesada por otra solicitud o ya está asociada a un recurso. La API responde con HTTP `409` y no reproduce la respuesta original. Consulta [Idempotencia](/docs/getting-started/idempotency). | | `rate_limit_exceeded` | Se excedió el límite de solicitudes. | | `date_range_too_large` | El rango de fechas excede el límite permitido. | | `image_file_required` | Se requiere un archivo de imagen. | diff --git a/website/docs/getting-started/idempotency.mdx b/website/docs/getting-started/idempotency.mdx new file mode 100644 index 000000000..146d0ed8d --- /dev/null +++ b/website/docs/getting-started/idempotency.mdx @@ -0,0 +1,53 @@ +--- +sidebar_position: 7 +--- + +# Idempotencia + +Los endpoints que aceptan `idempotency_key` usan este valor para evitar que una solicitud repetida o concurrente cree más de un recurso. La clave es opcional, puede tener hasta 100 caracteres y debe representar una sola operación lógica. + +Genera la clave antes de enviar la solicitud y conserva el mismo valor para todos los reintentos de esa operación. Si reintentas con una clave nueva, Facturapi interpreta la solicitud como una operación distinta y podría crear un duplicado. + +## Alcance de la clave + +La unicidad de `idempotency_key` se evalúa por: + +- organización; +- ambiente de API (`test` o `live`); y +- tipo de recurso, por ejemplo factura, recibo o retención. + +Por lo tanto, una misma clave puede existir en organizaciones, ambientes o tipos de recurso diferentes. Una cadena vacía se ignora. + +## Solicitudes concurrentes y repetidas + +Facturapi impide que dos solicitudes con la misma clave creen el mismo recurso. Si otra solicitud está procesando esa clave, o si la clave ya está asociada a un recurso, la API responde con HTTP `409` y el código `idempotency_key_in_use`: + +```json +{ + "ok": false, + "message": "La clave de idempotencia (idempotency_key) ya está siendo usada.", + "status": 409, + "code": "idempotency_key_in_use" +} +``` + +La API no reproduce la respuesta de la primera solicitud. Un conflicto tampoco confirma que esa solicitud haya fallado: puede seguir procesándose o haber creado el recurso. + +## Expiración después de un timeout + +Un timeout del cliente no cancela la solicitud en Facturapi. La operación puede continuar procesándose aunque tu integración ya no esté esperando la respuesta. + +Cuando Facturapi reserva temporalmente una clave durante la creación del recurso, libera la reserva en cuanto termina la solicitud. Si el procesamiento se interrumpe antes de poder liberarla, la reserva expira **10 minutos después de haberse creado**. + +Al terminar ese periodo, Facturapi vuelve a comprobar la clave antes de aceptar un reintento: + +- si no se creó ningún recurso, la misma clave puede volver a usarse; y +- si la solicitud original sí creó el recurso, la clave permanece asociada a éste y el reintento continúa respondiendo con `idempotency_key_in_use`. + +Los 10 minutos corresponden únicamente a la reserva temporal de una solicitud interrumpida. No son el tiempo de expiración de una clave que ya está asociada a un recurso ni garantizan que la solicitud original haya fallado. + +## Reintentos seguros + +Si pierdes la respuesta por un timeout o un error de red, reintenta con la misma `idempotency_key`. Si recibes `idempotency_key_in_use`, verifica el resultado de la operación original antes de volver a intentarla o de generar otra clave. + +Cuando una solicitud termina con error sin crear el recurso, la misma clave puede volver a usarse después de que la solicitud anterior haya terminado. diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/getting-started/errors.mdx b/website/i18n/en/docusaurus-plugin-content-docs/current/getting-started/errors.mdx index 293e62f02..2c400a07f 100644 --- a/website/i18n/en/docusaurus-plugin-content-docs/current/getting-started/errors.mdx +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/getting-started/errors.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 7 +sidebar_position: 8 --- import Tabs from '@theme/Tabs'; @@ -196,7 +196,7 @@ This list is the documented reference for public root API codes. We may add new | Code | Description | | --- | --- | -| `idempotency_key_in_use` | The idempotency key is already being used by another request. | +| `idempotency_key_in_use` | The `idempotency_key` is being processed by another request or is already associated with a resource. The API responds with HTTP `409` and does not replay the original response. See [Idempotency](/docs/getting-started/idempotency). | | `rate_limit_exceeded` | The request exceeded the allowed rate limit. | | `date_range_too_large` | The requested date range exceeds the allowed limit. | | `image_file_required` | An image file is required. | diff --git a/website/i18n/en/docusaurus-plugin-content-docs/current/getting-started/idempotency.mdx b/website/i18n/en/docusaurus-plugin-content-docs/current/getting-started/idempotency.mdx new file mode 100644 index 000000000..f57a3a45b --- /dev/null +++ b/website/i18n/en/docusaurus-plugin-content-docs/current/getting-started/idempotency.mdx @@ -0,0 +1,53 @@ +--- +sidebar_position: 7 +--- + +# Idempotency + +Endpoints that accept `idempotency_key` use this value to prevent a repeated or concurrent request from creating more than one resource. The key is optional, can contain up to 100 characters, and must represent a single logical operation. + +Generate the key before sending the request and keep the same value for every retry of that operation. If you retry with a new key, Facturapi treats the request as a different operation and may create a duplicate. + +## Key scope + +The uniqueness of `idempotency_key` is evaluated by: + +- organization; +- API environment (`test` or `live`); and +- resource type, such as invoice, receipt, or retention. + +The same key may therefore exist in different organizations, environments, or resource types. An empty string is ignored. + +## Concurrent and repeated requests + +Facturapi prevents two requests with the same key from creating the same resource. If another request is processing that key, or if the key is already associated with a resource, the API responds with HTTP `409` and the `idempotency_key_in_use` code: + +```json +{ + "ok": false, + "message": "The idempotency key is already being used by another request.", + "status": 409, + "code": "idempotency_key_in_use" +} +``` + +The API does not replay the first request's response. A conflict also does not confirm that the first request failed: it may still be processing or may have created the resource. + +## Expiration after a timeout + +A client-side timeout does not cancel the request in Facturapi. The operation may continue processing even after your integration stops waiting for the response. + +When Facturapi temporarily reserves a key while creating the resource, it releases the reservation as soon as the request finishes. If processing is interrupted before the reservation can be released, it expires **10 minutes after it was created**. + +After that period, Facturapi checks the key again before accepting a retry: + +- if no resource was created, the same key can be used again; and +- if the original request did create the resource, the key remains associated with it and the retry continues to return `idempotency_key_in_use`. + +The 10-minute period applies only to the temporary reservation left by an interrupted request. It is not an expiration time for a key already associated with a resource, and it does not guarantee that the original request failed. + +## Safe retries + +If you lose the response because of a timeout or network error, retry with the same `idempotency_key`. If you receive `idempotency_key_in_use`, reconcile the original operation's outcome before retrying again or generating another key. + +When a request ends with an error without creating the resource, the same key can be used again after the previous request has finished. diff --git a/website/openapi_v2.en.yaml b/website/openapi_v2.en.yaml index 0514d3d7d..05073e5cc 100644 --- a/website/openapi_v2.en.yaml +++ b/website/openapi_v2.en.yaml @@ -3542,6 +3542,8 @@ paths: $ref: "#/components/responses/Unauthenticated" "404": $ref: "#/components/responses/NotFound" + "409": + $ref: "#/components/responses/IdempotencyKeyInUse" "429": $ref: "#/components/responses/RateLimited" "500": @@ -5820,6 +5822,8 @@ paths: $ref: "#/components/responses/Unauthenticated" "404": $ref: "#/components/responses/NotFound" + "409": + $ref: "#/components/responses/IdempotencyKeyInUse" "429": $ref: "#/components/responses/RateLimited" "500": @@ -7246,6 +7250,8 @@ paths: $ref: "#/components/responses/Unauthenticated" "404": $ref: "#/components/responses/NotFound" + "409": + $ref: "#/components/responses/IdempotencyKeyInUse" "429": $ref: "#/components/responses/RateLimited" "500": @@ -12581,6 +12587,17 @@ components: application/json: schema: $ref: "#/components/schemas/GenericError" + IdempotencyKeyInUse: + description: The `idempotency_key` is being processed by another request or is already associated with a resource. Facturapi does not replay the original response. + content: + application/json: + schema: + $ref: "#/components/schemas/GenericError" + example: + message: The idempotency key is already being used by another request. + status: 409 + ok: false + code: idempotency_key_in_use NotFound: description: Resource not found content: @@ -17665,7 +17682,8 @@ components: description: Identifier that you can use to relate this invoice to your records to later search by this number. idempotency_key: type: string - 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. + maxLength: 100 + description: Key that identifies one operation to prevent duplicate resources. See [Idempotency](/docs/getting-started/idempotency). payment_form: type: [string, "null"] description: Payment form code according to the [Payment Form catalog](#payment-form). @@ -18122,10 +18140,11 @@ components: Optional identifier that you can use to relate this invoice to your records and later search by this number. Facturapi does not validate that this field is unique. idempotency_key: type: string + maxLength: 100 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. - - If left blank, it will not be taken into account. + Key that identifies one operation to prevent duplicate resources from repeated or concurrent requests. It must be unique within the same organization, environment, and resource type. + If another request is processing the key or it is already associated with a resource, the API responds with HTTP `409` and `code: idempotency_key_in_use`; it does not replay the original response. + An empty value is ignored. See [Idempotency](/docs/getting-started/idempotency). - $ref: "#/components/schemas/InvoiceableCommonInput" InvoiceCommonEditInput: allOf: @@ -18164,10 +18183,11 @@ components: Optional identifier that you can use to relate this invoice to your records and later search by this number. Facturapi does not validate that this field is unique. idempotency_key: type: string + maxLength: 100 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. - - If left blank, it will not be taken into account. + Key that identifies one operation to prevent duplicate resources from repeated or concurrent requests. It must be unique within the same organization, environment, and resource type. + If another request is processing the key or it is already associated with a resource, the API responds with HTTP `409` and `code: idempotency_key_in_use`; it does not replay the original response. + An empty value is ignored. See [Idempotency](/docs/getting-started/idempotency). - $ref: "#/components/schemas/InvoiceableCommonEditInput" InvoiceDraftCommonInput: allOf: @@ -19018,10 +19038,9 @@ components: Optional identifier that you can use to relate this receipt to your records and later search by this number. Facturapi does not validate that this field is unique. idempotency_key: type: string + maxLength: 100 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. - - If left blank, it will not be taken into account. + Key that identifies one operation to prevent duplicate resources. See [Idempotency](/docs/getting-started/idempotency). - $ref: "#/components/schemas/ReceiptCommonProperties" ReceiptInput: allOf: @@ -19065,10 +19084,11 @@ components: properties: idempotency_key: type: string + maxLength: 100 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. - - If left blank, it will not be taken into account. + Key that identifies one operation to prevent duplicate resources from repeated or concurrent requests. It must be unique within the same organization, environment, and resource type. + If another request is processing the key or it is already associated with a resource, the API responds with HTTP `409` and `code: idempotency_key_in_use`; it does not replay the original response. + An empty value is ignored. See [Idempotency](/docs/getting-started/idempotency). ReceiptCommonProperties: type: object properties: @@ -19562,10 +19582,10 @@ components: 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, 'null'] + maxLength: 100 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. - - If left blank, it will not be taken into account. + Key that identifies one operation to prevent duplicate resources. An empty value is ignored. + See [Idempotency](/docs/getting-started/idempotency). complements: type: array default: [] @@ -19778,8 +19798,10 @@ components: 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 + maxLength: 100 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. + Key that identifies one operation to prevent duplicate resources from repeated or concurrent requests. It must be unique within the same organization, environment, and resource type. + If another request is processing the key or it is already associated with a resource, the API responds with HTTP `409` and `code: idempotency_key_in_use`; it does not replay the original response. See [Idempotency](/docs/getting-started/idempotency). complements: type: array default: [] diff --git a/website/openapi_v2.yaml b/website/openapi_v2.yaml index a183e5963..dfc4b503c 100644 --- a/website/openapi_v2.yaml +++ b/website/openapi_v2.yaml @@ -3512,6 +3512,8 @@ paths: $ref: "#/components/responses/Unauthenticated" "404": $ref: "#/components/responses/NotFound" + "409": + $ref: "#/components/responses/IdempotencyKeyInUse" "429": $ref: "#/components/responses/RateLimited" "500": @@ -5785,6 +5787,8 @@ paths: $ref: "#/components/responses/Unauthenticated" "404": $ref: "#/components/responses/NotFound" + "409": + $ref: "#/components/responses/IdempotencyKeyInUse" "429": $ref: "#/components/responses/RateLimited" "500": @@ -7207,6 +7211,8 @@ paths: $ref: "#/components/responses/Unauthenticated" "404": $ref: "#/components/responses/NotFound" + "409": + $ref: "#/components/responses/IdempotencyKeyInUse" "429": $ref: "#/components/responses/RateLimited" "500": @@ -12532,6 +12538,17 @@ components: application/json: schema: $ref: "#/components/schemas/GenericError" + IdempotencyKeyInUse: + description: La `idempotency_key` está siendo procesada por otra solicitud o ya está asociada a un recurso. Facturapi no reproduce la respuesta original. + content: + application/json: + schema: + $ref: "#/components/schemas/GenericError" + example: + message: La clave de idempotencia (idempotency_key) ya está siendo usada. + status: 409 + ok: false + code: idempotency_key_in_use NotFound: description: No se encontró el recurso especificado. content: @@ -17617,7 +17634,8 @@ components: description: Identificador que puedes usar para relacionar esta factura con tus registros para después buscar por este número. idempotency_key: type: string - 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. + maxLength: 100 + description: Clave que identifica una operación para evitar recursos duplicados. Consulta [Idempotencia](/docs/getting-started/idempotency). payment_form: type: [string, "null"] description: Código que representa la forma de pago, de acuerdo al [catálogo del SAT](#forma-de-pago). @@ -18063,9 +18081,11 @@ components: description: Identificador opcional que puedes usar para relacionar esta factura con tus registros y poder hacer búsquedas usando este identificador. Facturapi no valida que este campo sea único. idempotency_key: type: string + maxLength: 100 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. + Clave que identifica una operación para evitar recursos duplicados al repetir o enviar solicitudes en paralelo. Debe ser única dentro de la misma organización, ambiente y tipo de recurso. + Si otra solicitud está procesando la clave o si ya está asociada a un recurso, la API responde HTTP `409` con `code: idempotency_key_in_use`; no reproduce la respuesta original. + Si se deja en blanco, no se toma en cuenta. Consulta [Idempotencia](/docs/getting-started/idempotency). - $ref: "#/components/schemas/InvoiceableCommonInput" InvoiceCommonEditInput: allOf: @@ -18103,9 +18123,11 @@ components: description: Identificador opcional que puedes usar para relacionar esta factura con tus registros y poder hacer búsquedas usando este identificador. Facturapi no valida que este campo sea único. idempotency_key: type: string + maxLength: 100 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. + Clave que identifica una operación para evitar recursos duplicados al repetir o enviar solicitudes en paralelo. Debe ser única dentro de la misma organización, ambiente y tipo de recurso. + Si otra solicitud está procesando la clave o si ya está asociada a un recurso, la API responde HTTP `409` con `code: idempotency_key_in_use`; no reproduce la respuesta original. + Si se deja en blanco, no se toma en cuenta. Consulta [Idempotencia](/docs/getting-started/idempotency). - $ref: "#/components/schemas/InvoiceableCommonEditInput" InvoiceDraftCommonInput: allOf: @@ -18946,7 +18968,8 @@ components: description: Identificador que puedes usar para relacionar este recibo con tus registros para después buscar por este número. idempotency_key: type: string - 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. + maxLength: 100 + description: Clave que identifica una operación para evitar recursos duplicados. Consulta [Idempotencia](/docs/getting-started/idempotency). - $ref: "#/components/schemas/ReceiptCommonProperties" ReceiptInput: allOf: @@ -18990,9 +19013,11 @@ components: properties: idempotency_key: type: string + maxLength: 100 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. + Clave que identifica una operación para evitar recursos duplicados al repetir o enviar solicitudes en paralelo. Debe ser única dentro de la misma organización, ambiente y tipo de recurso. + Si otra solicitud está procesando la clave o si ya está asociada a un recurso, la API responde HTTP `409` con `code: idempotency_key_in_use`; no reproduce la respuesta original. + Si se deja en blanco, no se toma en cuenta. Consulta [Idempotencia](/docs/getting-started/idempotency). ReceiptCommonProperties: type: object properties: @@ -19469,9 +19494,10 @@ components: 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, 'null'] + maxLength: 100 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. + Clave que identifica una operación para evitar recursos duplicados. Si se deja en blanco, no se toma en cuenta. + Consulta [Idempotencia](/docs/getting-started/idempotency). complements: type: array default: [] @@ -19676,8 +19702,10 @@ components: 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 + maxLength: 100 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. + Clave que identifica una operación para evitar recursos duplicados al repetir o enviar solicitudes en paralelo. Debe ser única dentro de la misma organización, ambiente y tipo de recurso. + Si otra solicitud está procesando la clave o si ya está asociada a un recurso, la API responde HTTP `409` con `code: idempotency_key_in_use`; no reproduce la respuesta original. Consulta [Idempotencia](/docs/getting-started/idempotency). complements: type: array default: []