Skip to content

fix(api): validate transaction category, merchant and tag ownership#2823

Open
erkdgn wants to merge 4 commits into
we-promise:mainfrom
erkdgn:fix/api-transaction-association-scoping
Open

fix(api): validate transaction category, merchant and tag ownership#2823
erkdgn wants to merge 4 commits into
we-promise:mainfrom
erkdgn:fix/api-transaction-association-scoping

Conversation

@erkdgn

@erkdgn erkdgn commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #2781

The problem

Api::V1::TransactionsController assigned category_id, merchant_id and tag_ids straight onto the record without validating them. That failed three different ways, verified against a dev container:

Input Actual Expected
Malformed UUID (e.g. "18bsy6-") 201 Created, "category": null 422
Well-formed but unknown UUID 500 internal_server_error 422
UUID owned by another family 201 Created, association assigned 422

The first row is what #2781 reports. ActiveRecord casts an invalid UUID to NULL for a uuid column, so the API accepts the request, reports success, and returns the transaction uncategorized — with nothing indicating what went wrong. The reporter reasonably concluded the field type must be wrong.

The second row happens because the unknown id reaches the transactions → categories foreign key, and the action's blanket rescue => e reports that as a generic 500.

The third row is a cross-family issue: a valid UUID from another family satisfies the foreign key, so a caller could attach another family's category, merchant or tag to their own transaction.

merchant_id and tag_ids behave identically to category_id on all three, and update silently ignores an unusable category_id the same way create does.

The fix

Validate all three references against the caller's own family before assigning them, returning 422 when they don't resolve. This is the same scoping already used by trades_controller.rb (category_id) and recurring_transactions_controller.rb (merchant_id) — transactions_controller.rb was the one place missing it.

The OpenAPI descriptions for category_id, merchant_id and tag_ids now state that the value must be a UUID from the caller's own family, which is the documentation half of the confusion in the issue.

Notes for reviewers

  • Behavior change: requests that previously got a silent 201 with the association dropped now get a 422. That is the point of the fix, but it is a visible change for any client that was unknowingly sending bad ids.
  • Clearing an association is unaffected — a blank/absent category_id skips validation as before.
  • The index filter (GET /api/v1/transactions?category_id=...) still returns an empty result set for an unusable id rather than a 4xx. Same class of silent failure, but different code path and different semantics, so it is left out of this PR.
  • docs/api/openapi.yaml was regenerated with rswag:specs:swaggerize. Regeneration also surfaces pre-existing drift unrelated to this change (merchants CSV import endpoint, transfer fee fields), which I deliberately excluded to keep this diff focused — that drift is still outstanding on main.

Verification

Run inside the dev container against this branch:

bin/rails test        6031 runs, 24077 assertions, 0 failures, 0 errors
bin/rubocop           2 files inspected, no offenses detected
bundle exec erb_lint  no errors
bin/brakeman          0 security warnings

Nine tests were added to test/controllers/api/v1/transactions_controller_test.rb covering the malformed, unknown and cross-family cases for category, merchant and tag on both create and update, plus the happy path. Eight of them fail on main and pass here.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Transaction creation and updates now validate category_id, merchant_id, and tag_ids are from the caller’s family before saving.
    • Rejects malformed, unknown, or unauthorized association IDs with 422 Unprocessable Entity.
    • Improves tag behavior: non-array tag_ids are rejected; an explicit empty array clears tags.
    • The merchants listing is now restricted to API-assignable merchants for the caller (including provider-surfaced merchants).
  • Documentation
    • Refined transaction mutation docs for family-scoped category_id, merchant_id, and tag_ids, including tag assignment vs clearing.
  • Tests
    • Added/expanded request specs for family-scoped association validation on create and update, including tag edge cases.

The transactions API assigned category_id, merchant_id and tag_ids
straight onto the record without checking them, which failed three
different ways:

