Skip to content

Add Attachments To Expense Create And Update - #69

Merged
dinoconstantinou87 merged 3 commits into
mainfrom
feat/expense-attachments
Oct 7, 2026
Merged

dinoconstantinou87 merged 3 commits into
mainfrom
feat/expense-attachments

Conversation

@dinoconstantinou87

@dinoconstantinou87 dinoconstantinou87 commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

An expense could show its attachment but not get one. FreeAgent takes an inline attachment on expense create and
update, so the spec now declares it and expense create and expense update take it as options.

$ freeagent expense create --category 285 --dated-on 2026-10-07 --description "Hotel" --gross-value -9.99 --attachment receipt.pdf --attachment-description "Hotel receipt"
✔ Success
  Created expense https://api.sandbox.freeagent.com/v2/expenses/577713

$ freeagent expense update 577713 --attachment replacement.png
✔ Success
  Updated expense 577713

$ freeagent expense update 577713 --remove-attachment
✔ Success
  Updated expense 577713
  • --attachment <path> on create and update attaches a pdf, png, jpg, jpeg or gif file of up to 5MB.
    --attachment-description goes with it, and is refused without it.
  • On update, --attachment replaces any attachment the expense has, and FreeAgent deletes the old file. The help
    says so.
  • expense update --remove-attachment removes it. It isn't confirmed, as an empty --receipt-reference isn'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 attachment group because they have their own attachments endpoint and
hold several files. No endpoint is added, so make coverage is unchanged at 119 of 203.

What The Sandbox Showed

Probed under the pinned X-Api-Version: 2026-09-01, no header and 2024-10-01. The shape is the same under all
three: attachment is a single object. Every probe expense was deleted, and its attachment with it.

  • Inline on create and update. An update that sends only attachment attaches it and leaves every other field
    unchanged.
  • One attachment per expense. Sending another replaces it and deletes the old file, which is then a 404.
  • _destroy removes it, whatever its value. 1, "true", true, 0 and "false" all remove the
    attachment. On an expense with none it is a 200 that does nothing.
  • content_type is ignored, on create and update. FreeAgent detects the type from the bytes: PNG bytes sent as
    application/pdf come back image/png. It accepts application/pdf, which the docs' list lacks, and even
    text/plain and text/csv. The CLI keeps its pdf, png, jpg, jpeg and gif check.
  • Refusals: over 5MB is a 422 attachment must be smaller than 5MB; file_name without data, or a
    description alone, is a 400.
  • The docs' file key does nothing. The docs say it links an existing attachment; a URL or an ID is a 200 that
    attaches nothing.
  • DELETE /v2/attachments/:id also removes an expense's attachment, and deleting the expense deletes its file.

Code

  • Spec: ExpenseCreatePayload gains attachment as a new ExpenseAttachmentPayload. ExpenseUpdatePayload
    gains it as a new ExpenseAttachmentUpdatePayload, which adds _destroy. Both type content_type as a plain
    string, since FreeAgent ignores it, rather than reusing AttachmentPayload's closed enum, which lacks
    application/pdf.
  • ExpenseAttachmentOptions in Options/ holds --attachment and --attachment-description, nested in
    ExpenseOptions as the recurring options are, and builds both payloads.
  • Models/AttachmentFile reads a file, base64-encodes it and maps its extension to a content type. It used to be
    inline 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 on
    update and deletes the expense. changed-suites selects only expenses; its 6 tests pass against the sandbox.

  • CLI unit tests: AttachmentFile content types and its unsupported-type error (moved from the explanation add
    tests) and its unreadable-file error; --attachment-description refused without --attachment; --attachment refused 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:

    • create with --attachment and a description, then a replace on update;
    • an unrelated update keeping the attachment;
    • both attachment flags together, a description alone and a .txt file (each exit 64);
    • a dry-run --remove-attachment showing _destroy: 1, then the removal itself.

    The expense was deleted afterwards.

🤖 Generated with Claude Code

dinoconstantinou87 and others added 2 commits October 7, 2026 08:19
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>
@dinoconstantinou87 dinoconstantinou87 changed the title Add Expense Attachments Add Attachments To Expense Create And Update Oct 7, 2026
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>
@dinoconstantinou87
dinoconstantinou87 merged commit fcfea1c into main Oct 7, 2026
4 checks passed
@dinoconstantinou87
dinoconstantinou87 deleted the feat/expense-attachments branch October 7, 2026 07:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant