AX/T/05 — AX/TUTORIALS
Opublikowano: 10 maj 2026 · 17 min czytania

Jak zautomatyzować pobieranie faktur (KSeF + email)

KSeF API, scraping portali kontrahentów, parsing PDF faktur z emaili, push do księgowości. Bez ręcznego ściągania.

IntermediateKSeF APIIMAPpdf-parsePlaywrightPostgreSQLOpenAI / Anthropic

Pobieranie faktur to klasyczny pożeracz czasu w księgowości. Faktury przychodzą z 3 źródeł:

  1. KSeF (obowiązkowe od 2026 dla B2B) — przez API
  2. Portale dostawców (Orange, Tauron, etc.) — login + download
  3. Email (PDF attachment) — najbardziej chaotyczne

Pokażemy unified pipeline który łapie wszystkie, parsuje do common schema, pushuje do księgowości. Realne oszczędności: 4-12h/tydz pracy księgowej dla mid-sized firmy.

Co potrzebujesz
  • Node.js 20+ lub Python 3.11+
  • Token KSeF (Ministerstwo Finansów) — wymaga profilu zaufanego
  • Dostęp do skrzynki email księgowej (Gmail / IMAP)
  • PDF parser (pdf-parse, pdfplumber)
  • System księgowy z API (iFirma, wFirma, Comarch)
Kroki
  1. 01

    KSeF API integration

    KSeF (Krajowy System e-Faktur) — Ministerstwo Finansów. Production endpoint: https://ksef.mf.gov.pl/api/. Wymaga autoryzacji przez token (profile zaufany lub kwalifikowany certyfikat).

    // ksef/client.ts
    import { z } from 'zod';
    
    const KsefInvoice = z.object({
      ksefReferenceNumber: z.string(),
      invoiceNumber: z.string(),
      issueDate: z.string(),
      sellerNip: z.string(),
      sellerName: z.string(),
      totalGross: z.number(),
      totalNet: z.number(),
      vat: z.number(),
    });
    
    export async function fetchKsefInvoices(since: Date) {
      const session = await ksefAuth(); // SessionToken
      
      const res = await fetch(`${KSEF_URL}/online/Invoice/Query`, {
        method: 'POST',
        headers: {
          'SessionToken': session.token,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          queryCriteria: {
            subjectType: 'subject1',
            type: 'incremental',
            invoicingDateRange: {
              from: since.toISOString(),
              to: new Date().toISOString(),
            },
          },
        }),
      });
      
      const data = await res.json();
      return data.invoiceHeaderList.map(h => KsefInvoice.parse(h));
    }
  2. 02

    Vendor portal scraping z Playwright

    Orange, Tauron, T-Mobile — każdy ma własny portal z fakturami. Standardowy flow: login → faktury → download PDF.

    // portals/orange.ts
    export async function downloadOrangeInvoices(creds) {
      const browser = await chromium.launch({ headless: true });
      const context = await browser.newContext();
      const page = await context.newPage();
      
      // Login
      await page.goto('https://www.orange.pl/zaloguj');
      await page.locator('#login').fill(creds.login);
      await page.locator('#password').fill(creds.password);
      await page.locator('button[type=submit]').click();
      await page.waitForURL('**/portfel');
      
      // Invoices section
      await page.goto('https://www.orange.pl/portfel/faktury');
      await page.waitForLoadState('networkidle');
      
      // Get PDF URLs
      const invoices = await page.$$eval('[data-test="invoice-row"]', rows =>
        rows.map(r => ({
          date: r.querySelector('.date')?.textContent,
          amount: r.querySelector('.amount')?.textContent,
          downloadUrl: r.querySelector('a.download')?.href,
        }))
      );
      
      // Download each PDF
      for (const inv of invoices) {
        const downloadPromise = page.waitForEvent('download');
        await page.goto(inv.downloadUrl);
        const download = await downloadPromise;
        const path = `./invoices/orange-${inv.date}.pdf`;
        await download.saveAs(path);
        inv.localPath = path;
      }
      
      await browser.close();
      return invoices;
    }

    Save storageState żeby nie logować się ciągle.

  3. 03

    Email parsing — IMAP + PDF attachments

    Najbardziej chaotyczne źródło. IMAP filter na "faktura" w title + załączony PDF:

    import { ImapFlow } from 'imapflow';
    import pdfParse from 'pdf-parse';
    
    const client = new ImapFlow({
      host: 'imap.gmail.com',
      port: 993, secure: true,
      auth: { user: 'ksiegowa@firma.pl', pass: process.env.IMAP_PASS },
    });
    
    async function fetchEmailInvoices() {
      await client.connect();
      await client.mailboxOpen('INBOX');
      
      const messages = client.fetch(
        { since: new Date(Date.now() - 7*24*3600*1000) }, // last 7 days
        { source: true, envelope: true, bodyStructure: true }
      );
      
      for await (const msg of messages) {
        const subject = msg.envelope.subject.toLowerCase();
        if (!/faktura|invoice|fv/.test(subject)) continue;
        
        // Find PDF attachments
        const pdfParts = findPdfAttachments(msg.bodyStructure);
        for (const part of pdfParts) {
          const att = await client.download(msg.uid, part.part, { uid: true });
          const pdfBuffer = att.content;
          const parsed = await pdfParse(pdfBuffer);
          // ... extract data from parsed.text
        }
      }
      
      await client.logout();
    }
  4. 04

    AI extraction z PDF — gdy regex zawodzi

    Faktury PDF mają różne layouty. Regex działa dla 60-70% — reszta wymaga AI:

    import Anthropic from '@anthropic-ai/sdk';
    const claude = new Anthropic();
    
    const InvoiceData = z.object({
      invoiceNumber: z.string(),
      issueDate: z.string(),
      sellerVatId: z.string(),
      sellerName: z.string(),
      totalNet: z.number(),
      totalGross: z.number(),
      vat: z.number(),
      currency: z.enum(['PLN', 'EUR', 'USD']),
      dueDate: z.string().optional(),
    });
    
    async function extractInvoiceData(pdfText: string) {
      const res = await claude.messages.create({
        model: 'claude-3-5-sonnet-latest',
        max_tokens: 1024,
        messages: [{
          role: 'user',
          content: `Extract structured data from this invoice. Return ONLY JSON matching schema:
            { invoiceNumber, issueDate (YYYY-MM-DD), sellerVatId, sellerName, totalNet, totalGross, vat, currency, dueDate }
            
            Invoice text:
            ${pdfText}`,
        }],
      });
      
      const json = JSON.parse(res.content[0].text);
      return InvoiceData.parse(json); // validate
    }

    Cost: ~$0.005 per invoice z Claude Sonnet. 200 faktur/mies = $1. Marginalne.

  5. 05

    Push do księgowości (iFirma / wFirma / Comarch)

    iFirma ma REST API. Każda zewn. faktura kosztowa: POST /invoices.

    async function pushToIFirma(invoice, pdfPath) {
      const formData = new FormData();
      formData.append('invoice', JSON.stringify({
        nazwa: invoice.sellerName,
        nip_sprzedawcy: invoice.sellerVatId,
        data_wystawienia: invoice.issueDate,
        netto: invoice.totalNet,
        vat: invoice.vat,
        brutto: invoice.totalGross,
        waluta: invoice.currency,
        numer_dokumentu: invoice.invoiceNumber,
      }));
      formData.append('pdf', await fs.readFile(pdfPath));
      
      const res = await fetch('https://api.ifirma.pl/iapi/invoices.json', {
        method: 'POST',
        headers: {
          'Authentication': `IAPIS user=${process.env.IFIRMA_USER}, hmac-sha1=${signature}`,
        },
        body: formData,
      });
      
      return res.json();
    }

    Po każdym sukcesie: mark invoice jako "uploaded", store reference ID, log dla audytu.