- A malformed UUID was cast to NULL by ActiveRecord, so the request
  returned 201 with the association silently dropped. This is what
  issue we-promise#2781 reports: the category is sent, the call succeeds, and
  the transaction comes back uncategorized with no error to explain
  why.
- An unknown but well-formed UUID tripped the foreign key, and the
  blanket rescue turned it into a 500 internal_server_error.
- A UUID belonging to another family passed the foreign key and was
  accepted, letting a caller attach another family's category,
  merchant or tag to their own transaction.

Validate all three against the caller's own family before assigning
them and return 422 otherwise, matching the scoping already applied in
trades_controller and recurring_transactions_controller. Also spell
out in the OpenAPI descriptions that these ids must be UUIDs from the
caller's own family.

Fixes we-promise#2781

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 862e2d5e-2478-47f3-80f8-24861d5fbcb7

📥 Commits

Reviewing files that changed from the base of the PR and between 751799a and fdb4178.

📒 Files selected for processing (2)
  • docs/api/openapi.yaml
  • spec/requests/api/v1/transactions_spec.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/api/openapi.yaml

📝 Walkthrough

Walkthrough

Transaction creation and updates now reject malformed or cross-family category, merchant, and tag IDs before processing. Merchant listings expose family and provider merchants assignable through the API, with tests and API schemas covering the updated behavior.

Changes

Transaction association scoping

Layer / File(s) Summary
Assignable merchant lookup
app/models/family.rb, app/controllers/api/v1/merchants_controller.rb
Merchant listings use family-scoped assignable merchants, including family merchants and provider merchants from accessible accounts.
Association validation and action guards
app/controllers/api/v1/transactions_controller.rb
Create and update validate referenced association UUIDs, payload shapes, and family ownership before continuing, rendering validation errors on failure.
Association validation coverage
test/controllers/api/v1/transactions_controller_test.rb
Tests cover valid associations, provider merchants, malformed and unknown IDs, cross-family IDs, tag clearing, and invalid tag payloads.
Request contract documentation
docs/api/openapi.yaml, spec/requests/api/v1/transactions_spec.rb
Request schemas describe family-scoped UUID requirements, tag assignment behavior, and update validation errors.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant APIClient
  participant TransactionsController
  participant Family
  participant Transaction
  APIClient->>TransactionsController: Create or update transaction
  TransactionsController->>Family: Validate category, merchant, and tag ownership
  Family-->>TransactionsController: Return validation result
  TransactionsController->>Transaction: Process mutation when valid
  TransactionsController-->>APIClient: Return transaction or validation error
Loading

Possibly related PRs

  • we-promise/sure#1729: Both changes modify transaction creation flow in app/controllers/api/v1/transactions_controller.rb.

Suggested labels: pr:verified

Suggested reviewers: jjmata, jsonbored

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: validating transaction category, merchant, and tag ownership in the API.
Linked Issues check ✅ Passed The PR addresses #2781 by making transaction category IDs text/UUID-based and validating them against the caller's family.
Out of Scope Changes check ✅ Passed All code and docs changes support transaction association validation and merchant scoping; no unrelated scope is evident.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4912ada6a1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

end

