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.