> ## Documentation Index
> Fetch the complete documentation index at: https://dev.acpay.do/llms.txt
> Use this file to discover all available pages before exploring further.

# Listar las notas de una factura

> Notas de crédito y débito que modifican la factura `{id}`, de la más reciente a la más antigua.



## OpenAPI

````yaml /api-reference/ecf.json get /v1/invoices/{id}/notes
openapi: 3.0.0
info:
  title: AC-PAY Facturación Electrónica API
  description: >

    API para emitir y consultar comprobantes fiscales electrónicos (e-CF) ante
    la DGII de República Dominicana.


    ## Autenticación

    Envía tu llave en cada solicitud: `Authorization: Bearer <token>`.

    Las llaves tienen el ambiente en el prefijo: `acpay_test_...` (pruebas, sin
    validez fiscal) o `acpay_live_...` (producción).

    Cada ambiente tiene su propia URL base; una llave de un ambiente es
    rechazada en el otro con `401`.


    ## Idempotencia

    Todas las operaciones `POST` (emitir, anular, reintentar, notas,
    comprobantes especiales, registrar y probar webhooks) exigen el encabezado
    `Idempotency-Key`. `PATCH` y `DELETE` no la usan: repetirlos deja el mismo
    resultado.

    Reintentar con la misma llave y la misma solicitud durante 24 h devuelve la
    respuesta original con `Idempotent-Replayed: true` sin volver a ejecutarla.

    Usar la misma llave con otra solicitud devuelve `422`; si la primera sigue
    en curso, `409`.

    No se guardan los `401`, `403`, `409`, `429`, los `5xx` ni los `400` de
    validación del cuerpo: después de cualquiera de ellos puedes reintentar con
    la misma llave.


    ## Errores

    Todas las respuestas de error tienen la forma `{ "error": { "status": 400,
    "message": "...", "details": ["..."] } }`.

    `details` aparece solo cuando hay errores por campo.


    ## Límites de uso

    Hay dos límites y ambos responden `429`:

    - **Solicitudes por minuto** de tu llave: el `429` trae `Retry-After`
    (segundos a esperar). Cuando están disponibles se incluyen los encabezados
    `X-RateLimit-*`.

    - **Comprobantes por mes** de cada tipo de e-CF (al emitir): el `429` no
    trae `Retry-After` y su mensaje dice cuándo se renueva. Consulta tu consumo
    con `GET /v1/limits` (sección **Límites**).


    ## Estados y webhooks

    El estado de cada e-CF ante la DGII se actualiza solo, en segundo plano: no
    necesitas consultar en bucle. Registra un webhook (sección **Webhooks**)
    para recibir los cambios de estado; las consultas `GET` siguen actualizando
    el estado al momento si lo necesitas.


    ## Trazabilidad

    Cada respuesta incluye `X-Request-Id` (puedes enviar el tuyo). Inclúyelo al
    reportar un problema.
  version: '1.0'
  contact: {}
servers:
  - url: https://e-cf.acpay.do/sandbox
    description: Sandbox (pruebas)
security: []
tags:
  - name: Facturas electrónicas (e-CF)
    description: ''
  - name: Notas de crédito y débito
    description: ''
  - name: Comprobantes especiales
    description: ''
  - name: Secuencias
    description: ''
  - name: Límites
    description: >

      Tu plan puede fijar un **límite mensual de comprobantes por tipo de e-CF**
      (p. ej. 500 e-CF 31 al mes). Consulta con `GET /v1/limits` cuánto llevas
      emitido y cuánto te queda.


      - Solo aparecen los tipos con límite; un tipo que no aparece no tiene
      límite.

      - El mes se cuenta en hora de República Dominicana y se renueva al inicio
      del mes siguiente (`resetsAt`).

      - Te avisamos por correo (al dueño del comercio y a los usuarios de su
      backoffice) al llegar al **80 %** y al **100 %** del límite de cada tipo,
      una vez por mes.

      - `remaining` nunca es negativo y `percentUsed` es un entero redondeado.

      - En el ambiente de pruebas los límites no se aplican (`enforced: false`).


      **Al alcanzar el límite**, emitir un e-CF de ese tipo (`POST
      /v1/invoices`, `POST /v1/invoices/{id}/credit-notes`, `POST
      /v1/invoices/{id}/debit-notes` y `POST /v1/special-documents`) responde
      `429` **sin** `Retry-After`, con un mensaje como:


      ```json

      { "error": { "status": 429, "message": "Alcanzaste el límite mensual de
      comprobantes 31 (500). Se renueva el 1 de octubre; para ampliarlo contacta
      a AC-PAY." } }

      ```


      No lo confundas con el `429` por exceso de solicitudes por minuto, que sí
      trae `Retry-After` (segundos a esperar). El `429` por límite mensual no se
      resuelve reintentando: espera la renovación o pide ampliarlo.


      - Consultar, descargar y anular documentos sigue funcionando con el límite
      alcanzado.

      - Reintentar un documento ya emitido (`/retry` o la misma
      `Idempotency-Key`) no vuelve a contar.

      - Ningún `429` se guarda como respuesta idempotente: cuando se amplíe el
      límite puedes reintentar con la misma `Idempotency-Key`.
  - name: Webhooks
    description: >

      Registra una URL HTTPS de tu servidor y te avisamos cuando cambia el
      estado de un e-CF ante la DGII, sin que tengas que consultar.


      ## Cómo recibir y verificar los eventos


      Enviamos un `POST` con cuerpo JSON a tu URL:


      ```json

      {
        "event": "ecf.accepted",
        "data": {
          "documentType": "invoice",
          "id": "3f1c2b9e-8a4d-4f6e-9c1a-2b7d5e8f0a13",
          "encf": "E310000000001",
          "ecfType": 31,
          "status": "accepted",
          "statusMessage": null,
          "invoiceId": null,
          "occurredAt": "2026-09-27T14:15:00.000Z"
        },
        "timestamp": "2026-09-27T14:15:01.000Z",
        "deliveryId": "b3d4f5a6-1c2e-4f7a-8b9c-0d1e2f3a4b5c"
      }

      ```


      - `event`: `ecf.accepted`, `ecf.rejected`, `ecf.cancelled` o `test`
      (entregas de `POST /v1/webhooks/{id}/test`, cuyo `data` es `{ "message":
      "..." }`).

      - `data.documentType`: `invoice`, `credit_note`, `debit_note` o
      `special_document`.

      - `data.status` / `data.statusMessage`: el nuevo estado y su detalle (p.
      ej. el motivo del rechazo).

      - `data.invoiceId`: en las notas, el e-CF de venta que modifican; `null`
      en el resto.

      - `data.occurredAt`: cuándo cambió el estado. `timestamp`: cuándo se envió
      esta entrega.


      Encabezados de cada entrega:


      | Encabezado | Contenido |

      | --- | --- |

      | `X-Acpay-Signature` | `sha256=<hex>`: HMAC-SHA256 del cuerpo **crudo**
      con el secreto del webhook. |

      | `X-Acpay-Event` | El mismo `event` del cuerpo. |

      | `X-Acpay-Delivery` | El mismo `deliveryId` del cuerpo. |


      **Verifica cada entrega antes de procesarla:**


      1. Calcula el HMAC-SHA256 del cuerpo tal como llegó (bytes crudos, antes
      de parsear el JSON) con tu secreto y compáralo con `X-Acpay-Signature` en
      tiempo constante. Si no coincide, responde `401` y descártala.

      2. Rechaza la entrega si `timestamp` tiene más de 5 minutos (protección
      contra repeticiones).

      3. Deduplica por `deliveryId`: un reintento trae el mismo `deliveryId`; si
      ya lo procesaste, responde `200` sin repetir el trabajo.


      El secreto solo se devuelve al crear el webhook (`POST /v1/webhooks`). Si
      lo pierdes, borra el webhook y crea otro.


      **Responde `2xx` en menos de 10 segundos** (procesa en segundo plano si
      tardas más). Si no respondes `2xx` a tiempo, reintentamos 1 min, 5 min, 30
      min, 2 h y 24 h después de cada fallo: hasta 6 intentos en total (el
      inicial y 5 reintentos). Puedes ver cada entrega y sus intentos en
      `recentDeliveries` de `GET /v1/webhooks/{id}`.


      **Los eventos son livianos:** para el detalle completo consulta el
      documento según `data.documentType`: `GET /v1/invoices/{id}` (`invoice`),
      `GET /v1/notes/{id}` (`credit_note` y `debit_note`) o `GET
      /v1/special-documents/{id}` (`special_document`).


      Ejemplo de verificación en Node.js (Express):


      ```js

      const crypto = require('crypto');

      const express = require('express');


      const WEBHOOK_SECRET = process.env.ECF_WEBHOOK_SECRET;

      const MAX_AGE_MS = 5 * 60 * 1000;


      const app = express();


      // express.raw: se necesita el cuerpo crudo para verificar la firma.

      app.post('/webhooks/ecf', express.raw({ type: 'application/json' }), (req,
      res) => {
        const expected = Buffer.from(
          'sha256=' + crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'),
        );
        const received = Buffer.from(req.get('X-Acpay-Signature') ?? '');
        if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {
          return res.sendStatus(401);
        }

        const payload = JSON.parse(req.body.toString('utf8'));
        const age = Date.now() - Date.parse(payload.timestamp);
        if (!Number.isFinite(age) || Math.abs(age) > MAX_AGE_MS) {
          return res.sendStatus(401);
        }

        // Deduplica por payload.deliveryId y procesa en segundo plano.
        res.sendStatus(200);
      });

      ```
  - name: Estado
    description: ''
