Repository navigation
Add Attachments To Expense Create And Update - #69
Merged
Merged
Conversation
Adds attachment to the expense create and update payloads, with a new ExpenseAttachmentUpdatePayload carrying _destroy, and an expense attachment add/remove command group. Both commands read the expense first: add refuses one that already has an attachment, and remove refuses one without. File reading moves to a shared AttachmentFile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The attachment is a field on the expense, with no endpoint of its own, so it belongs on the existing commands rather than in a command group. expense create and update take --attachment and --attachment-description, and update takes --remove-attachment, which sends _destroy. Create gets its own ExpenseAttachmentPayload, since FreeAgent ignores content_type there too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A missing, unreadable or directory path passed to --attachment or --file surfaced as a raw Foundation error with exit 1. AttachmentFile now reports it as a usage error naming the path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
An expense could show its attachment but not get one. FreeAgent takes an inline
attachmenton expense create andupdate, so the spec now declares it and
expense createandexpense updatetake it as options.--attachment <path>on create and update attaches a pdf, png, jpg, jpeg or gif file of up to 5MB.--attachment-descriptiongoes with it, and is refused without it.--attachmentreplaces any attachment the expense has, and FreeAgent deletes the old file. The helpsays so.
expense update --remove-attachmentremoves it. It isn't confirmed, as an empty--receipt-referenceisn't,and is refused with
--attachment.The attachment is a field of the expense, with no endpoint of its own, so it sits on the existing commands.
Explanations have a separate
explanation attachmentgroup because they have their own attachments endpoint andhold several files. No endpoint is added, so
make coverageis unchanged at 119 of 203.What The Sandbox Showed
Probed under the pinned
X-Api-Version: 2026-09-01, no header and2024-10-01. The shape is the same under allthree:
attachmentis a single object. Every probe expense was deleted, and its attachment with it.attachmentattaches it and leaves every other fieldunchanged.
_destroyremoves it, whatever its value.1,"true",true,0and"false"all remove theattachment. On an expense with none it is a 200 that does nothing.
content_typeis ignored, on create and update. FreeAgent detects the type from the bytes: PNG bytes sent asapplication/pdfcome backimage/png. It acceptsapplication/pdf, which the docs' list lacks, and eventext/plainandtext/csv. The CLI keeps its pdf, png, jpg, jpeg and gif check.attachment must be smaller than 5MB;file_namewithoutdata, or adescription alone, is a 400.
filekey does nothing. The docs say it links an existing attachment; a URL or an ID is a 200 thatattaches nothing.
DELETE /v2/attachments/:idalso removes an expense's attachment, and deleting the expense deletes its file.Code
ExpenseCreatePayloadgainsattachmentas a newExpenseAttachmentPayload.ExpenseUpdatePayloadgains it as a new
ExpenseAttachmentUpdatePayload, which adds_destroy. Both typecontent_typeas a plainstring, since FreeAgent ignores it, rather than reusing
AttachmentPayload's closed enum, which lacksapplication/pdf.ExpenseAttachmentOptionsinOptions/holds--attachmentand--attachment-description, nested inExpenseOptionsas the recurring options are, and builds both payloads.Models/AttachmentFilereads a file, base64-encodes it and maps its extension to a content type. It used to beinline in
explanation attachment add, which now uses it too; its error moved with it. A path it can't read(missing, a directory, no permission) is now a usage error naming it, exit 64, where it was a raw Foundation error
with exit 1.
Tests
Expense integration test: creates an expense with an attachment, removes it with
_destroy, adds one onupdate and deletes the expense.
changed-suitesselects onlyexpenses; its 6 tests pass against the sandbox.CLI unit tests:
AttachmentFilecontent types and its unsupported-type error (moved from the explanation addtests) and its unreadable-file error;
--attachment-descriptionrefused without--attachment;--attachmentrefused with--remove-attachment.154 CLI, 90 API and 21 tools unit tests pass, and format and lint are clean.
The debug CLI was run end to end against the sandbox. That covered:
--attachmentand a description, then a replace on update;.txtfile (each exit 64);--remove-attachmentshowing_destroy: 1, then the removal itself.The expense was deleted afterwards.
🤖 Generated with Claude Code