Skip to content

File Uploads

DayZero uses S3-backed file storage for bill attachments, bank statements, CSV/Excel imports, and other documents. Uploading is a two-step pattern: put the file in storage to get an s3_key, then pass that key to the endpoint that consumes it. Keys have the form {business_id}/{uuid}-{filename} and are only usable by the business that uploaded them.

Every uploaded file is scanned for malware. A file that is still being scanned is refused by the endpoint that reads it with 400 ("File is still being scanned for malware. Please try again in a few seconds"), so retry after a moment; a file the scan flags is refused for good.

The /api/v1/upload/* endpoints work with an API token but are not listed in the public API Reference. The endpoints that consume an s3_key (bills, bulk uploads, reconciliations, reports…) are all public and documented there.

Get a presigned upload URL, then upload the file directly to S3 from the client:

Step 1: Request a presigned URL

bash
curl -X POST "https://api.ondayzero.com/api/v1/upload" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID" \
  -H "Content-Type: application/json" \
  -d '{"filename": "receipt.pdf"}'

Returns (trimmed; upload_fields also carries the policy and signature entries S3 needs):

json
{
  "success": true,
  "data": {
    "s3_upload_url": "https://dayzero-uploads.s3.amazonaws.com/",
    "upload_method": "POST",
    "upload_fields": { "key": "0198a2c4-0000-7d3a-9a51-3f6f0c1d2e00/0198a2c4-aaaa-7d3a-9a51-3f6f0c1d2e01-receipt.pdf", "Content-Type": "application/pdf", "...": "..." },
    "max_bytes": 262144000,
    "file_format": "pdf",
    "content_type": "application/pdf",
    "s3_key": "0198a2c4-0000-7d3a-9a51-3f6f0c1d2e00/0198a2c4-aaaa-7d3a-9a51-3f6f0c1d2e01-receipt.pdf",
    "filename": "receipt.pdf"
  }
}
Field Description
s3_upload_url Presigned S3 POST URL, valid for one hour
upload_method Always POST
upload_fields Form fields that must accompany the file
max_bytes Largest body S3 will accept (250 MB)
s3_key The key to reference the file with
content_type The type DayZero inferred from the filename; it is part of the signed policy
file_format, filename, s3_bucket, business_id Detected extension, your filename, bucket and business

Supported extensions: pdf, csv, xls, xlsx, doc, docx, md, markdown, txt, json, gz, image formats (png, jpg, jpeg, gif, webp, heic, heif, bmp, tif, tiff, avif) and video formats (mp4, m4v, mov, webm, avi, mkv, ogv, 3gp, mpeg, mpg, wmv). Anything else (including html and svg) is rejected with 400.

Step 2: Upload to S3

Send one multipart/form-data POST to s3_upload_url containing every entry of upload_fields, in the order returned, followed by the file as the form field file (the file must be the last part):

bash
curl -X POST "{s3_upload_url}" \
  -F "key=<upload_fields.key>" \
  -F "Content-Type=application/pdf" \
  -F "<every other upload_fields entry>=<its value>" \
  -F "file=@receipt.pdf"

S3 answers with a 2xx on success and rejects a body larger than max_bytes or a Content-Type that differs from the one in the policy. A PUT to this URL does not work.

Step 3: Use the s3_key

Pass the s3_key to whichever endpoint consumes it. Common ones:

Use Endpoint and field
Attach a document to a bill s3_key on POST /api/v1/bills, POST /api/v1/bills/received, PUT /api/v1/bills/{bill_id} — see Bills
Create bills / invoices from PDFs with AI single_bill_s3_keys / multi_bill_s3_key on POST /api/v1/bills/ai-create; single_invoice_s3_keys / multi_invoice_s3_key on POST /api/v1/invoices/ai-create
Import bank transactions from CSV/Excel s3_key on POST /api/v1/transactions/bulk-upload/headers → /preview → /bulk-upload — see Transactions
Bulk-import invoices, bills or journal entries s3_key on POST /api/v1/invoices/bulk, POST /api/v1/bills/bulk, POST /api/v1/journal-entries/bulk-upload/preview and /post
Upload a bank statement for reconciliation statement_s3_key on POST /api/v1/reconciliations — see Reconciliation
Feed a spreadsheet to a report file_s3_key / s3_keys on POST /api/v1/reports — see Reports
Vendor contracts and note attachments s3_key on POST /api/v1/vendor-contracts, POST /api/v1/notes/{note_id}/attachments

Direct Upload

Upload a file through the API in one call (simpler, but the bytes transit DayZero's servers, so prefer presigned URLs for large files):

bash
curl -X POST "https://api.ondayzero.com/api/v1/upload/direct" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID" \
  -F "file=@receipt.pdf"

Returns s3_key, filename, file_size, business_id, status and message. The extension must be one of the supported types and the file's leading bytes must match it (400 VALIDATION_195 otherwise). Files over 250 MB are refused with 400 VALIDATION_054 (a request body over the server's size limit is refused with 413 before it is read; see Error Handling).

Some workflows have their own multipart shortcut that uploads and processes in one request, e.g. POST /api/v1/bills/upload (bill document) and POST /api/v1/reconciliations/upload (bank statement).

Download a File

Get a presigned download URL:

bash
curl "https://api.ondayzero.com/api/v1/upload/download?s3_key=path/to/file.pdf" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID"

Returns download_url and s3_key. The link is valid for 15 minutes and needs no DayZero headers. Report files (keys under reports/) are not served here; use GET /api/v1/reports/download (see Reports). Or stream the file through the API (avoids CORS issues with presigned URLs in browsers):

bash
curl "https://api.ondayzero.com/api/v1/upload/stream?s3_key=path/to/file.pdf&filename=receipt.pdf" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID" \
  -o downloaded-file.pdf

filename is optional and sets the download's Content-Disposition name. Only keys belonging to the business in x-business-id can be downloaded.

List Uploaded Files

bash
curl "https://api.ondayzero.com/api/v1/upload/files" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID"

Returns items of {id, name, s3_key, type, source, created_at, updated_at}. Filter with ?source=user_upload or ?source=ai_generated to separate files you uploaded from ones DayZero generated.

CSV Templates

Download templates for bulk imports. GET /api/v1/upload/templates lists the names — currently rec-txns (bank transactions), bulk-invoice-upload, bulk-bill-upload and bulk-journal-entries:

bash
curl "https://api.ondayzero.com/api/v1/upload/templates/bulk-invoice-upload" \
  -H "Authorization: Bearer dz_your_token_here" \
  -H "x-business-id: YOUR_BUSINESS_ID"

The response carries the template as csv_content_stringified (plain CSV text), csv_content_object (rows as JSON) and csv_content_base64; the journal-entries template is also returned as xls_content_base64, pre-filled with the business's own ledger names.

Common errors

Status Cause Fix
400 Extension not supported (for example html, svg), or VALIDATION_195 (file contents do not match the extension) Convert the file to a supported type
400 The file is still being scanned for malware, or was flagged by the scan Retry after a few seconds; a flagged file is refused for good
400 VALIDATION_054 File over 250 MB on /upload/direct Use the presigned upload
4xx from S3 (not DayZero) upload_fields sent out of order or incomplete, the Content-Type changed, or the body is over max_bytes Send every field exactly as returned, with file last
403 Download or stream of an s3_key that belongs to another business, or a key under reports/ on /upload/download Use the key from this business; reports go through GET /api/v1/reports/download
404 /upload/stream for a key that does not exist Check the s3_key