paths:
  /v1/invoices/{id}/notes:
    get:
      tags:
        - Notas de crédito y débito
      summary: Listar las notas de una factura
      description: >-
        Notas de crédito y débito que modifican la factura `{id}`, de la más
        reciente a la más antigua.
      operationId: notesFindByInvoice
      parameters:
        - name: id
          required: true
          in: path
          description: Identificador del documento (UUID).
          schema:
            type: string
            format: uuid
            example: 3f1c2b9e-8a4d-4f6e-9c1a-2b7d5e8f0a13
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/EcfNoteResponse'
          headers:
            X-Request-Id:
              description: >-
                Identificador de la solicitud (el tuyo si enviaste uno válido).
                Inclúyelo al reportar un problema.
              schema:
                type: string
        '400':
          description: Solicitud inválida (cuerpo, parámetros o Idempotency-Key).
          headers:
            X-Request-Id:
              description: >-
                Identificador de la solicitud (el tuyo si enviaste uno válido).
                Inclúyelo al reportar un problema.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelopeResponse'
              example:
                error:
                  status: 400
                  message: La solicitud no es válida.
                  details:
                    - 'items.0.quantity: debe ser un número entero.'
                    - 'dueDate: es obligatoria cuando paymentType es credit.'
        '401':
          description: >-
            Token ausente, con formato inválido, de otro ambiente, vencido o
            revocado.
          headers:
            X-Request-Id:
              description: >-
                Identificador de la solicitud (el tuyo si enviaste uno válido).
                Inclúyelo al reportar un problema.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelopeResponse'
              example:
                error:
                  status: 401
                  message: Esta llave pertenece a otro ambiente.
        '403':
          description: >-
            La llave no tiene permiso para esta operación o el comercio está
            inactivo.
          headers:
            X-Request-Id:
              description: >-
                Identificador de la solicitud (el tuyo si enviaste uno válido).
                Inclúyelo al reportar un problema.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelopeResponse'
              example:
                error:
                  status: 403
                  message: La llave no tiene permiso para esta operación.
        '404':
          description: El documento o webhook no existe o no pertenece a tu comercio.
          headers:
            X-Request-Id:
              description: >-
                Identificador de la solicitud (el tuyo si enviaste uno válido).
                Inclúyelo al reportar un problema.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelopeResponse'
              example:
                error:
                  status: 404
                  message: Documento no encontrado.
        '429':
          description: >-
            Se superó el límite de solicitudes de la llave. Respeta
            `Retry-After`.
          headers:
            Retry-After:
              description: Segundos a esperar antes de reintentar.
              schema:
                type: integer
            X-Request-Id:
              description: >-
                Identificador de la solicitud (el tuyo si enviaste uno válido).
                Inclúyelo al reportar un problema.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelopeResponse'
              example:
                error:
                  status: 429
                  message: Demasiadas solicitudes. Intenta de nuevo más tarde.
        '502':
          description: >-
            Error inesperado del servicio de facturación. Se puede reintentar
            con la misma Idempotency-Key.
          headers:
            X-Request-Id:
              description: >-
                Identificador de la solicitud (el tuyo si enviaste uno válido).
                Inclúyelo al reportar un problema.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelopeResponse'
              example:
                error:
                  status: 502
                  message: >-
                    El servicio de facturación electrónica tuvo un error
                    inesperado. Intenta de nuevo más tarde.
        '503':
          description: >-
            Servicio no disponible temporalmente. Se puede reintentar con la
            misma Idempotency-Key.
          headers:
            X-Request-Id:
              description: >-
                Identificador de la solicitud (el tuyo si enviaste uno válido).
                Inclúyelo al reportar un problema.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelopeResponse'
              example:
                error:
                  status: 503
                  message: >-
                    El servicio de facturación electrónica no está disponible.
                    Intenta de nuevo en unos minutos.
      security:
        - bearer: []