merchant_id = transaction_params[:merchant_id]
if merchant_id.present? && !family_owns?(family.merchants, merchant_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow accessible provider merchants

When a family has provider-synced transactions, Api::V1::MerchantsController#index intentionally returns their accessible ProviderMerchant records, but this check searches only family.merchants, whose association is restricted to FamilyMerchant. Consequently, passing a merchant ID returned by GET /api/v1/merchants to transaction create or update now incorrectly returns 422; validate against the same accessible merchant scope used by the merchants API.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 84f1df8 — thank you, this was a real regression.

Verified against the dev container: a ProviderMerchant attached to a transaction on an accessible account is returned by GET /api/v1/merchants, and passing that id to POST /api/v1/transactions returned 422 with my guard in place.

Rather than duplicate the index's scope, I extracted it into Family#api_assignable_merchants_for(user) and now use it from both MerchantsController#index and the transactions validation, so the two cannot drift apart again. Added a regression test that asserts the merchants index lists the provider merchant and that creating a transaction with it succeeds.

return
end

return unless validate_family_associations

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Document the new PATCH validation response

When PATCH supplies an invalid or foreign category, merchant, or tag, this new guard returns 422, but the PATCH operation in spec/requests/api/v1/transactions_spec.rb still documents only 200/404 responses and does not describe the ownership constraints; the generated OpenAPI YAML mirrors that omission. Update the rswag PATCH operation and regenerate the docs so clients receive the newly introduced contract.

AGENTS.md reference: AGENTS.md:L39-L46

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 84f1df8. The PATCH operation in spec/requests/api/v1/transactions_spec.rb now documents the family-ownership requirement on category_id, merchant_id and tag_ids (keeping the existing omit-vs-[] tag semantics) and adds the 422 response, and docs/api/openapi.yaml is regenerated.

One note on the regenerated file: rswag:specs:swaggerize also emits pre-existing drift unrelated to this PR (the merchants CSV import endpoint, MerchantImportResult, and the transfer fee fields), from specs already on main that were never regenerated. I left that out to keep this diff reviewable — it is still outstanding on main and probably worth a separate sync.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/controllers/api/v1/transactions_controller.rb`:
- Around line 334-350: Update the transaction parameter validation around
tags_provided? and the tag_ids handling to inspect the raw request value before
permit casts it. Reject tag_ids unless it is an array containing only strings,
including rejecting scalar, object, and blank-string elements, with the existing
422 validation response before create or update; only then normalize,
deduplicate, and apply tag IDs without clearing existing tags.

In `@spec/requests/api/v1/transactions_spec.rb`:
- Around line 174-179: Update the PATCH request schema in the transaction API
spec to document category_id, merchant_id, and tag_ids family-ownership
requirements consistently with POST, preserving the existing tag omission versus
[] semantics; then regenerate docs/api/openapi.yaml and ensure request coverage
reflects the same 422 validation behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e4d4591c-c498-4447-ade0-3f21d4fd8528

📥 Commits

Reviewing files that changed from the base of the PR and between c9a28e1 and 4912ada.

📒 Files selected for processing (4)
  • app/controllers/api/v1/transactions_controller.rb
  • docs/api/openapi.yaml
  • spec/requests/api/v1/transactions_spec.rb
  • test/controllers/api/v1/transactions_controller_test.rb

Comment thread app/controllers/api/v1/transactions_controller.rb
Comment thread spec/requests/api/v1/transactions_spec.rb
Addresses review feedback on the association scoping guard.

GET /api/v1/merchants returns a family's own merchants plus the provider
merchants attached to transactions in accounts the user can reach, but
Family#merchants is scoped to FamilyMerchant alone. Validating against
it meant a merchant id taken straight from the merchants index was
rejected with a 422 on create and update. Extract the scope the
merchants index already uses into Family#api_assignable_merchants_for
and validate against that, so the two endpoints cannot drift apart
again.

tag_ids needs checking before strong parameters see it. permit(tag_ids:
[]) drops any value that is not an array of scalars, so a string or an
object arrived as nil and then cleared the transaction's tags on update
while reporting 200. Reject a non-array shape with a 422. Blank entries
stay allowed and are simply skipped in the ownership check, because a
form-encoded empty array arrives as [""] and that is how a caller
clears tags.

Also document the family ownership requirement and the 422 response on
the PATCH operation, which previously described only 200 and 404.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
spec/requests/api/v1/transactions_spec.rb (1)

174-175: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Document provider merchants as assignable.

api_assignable_merchants_for and the new controller test permit provider merchants surfaced by GET /api/v1/merchants, but these descriptions say a merchant must belong to the caller’s family. Describe merchant_id as an ID returned by that endpoint instead.

  • spec/requests/api/v1/transactions_spec.rb#L174-L175: Correct the POST merchant description.
  • spec/requests/api/v1/transactions_spec.rb#L316-L317: Correct the PATCH merchant description.
  • docs/api/openapi.yaml#L7358-L7362: Regenerate/update the POST OpenAPI description.
  • docs/api/openapi.yaml#L7478-L7482: Regenerate/update the PATCH OpenAPI description.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/requests/api/v1/transactions_spec.rb` around lines 174 - 175, Update the
merchant_id descriptions for both POST and PATCH transaction schemas to state
that the ID must be returned by GET /api/v1/merchants and may refer to provider
merchants, rather than requiring membership in the caller’s family. Apply the
matching description changes in spec/requests/api/v1/transactions_spec.rb lines
174-175 and 316-317, and docs/api/openapi.yaml lines 7358-7362 and 7478-7482.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@spec/requests/api/v1/transactions_spec.rb`:
- Around line 174-175: Update the merchant_id descriptions for both POST and
PATCH transaction schemas to state that the ID must be returned by GET
/api/v1/merchants and may refer to provider merchants, rather than requiring
membership in the caller’s family. Apply the matching description changes in
spec/requests/api/v1/transactions_spec.rb lines 174-175 and 316-317, and
docs/api/openapi.yaml lines 7358-7362 and 7478-7482.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f04a7fa9-f538-41a3-a2fb-0552ad0eaf1e

📥 Commits

Reviewing files that changed from the base of the PR and between 4912ada and 84f1df8.

📒 Files selected for processing (6)
  • app/controllers/api/v1/merchants_controller.rb
  • app/controllers/api/v1/transactions_controller.rb
  • app/models/family.rb
  • docs/api/openapi.yaml
  • spec/requests/api/v1/transactions_spec.rb
  • test/controllers/api/v1/transactions_controller_test.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/controllers/api/v1/transactions_controller.rb

84f1df8 widened transaction create/update to accept the same provider
merchants GET /api/v1/merchants surfaces, but the merchant_id
description still said it had to belong to the caller's family. Word
it the way the validation actually behaves.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@erkdgn

erkdgn commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Confirmed and fixed in 751799a. The merchant_id description said must be in your own family, which stopped being accurate the moment 84f1df8 widened validation to also accept provider merchants. Updated it on both POST and PATCH to describe the actual rule: any merchant returned by GET /api/v1/merchants, whether family-owned or a provider-synced merchant already linked to one of your transactions. Regenerated docs/api/openapi.yaml accordingly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
spec/requests/api/v1/transactions_spec.rb (1)

174-179: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Broaden the POST 422 response documentation.

These field descriptions correctly add family-scoped association requirements, but the POST response documentation still identifies 422 only as a missing-required-fields error. Include invalid, unknown, and cross-family association IDs, then regenerate docs/api/openapi.yaml.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/requests/api/v1/transactions_spec.rb` around lines 174 - 179, Update the
POST transaction 422 response documentation in the surrounding request schema to
describe missing required fields plus invalid, unknown, and cross-family
association IDs, including category, merchant, and tag references. Then
regenerate docs/api/openapi.yaml so the committed OpenAPI specification matches
the updated documentation.
docs/api/openapi.yaml (1)

7356-7386: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Document POST association-validation failures in the 422 response.

The request fields now correctly state family-scoped IDs, but the POST response still describes 422 only as “missing required fields.” The endpoint also returns 422 for malformed, unknown, and cross-family category, merchant, and tag IDs. Broaden the response description in the request spec and regenerate this document.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/api/openapi.yaml` around lines 7356 - 7386, Broaden the POST endpoint’s
422 response description from missing required fields to include malformed,
unknown, and cross-family category, merchant, and tag ID association-validation
failures. Update the request specification source for this endpoint, then
regenerate docs/api/openapi.yaml so the generated document reflects the expanded
validation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@docs/api/openapi.yaml`:
- Around line 7356-7386: Broaden the POST endpoint’s 422 response description
from missing required fields to include malformed, unknown, and cross-family
category, merchant, and tag ID association-validation failures. Update the
request specification source for this endpoint, then regenerate
docs/api/openapi.yaml so the generated document reflects the expanded validation
behavior.

In `@spec/requests/api/v1/transactions_spec.rb`:
- Around line 174-179: Update the POST transaction 422 response documentation in
the surrounding request schema to describe missing required fields plus invalid,
unknown, and cross-family association IDs, including category, merchant, and tag
references. Then regenerate docs/api/openapi.yaml so the committed OpenAPI
specification matches the updated documentation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5df70b3c-fa8b-4603-80e5-c5214ed08dd9

📥 Commits

Reviewing files that changed from the base of the PR and between 84f1df8 and 751799a.

📒 Files selected for processing (2)
  • docs/api/openapi.yaml
  • spec/requests/api/v1/transactions_spec.rb

751799a fixed the merchant_id wording but missed that the POST 422
documentation never covered the family-scoping validation added in
4912ada -- only the PATCH operation had it. The two existing POST 422
examples ("missing account_id", "missing required fields") don't
demonstrate an invalid category_id, so the generated doc undersold
what the endpoint actually rejects with.

rswag's rake task runs with --order defined (lib in rswag-specs'
swaggerize.rake), so when multiple response '422' blocks exist on the
same operation, only the last-declared one's description survives the
merge -- there is no way to represent multiple distinct 422 causes as
separate entries. Rather than depend on which one happens to be
declared last, gave all three POST 422 examples on this operation the
same description covering every case, and added a third example that
actually exercises an invalid category_id so the scenario has
coverage, matching the PATCH operation's existing example.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@erkdgn