Ile to kosztuje uruchomić

Run cost dla 200 faktur/mies (typowa mid-firm):

  • VPS (Hetzner CX22): €5/mies
  • Anthropic API (AI extraction): ~$1-3/mies
  • Storage (PDFs + DB): ~$5/mies
  • KSeF, vendor portale: $0 (API free)

Total: ~$15/mies. Oszczędność: 4-12h pracy księgowej/tydz × ~€30/h = €480-1440/mies.

Częste pułapki
  • Brak idempotency — uploadowanie tej samej faktury 3× do księgowości tworzy 3 duplikaty. Track sent invoices przez external ID.
  • OCR vs text-layer PDF — niektóre PDFs są obrazem (scan). Wymagają OCR (Tesseract, Google Vision) zanim parsing.
  • Email security — IMAP credentials w plain. Użyj app-specific passwords + secrets manager.
  • Currency conversion — faktury EUR/USD trzeba przeliczyć po kursie NBP z dnia transakcji. Zaautomatyzuj.
  • Duplikaty cross-source — ta sama faktura może przyjść przez email AND KSeF. Dedup po invoice number + seller VAT.
Build yourself czy zlecić?

Pipeline taki jak powyższy zdejmuje 80-90% manual invoice processing. Pozostałe 10-20% to edge cases (zagraniczni dostawcy, korekt, faktury proforma) które wymagają review.

To bardzo częsty use-case który robimy dla firm B2B. Napisz jeśli chcesz audyt twojego current workflow.

Najczęściej zadawane pytania
Czy KSeF jest obowiązkowy w 2026?
Tak — od 1 lutego 2026 dla wszystkich podatników VAT czynnych w Polsce. Od 1 kwietnia 2026 obowiązek dla małych podatników (VAT-zwolnionych). Faktura wystawiona poza KSeF po tej dacie = nieważna w rozumieniu VAT.
Co z fakturami zagranicznymi?
Faktury od foreign vendorów (np. Stripe, AWS) nie idą przez KSeF — dochodzą przez email/portal. Pipeline musi obsłużyć: email parsing (IMAP), portal scraping per vendor, currency conversion (NBP daily rates), VAT handling (reverse charge dla EU B2B).
Ile to realnie oszczędza?
Mid-firmie 200 faktur/mies: typowa księgowa spędza 4-8h/tydz na receive → categorize → enter. Automation: 80-90% catch, 10-20% manual edge cases. Oszczędność: 3-7h/tydz × €30/h = €390-840/mies. Cost automation: $15-25/mies. ROI: 15-30× w pierwszym miesiącu.
Czy księgowość zaakceptuje automatyczne faktury?
Tak — większość systemów księgowych (iFirma, wFirma, Comarch, SAP) ma REST API specjalnie dla bulk invoice ingestion. Wymaga: external ID dedup, PDF attachment retention (KSeF wymaga 5 lat), audit log każdej operacji.