Tutorials
Download all of your company's data
Take every record and file Oatmilk keeps for your company, and close the company when you're done.
Your company's data is yours. This tutorial downloads all of it with an administrator's API key that holds accounting:admin: every record Oatmilk keeps in every table, and every stored file. In the web app it's Settings › Company › Your data, and in the terminal app it's oatmilk export.
1. Build the export
company.export builds one ZIP and returns a link to it that works for five minutes.
curl https://app.getoatmilk.com/api/v1/accounting/company.export \
-H "Authorization: Bearer $OATMILK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"includeFiles": true
}'The ZIP holds:
manifest.json: every table with its number of rows, every stored file with its size and whether it's in this download, and the columns left out because they hold secrets.data/<table>.json: the company's rows in each table, one row per line, with the database's column names. Amounts are in cents, the currency's smallest unit.files/<bucket>/<path>: the stored files, such as receipts, emails, statements, agreements and invoices.README.txt: the same layout in plain words.
Encrypted credentials, bank and tax details kept encrypted, one-time codes and private link tokens read "[redacted]".
2. Download the files that didn't fit
One download holds at most 50 MB. The records come first and files fill the rest, so a company with many files gets some of them listed with "included": false and counted in files.skipped. company.export.files lists every file with its own five-minute link, 100 at a time.
curl -G https://app.getoatmilk.com/api/v1/accounting/company.export.files \
-H "Authorization: Bearer $OATMILK_API_KEY" \
--data-urlencode 'offset=0' \
--data-urlencode 'limit=100'import { mkdir, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
for (let offset = 0; offset !== null; ) {
const page = await callOatmilk("company.export.files", { offset, limit: 100 });
for (const file of page.items) {
const target = join("oatmilk-files", file.bucket, file.path);
await mkdir(dirname(target), { recursive: true });
await writeFile(target, Buffer.from(await (await fetch(file.url)).arrayBuffer()));
}
offset = page.nextOffset;
}Closing the company
company.close permanently deletes the company: its stored files, every record, and the company itself with its memberships. It can't be undone, so export first. confirmName must be the company's name. Only an administrator can close it, with their own credential: the web app, the terminal app or their own API key with accounting:write and accounting:admin. AI apps and Ask AI are refused with INTERACTIVE_ADMIN_REQUIRED, and the company an Oatmilk installation runs for can't be closed.