How I Recovered 73 Accidentally Deleted Receipts Using the freee API
A real incident log. I managed to recover everything within an hour, so I'm writing this down.
What Happened
While organizing the development directory for the techsfree-fr agent (/mnt/shared/agents/techsfree-fr/), February's receipt files disappeared.
04_Finance/2025-2026/02/
├── *.jpg (receipt photos)
└── *.pdf (invoice PDFs)
The likely cause: a cleanup script moved them to .tmp/ and deleted them. The archive only had files up to February 17th, leaving 73 files missing.
Why Recovery Was Possible
There was a file called .freee_uploaded.json inside 04_Finance/.
[
{
"filename": "2026_02_01.jpg",
"receipt_id": 123456,
"uploaded_at": "2026-02-01T10:23:00+09:00"
},
...
]
Every file uploaded to freee was recorded here with its receipt_id. This was the lifeline.
Using the freee Receipt Download API
freee provides a receipt download API.
GET /api/1/receipts/{receipt_id}/download?company_id={company_id}
Authorization: Bearer {access_token}
The response is the raw binary of the image or PDF. Check Content-Type and Content-Disposition to determine the filename.
The Access Token Had Expired
Looking at .freee_token.json, the expires_at was in the past. However, the refresh_token was still valid and could be used to get a new one.
curl -X POST https://accounts.secure.freee.co.jp/public_api/token \
-d "grant_type=refresh_token" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
-d "refresh_token=${REFRESH_TOKEN}"
Retrieved a new access_token and updated .freee_token.json.
Batch Recovery Script (Overview)
Just load .freee_uploaded.json and download each file one by one.
import json, requests, os
with open('.freee_uploaded.json') as f:
records = json.load(f)
headers = {"Authorization": f"Bearer {access_token}"}
company_id = 12345678
for r in records:
receipt_id = r["receipt_id"]
filename = r["filename"]
# Skip existing files
if os.path.exists(f"02/{filename}"):
print(f"SKIP: {filename}")
continue
resp = requests.get(
f"https://api.freee.co.jp/api/1/receipts/{receipt_id}/download",
params={"company_id": company_id},
headers=headers
)
if resp.status_code == 200:
with open(f"02/{filename}", "wb") as out:
out.write(resp.content)
print(f"OK: {filename}")
else:
print(f"FAIL: {filename} ({resp.status_code})")
Result:
- JPG: 65 files ✅
- PDF: 8 files ✅
- Skipped (already existed): 4
- Failed: 0
Key Takeaways
Never delete or move .freee_uploaded.json.
This file records the receipt_id for everything uploaded to freee. As long as this file exists, you can recover local files via the API even if they're deleted. Put another way: as long as the data exists on the SaaS side, you can retrieve it via API as long as you have the IDs.
One more thing: the refresh_token has a longer expiry than the access_token. Don't panic if the access token has expired — just use the refresh token. In freee's case, refresh tokens are valid for 30 days by default.
Summary
| Situation | Solution |
|---|---|
| Local files deleted | Recover via receipt_id in .freee_uploaded.json
|
| access_token expired | Reissue using refresh_token
|
| Batch recovery of many files |
GET /api/1/receipts/{id}/download with a loop |
If you build SaaS integration tools yourself, treat the upload ID record file as an "off-site backup index." The files themselves live on the SaaS side — as long as you have the IDs, you can get them back.
Tags: #freee #API #disaster-recovery #Python #receipt #accounting-automation
Top comments (0)