USAspending download jobs: file_url returns 403 HTML until the job finishes Checked 2026-09-08 · Rate limits, pagination walls and retries A download request hands back file_url and status_url at once, but file_url is not live yet. Fetching it before the job finishes returns HTTP 403 with a 3.9 KB HTML page, so anything that writes the response straight to disk ends up with a .zip that is really a web page. Poll status_url instead; while it is running total_rows is 0, and total_size is kilobytes, not bytes. What we saw file_url fetched immediately after the job is created returns 403 and an HTML page, not the zip. Source: https://files.usaspending.gov/generated_downloads/ (response to GET file_url before status is finished, 2026-09-08). early http=403 size=3893 ct=text/html USAspending Downloads The 403 page is a status poller in disguise; it carries the message set for each job state. Source: https://files.usaspending.gov/generated_downloads/ (inline script, COMPONENT_MAP, 2026-09-08). "ready": ["Your download file is still in the works.","Use your custom link to check back and grab it later."], "non-existant": ["Your download doesn't seem to exist", "Please try again, and ensure you have the correct link."], "finished": ["Download file generation complete!","If your download does not start automatically, please refresh the page"], "failed": ["Your download file failed to generate. Please try again.","For more information, contact the service desk at servicedesk.usaspending.gov/."], "running": ["Your download file is still in the works.","Use your custom link to check back and grab it later."] While running, total_rows and total_columns are 0 and total_size is null. Zero rows does not mean an empty result. Source: https://api.usaspending.gov/api/v2/download/status (status response, 2026-09-08). 04:56:27 {'status': 'running', 'total_size': None, 'total_rows': 0, 'total_columns': 0, 'seconds_elapsed': '7.140288'} 04:56:43 {'status': 'running', 'total_size': None, 'total_rows': 0, 'total_columns': 0, 'seconds_elapsed': '22.983439'} 04:56:59 {'status': 'running', 'total_size': None, 'total_rows': 0, 'total_columns': 0, 'seconds_elapsed': '38.806025'} 04:57:14 {'status': 'finished', 'total_size': 338.845, 'total_rows': 948, 'total_columns': 297, 'seconds_elapsed': '47.653861'} total_size is kilobytes: the finished zip is 338,845 bytes against a reported 338.845. Source: https://files.usaspending.gov/generated_downloads/ (Content-Length of the finished zip, 2026-09-08). zip http=200 size=338845 An unknown file name gives a clear 404, so a job that vanished is distinguishable from one that is still working. Source: https://api.usaspending.gov/api/v2/download/status (detail, 2026-09-08). {"detail":"Download job with filename All_PrimeTransactions_2020-01-01_H00M00S00000000.zip does not exist."} http=404 There is a bulk_download path for awards but not for account balances; the accounts path is under download, and the wrong one gives a bare Django 404 with no hint. Source: https://api.usaspending.gov/api/v2/bulk_download/accounts/ (response body, 2026-09-08). http=404 size=179 Not Found

Not Found

The requested resource was not found on this server.

A small account-balances job can finish before the first poll, so code that assumes at least one running response can be wrong in the other direction too. Source: https://api.usaspending.gov/api/v2/download/status (first poll of a freshly created job, 2026-09-08). t=0s {'status': 'finished', 'total_size': 3.525, 'total_rows': 59, 'seconds_elapsed': '2.88617', 'message': None} The command curl -sS -H 'Content-Type: application/json' -d '{"filters":{"prime_award_types":["A","B","C","D"],"agencies":[{"type":"awarding","tier":"toptier","name":"Department of Education"}],"date_type":"action_date","date_range":{"start_date":"2024-10-01","end_date":"2025-09-30"}},"file_format":"csv"}' https://api.usaspending.gov/api/v2/bulk_download/awards/ # then immediately: curl -sS -o early.bin -w "early http=%{http_code} size=%{size_download} ct=%{content_type}\n" "$FILE_URL" https://files.usaspending.gov/generated_downloads/All_PrimeTransactions_2026-09-08_H04M57S28598302.zip early http=403 size=3893 ct=text/html Checked 2026-09-08. SU=; for i in $(seq 1 40); do curl -sS "$SU"; sleep 15; done 04:56:27 {'status': 'running', 'total_size': None, 'total_rows': 0, 'total_columns': 0, 'seconds_elapsed': '7.140288', 'message': None} 04:56:43 {'status': 'running', 'total_size': None, 'total_rows': 0, 'total_columns': 0, 'seconds_elapsed': '22.983439', 'message': None} 04:56:59 {'status': 'running', 'total_size': None, 'total_rows': 0, 'total_columns': 0, 'seconds_elapsed': '38.806025', 'message': None} 04:57:14 {'status': 'finished', 'total_size': 338.845, 'total_rows': 948, 'total_columns': 297, 'seconds_elapsed': '47.653861', 'message': None} Checked 2026-09-08. curl -sS -o bda.zip -w "zip http=%{http_code} size=%{size_download}\n" "$FILE_URL" # after finished zip http=200 size=338845 Checked 2026-09-08. curl -sS -w "\nhttp=%{http_code}\n" "https://api.usaspending.gov/api/v2/download/status?file_name=All_PrimeTransactions_2020-01-01_H00M00S00000000.zip" {"detail":"Download job with filename All_PrimeTransactions_2020-01-01_H00M00S00000000.zip does not exist."} http=404 Checked 2026-09-08. curl -sS -o bd.txt -w "http=%{http_code} size=%{size_download}\n" -H 'Content-Type: application/json' -d '{"account_level":"treasury_account","filters":{"budget_function":"all","agency":"46","submission_types":["account_balances"],"fy":2026,"period":10},"file_format":"csv"}' https://api.usaspending.gov/api/v2/bulk_download/accounts/ http=404 size=179 Checked 2026-09-08. Field in the status response Meaning While running status ready, running, finished, failed running total_size kilobytes of the finished zip null total_rows rows in the CSV 0 total_columns columns in the CSV 0 seconds_elapsed string, seconds since the job started counts up message populated on failure null Limits - The longest job measured here took 47.7 seconds. I did not reproduce the multi-hour jobs people report failing, so nothing here says what a failed status looks like in the wild; the failure message quoted above is the wait page's own text, not a job I saw fail. - The 403 is what files.usaspending.gov returned for a job created seconds earlier. I did not test whether it also 403s for a job in ready state that has been queued a long time. - Two award endpoints exist, /api/v2/download/awards/ and /api/v2/bulk_download/awards/. Only bulk_download/awards was exercised. Open question Whether file_url ever returns a 404 rather than a 403, given the wait page has a distinct 'non-existant' state. Every early or wrong URL tried here came back 403 with the same page. Know something this page does not say? Send it with one GET: https://opendatanotes.org/c?kind=correction&page=usaspending-download-job-status&text=… — no account needed. I read everything that comes in, and nothing sent here gets published. Everything here was run from one machine on the date shown.