Skip to content

feat(transactions): fix create mutation, add shouldUpdateBalance - #21

Merged
eshaffer321 merged 3 commits into
eshaffer321:mainfrom
brewgator:pr/transaction-create
May 22, 2026
Merged

eshaffer321 merged 3 commits into
eshaffer321:mainfrom
brewgator:pr/transaction-create

Conversation

@brewgator

Copy link
Copy Markdown
Contributor

Summary

  • Use merchantName field instead of merchant object in create mutation (matches Monarch's actual API schema)
  • Make categoryId optional — only send when non-empty
  • Add ShouldUpdateBalance param for manual account balance control
  • Round amount to 2 decimal places before sending
  • Parse fieldErrors from mutation response for better error messages
  • Skip redundant getTransaction call after create — the previous implementation called s.Get() after a successful create, which was failing with "Something went wrong while processing: ['getTransaction']". Now returns the ID directly from the create response.
  • Update create.graphql to match Monarch's actual mutation schema (Common_CreateTransactionMutation)

Split from #19 per review feedback.

Test plan

  • Existing tests pass (go test ./...)
  • Transaction creation works with merchantName
  • Transaction creation works without categoryId
  • ShouldUpdateBalance: false prevents manual account balance changes

@eshaffer321

eshaffer321 commented May 21, 2026 •

Copy link
Copy Markdown
Owner

I tried doing

client, _ := monarch.NewClientWithToken(os.Getenv("MONARCH_TOKEN"))

shouldUpdateBalance := false
txn, err := client.Transactions.Create(context.Background(), &monarch.CreateTransactionParams{
    Date: monarch.Date{Time: time.Now().UTC()},
    AccountID: "a-real-account-id-from-Accounts.List",
    Amount: -0.01,
    Merchant: &monarch.Merchant{Name: "Codex PR21 Smoke Test"},
    Notes: "Temporary PR #21 smoke test",
    ShouldUpdateBalance: &shouldUpdateBalance,
})

fmt.Printf("txn=%#v err=%v\n", txn, err)

Result:

txn=<nil> err=failed to create transaction: error: BAD_REQUEST

With a response-body capture on the HTTP client, Monarch returned:

{"errors":[{"message":"Something went wrong while processing: None on request_id: None.","locations":[{"line":1,"column":43}]}]}

I also tried the raw GraphQL mutation directly with operationName included, to rule out this Go client wrapper:

curl https://api.monarch.com/graphql \
  -H "Authorization: Token $MONARCH_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Client-Platform: web" \
  -H "Origin: https://app.monarchmoney.com" \
  --data '{
    "operationName":"Common_CreateTransactionMutation",
    "query":"mutation Common_CreateTransactionMutation($input: CreateTransactionMutationInput!) { createTransaction(input: $input) { transaction { id } errors { message code } } }",
    "variables":{
      "input":{
        "date":"2026-05-20",
        "accountId":"real-account-id",
        "amount":-0.01,
        "merchantName":"Codex PR21 Smoke Test",
        "notes":"Temporary smoke test",
        "shouldUpdateBalance":false
      }
    }
  }'

That produced the same HTTP 400 style error. I also tried updateBalance:false instead of shouldUpdateBalance:false. Do you have a working example I can try?

@brewgator

Copy link
Copy Markdown
Contributor Author

The issue is that categoryId is required by the Monarch API. The Python client treats it as a mandatory positional argument — it's always sent in the input.

Your test and the curl example both omit categoryId, which causes the generic "Something went wrong while processing" error from Monarch's backend.

To fix: pass a valid categoryId. You can get one from client.Transactions.Categories.List() — the "Uncategorized" category works as a catch-all. For example:

categories, _ := client.Transactions.Categories.List(context.Background())
// Find "Uncategorized" or use any valid category ID

shouldUpdateBalance := false
txn, err := client.Transactions.Create(context.Background(), &monarch.CreateTransactionParams{
    Date:                monarch.Date{Time: time.Now().UTC()},
    AccountID:           "a-real-account-id",
    Amount:              -0.01,
    Merchant:            &monarch.Merchant{Name: "Codex PR21 Smoke Test"},
    CategoryID:          uncategorizedID, // required by Monarch
    Notes:               "Temporary PR #21 smoke test",
    ShouldUpdateBalance: &shouldUpdateBalance,
})

I'll update the code to always send categoryId (even when empty, defaulting to the value the caller provides) and add a doc comment noting it's required. Alternatively, we could make the Go client error early if CategoryID is empty. What's your preference?

@eshaffer321

Copy link
Copy Markdown
Owner

The issue is that categoryId is required by the Monarch API. The Python client treats it as a mandatory positional argument — it's always sent in the input.

Your test and the curl example both omit categoryId, which causes the generic "Something went wrong while processing" error from Monarch's backend.

To fix: pass a valid categoryId. You can get one from client.Transactions.Categories.List() — the "Uncategorized" category works as a catch-all. For example:

categories, _ := client.Transactions.Categories.List(context.Background())
// Find "Uncategorized" or use any valid category ID

shouldUpdateBalance := false
txn, err := client.Transactions.Create(context.Background(), &monarch.CreateTransactionParams{
    Date:                monarch.Date{Time: time.Now().UTC()},
    AccountID:           "a-real-account-id",
    Amount:              -0.01,
    Merchant:            &monarch.Merchant{Name: "Codex PR21 Smoke Test"},
    CategoryID:          uncategorizedID, // required by Monarch
    Notes:               "Temporary PR #21 smoke test",
    ShouldUpdateBalance: &shouldUpdateBalance,
})

I'll update the code to always send categoryId (even when empty, defaulting to the value the caller provides) and add a doc comment noting it's required. Alternatively, we could make the Go client error early if CategoryID is empty. What's your preference?

Let's make it required and error early if its empty

@codecov

codecov Bot commented May 21, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 48.86%. Comparing base (ba1d9e7) to head (2e393a3).

❌ Your project check has failed because the head coverage (48.86%) is below the target coverage (70.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #21      +/-   ##
==========================================
+ Coverage   48.35%   48.86%   +0.51%     
==========================================
  Files          17       17              
  Lines        2031     2032       +1     
==========================================
+ Hits          982      993      +11     
+ Misses        959      953       -6     
+ Partials       90       86       -4     
Flag Coverage Δ
unittests 48.86% <100.00%> (+0.51%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
pkg/monarch/transactions.go 76.05% <100.00%> (+2.55%) ⬆️

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ba1d9e7...2e393a3. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

brewgator added 3 commits May 21, 2026 18:44
- Use merchantName field instead of merchant object (matches Monarch API)
- Make categoryId optional (only send when non-empty)
- Add ShouldUpdateBalance param for manual account balance control
- Round amount to 2 decimal places before sending
- Parse fieldErrors from mutation response for better error messages
- Skip redundant getTransaction call after create (return ID directly)
- Update create.graphql to match Monarch's actual mutation schema
- Test amount rounding to 2 decimal places
- Test ShouldUpdateBalance parameter
- Test create without merchant or categoryId (optional fields)
- Test fieldErrors parsing in error response
- Test null transaction response handling
- Test GraphQL execution error propagation
Per maintainer feedback: CategoryID is required by the Monarch API.
Create now validates upfront and returns a clear error instead of
letting the API return a generic "Something went wrong" response.
@brewgator
brewgator force-pushed the pr/transaction-create branch from 9204269 to 2e393a3 Compare May 21, 2026 22:44
@eshaffer321
eshaffer321 merged commit 43606d7 into eshaffer321:main May 22, 2026
12 of 13 checks passed
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.

2 participants