From cea61c3c70d2632d2d5f567d1f086267e759bc4a Mon Sep 17 00:00:00 2001 From: nusquama Date: Sat, 14 Mar 2026 12:00:25 +0800 Subject: [PATCH] creation --- .../readme-13925.md | 668 ++++++++++++++++++ 1 file changed, 668 insertions(+) create mode 100644 workflows/Download KSeF (Poland’s e-invoicing system) invoices to an Excel spreadsheet-13925/readme-13925.md diff --git a/workflows/Download KSeF (Poland’s e-invoicing system) invoices to an Excel spreadsheet-13925/readme-13925.md b/workflows/Download KSeF (Poland’s e-invoicing system) invoices to an Excel spreadsheet-13925/readme-13925.md new file mode 100644 index 000000000..2689ce067 --- /dev/null +++ b/workflows/Download KSeF (Poland’s e-invoicing system) invoices to an Excel spreadsheet-13925/readme-13925.md @@ -0,0 +1,668 @@ +Download KSeF (Poland’s e-invoicing system) invoices to an Excel spreadsheet + +https://n8nworkflows.xyz/workflows/download-ksef--poland-s-e-invoicing-system--invoices-to-an-excel-spreadsheet-13925 + + +# Download KSeF (Poland’s e-invoicing system) invoices to an Excel spreadsheet + +# 1. Workflow Overview + +This workflow authenticates against Poland’s **KSeF v2 API**, retrieves invoice metadata for a specified date range, transforms the returned data into flat tabular rows, and exports the result as an **XLSX spreadsheet**. + +Typical use cases: +- Downloading invoice metadata you **received** or **issued** +- Building a spreadsheet export for accounting or reporting +- Using KSeF data as a starting point for email, storage, database, or spreadsheet integrations + +The workflow is organized into four logical blocks: + +## 1.1 Manual Start and Configuration +The workflow starts manually and defines all user-editable runtime settings in a single configuration node: API base URL, NIP, KSeF token, date range, and invoice subject type. + +## 1.2 KSeF Authentication Flow +KSeF uses a multi-step authentication model. The workflow retrieves the public certificate, requests a challenge, encrypts the token with RSA-OAEP SHA-256, initializes authentication, waits briefly, checks status, and redeems the temporary token for an access token. + +## 1.3 Invoice Query and Pagination +Using the final access token, the workflow queries invoice metadata from KSeF with pagination enabled until all pages are retrieved. + +## 1.4 Data Flattening, Spreadsheet Export, and Session Cleanup +The returned invoice metadata is normalized into spreadsheet-friendly columns, written to an XLSX file, and the API session is then closed. + +--- + +# 2. Block-by-Block Analysis + +## 2.1 Manual Start and Configuration + +### Overview +This block provides the entry point and all required runtime inputs. It centralizes user configuration so the rest of the workflow can reference these values via expressions. + +### Nodes Involved +- `When clicking 'Test workflow'` +- `⚙️ Config` + +### Node Details + +#### When clicking 'Test workflow' +- **Type and technical role:** `n8n-nodes-base.manualTrigger` + Manual execution trigger for testing or ad hoc runs. +- **Configuration choices:** No special configuration. +- **Key expressions or variables used:** None. +- **Input and output connections:** + - Input: none + - Output: `⚙️ Config` +- **Version-specific requirements:** Type version `1`. +- **Edge cases or potential failure types:** None; only runs when manually triggered. +- **Sub-workflow reference:** None. + +#### ⚙️ Config +- **Type and technical role:** `n8n-nodes-base.set` + Defines workflow parameters as raw JSON. +- **Configuration choices:** Uses **raw mode** and outputs a single JSON object containing: + - `baseUrl`: `https://api.ksef.mf.gov.pl/v2` + - `nip`: user NIP + - `authToken`: KSeF authorization token + - `startDate`: ISO 8601 datetime + - `endDate`: ISO 8601 datetime + - `subjectType`: `Subject1` or `Subject2` +- **Key expressions or variables used:** These fields are later referenced with expressions such as: + - `$('⚙️ Config').first().json.baseUrl` + - `$('⚙️ Config').first().json.nip` + - `$('⚙️ Config').first().json.authToken` +- **Input and output connections:** + - Input: `When clicking 'Test workflow'` + - Output: `Get Public Key` +- **Version-specific requirements:** Type version `3.4`. +- **Edge cases or potential failure types:** + - Placeholder values left unchanged + - Invalid NIP format + - Invalid or expired KSeF token + - Incorrect date format + - `subjectType` not accepted by KSeF +- **Sub-workflow reference:** None. + +--- + +## 2.2 KSeF Authentication Flow + +### Overview +This block performs the full KSeF v2 authentication sequence. It converts the long-lived KSeF authorization token into a temporary auth token, waits for processing, then redeems it into the usable API access token. + +### Nodes Involved +- `Get Public Key` +- `Get Challenge` +- `Encrypt Token` +- `Init Auth` +- `Wait 2s` +- `Check Auth Status` +- `Redeem Token` + +### Node Details + +#### Get Public Key +- **Type and technical role:** `n8n-nodes-base.httpRequest` + Fetches KSeF public certificate data used for token encryption. +- **Configuration choices:** Sends a GET request to: + - `{{$json.baseUrl}}/security/public-key-certificates` +- **Key expressions or variables used:** + - `={{ $json.baseUrl }}/security/public-key-certificates` +- **Input and output connections:** + - Input: `⚙️ Config` + - Output: `Get Challenge` +- **Version-specific requirements:** Type version `4.4`. +- **Edge cases or potential failure types:** + - KSeF endpoint unavailable + - SSL/network issues + - Unexpected response structure +- **Sub-workflow reference:** None. + +#### Get Challenge +- **Type and technical role:** `n8n-nodes-base.httpRequest` + Requests a challenge and timestamp from KSeF. +- **Configuration choices:** POST request to: + - `{{ $('⚙️ Config').first().json.baseUrl }}/auth/challenge` +- **Key expressions or variables used:** + - `={{ $('⚙️ Config').first().json.baseUrl }}/auth/challenge` +- **Input and output connections:** + - Input: `Get Public Key` + - Output: `Encrypt Token` +- **Version-specific requirements:** Type version `4.4`. +- **Edge cases or potential failure types:** + - Endpoint unavailable + - API contract changes + - Missing `timestampMs` or `challenge` in response +- **Sub-workflow reference:** None. + +#### Encrypt Token +- **Type and technical role:** `n8n-nodes-base.code` + Executes Node.js code to build and encrypt the `authToken|timestampMs` payload with the KSeF public certificate. +- **Configuration choices:** + - Imports `crypto` + - Reads config from `⚙️ Config` + - Reads challenge data from current input + - Reads public keys from `Get Public Key` + - Selects a certificate intended for `KsefTokenEncryption` + - Converts a certificate string to PEM if needed + - Uses RSA OAEP padding with SHA-256 + - Outputs: + - `challenge` + - `contextIdentifier.type = "Nip"` + - `contextIdentifier.value = config.nip` + - `encryptedToken` +- **Key expressions or variables used:** + - `$('⚙️ Config').first().json` + - `$('Get Public Key').all()` + - `$input.first().json` +- **Input and output connections:** + - Input: `Get Challenge` + - Output: `Init Auth` +- **Version-specific requirements:** Type version `2`. + Requires code node execution with access to Node.js built-in `crypto`. +- **Edge cases or potential failure types:** + - Missing or malformed certificate + - Certificate array shape differs from expected logic + - `match(/.{1,64}/g)` could fail if certificate string is empty + - `authToken` or `timestampMs` missing + - Runtime restrictions in environments that limit `require('crypto')` +- **Sub-workflow reference:** None. + +#### Init Auth +- **Type and technical role:** `n8n-nodes-base.httpRequest` + Sends the encrypted payload to initialize authentication. +- **Configuration choices:** + - POST to `/auth/ksef-token` + - Sends JSON body from current item + - Explicit `Content-Type: application/json` +- **Key expressions or variables used:** + - `={{ $('⚙️ Config').first().json.baseUrl }}/auth/ksef-token` + - `={{ JSON.stringify($json) }}` +- **Input and output connections:** + - Input: `Encrypt Token` + - Output: `Wait 2s` +- **Version-specific requirements:** Type version `4.4`. +- **Edge cases or potential failure types:** + - Invalid encrypted payload + - Invalid NIP/token binding + - HTTP 4xx/5xx from KSeF + - Temporary `authenticationToken` returned but auth not yet ready +- **Sub-workflow reference:** None. + +#### Wait 2s +- **Type and technical role:** `n8n-nodes-base.code` + Introduces a fixed delay before checking auth status. +- **Configuration choices:** Uses `setTimeout` for 2000 ms, then returns input unchanged. +- **Key expressions or variables used:** `$input.all()` +- **Input and output connections:** + - Input: `Init Auth` + - Output: `Check Auth Status` +- **Version-specific requirements:** Type version `2`. +- **Edge cases or potential failure types:** + - Two seconds may be insufficient under KSeF latency or load + - No retry loop exists if status is not ready yet +- **Sub-workflow reference:** None. + +#### Check Auth Status +- **Type and technical role:** `n8n-nodes-base.httpRequest` + Polls the auth session status using the temporary authentication token. +- **Configuration choices:** + - GET to `/auth/{referenceNumber}` + - Authorization header uses temp token from `Init Auth` + - Accept header set to `application/json` +- **Key expressions or variables used:** + - `={{ $('⚙️ Config').first().json.baseUrl }}/auth/{{ $('Init Auth').first().json.referenceNumber }}` + - `=Bearer {{ $('Init Auth').first().json.authenticationToken.token }}` +- **Input and output connections:** + - Input: `Wait 2s` + - Output: `Redeem Token` +- **Version-specific requirements:** Type version `4.4`. +- **Edge cases or potential failure types:** + - Status still pending after 2 seconds + - Missing `referenceNumber` + - Temporary token expired + - Endpoint errors not explicitly handled +- **Sub-workflow reference:** None. + +#### Redeem Token +- **Type and technical role:** `n8n-nodes-base.httpRequest` + Exchanges the temporary auth token for the final access token. +- **Configuration choices:** + - POST to `/auth/token/redeem` + - Authorization uses temp token from `Init Auth` + - Accept header set to `application/json` +- **Key expressions or variables used:** + - `={{ $('⚙️ Config').first().json.baseUrl }}/auth/token/redeem` + - `=Bearer {{ $('Init Auth').first().json.authenticationToken.token }}` +- **Input and output connections:** + - Input: `Check Auth Status` + - Output: `Query Invoices` +- **Version-specific requirements:** Type version `4.4`. +- **Edge cases or potential failure types:** + - Attempting redeem before auth is actually ready + - Expired temporary token + - KSeF may reject flow if previous status was unsuccessful +- **Sub-workflow reference:** None. + +--- + +## 2.3 Invoice Query and Pagination + +### Overview +This block requests invoice metadata from KSeF using the access token and automatically paginates through all available result pages. + +### Nodes Involved +- `Query Invoices` +- `Extract Invoices` + +### Node Details + +#### Query Invoices +- **Type and technical role:** `n8n-nodes-base.httpRequest` + Queries KSeF invoice metadata with body filters and query pagination controls. +- **Configuration choices:** + - POST to `/invoices/query/metadata` + - Sends JSON body: + - `subjectType` from config + - `dateRange.dateType = "PermanentStorage"` + - `from` and `to` from config + - Query parameters: + - `pageSize = 250` + - `sortOrder = Desc` + - Authorization uses redeemed access token + - Pagination: + - `pageOffset = {{$pageCount}}` + - stops when `{{$response.body.hasMore === false}}` +- **Key expressions or variables used:** + - `={{ $('⚙️ Config').first().json.baseUrl }}/invoices/query/metadata` + - `={{ JSON.stringify({ subjectType: $('⚙️ Config').first().json.subjectType, dateRange: { dateType: 'PermanentStorage', from: $('⚙️ Config').first().json.startDate, to: $('⚙️ Config').first().json.endDate } }) }}` + - `=Bearer {{ $('Redeem Token').first().json.accessToken.token }}` + - `={{ $pageCount }}` + - `={{ $response.body.hasMore === false }}` +- **Input and output connections:** + - Input: `Redeem Token` + - Output: `Extract Invoices` +- **Version-specific requirements:** Type version `4.4`. + Uses built-in HTTP Request pagination features. +- **Edge cases or potential failure types:** + - Access token expired or invalid + - API response lacks `hasMore` + - `pageOffset` semantics may differ if KSeF changes paging behavior + - Large result sets could increase runtime + - Date range may return no data +- **Sub-workflow reference:** None. + +#### Extract Invoices +- **Type and technical role:** `n8n-nodes-base.code` + Aggregates invoice arrays from all paginated HTTP responses into a single item stream. +- **Configuration choices:** + - Iterates over all pages + - Collects `page.json.invoices` + - If none found, emits a single marker item: + - `_noInvoices: true` + - message + - count: 0 + - Otherwise emits one item per invoice +- **Key expressions or variables used:** `$input.all()` +- **Input and output connections:** + - Input: `Query Invoices` + - Output: `Format for Spreadsheet` +- **Version-specific requirements:** Type version `2`. +- **Edge cases or potential failure types:** + - Response structure may not contain `invoices` + - Empty result set intentionally produces a special record instead of failing + - Very large datasets may increase memory usage because all pages are collected in memory +- **Sub-workflow reference:** None. + +--- + +## 2.4 Data Flattening, Spreadsheet Export, and Session Cleanup + +### Overview +This block converts nested KSeF invoice metadata into flat spreadsheet columns, creates an XLSX binary file, and then closes the active KSeF session. + +### Nodes Involved +- `Format for Spreadsheet` +- `Write XLSX` +- `Close Session` + +### Node Details + +#### Format for Spreadsheet +- **Type and technical role:** `n8n-nodes-base.code` + Maps invoice objects into spreadsheet-ready rows with fixed column names. +- **Configuration choices:** + - If `_noInvoices` marker is present, returns one row with `message: 'No invoices to export'` + - Otherwise maps each invoice into columns such as: + - `KSeF Number` + - `Invoice Number` + - `Issue Date` + - `Invoicing Date` + - `Acquisition Date` + - `Seller NIP` + - `Seller Name` + - `Buyer NIP` + - `Buyer Name` + - `Net Amount` + - `VAT Amount` + - `Gross Amount` + - `Currency` + - `Invoice Type` + - `Has Attachment` + - `Self Invoicing` + - Uses optional chaining and fallback values for missing fields + - Splits dates at `T` for some date fields +- **Key expressions or variables used:** `$input.all()` +- **Input and output connections:** + - Input: `Extract Invoices` + - Output: `Write XLSX` +- **Version-specific requirements:** Type version `2`. +- **Edge cases or potential failure types:** + - If invoice schema changes, some columns may be blank + - Buyer identifier path assumes `buyer.identifier.value` + - Numeric defaults of `0` may blur distinction between missing and actual zero values + - Empty output still creates spreadsheet content if `_noInvoices` case occurs +- **Sub-workflow reference:** None. + +#### Write XLSX +- **Type and technical role:** `n8n-nodes-base.spreadsheetFile` + Converts JSON rows into an XLSX file in binary output. +- **Configuration choices:** + - Operation: `toFile` + - File format: `xlsx` + - File name: `ksef_invoices.xlsx` + - Sheet name: `Invoices` + - Header row enabled +- **Key expressions or variables used:** None. +- **Input and output connections:** + - Input: `Format for Spreadsheet` + - Output: `Close Session` +- **Version-specific requirements:** Type version `2`. +- **Edge cases or potential failure types:** + - Large datasets may create large binary files + - If upstream returns only a message row, the file still gets created + - Binary output must be handled explicitly if saving externally +- **Sub-workflow reference:** None. + +#### Close Session +- **Type and technical role:** `n8n-nodes-base.httpRequest` + Deletes the current KSeF auth session after export. +- **Configuration choices:** + - DELETE to `/auth/sessions/current` + - Authorization uses final access token + - `onError` is set to `continueRegularOutput` +- **Key expressions or variables used:** + - `={{ $('⚙️ Config').first().json.baseUrl }}/auth/sessions/current` + - `=Bearer {{ $('Redeem Token').first().json.accessToken.token }}` +- **Input and output connections:** + - Input: `Write XLSX` + - Output: none +- **Version-specific requirements:** Type version `4.4`. +- **Edge cases or potential failure types:** + - Session may already be expired or closed + - Cleanup failure does not stop normal workflow output because error handling continues +- **Sub-workflow reference:** None. + +--- + +# 3. Summary Table + +| Node Name | Node Type | Functional Role | Input Node(s) | Output Node(s) | Sticky Note | +|---|---|---|---|---|---| +| When clicking 'Test workflow' | Manual Trigger | Manual workflow start | | ⚙️ Config | | +| ⚙️ Config | Set | Central runtime configuration for KSeF API, credentials, date range, and subject type | When clicking 'Test workflow' | Get Public Key | ## 🔧 Configuration
Edit the JSON below to set your:
- **nip** — your 10-digit NIP number
- **authToken** — KSeF authorization token
- **startDate / endDate** — ISO 8601 format
- **subjectType** —
`Subject2` = invoices you **received** (buyer)
`Subject1` = invoices you **issued** (seller)

