Skip to content

Instantly share code, notes, and snippets.

@signorecello
Last active October 24, 2025 09:19
Show Gist options
  • Select an option

  • Save signorecello/91295d531bc28377f20d556ca0d82602 to your computer and use it in GitHub Desktop.

Select an option

Save signorecello/91295d531bc28377f20d556ca0d82602 to your computer and use it in GitHub Desktop.
Converts a Gnosis Pay transaction to CSV format

Gnosis Pay -> CSV

If you're like me and you use great open-source finance software like ActualBudget you probably want your Gnosis Pay transactions in CSV format for an easy import.

Fear not, me (and ChatGPT) have your back:

  1. Login to GnosisPay
  2. Open the console (Ctrl + Shift + J or Cmd + Option + J)
  3. Paste this script. Don't blindly trust me, if you don't know JS then ask ChatGPT to explain what does this script do.
// Gnosis Pay – Export transactions since 12 Oct 2025 to CSV (with signed amounts)
(async () => {
  const API_BASE = 'https://api.gnosispay.com';
  const START_PATH = '/api/v1/cards/transactions?limit=200&offset=0';
  const FILTER_DATE = new Date('2025-10-12T00:00:00Z');

  // Helper to find or prompt for a Bearer token
  function discoverToken() {
    const looksLikeJWT = s => typeof s === 'string' && s.startsWith('eyJ');
    for (const store of [localStorage, sessionStorage]) {
      for (let i = 0; i < store.length; i++) {
        const val = store.getItem(store.key(i));
        if (looksLikeJWT(val)) return val;
        if (val && val.includes('eyJ')) {
          try {
            const obj = JSON.parse(val);
            for (const v of Object.values(obj)) if (looksLikeJWT(v)) return v;
          } catch {}
        }
      }
    }
    return null;
  }

  async function fetchAll(bearer) {
    const headers = { accept: 'application/json', authorization: `Bearer ${bearer}` };
    let url = API_BASE + START_PATH;
    const all = [];
    while (url) {
      const r = await fetch(url, { headers, credentials: 'include' });
      if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
      const j = await r.json();
      if (Array.isArray(j.results)) all.push(...j.results);
      url = j.next ? (j.next.startsWith('http') ? j.next : API_BASE + j.next) : null;
    }
    return all;
  }

  const toCSV = items => {
    const lines = ['"Date","Transaction Amount","Merchant Name","Type"'];
    for (const item of items) {
      const date = item.createdAt
        ? new Date(item.createdAt).toLocaleDateString('en-GB').replace(/\//g, '-')
        : '';
      const decimals = item.billingCurrency?.decimals ?? 2;
      const raw = Number(item.billingAmount || 0);
      let amount = (raw / 10 ** decimals).toFixed(decimals);

      // negative for spends, positive for refunds/credits
      if (item.kind === 'Payment' || item.transactionType === '00') amount = -amount;

      let merchant = (item.merchant?.name ?? 'Unknown').trim().replace(/"/g, '""');
      const type = item.kind || 'Payment';
      lines.push(`"${date}","${amount}","${merchant}","${type}"`);
    }
    return lines.join('\r\n');
  };

  try {
    let token = discoverToken();
    if (!token) token = prompt('Paste your Bearer token (without "Bearer "):', '');
    if (!token) throw new Error('No Bearer token.');

    const all = await fetchAll(token);
    const filtered = all.filter(
      t => new Date(t.createdAt) >= FILTER_DATE && t.status === 'Approved'
    );

    const csv = toCSV(filtered);
    const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'transactions.csv';
    document.body.appendChild(a);
    a.click();
    a.remove();

    console.log(`✅ Downloaded ${filtered.length} transactions from 12 Oct 2025 onward.`);
  } catch (e) {
    console.error('Error:', e);
    alert(`Export failed: ${e.message}`);
  }
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment