{
  "id": "ing-es",
  "name": "ING España",
  "service": "ing",
  "trust": "community",
  "domain": "ingdirect.es",
  "country": "ES",
  "version": "2026-08-09",
  "minVersion": "0.9.11.4",
  "categories": [
    "banking"
  ],
  "gaps": [
    "inversión"
  ],
  "throttle": {
    "minMs": 300,
    "jitterMs": 500
  },
  "retry": {
    "status": [
      401
    ],
    "max": 4,
    "delayMs": 700,
    "bodyMatch": "Authorization Required"
  },
  "match": [
    "https://ing.ingdirect.es/*"
  ],
  "openUrl": "https://ing.ingdirect.es/app/login",
  "currency": "EUR",
  "auth": {
    "mode": "bearer",
    "tokenMatch": "^Bearer eyJ[A-Za-z0-9._-]+$",
    "replayHeaders": [
      "authorization",
      "x-ing-extendedsessioncontext",
      "baggage"
    ],
    "pageOnly": true,
    "readyUrl": [
      "https://ing.ingdirect.es/pfm/#producto/",
      "https://ing.ingdirect.es/pfm/#overall-position"
    ]
  },
  "api": {
    "host": "https://api.ing.ingdirect.es"
  },
  "streams": [
    {
      "id": "movimientos",
      "name": "Movimientos",
      "schema": "transaction@1",
      "keepRaw": true,
      "api": {
        "groups": {
          "path": "/position-keeping",
          "itemsPath": "products",
          "fields": {
            "id": "uuid",
            "name": "commercialName",
            "type": "type",
            "iban": "identifiers[type=PRODUCT_NUMBER].value",
            "currency": "denominationCurrency"
          },
          "mask": [
            "iban"
          ]
        },
        "list": {
          "path": "/v2/products/{group.id}/transactions",
          "paging": "offset",
          "offsetParam": "offset",
          "offsetStep": 100,
          "itemsPath": "transactions",
          "window": "89d",
          "maxAgeDays": 89,
          "range": {
            "from": "fromDate",
            "to": "toDate",
            "format": "date"
          },
          "params": {
            "limit": 100,
            "filterEru": "false"
          },
          "idOverride": {
            "when": {
              "field": "status",
              "present": true
            },
            "template": "card|{transactionDate}|{amount}|{transactionId.productId}"
          }
        }
      },
      "fields": {
        "internalId": "transactionLocalUUID",
        "date": "transactionDate",
        "amount": "amount",
        "description": "description",
        "type": "transactionCode",
        "account": "{group.iban}",
        "balanceAfter": "balance"
      },
      "normalize": {
        "counterparty": {
          "from": "description",
          "flags": "i",
          "re": [
            "(?:enviad[oa] a|recibid[oa] de|a favor de|domiciliaci[oó]n de)\\s+(.+)$",
            "^N[oó]mina recibida\\s+(.+)$",
            "^Recibo\\s+(.+?)\\s*(?:·.*)?$"
          ]
        }
      }
    },
    {
      "id": "informe",
      "name": "Extracto por cuenta",
      "schema": "invoice@1",
      "api": {
        "groups": {
          "path": "/position-keeping",
          "itemsPath": "products",
          "fields": {
            "id": "uuid",
            "name": "commercialName",
            "type": "type",
            "iban": "identifiers[type=PRODUCT_NUMBER].value"
          },
          "mask": [
            "iban"
          ]
        },
        "list": {
          "paging": "synthetic",
          "synthetic": {
            "each": "group-months"
          },
          "maxAgeDays": 89
        }
      },
      "fields": {
        "internalId": "{group.id}-{period}",
        "date": "{date}",
        "number": "{period}",
        "account": "{group.iban}",
        "description": "Extracto {group.name} {period}"
      },
      "formats": [
        {
          "id": "pdf",
          "name": "PDF",
          "api": {
            "pdf": {
              "path": "/v2/products/{group.id}/transactions/report?fromDate={fromDate}&toDate={toDate}&limit=1000&filterEru=false&reportType=pdf",
              "method": "GET",
              "ext": "pdf"
            }
          }
        },
        {
          "id": "excel",
          "name": "Excel",
          "api": {
            "pdf": {
              "path": "/v2/products/{group.id}/transactions/report?fromDate={fromDate}&toDate={toDate}&limit=1000&filterEru=false&reportType=xls",
              "method": "GET",
              "ext": "xls"
            }
          }
        }
      ]
    },
    {
      "id": "extracto-mensual",
      "name": "Extracto integrado mensual",
      "schema": "invoice@1",
      "api": {
        "host": "https://ing.ingdirect.es",
        "list": {
          "paging": "synthetic",
          "synthetic": {
            "each": "months"
          },
          "maxAgeDays": 89
        },
        "pdf": {
          "path": "/genoma_api/rest/products/statement?year={year}&month={month}",
          "method": "GET",
          "ext": "pdf"
        }
      },
      "fields": {
        "internalId": "{period}",
        "date": "{date}",
        "number": "{period}",
        "description": "Extracto integrado {period}"
      }
    }
  ],
  "changelog": [
    {
      "version": "2026-08-09",
      "changes": "La guía dice ya que ING solo sirve unos 90 días de histórico y que en cuenta corriente lo aplica de forma estricta. Quien busca \"descargar mis extractos de ING\" espera su histórico completo; encontrarse tres meses sin aviso es peor que no tener página. Verificado contra una cuenta real: con el recorte del mes que cruza la ventana (extensión 0.9.12) los extractos de ese mes ya se descargan."
    },
    {
      "version": "2026-08-08.2",
      "changes": "Ventana a 89 días (no 90): el límite de ING cuenta el día de hoy, así que el día más antiguo válido es hoy−89 y pedir hoy−90 se sale por uno. Requiere extensión 0.9.11.4, que además recorta el fromDate del informe mensual a la ventana en vez de pedir desde el día 1 del mes."
    },
    {
      "version": "2026-08-08.1",
      "changes": "Añade `content` (copy bilingüe de la guía en habeas.dev) y `brand`. La página por fuente pasa a generarse desde la propia definición, no desde un fichero aparte en la web."
    },
    {
      "version": "2026-08-08",
      "changes": "Corrige `gaps`: los movimientos de tarjeta ya se extraen (ver 2026-07-23.3), así que solo queda inversión fuera de cobertura. El dato anterior era obsoleto y se publicaba como limitación."
    },
    {
      "version": "2026-07-31.1",
      "changes": "Fix intermittent 401 'Authorization Required' (ING Webserver edge) that aborted the whole sync — most often at the account-enumeration step (/position-keeping). Verified via mitmproxy that the SAME authenticated request (same bearer, same 36 session cookies, browser-agnostic) returns 401 one call and 200 the next — a load-balancer/pool routing race (dual lb-3-p-288 / lb-4-p-288 + x-pool-from-dc cookies). ING's own SPA rides it out by retrying; Habeas now does the same via a bounded retry (retry: 401 ×4, 700ms), gated on the raw edge-HTML body so a genuine token-expiry (JSON) 401 still surfaces for re-login. Also replay `baggage` to mirror the SPA. Needs extension >= 0.9.7 (declarative retry in netFetch)."
    },
    {
      "version": "2026-07-23.3",
      "changes": "Fix credit-card duplicates WITHOUT hiding real movements. A current-month card purchase is confirmed but stays \"Pendiente de liquidar\" until the monthly settlement, and ING re-lists a still-pending charge with a fresh transactionLocalUUID every sync → duplicates. Card movements (items with a `status`) now key by a stable natural id {date|amount|productId} PLUS a per-import ordinal (list.idOverride): two genuinely-distinct charges sharing (date,amount,productId) arrive together in one import → they get ordinals 0,1 and stay separate; a churning re-list of one charge is always ordinal 0 → dedupes; the pending→settled transition dedupes too. Account movements (no status) keep their stable UUID. Needs extension >= 0.7.0.25."
    },
    {
      "version": "2026-07-16",
      "changes": "Map the movement's running balance to record.balanceAfter (promoted to a canonical field for the Cuéntamo contract). Requires extension >= 0.3.0.24."
    },
    {
      "version": "2026-07-14",
      "changes": "Initial: movements (transactions), per-account monthly statements (PDF/Excel), and the integrated monthly statement (PDF). Grouped by account, 90-day window."
    }
  ],
  "content": {
    "en": {
      "slug": "ing-transactions",
      "docs": "transactions and statements",
      "h1": "How to download your ING transactions and statements",
      "intro": "ING lets you view your transactions and download monthly statements, but one account and one month at a time. Pulling a whole year, or several accounts together, is repetitive manual work.",
      "note": "ING serves a limited history — around 90 days — and enforces it strictly on current accounts, so Habeas brings that window rather than years back: the sooner you start, the more you keep. If you hold several accounts, it lets you choose which ones the export covers and remembers that choice between syncs."
    },
    "es": {
      "slug": "movimientos-ing",
      "docs": "movimientos y extractos",
      "h1": "Cómo descargar tus movimientos y extractos de ING",
      "intro": "ING te deja ver tus movimientos y descargar extractos mensuales, pero cuenta por cuenta y mes a mes. Reunir un año entero, o varias cuentas a la vez, es un trabajo manual repetitivo.",
      "note": "ING sirve un histórico limitado —unos 90 días— y en las cuentas corrientes lo aplica de forma estricta, así que Habeas trae ese periodo, no años atrás: cuanto antes empieces, más conservas. Si tienes varias cuentas, te deja elegir cuáles entran en la exportación y recuerda esa selección entre sincronizaciones."
    }
  },
  "brand": "ING"
}