Dates use `PermanentStorage` type (when KSeF stored the invoice). | +| Get Public Key | HTTP Request | Fetch KSeF public encryption certificate(s) | ⚙️ Config | Get Challenge | ## 🔐 Authentication Flow (v2 API)
KSeF uses a multi-step auth:
1. **Get Public Key** — fetch RSA certificate
2. **Get Challenge** — get a challenge + timestamp
3. **Encrypt Token** — RSA-OAEP encrypt `token|timestamp`
4. **Init Auth** — submit encrypted token, get temp JWT
5. **Wait + Check Status** — poll until auth is ready
6. **Redeem Token** — exchange temp JWT for access token

⚠️ The `authenticationToken` from step 4 is **temporary**!
Only the `accessToken` from step 6 works for API calls. | +| Get Challenge | HTTP Request | Request KSeF challenge and timestamp | Get Public Key | Encrypt Token | ## 🔐 Authentication Flow (v2 API)
KSeF uses a multi-step auth:
1. **Get Public Key** — fetch RSA certificate
2. **Get Challenge** — get a challenge + timestamp
3. **Encrypt Token** — RSA-OAEP encrypt `token|timestamp`
4. **Init Auth** — submit encrypted token, get temp JWT
5. **Wait + Check Status** — poll until auth is ready
6. **Redeem Token** — exchange temp JWT for access token

