BILL_UPLOAD on Unlisted supplier and Prices, for when the supplier is not listed or has no login. Login stays the primary path, so the upload is not offered on the supplier list or the login screen. A bill from a supplier that does not serve the address counts as not recognized, and a user who cancels the supplier login lands on library_select.
- Upload
- Pages
- Reading
- Not recognized
- Code




{
"connect_session": "cos_6587fa4362341be5b524de3b",
"route": "bill_upload",
"live_mode": false,
"actions": [
"PRICE_INPUT",
"BACK",
"CLOSE"
],
"data": {
"device_type": "BATTERY_STORAGE",
"location": {
"timezone": "Europe/London",
"currency_code": "GBP"
},
"status": "AWAITING_UPLOAD",
"upload": {
"upload_id": "upl_6a3196d6e61a44f33f7307ed",
"page_urls": [
"https://uploads.flatpeak.com/connect/upl_6a3196d6e61a44f33f7307ed/1",
"https://uploads.flatpeak.com/connect/upl_6a3196d6e61a44f33f7307ed/2",
"https://uploads.flatpeak.com/connect/upl_6a3196d6e61a44f33f7307ed/3",
"https://uploads.flatpeak.com/connect/upl_6a3196d6e61a44f33f7307ed/4",
"https://uploads.flatpeak.com/connect/upl_6a3196d6e61a44f33f7307ed/5",
"https://uploads.flatpeak.com/connect/upl_6a3196d6e61a44f33f7307ed/6"
],
"content_types": ["image/jpeg", "image/png", "image/heic", "application/pdf"],
"max_bytes": 20000000,
"time_expiry": "2026-09-29T10:45:00Z"
}
}
}
curl --request PUT \
--url https://uploads.flatpeak.com/connect/upl_6a3196d6e61a44f33f7307ed/1 \
--header 'Content-Type: image/jpeg' \
--data-binary @page1.jpg
curl --request POST \
--url https://connect.flatpeak.com \
--header 'Content-Type: application/json' \
--data '{
"connect_session": "cos_6587fa4362341be5b524de3b",
"route": "bill_upload",
"data": {
"upload_id": "upl_6a3196d6e61a44f33f7307ed",
"pages": 2
}
}'
Flow
| Step | Your app | Connect |
|---|---|---|
| 1 | Show Upload. The user takes a photo or picks a file. After the first photo, show Pages: one thumbnail per page and Add another page | AWAITING_UPLOAD with six page_urls slots |
| 2 | PUT page 1 to slot 1, page 2 to slot 2, and so on. Then POST upload_id and pages | READING with retry_after_seconds |
| 3 | Show Reading. POST REFRESH every retry_after_seconds | The next route, or FAILED |
Where it lands
The best connection the bill allows wins.| The bill shows | REFRESH returns |
|---|---|
| A supplier with a login | Redirects with the supplier preselected. The user logs in, or lands on Unlisted supplier if they cancel |
| Unit rates, no login | Prices prefilled, with data.source BILL and bill.date, for the user to check and edit. Prices from a bill are a MANUAL connection and do not update by themselves |
| Neither | bill_upload with status FAILED and a reason |
Capturing pages
A PDF takes one slot and carries its own pages, and each photo takes one slot. Before thePUT, downscale a photo to 2,500 px on its long edge at JPEG quality 0.85, and convert HEIC on the device. A 12 MP photo drops from about 4 MB to under 1 MB.| Web or WebView | Native | |
|---|---|---|
| Camera | <input type="file" accept="image/*" capture="environment"> | Camera |
| File | <input type="file" accept="image/*,application/pdf"> | Photo library or document picker |
| Upload | fetch(url, { method: "PUT", body: file, headers: { "Content-Type": file.type } }) | URLSession upload task |
PUT from any origin, so a WebView needs no proxy.The data object
| Field | Description |
|---|---|
status | AWAITING_UPLOAD, READING or FAILED. A successful read leaves this route. |
upload | Present at AWAITING_UPLOAD. Each page_urls slot accepts one PUT of up to max_bytes in one of content_types, until time_expiry. |
retry_after_seconds | Present at READING. Wait this long before REFRESH. |
reason | Present at FAILED: bill_not_readable, not_an_electricity_bill, bill_too_old or upload_expired. |
bill.date | Carried on the route the read lands on: the bill date as read, YYYY-MM-DD, or null. Show it, so the user can judge how current the prices are. |
Actions
| Action | Result |
|---|---|
REFRESH | Offered at READING. Re-reads the status. |
CANCEL | Offered at READING. Stops reading and returns to the page that offered the upload. |
RETRY | Offered at FAILED. Returns AWAITING_UPLOAD with new page_urls. |
PRICE_INPUT | Returns Prices empty. |
BACK | Returns the previous page. |
CLOSE | Returns the callback_url set on the Connect session. |
Errors
| Error code | Description |
|---|---|
upload_id_unknown | No upload with that ID on this session. |
upload_incomplete | Nothing was PUT to slot 1 before the POST. |
pages_missing | pages is higher than the number of slots that received a file. |
pages_invalid | pages is below 1 or above the number of slots. |
upload_too_large | A page exceeds max_bytes. |
content_type_unsupported | A page is not one of content_types. |
data_json_invalid | Data does not conform to the JSON schema. |

