Skip to main content
Glama

YNAB Assistant

by franccesco
transactions.py26.5 kB
"""Transaction management tools for YNAB API.""" from typing import Annotated, Any import ynab from auth import get_api_client, get_api_configuration def get_transaction_by_id( transaction_id: Annotated[ str, "The ID of the transaction to retrieve. Transaction IDs are returned " "from other transaction queries.", ], budget_id: Annotated[ str, "The ID of the budget to query. Use 'last-used' for the most recently " "accessed budget, or provide a specific budget ID.", ] = "last-used", ) -> dict[str, Any]: """Get a single transaction by its ID. Retrieves detailed information for a specific transaction including date, amount, payee, category, memo, cleared status, and other transaction metadata. Args: transaction_id: The ID of the transaction to retrieve. Transaction IDs are returned from other transaction queries. budget_id: The ID of the budget to query. Use 'last-used' for the most recently accessed budget, or provide a specific budget ID. Returns: dict[str, Any]: Complete transaction data including all fields and metadata. Raises: Exception: If the API call fails or transaction not found. """ configuration = get_api_configuration() with get_api_client(configuration) as api_client: api_instance = ynab.TransactionsApi(api_client) try: api_response = api_instance.get_transaction_by_id(budget_id, transaction_id) transaction = api_response.data.transaction # type: ignore[attr-defined] return transaction.model_dump() # type: ignore[attr-defined] except Exception as e: msg = f"Error fetching transaction: {e!s}" raise Exception(msg) from e def get_transactions( budget_id: Annotated[ str, "The ID of the budget to query. Use 'last-used' for the most recently " "accessed budget, or provide a specific budget ID.", ] = "last-used", since_date: Annotated[ str | None, "Only include transactions on or after this date in ISO format " "YYYY-MM-DD (e.g., '2024-01-01'). Use None to retrieve all transactions.", ] = None, ) -> dict[str, Any]: """Get all transactions for a budget. Retrieves all transactions for the specified budget, optionally filtered by date. This returns transactions across all accounts and categories. For more targeted queries, use the account, category, month, or payee-specific transaction tools. Args: budget_id: The ID of the budget to query. Use 'last-used' for the most recently accessed budget, or provide a specific budget ID. since_date: Only include transactions on or after this date in ISO format YYYY-MM-DD (e.g., '2024-01-01'). Use None to retrieve all transactions. Returns: dict[str, Any]: List of all transactions with server knowledge for efficient delta synchronization. Raises: Exception: If the API call fails. """ configuration = get_api_configuration() with get_api_client(configuration) as api_client: api_instance = ynab.TransactionsApi(api_client) try: api_response = api_instance.get_transactions( budget_id, since_date=since_date # type: ignore[arg-type] ) return api_response.data.model_dump() # type: ignore[attr-defined] except Exception as e: msg = f"Error fetching transactions: {e!s}" raise Exception(msg) from e def get_transactions_by_account( account_id: Annotated[ str, "The ID of the account to query. Use get_accounts to discover available " "account IDs.", ], budget_id: Annotated[ str, "The ID of the budget to query. Use 'last-used' for the most recently " "accessed budget, or provide a specific budget ID.", ] = "last-used", since_date: Annotated[ str | None, "Only include transactions on or after this date in ISO format " "YYYY-MM-DD (e.g., '2024-01-01'). Use None to retrieve all transactions.", ] = None, ) -> dict[str, Any]: """Get all transactions for a specific account. Retrieves transactions filtered by a specific account, optionally limited by date. If you don't know the account ID, use get_accounts first to list all accounts. Args: account_id: The ID of the account to query. Use get_accounts to discover available account IDs. budget_id: The ID of the budget to query. Use 'last-used' for the most recently accessed budget, or provide a specific budget ID. since_date: Only include transactions on or after this date in ISO format YYYY-MM-DD (e.g., '2024-01-01'). Use None to retrieve all transactions. Returns: dict[str, Any]: List of transactions for the specified account. Raises: Exception: If the API call fails. """ configuration = get_api_configuration() with get_api_client(configuration) as api_client: api_instance = ynab.TransactionsApi(api_client) try: api_response = api_instance.get_transactions_by_account( budget_id, account_id, since_date=since_date # type: ignore[arg-type] ) return api_response.data.model_dump() # type: ignore[attr-defined] except Exception as e: msg = f"Error fetching account transactions: {e!s}" raise Exception(msg) from e def get_transactions_by_category( category_id: Annotated[ str, "The ID of the category to query. Use get_categories to discover " "available category IDs.", ], budget_id: Annotated[ str, "The ID of the budget to query. Use 'last-used' for the most recently " "accessed budget, or provide a specific budget ID.", ] = "last-used", since_date: Annotated[ str | None, "Only include transactions on or after this date in ISO format " "YYYY-MM-DD (e.g., '2024-01-01'). Use None to retrieve all transactions.", ] = None, ) -> dict[str, Any]: """Get all transactions for a specific category. Retrieves transactions filtered by a specific category, optionally limited by date. If you don't know the category ID, use get_categories first to list all categories. Args: category_id: The ID of the category to query. Use get_categories to discover available category IDs. budget_id: The ID of the budget to query. Use 'last-used' for the most recently accessed budget, or provide a specific budget ID. since_date: Only include transactions on or after this date in ISO format YYYY-MM-DD (e.g., '2024-01-01'). Use None to retrieve all transactions. Returns: dict[str, Any]: List of transactions for the specified category. Raises: Exception: If the API call fails. """ configuration = get_api_configuration() with get_api_client(configuration) as api_client: api_instance = ynab.TransactionsApi(api_client) try: api_response = api_instance.get_transactions_by_category( budget_id, category_id, since_date=since_date # type: ignore[arg-type] ) return api_response.data.model_dump() # type: ignore[attr-defined] except Exception as e: msg = f"Error fetching category transactions: {e!s}" raise Exception(msg) from e def get_transactions_by_month( month: Annotated[ str, "The budget month in ISO format YYYY-MM-DD (e.g., '2024-12-01') or " "'current' for the current month.", ], budget_id: Annotated[ str, "The ID of the budget to query. Use 'last-used' for the most recently " "accessed budget, or provide a specific budget ID.", ] = "last-used", ) -> dict[str, Any]: """Get all transactions for a specific month. Retrieves all transactions that occurred within the specified budget month, across all accounts and categories. This is useful for monthly financial analysis and reporting. Args: month: The budget month in ISO format YYYY-MM-DD (e.g., '2024-12-01') or 'current' for the current month. budget_id: The ID of the budget to query. Use 'last-used' for the most recently accessed budget, or provide a specific budget ID. Returns: dict[str, Any]: List of all transactions for the specified month. Raises: Exception: If the API call fails. """ configuration = get_api_configuration() with get_api_client(configuration) as api_client: api_instance = ynab.TransactionsApi(api_client) try: api_response = api_instance.get_transactions_by_month(budget_id, month) return api_response.data.model_dump() # type: ignore[attr-defined] except Exception as e: msg = f"Error fetching month transactions: {e!s}" raise Exception(msg) from e def get_transactions_by_payee( payee_id: Annotated[ str, "The ID of the payee to query. Payee IDs can be discovered from " "transaction data or payee-related queries.", ], budget_id: Annotated[ str, "The ID of the budget to query. Use 'last-used' for the most recently " "accessed budget, or provide a specific budget ID.", ] = "last-used", since_date: Annotated[ str | None, "Only include transactions on or after this date in ISO format " "YYYY-MM-DD (e.g., '2024-01-01'). Use None to retrieve all transactions.", ] = None, ) -> dict[str, Any]: """Get all transactions for a specific payee. Retrieves transactions filtered by a specific payee, optionally limited by date. This is useful for analyzing spending patterns with particular vendors or merchants. Args: payee_id: The ID of the payee to query. Payee IDs can be discovered from transaction data or payee-related queries. budget_id: The ID of the budget to query. Use 'last-used' for the most recently accessed budget, or provide a specific budget ID. since_date: Only include transactions on or after this date in ISO format YYYY-MM-DD (e.g., '2024-01-01'). Use None to retrieve all transactions. Returns: dict[str, Any]: List of transactions for the specified payee. Raises: Exception: If the API call fails. """ configuration = get_api_configuration() with get_api_client(configuration) as api_client: api_instance = ynab.TransactionsApi(api_client) try: api_response = api_instance.get_transactions_by_payee( budget_id, payee_id, since_date=since_date # type: ignore[arg-type] ) return api_response.data.model_dump() # type: ignore[attr-defined] except Exception as e: msg = f"Error fetching payee transactions: {e!s}" raise Exception(msg) from e def create_transaction( account_id: Annotated[ str, "The ID of the account for this transaction. Use get_accounts to " "discover available account IDs.", ], date: Annotated[ str, "Transaction date in ISO format YYYY-MM-DD (e.g., '2024-12-01').", ], amount: Annotated[ int, "Transaction amount in milliunits. Negative for outflows (spending), " "positive for inflows (income).", ], budget_id: Annotated[ str, "The ID of the budget to query. Use 'last-used' for the most recently " "accessed budget, or provide a specific budget ID.", ] = "last-used", payee_id: Annotated[ str | None, "The ID of the payee for this transaction. Leave as None if not applicable.", ] = None, category_id: Annotated[ str | None, "The ID of the category for this transaction. Use get_categories to " "discover category IDs. Leave as None if uncategorized.", ] = None, memo: Annotated[ str | None, "Optional memo/note for this transaction. Leave as None for no memo.", ] = None, cleared: Annotated[ str, "Cleared status: 'cleared', 'uncleared', or 'reconciled'. Defaults to " "'uncleared'.", ] = "uncleared", approved: Annotated[ bool, "Whether the transaction is approved. Unapproved transactions may appear " "differently in YNAB.", ] = False, ) -> dict[str, Any]: """Create a new transaction in YNAB. Amounts in YNAB are expressed in milliunits: 1000 milliunits = $1.00. For example, to record spending $50.00, use -50000 (negative for outflows). To record income of $125.00, use 125000 (positive for inflows). Args: account_id: The ID of the account for this transaction. Use get_accounts to discover available account IDs. date: Transaction date in ISO format YYYY-MM-DD (e.g., '2024-12-01'). amount: Transaction amount in milliunits. Negative for outflows (spending), positive for inflows (income). budget_id: The ID of the budget to query. Use 'last-used' for the most recently accessed budget, or provide a specific budget ID. payee_id: The ID of the payee for this transaction. Leave as None if not applicable. category_id: The ID of the category for this transaction. Use get_categories to discover category IDs. Leave as None if uncategorized. memo: Optional memo/note for this transaction. Leave as None for no memo. cleared: Cleared status: 'cleared', 'uncleared', or 'reconciled'. Defaults to 'uncleared'. approved: Whether the transaction is approved. Unapproved transactions may appear differently in YNAB. Returns: dict[str, Any]: Created transaction data with assigned transaction ID and all field values. Raises: Exception: If the API call fails. """ configuration = get_api_configuration() with get_api_client(configuration) as api_client: api_instance = ynab.TransactionsApi(api_client) try: # Build transaction data transaction_data = { "account_id": account_id, "date": date, "amount": amount, "cleared": cleared, "approved": approved, } # Add optional fields if provided if payee_id: transaction_data["payee_id"] = payee_id if category_id: transaction_data["category_id"] = category_id if memo: transaction_data["memo"] = memo # Create wrapper with transaction wrapper = ynab.PostTransactionsWrapper(transaction=transaction_data) # type: ignore[arg-type] api_response = api_instance.create_transaction(budget_id, wrapper) return api_response.data.model_dump() # type: ignore[attr-defined] except Exception as e: msg = f"Error creating transaction: {e!s}" raise Exception(msg) from e def create_transactions( transactions: Annotated[ list[dict[str, Any]], "List of transaction dictionaries. Each must have account_id, date (ISO " "format YYYY-MM-DD), and amount (in milliunits). Optional fields: " "payee_id, payee_name, category_id, memo, cleared, approved, flag_color.", ], budget_id: Annotated[ str, "The ID of the budget to query. Use 'last-used' for the most recently " "accessed budget, or provide a specific budget ID.", ] = "last-used", ) -> dict[str, Any]: """Create multiple transactions in a single API call (bulk import). Amounts in YNAB are expressed in milliunits: 1000 milliunits = $1.00. For example, to record spending $50.00, use -50000 (negative for outflows). To record income of $125.00, use 125000 (positive for inflows). This is more efficient than creating transactions one at a time when you need to import multiple transactions. Args: transactions: List of transaction dictionaries. Each must have account_id, date (ISO format YYYY-MM-DD), and amount (in milliunits). Optional fields: payee_id, payee_name, category_id, memo, cleared, approved, flag_color. budget_id: The ID of the budget to query. Use 'last-used' for the most recently accessed budget, or provide a specific budget ID. Returns: dict[str, Any]: Created transactions data including the list of created transactions, any duplicate_import_ids encountered, and server_knowledge for synchronization. Raises: Exception: If the API call fails. """ configuration = get_api_configuration() with get_api_client(configuration) as api_client: api_instance = ynab.TransactionsApi(api_client) try: # Create wrapper with transactions list wrapper = ynab.PostTransactionsWrapper(transactions=transactions) # type: ignore[arg-type] api_response = api_instance.create_transaction(budget_id, wrapper) return api_response.data.model_dump() # type: ignore[attr-defined] except Exception as e: msg = f"Error creating transactions: {e!s}" raise Exception(msg) from e def update_transaction( transaction_id: Annotated[ str, "The ID of the transaction to update. Transaction IDs are returned from " "other transaction queries.", ], budget_id: Annotated[ str, "The ID of the budget to query. Use 'last-used' for the most recently " "accessed budget, or provide a specific budget ID.", ] = "last-used", account_id: Annotated[ str | None, "The ID of the account to move this transaction to. Leave as None to " "keep unchanged.", ] = None, date: Annotated[ str | None, "Transaction date in ISO format YYYY-MM-DD (e.g., '2024-12-01'). Leave " "as None to keep unchanged.", ] = None, amount: Annotated[ int | None, "Transaction amount in milliunits. Leave as None to keep unchanged.", ] = None, payee_id: Annotated[ str | None, "The ID of the payee. Leave as None to keep unchanged.", ] = None, category_id: Annotated[ str | None, "The ID of the category. Leave as None to keep unchanged.", ] = None, memo: Annotated[ str | None, "Transaction memo/note. Leave as None to keep unchanged.", ] = None, cleared: Annotated[ str | None, "Cleared status: 'cleared', 'uncleared', or 'reconciled'. Leave as None " "to keep unchanged.", ] = None, approved: Annotated[ bool | None, "Whether the transaction is approved. Leave as None to keep unchanged.", ] = None, ) -> dict[str, Any]: """Update an existing transaction. Amounts in YNAB are expressed in milliunits: 1000 milliunits = $1.00. Only provide the fields you want to update; fields left as None will remain unchanged. For example, to only update the amount, provide transaction_id and amount, leaving all other fields as None. Args: transaction_id: The ID of the transaction to update. Transaction IDs are returned from other transaction queries. budget_id: The ID of the budget to query. Use 'last-used' for the most recently accessed budget, or provide a specific budget ID. account_id: The ID of the account to move this transaction to. Leave as None to keep unchanged. date: Transaction date in ISO format YYYY-MM-DD (e.g., '2024-12-01'). Leave as None to keep unchanged. amount: Transaction amount in milliunits. Leave as None to keep unchanged. payee_id: The ID of the payee. Leave as None to keep unchanged. category_id: The ID of the category. Leave as None to keep unchanged. memo: Transaction memo/note. Leave as None to keep unchanged. cleared: Cleared status: 'cleared', 'uncleared', or 'reconciled'. Leave as None to keep unchanged. approved: Whether the transaction is approved. Leave as None to keep unchanged. Returns: dict[str, Any]: Updated transaction data with all current field values. Raises: Exception: If the API call fails. """ configuration = get_api_configuration() with get_api_client(configuration) as api_client: api_instance = ynab.TransactionsApi(api_client) try: # Build transaction data with only provided fields transaction_data: dict[str, Any] = {} if account_id is not None: transaction_data["account_id"] = account_id if date is not None: transaction_data["date"] = date if amount is not None: transaction_data["amount"] = amount if payee_id is not None: transaction_data["payee_id"] = payee_id if category_id is not None: transaction_data["category_id"] = category_id if memo is not None: transaction_data["memo"] = memo if cleared is not None: transaction_data["cleared"] = cleared if approved is not None: transaction_data["approved"] = approved # Create wrapper wrapper = ynab.PutTransactionWrapper(transaction=transaction_data) # type: ignore[arg-type] api_response = api_instance.update_transaction( budget_id, transaction_id, wrapper ) return api_response.data.model_dump() # type: ignore[attr-defined] except Exception as e: msg = f"Error updating transaction: {e!s}" raise Exception(msg) from e def update_transactions( transactions: Annotated[ list[dict[str, Any]], "List of transaction dictionaries. Each must have 'id' and the fields to " "update (e.g., amount, date, category_id, memo, cleared, approved). Only " "include fields you want to change.", ], budget_id: Annotated[ str, "The ID of the budget to query. Use 'last-used' for the most recently " "accessed budget, or provide a specific budget ID.", ] = "last-used", ) -> dict[str, Any]: """Update multiple transactions in a single API call (bulk update). Amounts in YNAB are expressed in milliunits: 1000 milliunits = $1.00. This is more efficient than updating transactions one at a time when you need to modify multiple transactions. Args: transactions: List of transaction dictionaries. Each must have 'id' and the fields to update (e.g., amount, date, category_id, memo, cleared, approved). Only include fields you want to change. budget_id: The ID of the budget to query. Use 'last-used' for the most recently accessed budget, or provide a specific budget ID. Returns: dict[str, Any]: Updated transactions data including all modified transactions with their current values. Raises: Exception: If the API call fails. """ configuration = get_api_configuration() with get_api_client(configuration) as api_client: api_instance = ynab.TransactionsApi(api_client) try: # Create wrapper with transactions list wrapper = ynab.PatchTransactionsWrapper(transactions=transactions) # type: ignore[arg-type] api_response = api_instance.update_transactions(budget_id, wrapper) # Check if response is None (defensive check despite type annotation) if api_response is None: # type: ignore[reportUnnecessaryComparison] msg = "API returned None response" raise Exception(msg) # Check if response has data attribute if not hasattr(api_response, "data"): msg = ( f"API response missing data attribute. " f"Response type: {type(api_response)}, " f"Response: {api_response}" ) raise Exception(msg) return api_response.data.model_dump() # type: ignore[attr-defined] except Exception as e: msg = f"Error updating transactions: {e!s}" raise Exception(msg) from e def delete_transaction( transaction_id: Annotated[ str, "The ID of the transaction to delete. Transaction IDs are returned from " "other transaction queries.", ], budget_id: Annotated[ str, "The ID of the budget to query. Use 'last-used' for the most recently " "accessed budget, or provide a specific budget ID.", ] = "last-used", ) -> dict[str, Any]: """Delete a transaction (destructive operation). This permanently removes a transaction from YNAB. This operation cannot be undone via the API. Use with caution. Args: transaction_id: The ID of the transaction to delete. Transaction IDs are returned from other transaction queries. budget_id: The ID of the budget to query. Use 'last-used' for the most recently accessed budget, or provide a specific budget ID. Returns: dict[str, Any]: Deleted transaction data showing the transaction that was removed. Raises: Exception: If the API call fails or transaction not found. """ configuration = get_api_configuration() with get_api_client(configuration) as api_client: api_instance = ynab.TransactionsApi(api_client) try: api_response = api_instance.delete_transaction(budget_id, transaction_id) transaction = api_response.data.transaction # type: ignore[attr-defined] return transaction.model_dump() # type: ignore[attr-defined] except Exception as e: msg = f"Error deleting transaction: {e!s}" raise Exception(msg) from e

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/franccesco/ynab-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server