⚠️ The `authenticationToken` from step 4 is **temporary**!
Only the `accessToken` from step 6 works for API calls. | +| Encrypt Token | Code | Build and RSA-encrypt KSeF token payload | Get Challenge | Init Auth | ## 🔐 Authentication Flow (v2 API)
KSeF uses a multi-step auth:
1. **Get Public Key** — fetch RSA certificate
2. **Get Challenge** — get a challenge + timestamp
3. **Encrypt Token** — RSA-OAEP encrypt `token|timestamp`
4. **Init Auth** — submit encrypted token, get temp JWT
5. **Wait + Check Status** — poll until auth is ready
6. **Redeem Token** — exchange temp JWT for access token

⚠️ The `authenticationToken` from step 4 is **temporary**!
Only the `accessToken` from step 6 works for API calls. | +| Init Auth | HTTP Request | Initialize KSeF authentication with encrypted token | Encrypt Token | Wait 2s | ## 🔐 Authentication Flow (v2 API)
KSeF uses a multi-step auth:
1. **Get Public Key** — fetch RSA certificate
2. **Get Challenge** — get a challenge + timestamp
3. **Encrypt Token** — RSA-OAEP encrypt `token|timestamp`
4. **Init Auth** — submit encrypted token, get temp JWT
5. **Wait + Check Status** — poll until auth is ready
6. **Redeem Token** — exchange temp JWT for access token

