Skip to main content
Glama
months.js5.72 kB
/** * YNAB Month API operations * Handles budget month listing and retrieval */ const { API } = require('ynab'); const { logger } = require('../utils/logger'); const { tokenManager } = require('../auth/tokenManager'); const { ValidationError, NotFoundError } = require('../utils/errorHandler'); const { rateLimiter } = require('../utils/rateLimit'); /** * List all months in a budget * @param {object} params - Parameters with email and budgetId * @returns {Promise<object>} List of months */ async function listMonths(params) { if (!params.email) { throw new ValidationError('Email parameter is required'); } if (!params.budgetId) { throw new ValidationError('Budget ID parameter is required'); } // Get fresh access token const accessToken = await tokenManager.getFreshAccessToken(params.email); // Create YNAB API instance const ynabAPI = new API(accessToken); // Use rate limiter to handle YNAB API limits return await rateLimiter.executeWithRateLimit(params.email, async () => { logger.info(`Listing months for budget ${params.budgetId} for ${params.email}`); try { const response = await ynabAPI.months.getBudgetMonths(params.budgetId); const months = response.data.months; // Format the response return { months: months.map(month => ({ month: month.month, note: month.note, income: month.income, income_formatted: formatCurrency(month.income), budgeted: month.budgeted, budgeted_formatted: formatCurrency(month.budgeted), activity: month.activity, activity_formatted: formatCurrency(month.activity), to_be_budgeted: month.to_be_budgeted, to_be_budgeted_formatted: formatCurrency(month.to_be_budgeted), age_of_money: month.age_of_money })), server_knowledge: response.data.server_knowledge }; } catch (error) { // Check for 404 error if (error.error && error.error.id === '404') { throw new NotFoundError(`Budget with ID ${params.budgetId} not found`); } // Re-throw other errors throw error; } }); } /** * Get details of a specific budget month * @param {object} params - Parameters with email, budgetId, and month * @returns {Promise<object>} Month details with category groups */ async function getMonth(params) { if (!params.email) { throw new ValidationError('Email parameter is required'); } if (!params.budgetId) { throw new ValidationError('Budget ID parameter is required'); } if (!params.month) { throw new ValidationError('Month parameter is required (format: YYYY-MM)'); } // Validate month format (YYYY-MM) if (!/^\d{4}-\d{2}$/.test(params.month)) { throw new ValidationError('Month must be in format YYYY-MM (e.g., 2025-04)'); } // Get fresh access token const accessToken = await tokenManager.getFreshAccessToken(params.email); // Create YNAB API instance const ynabAPI = new API(accessToken); // Use rate limiter to handle YNAB API limits return await rateLimiter.executeWithRateLimit(params.email, async () => { logger.info(`Getting month ${params.month} for budget ${params.budgetId}`); try { // Format month for YNAB API: YYYY-MM becomes YYYY-MM-01 const monthFormatted = `${params.month}-01`; const response = await ynabAPI.months.getBudgetMonth( params.budgetId, monthFormatted ); const month = response.data.month; // Format the month data return { month: month.month, note: month.note, income: month.income, income_formatted: formatCurrency(month.income), budgeted: month.budgeted, budgeted_formatted: formatCurrency(month.budgeted), activity: month.activity, activity_formatted: formatCurrency(month.activity), to_be_budgeted: month.to_be_budgeted, to_be_budgeted_formatted: formatCurrency(month.to_be_budgeted), age_of_money: month.age_of_money, categories: month.categories ? month.categories.map(category => ({ id: category.id, name: category.name, hidden: category.hidden, category_group_id: category.category_group_id, budgeted: category.budgeted, budgeted_formatted: formatCurrency(category.budgeted), activity: category.activity, activity_formatted: formatCurrency(category.activity), balance: category.balance, balance_formatted: formatCurrency(category.balance), goal_type: category.goal_type, goal_target: category.goal_target, goal_target_month: category.goal_target_month, goal_percentage_complete: category.goal_percentage_complete })) : [] }; } catch (error) { // Check for 404 error if (error.error && error.error.id === '404') { throw new NotFoundError( `Month ${params.month} not found in budget ${params.budgetId}` ); } // Re-throw other errors throw error; } }); } /** * Format currency amount (in milliunits) to a readable string * @param {number} amountInMilliunits - Amount in milliunits * @returns {string} Formatted currency string */ function formatCurrency(amountInMilliunits) { const amount = amountInMilliunits / 1000; return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2 }).format(amount); } module.exports = { listMonths, getMonth };

Latest Blog Posts

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/mattweg/ynab-mcp'

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