Screenshots → ledger

A receipt you only ever screenshotted

Copy one instruction, paste it into any AI chat with your payment screenshots attached, and it hands back a JSON file the ledger imports. No account, no upload, no converter to install — the chat you already use does the reading.

you take the screenshotjudgementis this deductible at all?
the AI chatautomatedreads the image, writes the JSON
Ledger ▸ Data ▸ Importcheckpointpreview shows add and skip, you confirm
you set the work use %judgementevery row arrives at 0%
Open the ledger

Roughly 400 lines. It is printed in full below — read it before you trust it.

How to use it

  1. Copy the instruction with the button above.
  2. Open any AI chat that can read images — Claude, ChatGPT, Gemini.
  3. Attach your payment screenshots, then paste the instruction and send.
  4. It returns a .json file. Download it.
  5. In the ledger, Data ▸ Import rows… and pick the file.

If your chat cannot attach a file, it will print the JSON instead. Copy that into a text editor and save it with a .json extension — the ledger reads it exactly the same way.

Send a whole batch at once. A subscription that recurs monthly is never an asset at any price, and recurrence is something the chat can only see when it holds several screenshots together.

What still has to come from you

The work-use percentage. Every row arrives at 0, deliberately, even when the receipt read perfectly. A phone bill that parsed cleanly says nothing about how much of the phone relates to earning income. Under-claiming is recoverable; over-claiming is an amendment. Set it in the ledger, after checking.

Whether it is deductible. The chat reads a receipt. It cannot know why you bought the thing.

The import preview. It names what will be added, what it already holds and will skip, and any supplier it has already recorded from a different source. Read it — cancelling is still free at that point, and Undo last import is there afterwards if it was not.

Why the instruction is this long

Almost nothing in an import file is validated. A key the ledger does not recognise is kept and silently ignored; a date written 14/08/2025 puts the row in no financial year, where it is invisible in every view; an asset with no work-use percentage is read as 100%. Each of those imports without an error and produces a wrong figure. The length is what buys a correct file from a model that has never seen this ledger.

The format itself is documented in IMPORT-FORMAT.md, which every converter has to satisfy, and SCREENSHOT.md covers this workflow in the repository.

The instruction

Generated from screenshot-prompt.md. The button copies exactly what is shown here.


# Convert payment screenshots into a tax-ledger import file

You are converting screenshots of payments — receipts, tax invoices, order confirmations, card or
bank transaction detail — into one JSON file that an Australian individual tax ledger imports
directly.

Read every image attached to this message and return **one downloadable `.json` file**.

Take the format literally. Almost nothing in this file is validated when it is imported: an
unrecognised key is kept and silently ignored, a badly formatted date makes the row invisible in
every view, and a misread number becomes a wrong figure on a tax return. There is no error message
for any of it.

---

## 1. The file

Exactly this wrapper. Never rename a key, never add a top-level key, never wrap the rows in
anything else.

```
{
  "schema": 6,
  "generated": "<today, YYYY-MM-DD>",
  "source": "screenshot",
  "years": [],
  "assets": [],
  "expenses": [],
  "income": []
}
```

Every row goes in `expenses` or `assets`. Leave `years` and `income` as empty arrays. Rows put
under any other key — `transactions`, `payments`, `items`, `receipts` — import as nothing at all,
and the only message is "The file contained no rows."

**Amounts are plain JSON numbers**: `34`, `310.32`, `1234.56`. Never a string, never a currency
symbol, never a thousands separator. `"$34.00"` is read as **zero**.

**Dates are `YYYY-MM-DD`.** Nothing else. `14/08/2025`, `14 Aug 2025` and `Aug 14, 2025` all put
the row in no financial year, where it sits in the file, is counted in the import preview, and
appears in no view of the ledger.

---

## 2. Which number is the amount

Every row must carry a **payment amount**, and a receipt offers several numbers that are not it:

```
Subtotal              A$30.91
Total excluding tax   A$30.91
GST - Australia (10%)  A$3.09
Total                 A$34.00
Amount paid           A$34.00     <- this one
```

Take what was paid, **GST inclusive**, in this order of preference:

1. an explicit **Amount paid** / **Amount charged** / **Paid** / **Total charged**
2. failing that, **Total** — the one that includes GST
3. failing that, the screenshot has no payment amount and **the row must not be emitted**

Never a subtotal. Never a total excluding tax. Never "the largest number on the screen" — that is
a guess dressed as a rule, and it lands on an account balance or a next-invoice estimate as
readily as on the amount.