⚠️ The `authenticationToken` from step 4 is **temporary**!
Only the `accessToken` from step 6 works for API calls. | +| Wait 2s | Code | Delay before auth status polling | Init Auth | Check Auth Status | ## 🔐 Authentication Flow (v2 API)
KSeF uses a multi-step auth:
1. **Get Public Key** — fetch RSA certificate
2. **Get Challenge** — get a challenge + timestamp
3. **Encrypt Token** — RSA-OAEP encrypt `token|timestamp`
4. **Init Auth** — submit encrypted token, get temp JWT
5. **Wait + Check Status** — poll until auth is ready
6. **Redeem Token** — exchange temp JWT for access token

⚠️ The `authenticationToken` from step 4 is **temporary**!
Only the `accessToken` from step 6 works for API calls. | +| Check Auth Status | HTTP Request | Poll auth status using temporary token | Wait 2s | Redeem Token | ## 🔐 Authentication Flow (v2 API)
KSeF uses a multi-step auth:
1. **Get Public Key** — fetch RSA certificate
2. **Get Challenge** — get a challenge + timestamp
3. **Encrypt Token** — RSA-OAEP encrypt `token|timestamp`
4. **Init Auth** — submit encrypted token, get temp JWT
5. **Wait + Check Status** — poll until auth is ready
6. **Redeem Token** — exchange temp JWT for access token

