Skip to content

Instantly share code, notes, and snippets.

@azagniotov
Last active July 15, 2026 16:51
Show Gist options
  • Select an option

  • Save azagniotov/210c31540712c10206484d5297616842 to your computer and use it in GitHub Desktop.

Select an option

Save azagniotov/210c31540712c10206484d5297616842 to your computer and use it in GitHub Desktop.
Downloads all pay-slips from ADP website
/*
This has been tested in Chrome v55.0.2883.95 (64-bit)
1. Log into ADP website using the URL: https://my.adp.com/static/redbox/login.html (Make sure you are NOT in Incognito mode)
2. Open Developer Tools console
3. Run the following code in the console:
*/
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://my.adp.com/v1_0/O/A/payStatements?adjustments=yes&numberoflastpaydates=300');
xhr.setRequestHeader("Access-Control-Allow-Origin", "*"); // CORS policy
xhr.onload = function() {
if (xhr.status === 200) {
var rawData = JSON.parse(xhr.responseText);
for (var index = rawData.payStatements.length - 1; index >= 0; --index) {
var entry = rawData.payStatements[index];
var url = "https://my.adp.com" + entry.statementImageUri.href.substring(3);
var a = document.createElement('a');
var trueIndex = (rawData.payStatements.length - index);
if (trueIndex < 10) {
trueIndex = "00" + trueIndex;
} else if (trueIndex < 100) {
trueIndex = "0" + trueIndex;
}
a.download = "payslip.no." + trueIndex + ".from." + entry.payDate + ".pdf";
a.href = url;
document.body.appendChild(a);
a.click();
delete a;
}
} else {
console.log('Request failed. Returned status of ' + xhr.status);
}
};
xhr.send();
@rcrocker

rcrocker commented Jan 4, 2023

Copy link
Copy Markdown

Thanks for this. I found that the Zip version didn't quite work if you had more than one paystub on the same day, so I made a small modification to the callback to append an index to the filename and now that works.

var zip = new JSZip();
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://my.adp.com/v1_0/O/A/payStatements?adjustments=yes&numberoflastpaydates=300');
xhr.setRequestHeader("Access-Control-Allow-Origin", "*"); // CORS policy
xhr.onload = async function() {
    if (xhr.status === 200) {
        var rawData = JSON.parse(xhr.responseText);
        var lastPaydate = ""
        var consecutiveEntries = 0
        for (var index = rawData.payStatements.length - 1; index >= 0; --index) {
            var entry = rawData.payStatements[index];
            var date = entry.payDate
            // Handle multiple paystubs on the same day
            var dateSuffix = ""
            if (date === lastPaydate) {
                consecutiveEntries++
                dateSuffix = `-${consecutiveEntries}`
            } else {
                lastPaydate = date
                consecutiveEntries = 0
            }
            var year = entry.payDate.split("-")[0]
            var url = "https://my.adp.com" + entry.statementImageUri.href.substring(3);

            var trueIndex = (rawData.payStatements.length - index);
            if (trueIndex < 10) {
                trueIndex = "00" + trueIndex;
            } else if (trueIndex < 100) {
                trueIndex = "0" + trueIndex;
            }

            var xhttp = new XMLHttpRequest();
            xhttp.onreadystatechange = function() {
                if(this.readyState == 4 && this.status == 200) {
                    zip.folder(year).file(date + dateSuffix + ".pdf", this.response)
                }
            };
            xhttp.open('GET', url, true);
            xhttp.responseType = "blob";
            xhttp.send();
            while (xhttp.readyState != 4) {
                console.log("Waiting for download " + trueIndex + " from " + date);
                await new Promise(r => setTimeout(r, 500));
            }
        }
    } else {
        console.log('Request failed.  Returned status of ' + xhr.status);
    }
};
xhr.send();

@FrankTaylorLieder

Copy link
Copy Markdown

Hi, I've just used the original script in Chrome and as before it stops after 10 downloads. I added a simple fix to sleep between downloads and it then was able to download all 159 of my payslips without any problems. (Note I also added some logging so I can see progress.)