GST-inclusive matters beyond tidiness: for someone not registered for GST the cost of an item
**is** the GST-inclusive price, and that is the figure a $300 threshold is tested against. Taking
a $282.11 subtotal instead of the $310.32 paid changes the tax treatment of the purchase, not just
the cents.

### Unpaid, part-paid, refunded, foreign

- An invoice issued but not paid is **not a row**. It becomes one when it is paid.
- Part-paid: the row is the payment — for what was paid, dated when it was paid.
- A refund nets against the charge it reverses, **inside the same row**, and `basis` says so.
- Foreign currency: convert it yourself, emit AUD, and put the rate and its date in `basis`. A
  figure in another currency is not a payment amount.

---

## 3. `issuer` — who you bought from

`issuer` is the join key that lets the ledger recognise the same bill arriving through a different
channel. The same Optus bill can reach a person three ways, and nothing about the three records
matches — not the name, not the date, not even the number of rows:

| Where it came from | What it calls the supplier | Shape |
| --- | --- | --- |
| PayPal export | `Optus` | one row, $300 |
| Emailed receipts | `PayPal` | twelve rows, $25 |
| Card statement | `PAYPAL *OPTUS` | twelve rows, $25, dated a day later |

All three carry `issuer: "optus"`, and that is the only thing that tells the ledger they are one
obligation.

> **The issuer is who issued the invoice. Never how you paid, and never where the record came
> from.**

PayPal is a payment channel. A credit card is a funding instrument. A screenshot is a transport.
None of them issues an invoice — unless PayPal is charging *you* a fee, in which case PayPal
really is the issuer.

Write it lowercase, no punctuation, no company suffix, and **reuse these exact spellings** where
the merchant is one of them, because a different spelling silently splits one merchant into two:

`openai` · `adobe` · `atlassian` · `github` · `google` · `dreamhost` · `godaddy` · `namecheap` ·
`crazy-domains` · `telstra` · `optus` · `vodafone` · `belong` · `amaysim` · `aussie-broadband` ·
`officeworks` · `jb-hi-fi` · `apple` · `h-r-block`

For anything else, follow the same style: `cloudflare`, `dell`, `anthropic`, `harvey-norman`.

If you genuinely cannot tell who issued the invoice, emit the row with `basis: "issuer unknown"`
rather than guessing. A wrong issuer silently merges two unrelated suppliers, which is worse than
a missing one.

---

## 4. `source` and `source_ref` — so a re-import adds nothing twice

Every row carries `"source": "screenshot"`.

`source_ref` must be **unique and permanent** for that payment. The ledger skips any row whose
`source` + `source_ref` it already holds, which is what makes importing the same file twice safe.

1. **Prefer a number printed on the screenshot** — invoice number, receipt number, order number,
   transaction ID. `"INV-4471928"`, `"AU-2026-88117"`, `"1KP42871TW3849302"`.
2. **Where the image shows none**, build one deterministically from the row itself:

   ```
   issuer|date|amount-to-2-decimals

   optus|2025-08-14|25.00
   dell|2025-11-24|310.32
   ```

Do not invent a random id, do not use a UUID, do not number the rows `1`, `2`, `3`, and do not use
the image filename. The point of the fallback is that the *same screenshot handed to a different
chat next month produces the same string* — so the ledger recognises it and skips it, instead of
claiming the expense a second time.

---

## 5. Expense or asset — decide this before anything else

The wrapper has two arrays and every row has to go in one. This is not filing. It decides **which
year the money is claimed**:

| A $900 dock emitted into | Year one | Year two | Year three |
| --- | --- | --- | --- |
| `expenses` | **$900.00** | — | — |
| `assets` | **$168.75** | $274.22 | $171.39 |

### Two questions, in order

**1. Is it a thing, or a service?** A service is consumed inside a period — a month of phone, a
year of a licence, an hour of an accountant. A thing is still working after 30 June.

**2. Does it cost more than $300?** At or under $300 both arrays claim the whole amount in the
same year, so the first question stops mattering. Above $300, everything changes.

|  | $300 or less | more than $300 |
| --- | --- | --- |
| clearly a service | `expenses` | `expenses` |
| clearly a thing | `expenses` | **`assets`** |
| **cannot tell** | `expenses` | **`assets`** |

**That last cell is the rule.** Send an unresolved row over $300 to `assets`, because the two
mistakes are not symmetrical: a thing wrongly in `expenses` claims its whole cost this year — an
over-claim, and an amendment to undo. A service wrongly in `assets` spreads a cost that was
deductible at once — an under-claim, fixed by moving the row. Guess toward the recoverable side,
say why in `basis`, and let the person holding the receipt decide.

### Signals you can actually read

**Says service:** a billing period on the document (`1 Aug – 31 Aug`, `renews on`, `your plan`,
`next invoice`) · the same issuer appearing on a monthly or yearly rhythm across the screenshots
you were given — a subscription is never an asset at any price.

**Says thing:** shipping, delivery, freight or a pickup address — nobody ships a subscription · a
serial or model number · `quantity × unit price` · warranty or returns wording.

**Signals that look decisive and are not:**

- **The issuer.** Apple bills a $19 cloud plan and a $3,499 laptop from the same address. Amazon,
  Officeworks, JB Hi-Fi and Harvey Norman all sell both. The issuer decides the row's *name*, never
  its *kind*.
- **A large total.** A $700 Adobe plan is a service. A $310 dock is a thing.
- **The word "purchase".** Every receipt says it.

### One receipt is not one row

A $2,400 Officeworks receipt holding an $1,800 laptop, a $310 dock and $40 of paper is three rows
across two arrays. **Emit the lines, not the total.**

Where the receipt does not itemise and the total is over $300, emit a single `assets` row with
`basis: "receipt not itemised — split before lodging"`. The flag is the output there, not the
routing.

---

## 6. An expense row

| Field | Required | Notes |
| --- | --- | --- |
| `issuer` | **yes** | §3. Lowercase, canonical, stable forever. |
| `supplier` | **yes** | Display name, spelled how a person reads it: `Optus`, `Dell Australia`. |
| `source` | **yes** | Always `"screenshot"`. |
| `source_ref` | **yes** | §4. |
| `date` | **yes** | `YYYY-MM-DD`. The date it was paid. |
| `amount` | **yes** | §2. Paid, GST inclusive, AUD, plain number. |
| `work_pct` | **yes** | Always `0`. §8. |
| `label` | **yes** | The ATO label. Take it from the table below. |
| `category` | no | From the table below, and it must be the partner of the label you used. Omit it rather than guess. |
| `basis` | no | Why this row reads the way it does, and what a person still has to decide. |
| `evidence` | no | The screenshot's filename. |

### Category and label go together

Pick one row of this table. Never take the category from one row and the label from another, and
never invent a category — an unrecognised one is kept verbatim and quietly groups the expense
under nothing.

| `category` | `label` |
| --- | --- |
| `Car expenses` | `D1` |
| `Travel expenses` | `D2` |
| `Clothing and laundry` | `D3` |
| `Self-education` | `D4` |
| `Information services` (phone, internet, data) | `D5` |
| `Hosting and domains` | `D5` |
| `Software and subscriptions` | `D5` |
| `Storage and backup` | `D5` |
| `Professional memberships` | `D5` |
| `Repairs and consumables` | `D5` |
| `Energy` | `D5` |
| `Water and council rates` | `D5` |
| `Other work expenses` | `D5` |
| `Interest and investment expenses` | `D7` |
| `Dividend expenses` | `D8` |
| `Donations` | `D9` |
| `Tax affairs` (accountant, tax agent) | `D10` |
| `Personal super contributions` | `D12` |
| `Insurance` (income protection) | `D15` |
| `Other deductions` | `D15` |

When nothing fits, use `label: "D5"` and omit `category`.

**Never put an asset category (§7) on an expense row**, or the reverse. The categories are how the
ledger's own checks notice a misrouted purchase, and crossing them makes the mistake invisible.

---

## 7. An asset row

Shaped differently from an expense. `date` becomes `purchase_date`, `amount` becomes `cost`, and
`supplier` is folded into `item_supplier`.

| Field | Required | Notes |
| --- | --- | --- |
| `issuer`, `source`, `source_ref` | **yes** | Exactly as for an expense — identity does not change with the array. |
| `item_supplier` | **yes** | `Item — Supplier`. See below; the separator is exact. |
| `purchase_date` | **yes** | `YYYY-MM-DD`. |
| `cost` | **yes** | §2. Paid, GST inclusive. This is the figure the $300 test is applied to. |
| `work_pct` | **yes** | An **object keyed by financial year**: `{"2025-26": 0}`. See §8. |
| `category` | no | From the asset list below only. Never an expense category. |
| `basis` | no | As for an expense. |
| `start_date` | no | When it was first used to earn income. Omit unless the screenshot proves it. |

### `item_supplier`

One string, in the form `Item — Supplier`, using an **em dash with a single space on each side**:

```
14-inch MacBook Pro — Apple
USB-C dock — Dell Australia
Review item — Officeworks
```

A hyphen (`-`), an en dash (`–`), or no spaces will not split, and the supplier half is lost
silently. The string is split on the **last** ` — ` in it, so an item containing a dash is fine.