⚠️ The `authenticationToken` from step 4 is **temporary**!
Only the `accessToken` from step 6 works for API calls. | +| Redeem Token | HTTP Request | Exchange temporary auth token for final access token | Check Auth Status | Query Invoices | ## 🔐 Authentication Flow (v2 API)
KSeF uses a multi-step auth:
1. **Get Public Key** — fetch RSA certificate
2. **Get Challenge** — get a challenge + timestamp
3. **Encrypt Token** — RSA-OAEP encrypt `token|timestamp`
4. **Init Auth** — submit encrypted token, get temp JWT
5. **Wait + Check Status** — poll until auth is ready
6. **Redeem Token** — exchange temp JWT for access token

⚠️ The `authenticationToken` from step 4 is **temporary**!
Only the `accessToken` from step 6 works for API calls. | +| Query Invoices | HTTP Request | Query paginated invoice metadata | Redeem Token | Extract Invoices | ## 🔐 Authentication Flow (v2 API)
KSeF uses a multi-step auth:
1. **Get Public Key** — fetch RSA certificate
2. **Get Challenge** — get a challenge + timestamp
3. **Encrypt Token** — RSA-OAEP encrypt `token|timestamp`
4. **Init Auth** — submit encrypted token, get temp JWT
5. **Wait + Check Status** — poll until auth is ready
6. **Redeem Token** — exchange temp JWT for access token

⚠️ The `authenticationToken` from step 4 is **temporary**!
Only the `accessToken` from step 6 works for API calls. | +| Extract Invoices | Code | Merge paginated invoice arrays into item stream | Query Invoices | Format for Spreadsheet | ## 🔐 Authentication Flow (v2 API)
KSeF uses a multi-step auth:
1. **Get Public Key** — fetch RSA certificate
2. **Get Challenge** — get a challenge + timestamp
3. **Encrypt Token** — RSA-OAEP encrypt `token|timestamp`
4. **Init Auth** — submit encrypted token, get temp JWT
5. **Wait + Check Status** — poll until auth is ready
6. **Redeem Token** — exchange temp JWT for access token

⚠️ The `authenticationToken` from step 4 is **temporary**!
Only the `accessToken` from step 6 works for API calls. | +| Format for Spreadsheet | Code | Flatten invoice metadata into spreadsheet columns | Extract Invoices | Write XLSX | ## 📊 Output
Invoice metadata is flattened into columns:
KSeF Number, Invoice Number, Issue Date, Seller/Buyer NIP & Name, Net/VAT/Gross amounts, Currency, Type.

The XLSX file is available as binary output in the **Write XLSX** node.

To save to disk, connect a **Write Binary File** node after Write XLSX.
To email it, connect a **Send Email** node and attach the binary.

Swap it to Google Spreadsheet or database of your choice. | +| Write XLSX | Spreadsheet File | Generate XLSX file from flattened rows | Format for Spreadsheet | Close Session | ## 📊 Output
Invoice metadata is flattened into columns:
KSeF Number, Invoice Number, Issue Date, Seller/Buyer NIP & Name, Net/VAT/Gross amounts, Currency, Type.

The XLSX file is available as binary output in the **Write XLSX** node.

To save to disk, connect a **Write Binary File** node after Write XLSX.
To email it, connect a **Send Email** node and attach the binary.

Swap it to Google Spreadsheet or database of your choice. | +| Close Session | HTTP Request | Close current KSeF session after export | Write XLSX | | ## 📊 Output
Invoice metadata is flattened into columns:
KSeF Number, Invoice Number, Issue Date, Seller/Buyer NIP & Name, Net/VAT/Gross amounts, Currency, Type.

The XLSX file is available as binary output in the **Write XLSX** node.

To save to disk, connect a **Write Binary File** node after Write XLSX.
To email it, connect a **Send Email** node and attach the binary.

Swap it to Google Spreadsheet or database of your choice. | +| Sticky Note | Sticky Note | Visual documentation and quick start guidance | | | ## 🇵🇱 KSeF — Download Invoices to Spreadsheet

Downloads invoice metadata from Poland's **KSeF** (Krajowy System e-Faktur) and exports it as an **XLSX spreadsheet**.

### Quick Start
1. Open the **⚙️ Config** node and fill in your **NIP** and **KSeF token**
2. Set the **date range** (startDate / endDate)
3. Click **Test workflow**
4. Your spreadsheet will appear in the **Write XLSX** node output