Here is the updated code:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://my.adp.com/v1_0/O/A/payStatements?adjustments=yes&numberoflastpaydates=300');
xhr.setRequestHeader("Access-Control-Allow-Origin", "*"); // CORS policy
xhr.onload = async function() {
    if (xhr.status === 200) {
        var rawData = JSON.parse(xhr.responseText);
        console.log('Number of items:', rawData.payStatements.length);
        for (var index = rawData.payStatements.length - 1; index >= 0; --index) {
            var entry = rawData.payStatements[index];
            var url = "https://my.adp.com" + entry.statementImageUri.href.substring(3);
            console.log("Downloading:", url);
            var a = document.createElement('a');

            var trueIndex = (rawData.payStatements.length - index);
            if (trueIndex < 10) {
                trueIndex = "00" + trueIndex;
            } else if (trueIndex < 100) {
                trueIndex = "0" + trueIndex;
            }

            a.download = "payslip.no." + trueIndex + ".from." + entry.payDate + ".pdf";
            a.href = url;
            document.body.appendChild(a);
            a.click();
            delete a;

            await new Promise(r => setTimeout(r, 1000));
        }
    } else {
        console.log('Request failed.  Returned status of ' + xhr.status);
    }
};
xhr.send();

@corymayer

Copy link
Copy Markdown

Updated version for 2024:

You need to get your OID by inspecting web requests, and replace YOUR_OID_HERE. I tried to make it generic by calling this API: https://my.adp.com/myadp_prefix/myadpapi/core/v1/version which returns the OID, but the API has some special CORs protection I couldn't figure out.

function sleep(ms) {
   return new Promise(resolve => setTimeout(resolve, ms));
}

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://my.adp.com/myadp_prefix/payroll/v1/workers/YOUR_OID_HERE/pay-statements?adjustments=yes&numberoflastpaydates=300');
xhr.setRequestHeader("Access-Control-Allow-Origin", "*"); // CORS policy
xhr.onload = async function() {
    if (xhr.status === 200) {
        var rawData = JSON.parse(xhr.responseText);
        var lastPaydate = ""
        var consecutiveEntries = 0
        for (var index = rawData.payStatements.length - 1; index >= 0; --index) {
            var entry = rawData.payStatements[index];
            var date = entry.payDate
            // Handle multiple paystubs on the same day
            var dateSuffix = ""
            if (date === lastPaydate) {
                consecutiveEntries++
                dateSuffix = `-${consecutiveEntries}`
            } else {
                lastPaydate = date
                consecutiveEntries = 0
            }
            var year = entry.payDate.split("-")[0]
            var url = "https://my.adp.com/myadp_prefix" + entry.statementImageUri.href + "?rolecode=employee";

            var a = document.createElement('a');

            var trueIndex = (rawData.payStatements.length - index);
            if (trueIndex < 10) {
                trueIndex = "00" + trueIndex;
            } else if (trueIndex < 100) {
                trueIndex = "0" + trueIndex;
            }

            a.download = "payslip.no." + trueIndex + ".from." + entry.payDate + ".pdf";
            a.href = url;
            document.body.appendChild(a);

            await sleep(500);
            a.click();
            delete a;
        }
    } else {
        console.log('Request failed.  Returned status of ' + xhr.status);
    }
};
xhr.send();

@jewelhuq

jewelhuq commented Sep 5, 2024

Copy link
Copy Markdown

Is there any full code can be run from the backend in 2024

@ebertland

Copy link
Copy Markdown

This is great. Thank you to all who have developed this code. I used the version by @corymayer using Firefox 133.0 on Windows 11 without any modifications except for inserting the OID.

@hakimh02

Copy link
Copy Markdown

This is great. Thank you to all who have developed this code. I used the version by @corymayer using Firefox 133.0 on Windows 11 without any modifications except for inserting the OID.

Hey there, can you please explain how you got the OID? Tried looking for it in responses but wasn't able to do so...would appreciate if you can run me through how you achieved that. Thank you