Make the item specific enough to imply how long it lasts — `laptop`, `monitor`, `dock`, `desk`,
`iPhone` — because that text is what the ledger reads to work out an effective life. Where only
the merchant is known, write `Review item — Officeworks` rather than repeating the merchant on
both sides.

### Asset categories

`Computers` · `Phones and tablets` · `Peripherals` (monitor, keyboard, mouse, dock, printer,
webcam, headset, drive) · `Furniture` (desk, chair, cabinet) · `Recording equipment` (camera,
lens, microphone, lighting) · `Tools and safety equipment`

Omit `category` rather than guess.

### Never emit these on an asset

| Field | Why |
| --- | --- |
| `asset_id` | Discarded and reallocated on import. Supplying one changes nothing. |
| `treatment` | Derived from `cost`. A value you supply **is honoured**, which overrules the $300 and $1,000 tests that decide how the asset is written off. |
| `method` | The depreciation method is the taxpayer's choice, not a property of the purchase. A receipt cannot know it. |
| `id` | Allocated on import. |

Also never emit `currency` or `gst` — nothing reads them.

---

## 8. `work_pct` is always zero

A work-use percentage never comes from a converter. Not when the supplier was recognised, not when
the amount parsed cleanly, not ever.

- On an **expense**: `"work_pct": 0` — a plain number, on a 0–100 scale.
- On an **asset**: `"work_pct": {"2025-26": 0}` — an object keyed by the financial year of
  `purchase_date`.

**An asset with no `work_pct` at all is read as 100%.** It is the one default in the ledger that
errs toward over-claiming, and an imported row is exactly how you reach it. Emit the object, never
a bare `0`, never nothing.

Australian financial years run **1 July to 30 June** and are written `2025-26`:

| `purchase_date` | financial year key |
| --- | --- |
| `2025-07-01` | `2025-26` |
| `2025-11-24` | `2025-26` |
| `2026-06-30` | `2025-26` |
| `2026-07-01` | `2026-27` |

A receipt that parsed perfectly says nothing whatever about how much of a phone plan relates to
earning income. Under-claiming is recoverable; over-claiming is an amendment. That number must
come from a person, in the ledger, after checking.

---

## 9. Worked example

Two screenshots: a monthly AI subscription receipt showing *Amount paid A$34.00*, and a Dell order
confirmation for a USB-C dock, shipped, *Total A$310.32*, with order number `AU-88117`.

The subscription recurs, so it is a service, so it is an expense whatever it costs. The dock is
shipped hardware over $300, so it is an asset. Neither carries a work-use percentage.

```json
{
  "schema": 6,
  "generated": "2026-08-26",
  "source": "screenshot",
  "years": [],
  "assets": [
    {
      "issuer": "dell",
      "source": "screenshot",
      "source_ref": "AU-88117",
      "item_supplier": "USB-C dock — Dell Australia",
      "purchase_date": "2025-11-24",
      "cost": 310.32,
      "work_pct": { "2025-26": 0 },
      "category": "Peripherals",
      "basis": "shipped hardware over $300; confirm the date it was first used",
      "evidence": "dell-dock-order.png"
    }
  ],
  "expenses": [
    {
      "issuer": "anthropic",
      "supplier": "Anthropic",
      "source": "screenshot",
      "source_ref": "anthropic|2026-06-20|34.00",
      "date": "2026-06-20",
      "amount": 34,
      "work_pct": 0,
      "label": "D5",
      "category": "Software and subscriptions",
      "basis": "amount taken from 'Amount paid'; no invoice number shown",
      "evidence": "anthropic-receipt.png"
    }
  ],
  "income": []
}
```

---

## 10. Before you answer

Check every row:

- [ ] the amount is what was **paid**, GST inclusive, and is a plain number
- [ ] the date is `YYYY-MM-DD`
- [ ] `issuer` is the merchant, not the payment method
- [ ] `source_ref` is a printed number, or `issuer|date|amount` — never random
- [ ] anything over $300 that is not clearly a service is in `assets`
- [ ] every asset has `work_pct` as an FY-keyed object; every expense has `work_pct: 0`
- [ ] no `asset_id`, no `treatment`, no `method`
- [ ] the category matches the label it is paired with

Then return the file, and **list separately any screenshot that produced no row and why** — an
amount you could not find, a date you could not read, an issuer you could not identify. Do not
guess a missing value to avoid an empty result. A row that claims nothing and says nothing is
noise in a register a person has to read.

This is a record-keeping tool, not tax advice. A chat reading a receipt is transcription, not a judgement about what you may claim. You remain responsible for what you lodge — verify anything you act on against ato.gov.au.