erkdgn commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Both outside-diff findings from the latest review (on 751799a) confirmed and fixed in fdb4178.

The POST 422 documentation only ever showed "missing account_id" or "missing required fields" — the association-validation failures added in 4912ada were never represented for POST, only for PATCH.

While fixing it I found the underlying reason this kept happening: rswag's swaggerize rake task runs with --order defined (see rswag-specs' own rake task), so when an operation has multiple response '422', ... blocks, only the last-declared one's description survives the merge into the generated doc — there's no way to represent two distinct 422 causes as two separate entries in OpenAPI, they collide on the same status-code key. POST already had two such blocks before this PR: whichever was declared last ("missing required fields") is what showed up, silently dropping the other's meaning, and my new association-validation case would have suffered the same fate as a third one.

Fixed by giving all three POST 422 examples the same comprehensive description, and adding a third example that actually exercises an invalid category_id so the case has real test coverage, matching what PATCH already had.

@jjmata jjmata left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid fix for a real correctness/security gap — the three failure modes in the PR description (malformed UUID silently dropped, unknown UUID → 500, cross-family id accepted) all check out against the diff, and transactions_controller.rb was indeed the outlier vs. trades_controller.rb/recurring_transactions_controller.rb, which already scope by family.

A few things I liked:

  • Extracting Family#api_assignable_merchants_for out of the merchants controller and reusing it for merchant ownership checks keeps the "assignable to this family" definition in one place instead of duplicating the provider-merchant logic.
  • validate_tag_ids reading from the raw params[:transaction][:tag_ids] rather than the permitted params, with the comment explaining why (permit(tag_ids: []) would silently coerce a bad value to nil and wipe existing tags on update) — that's a subtle trap and worth documenting the way it's documented.
  • Test coverage is thorough: malformed/unknown/cross-family × category/merchant/tag × create/update, plus the tag-array-vs-scalar edge case and the empty-array-clears-tags case.

Only note (already called out in the PR description, not a blocker): the GET /api/v1/transactions?category_id=... index filter still silently returns an empty set for an unusable id rather than a 4xx — different code path, fine to leave for a follow-up.


Generated by Claude Code

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.

API Transaction Category not working

2 participants