components:
  schemas:
    EcfNoteResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 3f1c2b9e-8a4d-4f6e-9c1a-2b7d5e8f0a13
        status:
          type: string
          enum:
            - pending
            - accepted
            - accepted_with_observations
            - rejected
            - cancelled
            - error
          description: >-
            `pending`: enviado, esperando a la DGII (se actualiza solo en
            segundo plano, y también al consultar). `accepted` /
            `accepted_with_observations`: válido. `rejected`: la DGII lo rechazó
            (ver `statusMessage`). `cancelled`: anulado. `error`: no se pudo
            procesar (se puede reintentar).
          example: accepted
        statusMessage:
          type: string
          nullable: true
          description: Detalle del estado (motivo de rechazo u observaciones).
          example: null
        securityCode:
          type: string
          nullable: true
          description: >-
            Código de seguridad del e-CF (va impreso en la representación
            impresa).
          example: a1B2c3
        stampUrl:
          type: string
          nullable: true
          description: URL de consulta de la DGII (contenido del código QR).
          example: >-
            https://ecf.dgii.gov.do/testecf/ConsultaTimbre?RncEmisor=101000000&RncComprador=131793916&ENCF=E310000000001&FechaEmision=27-09-2026&MontoTotal=3541.18&FechaFirma=27-09-2026%2010:15:00&CodigoSeguridad=a1B2c3
        signedAt:
          type: string
          nullable: true
          format: date-time
          example: '2026-09-27T14:15:00.000Z'
        issuedAt:
          type: string
          format: date-time
          example: '2026-09-27T14:14:58.000Z'
        environment:
          type: string
          enum:
            - test
            - live
          example: test
        currency:
          type: string
          enum:
            - DOP
          example: DOP
        subtotal:
          type: number
          description: Monto sin ITBIS.
          example: 3001
        itbis:
          type: number
          description: ITBIS total.
          example: 540.18
        total:
          type: number
          description: Total con ITBIS.
          example: 3541.18
        encf:
          type: string
          nullable: true
          description: e-NCF de la nota. `null` mientras no se haya asignado.
          example: E340000000003
        ecfType:
          type: number
          enum:
            - 33
            - 34
          description: 33 nota de débito, 34 nota de crédito.
          example: 34
        invoiceId:
          type: string
          format: uuid
          description: Factura que modifica la nota.
          example: 3f1c2b9e-8a4d-4f6e-9c1a-2b7d5e8f0a13
        invoiceEncf:
          type: string
          nullable: true
          description: e-NCF de la factura modificada.
          example: E310000000001
        modificationCode:
          type: number
          enum:
            - 1
            - 2
            - 3
            - 4
          description: >-
            1 anulación total, 2 corrige textos, 3 corrige montos, 4 reemplazo
            de un comprobante en contingencia.
          example: 3
        reason:
          type: string
          example: Devolución parcial de mercancía
        items:
          type: array
          items:
            $ref: '#/components/schemas/EcfDocumentItemResponse'
      required:
        - id
        - status
        - statusMessage
        - securityCode
        - stampUrl
        - signedAt
        - issuedAt
        - environment
        - currency
        - subtotal
        - itbis
        - total
        - encf
        - ecfType
        - invoiceId
        - invoiceEncf
        - modificationCode
        - reason
        - items
    ErrorEnvelopeResponse:
      type: object
      properties:
        error:
          $ref: '#/components/schemas/ErrorEnvelopeBodyResponse'
      required:
        - error
    EcfDocumentItemResponse:
      type: object
      properties:
        description:
          type: string
          example: Servicio de mantenimiento mensual
        quantity:
          type: integer
          example: 2
        unitPrice:
          type: number
          example: 1500.5
        itbisRate:
          type: number
          description: Tasa de ITBIS aplicada (18, 16 o 0).
          example: 18
        subtotal:
          type: number
          description: Subtotal de la línea sin ITBIS.
          example: 3001
      required:
        - description
        - quantity
        - unitPrice
        - itbisRate
        - subtotal
    ErrorEnvelopeBodyResponse:
      type: object
      properties:
        status:
          type: number
          description: Código HTTP (igual al de la respuesta).
          example: 400
        message:
          type: string
          example: La solicitud no es válida.
        details:
          description: 'Detalle por campo, cuando aplica (`ruta.del.campo: mensaje`).'
          example:
            - 'items.0.quantity: debe ser un número entero.'
          type: array
          items:
            type: string
      required:
        - status
        - message
  securitySchemes:
    bearer:
      scheme: bearer
      bearerFormat: acpay_<test|live>_<secreto>
      type: http
      description: Llave de API de AC-PAY del ambiente de esta URL.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.