@cwlls

cwlls commented Sep 18, 2025

Copy link
Copy Markdown

Login to ADP, as usual, and then go to the following URL in the same browser session: https://my.adp.com/myadp_prefix/myadpapi/core/v1/version

In the resulting JSON output look for the value labeled: 'associateoid'. Paste it into the code seen above and run it in the javascript console of your browser. I can confirm this works as of today, using Safari Version 18.6 (20621.3.11.11.3)

@hakimh02

Copy link
Copy Markdown

maybe I'm missing something, but I can't get the JSON file to show up using the above URL.
When I login and then try to use the above URL, the page reloads and shows that's I'm not logged in.
Btw, this is the URL my company is using "https://adpworld.adp.com/

@hakimh02

Copy link
Copy Markdown

@cwlls would appreciate any help here. Thank you

@TechplexEngineer

Copy link
Copy Markdown

This works great as a tampermonkey user script, downloads all paystubs:
https://github.com/TechplexEngineer/userscript_download_all_payslips/tree/main

@bennypowers

bennypowers commented Jul 1, 2026

Copy link
Copy Markdown

if you're on portal.people.adp.com "oneux"

(async () => {
  const wrapper = document.querySelector('gmv-pay-wrapper').shadowRoot;
  const boxes = Array.from(wrapper.querySelectorAll('sdf-expandable-box'));
  let prevSrc = '';
  let count = 0;
  let skipped = [];

  const months = {
    january:'01', february:'02', march:'03', april:'04',
    may:'05', june:'06', july:'07', august:'08',
    september:'09', october:'10', november:'11', december:'12'
  };

  async function downloadItem(item, filename, attempt) {
    item.click();

    const iframe = wrapper.querySelector('iframe');
    let waited = 0;
    while (iframe?.src === prevSrc && waited < 15000) {
      await new Promise(r => setTimeout(r, 500));
      waited += 500;
    }

    if (!iframe?.src || iframe.src === prevSrc) return false;

    prevSrc = iframe.src;
    const resp = await fetch(iframe.src, {credentials: 'include'});
    if (!resp.ok) return false;

    const blob = await resp.blob();
    if (blob.size < 1000) return false;

    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.click();
    URL.revokeObjectURL(url);
    console.log(`  → ${filename} (${(blob.size/1024).toFixed(0)}KB) attempt ${attempt}`);
    return true;
  }

  for (const box of boxes) {
    const year = box.shadowRoot?.textContent?.match(/(20\d{2})/)?.[1];
    if (!year) continue;

    const select = box.querySelector('sdf-select-simple');
    if (!select) continue;

    const items = Array.from(select.shadowRoot.querySelectorAll('sdf-select-item'));
    console.log(`${year}: ${items.length} payslips`);

    for (const item of items) {
      const label = item.textContent.trim();
      const match = label.match(/(\d+)\s+(\w+)/);
      if (!match) continue;

      const mm = months[match[2].toLowerCase()];
      if (!mm) continue;

      const filename = `${year}-${mm}-01.pdf`;
      count++;
      console.log(`  ${count}: ${label} -> ${filename}`);

      let ok = false;
      for (let attempt = 1; attempt <= 3 && !ok; attempt++) {
        if (attempt > 1) {
          console.log(`  retrying ${filename} (attempt ${attempt})`);
          document.dispatchEvent(new KeyboardEvent('keydown', {key: 'Escape'}));
          await new Promise(r => setTimeout(r, 2000));
        }
        ok = await downloadItem(item, filename, attempt);
      }

      if (!ok) {
        console.warn(`  ✗ FAILED after 3 attempts: ${filename}`);
        skipped.push(filename);
      }

      document.dispatchEvent(new KeyboardEvent('keydown', {key: 'Escape'}));
      await new Promise(r => setTimeout(r, 1500));
    }
  }

  console.log(`Done! ${count - skipped.length}/${count} downloaded`);
  if (skipped.length) console.log(`Failed: ${skipped.join(', ')}`);
})();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment