--- name: miro-cost-tuning description: 'Optimize Miro API costs through credit monitoring, request reduction, and plan selection based on the credit-based rate limiting model. Trigger with phrases like "miro cost", "miro billing", "reduce miro costs", "miro pricing", "miro credits usage". ' allowed-tools: Read, Grep version: 1.0.0 license: MIT author: Jeremy Longshore tags: - saas - miro - cost-optimization - billing compatibility: Designed for Claude Code --- # Miro Cost Tuning ## Overview Miro's API pricing is based on your plan tier (Free, Business, Enterprise), not per-API-call billing. However, the **credit-based rate limiting** system (100,000 credits/minute) effectively caps throughput. Cost optimization means minimizing API calls to stay within your plan's rate limits and reduce the need for higher-tier upgrades. ## Miro Plan Comparison | Feature | Free | Business | Enterprise | |---------|------|----------|------------| | Price | $0/user | $12-20/user/mo | Custom | | Boards | 3 editable | Unlimited | Unlimited | | API access | Yes | Yes | Yes | | Rate limit | 100K credits/min | 100K credits/min | Higher (negotiable) | | OAuth scopes | All standard | All standard | All + enterprise scopes | | SCIM provisioning | No | No | Yes | | Audit logs API | No | No | Yes | | SSO/SAML | No | No | Yes | ## Credit Usage Tracking ```typescript class MiroUsageTracker { private minuteCredits = 0; private dailyRequests = 0; private minuteStart = Date.now(); private dailyStart = Date.now(); trackRequest(response: Response): void { // Reset minute window if (Date.now() - this.minuteStart > 60_000) { this.minuteCredits = 0; this.minuteStart = Date.now(); } // Reset daily window if (Date.now() - this.dailyStart > 86_400_000) { this.dailyRequests = 0; this.dailyStart = Date.now(); } const limit = parseInt(response.headers.get('X-RateLimit-Limit') ?? '100000'); const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') ?? '100000'); this.minuteCredits = limit - remaining; this.dailyRequests++; } getReport(): UsageReport { return { currentMinuteCredits: this.minuteCredits, creditUtilizationPercent: Math.round((this.minuteCredits / 100000) * 100), dailyRequests: this.dailyRequests, projectedMonthlyRequests: this.dailyRequests * 30, recommendation: this.getRecommendation(), }; } private getRecommendation(): string { if (this.minuteCredits > 80000) { return 'CRITICAL: >80% credit usage. Reduce request rate or upgrade to Enterprise.'; } if (this.minuteCredits > 50000) { return 'WARNING: >50% credit usage. Consider caching and batching.'; } return 'Healthy credit usage.'; } } ``` ## Cost Reduction Strategies ### Strategy 1: Reduce Read Requests with Caching The biggest cost saver. Most Miro board reads return data that changes infrequently. ```typescript // EXPENSIVE: Fetching board on every page load app.get('/dashboard', async (req, res) => { const board = await miroFetch(`/v2/boards/${boardId}`); // 1 credit per load const items = await miroFetch(`/v2/boards/${boardId}/items`); // 1 credit per load res.render('dashboard', { board, items }); }); // OPTIMIZED: Cache board data for 2 minutes app.get('/dashboard', async (req, res) => { const board = await getCachedBoard(boardId); // Cache hit = 0 credits const items = await getCachedItems(boardId); // Cache hit = 0 credits res.render('dashboard', { board, items }); }); // With webhook-driven invalidation, cache hits can be 95%+ // That is a 20x reduction in API calls ``` ### Strategy 2: Filter Items by Type Don't fetch all items if you only need sticky notes. ```typescript // EXPENSIVE: Fetch all items, filter client-side const allItems = await miroFetch(`/v2/boards/${boardId}/items?limit=50`); const notes = allItems.data.filter(i => i.type === 'sticky_note'); // OPTIMIZED: Server-side type filter const notes = await miroFetch(`/v2/boards/${boardId}/items?type=sticky_note&limit=50`); // Fewer items returned = smaller response = faster ``` ### Strategy 3: Batch Writes with Controlled Concurrency ```typescript // EXPENSIVE: Sequential writes (slow + same credits) for (const note of notes) { await createStickyNote(boardId, note); // 200ms * 50 = 10 seconds } // OPTIMIZED: Parallel with concurrency control (same credits, 5x faster) import PQueue from 'p-queue'; const queue = new PQueue({ concurrency: 5 }); for (const note of notes) { queue.add(() => createStickyNote(boardId, note)); } await queue.onIdle(); // ~2 seconds ``` ### Strategy 4: Use Webhooks Instead of Polling ```typescript // EXPENSIVE: Poll every 10 seconds (8,640 requests/day) setInterval(async () => { const items = await miroFetch(`/v2/boards/${boardId}/items`); detectChanges(items); }, 10_000); // OPTIMIZED: Webhook subscription (0 polling requests) // Miro pushes changes to your endpoint in real-time // See miro-webhooks-events for setup ``` ### Strategy 5: Smart Pagination Limits ```typescript // WASTEFUL: Small page size = more round trips let cursor; do { const page = await miroFetch(`/v2/boards/${boardId}/items?limit=10&cursor=${cursor ?? ''}`); // 10 items per page = 10 requests for 100 items } while (cursor); // OPTIMIZED: Max page size let cursor; do { const page = await miroFetch(`/v2/boards/${boardId}/items?limit=50&cursor=${cursor ?? ''}`); // 50 items per page = 2 requests for 100 items } while (cursor); ``` ## Usage Dashboard Query If you track API calls in a database: ```sql SELECT DATE_TRUNC('hour', created_at) AS hour, endpoint, COUNT(*) AS requests, AVG(duration_ms) AS avg_latency_ms, COUNT(*) FILTER (WHERE status = 429) AS rate_limited FROM miro_api_logs WHERE created_at >= NOW() - INTERVAL '24 hours' GROUP BY 1, 2 ORDER BY requests DESC; ``` ## Budget Alerts ```typescript // Alert when approaching credit limit const tracker = new MiroUsageTracker(); // After each API call tracker.trackRequest(response); const report = tracker.getReport(); if (report.creditUtilizationPercent > 80) { await sendSlackAlert({ channel: '#engineering-alerts', text: `Miro API credit usage at ${report.creditUtilizationPercent}%. ${report.recommendation}`, }); } ``` ## When to Upgrade to Enterprise Consider Enterprise if you need: - Higher rate limits (negotiated per account) - SCIM API for automated user provisioning - Audit logs API for compliance - Organization-level management endpoints - SSO/SAML integration APIs - Dedicated support for API issues ## Error Handling | Issue | Cause | Solution | |-------|-------|----------| | Hitting 429 frequently | Too many requests | Implement caching + webhooks | | Credit spikes | Runaway polling loops | Audit all `setInterval` calls | | Unnecessary full-board fetches | No type filtering | Add `?type=` parameter | | Small page sizes | Low `limit` parameter | Use `limit=50` (maximum) | ## Resources - [Miro Pricing](https://miro.com/pricing/) - [Rate Limiting](https://developers.miro.com/reference/rate-limiting) - [Miro Enterprise](https://miro.com/enterprise/) ## Next Steps For architecture patterns, see `miro-reference-architecture`.