Somewhere in your company right now, someone is opening twelve emails to find seven receipts, squinting at a phone photo of a fuel pump display, and retyping a hotel folio into a spreadsheet by hand. Expense reporting is one of the last places in the modern back office where "automation" still means a human being a very slow, very literal-minded parser.
The frustrating part is that the underlying problem is not hard. A receipt is a document. It has a type. It has a merchant, a date, some line items, a tax figure, and a total. Once you can get software to read those five things reliably, the rest is plumbing. This is a plumbing problem, and it takes exactly three API calls to solve.
The shape of the problem
Expense automation usually falls apart at one of three points:
- Sorting. The inbox has invoices, contracts, and receipts all mixed together, and someone has to figure out which is which before anything else can happen.
- Reading. Even once you know something is a receipt, pulling the merchant name, the line items, and the total out of a photo or a scanned PDF reliably is its own project.
- Packaging. Finance does not want forty separate PDF attachments per expense report. They want one file, in order, that they can archive against the reimbursement.
Three PDF4me endpoints map directly onto those three points: Classify Document for sorting, the AI Receipt Parser for reading, and Merge Multiple PDFs for packaging. Wire them together in order and you have gone from "pile of attachments" to "structured expense report plus one clean archive PDF" without a human touching a single line item.
Step one: know what you're looking at before you extract anything
Every automated intake pipeline eventually receives something that isn't what it expected. An employee forwards a signed contract to the expenses inbox by mistake, or attaches an invoice from a vendor instead of their own reimbursable receipt. If your pipeline runs receipt extraction on all of it blindly, you get garbage fields back with no warning that the document was never a receipt in the first place.
Classify Document is a POST to /api/v2/ClassifyDocument that takes a Base64-encoded file (docContent) and its filename (docName) and returns a predicted documentType (invoice, contract, receipt, and so on), a category, a confidence score, and basic metadata like page count. Set the optional async flag to true and large batches get a 202 Accepted with a Location header to poll instead of holding the connection open.
import base64
import requests
with open("inbox-attachment.pdf", "rb") as f:
doc_content = base64.b64encode(f.read()).decode("utf-8")
payload = {
"docContent": doc_content,
"docName": "inbox-attachment.pdf",
"async": False
}
headers = {
"Authorization": "Basic YOUR_BASE64_ENCODED_API_KEY",
"Content-Type": "application/json"
}
response = requests.post(
"https://api.pdf4me.com/api/v2/ClassifyDocument",
json=payload,
headers=headers
)
result = response.json()
print(result["documentType"], result["category"], result["confidence"])
That confidence score matters more than it looks like it should. It's the field that decides whether an attachment goes straight into the receipt-parsing step below or gets routed to a human for a second look. A pipeline that skips this gate isn't actually automated, it's just automation-shaped, quietly feeding it whatever lands in the inbox and hoping.
Step two: turn a receipt into structured data, not a wall of OCR text
Once you know you're holding a receipt, the actual extraction is a pre-tuned, no-code endpoint rather than a general-purpose parser you have to configure yourself: the AI Receipt Parser. It's available directly inside Power Automate, Make, and n8n, which means the version of this pipeline that never touches raw REST calls at all is a completely reasonable way to build it, if your team already lives in one of those tools.
Feed it a receipt as binary data, a Base64 string, or a URL, along with a filename PDF4me uses for format detection (PDF, PNG, JPG, and JPEG are all fair game). What comes back is not a transcript, it's a schema: merchant name, address, phone, and website; a line-items array with quantity, unit price, and category per item; subtotal, tax, total amount, and payment method; and a receipt-type-specific field or two depending on what you're looking at (a roomNumber on a hotel folio, a fuel type on a gas station receipt).
Two details are worth building your logic around instead of ignoring:
- Receipt Type is optional but not decorative. Passing a hint like "meal," "hotel," "fuel," "healthcare," or "training" measurably improves extraction accuracy and unlocks the category-specific fields above. If you already know the expense category from context (a corporate card feed, a policy tag), pass it.
-
warningsandfallbackUsedare your real confidence signal. The parser doesn't just hand back numbers and hope for the best. When something was ambiguous or estimated, it tells you. Route anything carrying a warning to a review queue instead of straight into the general ledger.
For receipts specifically, the pre-tuned parser above already covers the fields finance actually asks for, including the optional custom-field keys for anything genuinely one-off, like a loyalty program number.
Step three: give finance one file, not forty
You now have structured expense data flowing wherever your ledger lives. What you still have is a pile of original receipt files that need to survive as an audit trail. Nobody downstream wants forty individual attachments per report.
Merge Multiple PDFs is the plainest endpoint in this pipeline and does exactly what the name says: POST an array of Base64-encoded PDFs to docContent (minimum two), a docName for the result, and it concatenates them in the exact order the array was in. The array order is the merge order, full stop, so if sequencing matters (chronological, by amount, by category), sort before you call it, not after. The synchronous response is the merged PDF as raw bytes, not JSON. Write those bytes straight to a file. There's an async flag here too, for merging large batches without holding a connection open while it works.
import base64
import requests
def to_base64(path):
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
payload = {
"docContent": [
to_base64("receipt-01.pdf"),
to_base64("receipt-02.pdf"),
to_base64("receipt-03.pdf")
],
"docName": "expense-batch-merged.pdf",
"async": False
}
headers = {
"Authorization": "Basic YOUR_BASE64_ENCODED_API_KEY",
"Content-Type": "application/json"
}
response = requests.post(
"https://api.pdf4me.com/api/v2/Merge",
json=payload,
headers=headers
)
with open("expense-batch-merged.pdf", "wb") as out:
out.write(response.content)
If your receipts arrive as images rather than PDFs, that's a one-step conversion before this call, not a blocker; the point stands that whatever format they land in, this is the step that turns "forty attachments" into "one exhibit."
This isn't REST-only, either. Power Automate exposes it as an action that takes File Contents 1 and 2 as required inputs and lets you add more slots on demand. Zapier takes a mappable list of files (or direct URLs) and merges them in the order you list them, no separate sort field, which is exactly the ordering behavior above stated a different way. Make adds a genuinely useful option the others don't: Skip Protected PDFs, so a password-protected receipt scan doesn't silently break the whole batch. n8n returns fileName, mimeType, fileSize, success, and inputFileCount, which is exactly the kind of structured confirmation you want logged before a merge job is allowed to mark an expense report "closed."
Putting the three calls in a row
The pipeline, end to end, looks like this: an attachment lands in a shared inbox or a designated cloud folder. Classify Document decides whether it's actually a receipt, and if the confidence score is low, it goes to a human instead of the next step. If it clears that bar, the AI Receipt Parser turns it into merchant, line items, totals, and payment method, tagged with whatever receipt type you already know from context, with any warnings flagged for review rather than trusted blindly. In parallel, the same original file gets queued for the batch, and once a reporting period (a week, a trip, a month) closes, Merge Multiple PDFs stitches every receipt in that batch into one archive PDF, in whatever order finance actually wants it filed.
The employee who forwarded twelve emails never touches a spreadsheet. Finance gets clean structured data and one attachment instead of forty. And the whole thing is three API calls that happen to already exist, wired together in the order the problem actually unfolds.
What this doesn't solve
None of this replaces your expense policy engine, and it shouldn't. Classification and extraction tell you what a document is and what it says; they don't decide whether a $400 dinner is within policy. Build the approval logic you already have around this pipeline, not instead of it. And treat every warnings flag and every sub-threshold classification confidence score as exactly what it is: a signal that a human should look before the number hits the books.
Website: pdf4me.com
Documentation: docs.pdf4me.com
Developer portal: dev.pdf4me.com
Top comments (0)