### How to get a KSeF token
Generate an authorization token at [ksef.mf.gov.pl](https://ksef.mf.gov.pl) → Log in → Manage tokens.
Tokens look like: `YYYYMMDD-XX-XXXXXXXXXX-XXXXXXXXXX-XX\|nip-XXXXXXXXXX\|hash`

### Need help?
KSeF docs: https://www.gov.pl/web/kas/krajowy-system-e-faktur

Made with ❤️ by [Greg Brzezinka](greg@prosit.no) Need help? [Reach out to me](https://www.linkedin.com/in/brzezinka)! | +| Sticky Note1 | Sticky Note | Visual configuration instructions | | | ## 🔧 Configuration
Edit the JSON below to set your:
- **nip** — your 10-digit NIP number
- **authToken** — KSeF authorization token
- **startDate / endDate** — ISO 8601 format
- **subjectType** —
`Subject2` = invoices you **received** (buyer)
`Subject1` = invoices you **issued** (seller)

Dates use `PermanentStorage` type (when KSeF stored the invoice). | +| Sticky Note2 | Sticky Note | Visual explanation of KSeF auth sequence | | | ## 🔐 Authentication Flow (v2 API)
KSeF uses a multi-step auth:
1. **Get Public Key** — fetch RSA certificate
2. **Get Challenge** — get a challenge + timestamp
3. **Encrypt Token** — RSA-OAEP encrypt `token\|timestamp`
4. **Init Auth** — submit encrypted token, get temp JWT
5. **Wait + Check Status** — poll until auth is ready
6. **Redeem Token** — exchange temp JWT for access token

⚠️ The `authenticationToken` from step 4 is **temporary**!
Only the `accessToken` from step 6 works for API calls. | +| Sticky Note3 | Sticky Note | Visual description of spreadsheet output | | | ## 📊 Output
Invoice metadata is flattened into columns:
KSeF Number, Invoice Number, Issue Date, Seller/Buyer NIP & Name, Net/VAT/Gross amounts, Currency, Type.

The XLSX file is available as binary output in the **Write XLSX** node.

To save to disk, connect a **Write Binary File** node after Write XLSX.
To email it, connect a **Send Email** node and attach the binary.

Swap it to Google Spreadsheet or database of your choice. | + +--- + +# 4. Reproducing the Workflow from Scratch + +1. **Create a new workflow** + Name it something like: `KSeF - Download Invoices to Spreadsheet`. + +2. **Add a Manual Trigger node** + - Node type: **Manual Trigger** + - Keep default settings. + - This is the workflow entry point. + +3. **Add a Set node named `⚙️ Config`** + - Node type: **Set** + - Set mode to **Raw** + - Paste a JSON object with these keys: + - `baseUrl` + - `nip` + - `authToken` + - `startDate` + - `endDate` + - `subjectType` + - Example values: + - `baseUrl`: `https://api.ksef.mf.gov.pl/v2` + - `nip`: your 10-digit NIP + - `authToken`: your KSeF token + - `startDate`: ISO timestamp like `2026-02-01T00:00:00Z` + - `endDate`: ISO timestamp like `2026-03-06T23:59:59Z` + - `subjectType`: `Subject2` for received invoices or `Subject1` for issued invoices + - Connect **Manual Trigger → ⚙️ Config** + +4. **Add an HTTP Request node named `Get Public Key`** + - Method: **GET** + - URL: + - `={{ $json.baseUrl }}/security/public-key-certificates` + - No authentication required. + - Connect **⚙️ Config → Get Public Key** + +5. **Add an HTTP Request node named `Get Challenge`** + - Method: **POST** + - URL: + - `={{ $('⚙️ Config').first().json.baseUrl }}/auth/challenge` + - No request body needed. + - Connect **Get Public Key → Get Challenge** + +6. **Add a Code node named `Encrypt Token`** + - Node type: **Code** + - Language: JavaScript + - Paste code that: + - imports Node’s `crypto` + - reads config from `$('⚙️ Config').first().json` + - reads `challenge` and `timestampMs` from the input item + - reads the public certificate(s) from `$('Get Public Key').all()` + - selects the key whose usage includes `KsefTokenEncryption` if present + - converts the certificate into PEM format if needed + - encrypts the plaintext `authToken|timestampMs` using: + - `RSA_PKCS1_OAEP_PADDING` + - `oaepHash: 'sha256'` + - returns JSON with: + - `challenge` + - `contextIdentifier: { type: 'Nip', value: config.nip }` + - `encryptedToken` + - Connect **Get Challenge → Encrypt Token** + +7. **Add an HTTP Request node named `Init Auth`** + - Method: **POST** + - URL: + - `={{ $('⚙️ Config').first().json.baseUrl }}/auth/ksef-token` + - Send Headers: enabled + - Header: + - `Content-Type: application/json` + - Send Body: enabled + - Body Content Type: **JSON** + - JSON body: + - `={{ JSON.stringify($json) }}` + - Connect **Encrypt Token → Init Auth** + +8. **Add a Code node named `Wait 2s`** + - Paste JavaScript: + - wait 2000 ms using `await new Promise(r => setTimeout(r, 2000));` + - return `$input.all();` + - Purpose: give KSeF time to prepare auth status. + - Connect **Init Auth → Wait 2s** + +9. **Add an HTTP Request node named `Check Auth Status`** + - Method: **GET** + - URL: + - `={{ $('⚙️ Config').first().json.baseUrl }}/auth/{{ $('Init Auth').first().json.referenceNumber }}` + - Send Headers: enabled + - Headers: + - `Authorization: =Bearer {{ $('Init Auth').first().json.authenticationToken.token }}` + - `Accept: application/json` + - Connect **Wait 2s → Check Auth Status** + +10. **Add an HTTP Request node named `Redeem Token`** + - Method: **POST** + - URL: + - `={{ $('⚙️ Config').first().json.baseUrl }}/auth/token/redeem` + - Send Headers: enabled + - Headers: + - `Authorization: =Bearer {{ $('Init Auth').first().json.authenticationToken.token }}` + - `Accept: application/json` + - No body required. + - Connect **Check Auth Status → Redeem Token** + +11. **Add an HTTP Request node named `Query Invoices`** + - Method: **POST** + - URL: + - `={{ $('⚙️ Config').first().json.baseUrl }}/invoices/query/metadata` + - Send Headers: enabled + - Headers: + - `Content-Type: application/json` + - `Authorization: =Bearer {{ $('Redeem Token').first().json.accessToken.token }}` + - Send Query Parameters: enabled + - Query parameters: + - `pageSize = 250` + - `sortOrder = Desc` + - Send Body: enabled + - Body Content Type: **JSON** + - JSON body should contain: + - `subjectType` from config + - `dateRange.dateType = PermanentStorage` + - `dateRange.from = startDate` + - `dateRange.to = endDate` + - Use this expression: + - `={{ JSON.stringify({ subjectType: $('⚙️ Config').first().json.subjectType, dateRange: { dateType: 'PermanentStorage', from: $('⚙️ Config').first().json.startDate, to: $('⚙️ Config').first().json.endDate } }) }}` + - Enable **pagination** + - Configure pagination: + - parameter name: `pageOffset` + - value: `={{ $pageCount }}` + - completion expression: `={{ $response.body.hasMore === false }}` + - completion condition type: **other** + - Connect **Redeem Token → Query Invoices** + +12. **Add a Code node named `Extract Invoices`** + - JavaScript logic: + - read all pages using `$input.all()` + - collect each page’s `json.invoices` array + - concatenate all invoices into one list + - if no invoices found, return one item with: + - `_noInvoices: true` + - message + - count: 0 + - otherwise return one item per invoice + - Connect **Query Invoices → Extract Invoices** + +13. **Add a Code node named `Format for Spreadsheet`** + - JavaScript logic: + - if input contains only `_noInvoices`, return one row with `message: 'No invoices to export'` + - otherwise map invoice data into flat columns: + - `KSeF Number` + - `Invoice Number` + - `Issue Date` + - `Invoicing Date` + - `Acquisition Date` + - `Seller NIP` + - `Seller Name` + - `Buyer NIP` + - `Buyer Name` + - `Net Amount` + - `VAT Amount` + - `Gross Amount` + - `Currency` + - `Invoice Type` + - `Has Attachment` + - `Self Invoicing` + - use optional chaining and default values + - for `invoicingDate` and `acquisitionDate`, split on `T` and keep the date part + - Connect **Extract Invoices → Format for Spreadsheet** + +14. **Add a Spreadsheet File node named `Write XLSX`** + - Operation: **To File** + - File format: **XLSX** + - File name: `ksef_invoices.xlsx` + - Sheet name: `Invoices` + - Header row: enabled + - This node will output a binary file. + - Connect **Format for Spreadsheet → Write XLSX** + +15. **Add an HTTP Request node named `Close Session`** + - Method: **DELETE** + - URL: + - `={{ $('⚙️ Config').first().json.baseUrl }}/auth/sessions/current` + - Send Headers: enabled + - Header: + - `Authorization: =Bearer {{ $('Redeem Token').first().json.accessToken.token }}` + - Set node error handling to **continue on error / continue regular output** + - This ensures session cleanup failure does not break the workflow. + - Connect **Write XLSX → Close Session** + +16. **Optional: add sticky notes** + - Add one note with quick-start information and KSeF links + - Add one near the config node describing `nip`, `authToken`, `startDate`, `endDate`, `subjectType` + - Add one near the auth chain explaining temp token vs access token + - Add one near the XLSX output explaining how to save or email the file + +17. **Set workflow settings** + - Timezone: `Europe/Warsaw` + - Execution order: `v1` + +18. **Test the workflow** + - Open `⚙️ Config` + - Replace placeholders: + - `YOUR_NIP_HERE` + - `YOUR_KSEF_TOKEN_HERE` + - Set the desired date range + - Choose `Subject1` or `Subject2` + - Click **Test workflow** + +19. **Validate outputs** + - `Redeem Token` should return an `accessToken` + - `Query Invoices` should return paginated results + - `Write XLSX` should expose binary output containing `ksef_invoices.xlsx` + +20. **Optional post-processing** + - To save locally: connect **Write Binary File** after `Write XLSX` + - To send by email: connect an email node and attach the binary + - To write elsewhere: replace or branch after `Format for Spreadsheet` or `Write XLSX` + +### Credential configuration +This workflow does **not** use stored n8n credentials by default. +Authentication is handled directly through the KSeF token placed in the `⚙️ Config` node and then converted into KSeF access tokens through API calls. + +### Sub-workflow setup +There are **no sub-workflows** and no Execute Workflow nodes in this workflow. + +--- + +# 5. General Notes & Resources + +| Note Content | Context or Link | +|---|---| +| Generate a KSeF authorization token from the KSeF portal under token management. | https://ksef.mf.gov.pl | +| Official KSeF documentation and general information. | https://www.gov.pl/web/kas/krajowy-system-e-faktur | +| Author credit: Greg Brzezinka | `greg@prosit.no` | +| Contact / professional profile of the workflow author. | https://www.linkedin.com/in/brzezinka | +| The workflow exports invoice metadata only, not the full invoice file payload. | General behavior | +| The `authenticationToken` from `Init Auth` is temporary; API calls require the `accessToken` from `Redeem Token`. | Authentication design note | +| The date filter uses `PermanentStorage` as the KSeF date type. | Query design note | +| To persist the generated spreadsheet, attach a `Write Binary File`, email node, cloud storage node, or database flow after `Write XLSX`. | Extension pattern | \ No newline at end of file