{"slug": "how-i-wired-free-llm-access-9router-freebuff-blueprint", "title": "How I Wired Free LLM Access: 9router & Freebuff Blueprint", "summary": "A developer has shared a blueprint for integrating free LLM access into AI applications using Freebuff and 9router, an openai-compatible gateway that handles device-code OAuth authentication. The setup allows developers to route non-critical tasks to free models like gpt-3.5-turbo and llama3, reducing inference costs without sacrificing compatibility. The approach includes a Node.js token manager to automate the OAuth flow and keep tokens fresh.", "body_md": "This article was originally published on[BuildZn].\n\nPaying for LLM inference gets expensive, fast. Especially when you're spinning up agents for FarahGPT or experimenting with NexusOS prototypes. Everyone talks about \"AI cost optimization\" but nobody explains how to actually get **free LLM access 9router** style, integrating free tiers without sacrificing OpenAI compatibility. Figured it out the hard way, so you don't have to.\n\nLook, running AI agents, especially multi-agent systems, can drain your wallet faster than a crypto crash. OpenAI's API is great, but those tokens add up. When I was building the YouTube automation pipeline, I needed hundreds of thousands of cheap calls for pre-processing and content generation drafts. Paying retail wasn't an option.\n\nThis isn't about ditching paid APIs entirely. It's about smart **LLM free tier routing** for tasks where the absolute bleeding edge isn't necessary, or for dev/staging environments. You want an **openai compatible gateway** that can seamlessly switch between paid and free, and Freebuff combined with 9router is that setup. Freebuff gives you access to models like `gpt-3.5-turbo`\n\nand `llama3`\n\nthrough an OpenAI-compatible API, but it needs an OAuth dance. That's where 9router comes in, acting as your local **local LLM adapter** and token manager.\n\nHere's why this matters for developers and clients:\n\nThe core idea is simple: Freebuff offers free access to various LLMs, but requires a user to \"authenticate\" using a device code flow. This isn't your typical API key. You get a device code, go to a URL, approve it, and then your application polls for a token. This token then acts as your \"API key\" for a limited time.\n\n9router is a local proxy I use. It can handle custom authentication logic before forwarding requests. We'll use it to:\n\nThis means your AI agents or frontend code just point to your local 9router instance, and 9router handles all the Freebuff complexity behind the scenes. This is crucial for **device-code OAuth AI** integrations because you don't want every agent instance doing the OAuth dance.\n\nHere's the high-level flow:\n\n`https://freebuff.com/api/v1/chat/completions`\n\n.Let's get into the actual code. You'll need a Node.js service to manage the Freebuff token. This service will run alongside your 9router instance.\n\nFirst, ensure you have 9router installed globally or locally:\n\n`npm install -g 9router`\n\nThis script will handle the device code flow and keep your Freebuff token fresh.\n\n``` js\n// tokenManager.js\nconst axios = require('axios');\nconst fs = require('fs');\nconst path = require('path');\nconst { spawn } = require('child_process');\n\nconst FREEBUFF_API_BASE = 'https://freebuff.com/api/v1';\nconst TOKEN_FILE = path.join(__dirname, 'freebuff_token.json');\n\nlet currentToken = null;\nlet tokenRefreshTimeout = null;\n\nasync function saveToken(tokenData) {\n    fs.writeFileSync(TOKEN_FILE, JSON.stringify(tokenData, null, 2));\n    currentToken = tokenData;\n    console.log('Freebuff token saved and updated.');\n    scheduleTokenRefresh(tokenData.expires_in);\n}\n\nfunction loadToken() {\n    if (fs.existsSync(TOKEN_FILE)) {\n        try {\n            const tokenData = JSON.parse(fs.readFileSync(TOKEN_FILE, 'utf8'));\n            if (tokenData && tokenData.access_token && tokenData.refresh_token && tokenData.expires_at > Date.now()) {\n                currentToken = tokenData;\n                console.log('Loaded valid Freebuff token from file.');\n                scheduleTokenRefresh(tokenData.expires_at - Date.now());\n                return true;\n            }\n        } catch (e) {\n            console.error('Error loading Freebuff token from file:', e.message);\n        }\n    }\n    console.log('No valid Freebuff token found. Will initiate new flow.');\n    return false;\n}\n\nfunction scheduleTokenRefresh(expiresInMs) {\n    if (tokenRefreshTimeout) {\n        clearTimeout(tokenRefreshTimeout);\n    }\n    // Refresh 5 minutes before expiry\n    const refreshInterval = Math.max(0, expiresInMs - (5 * 60 * 1000)); \n    tokenRefreshTimeout = setTimeout(refreshToken, refreshInterval);\n    console.log(`Scheduled token refresh in ${Math.round(refreshInterval / (60 * 1000))} minutes.`);\n}\n\nasync function refreshToken() {\n    console.log('Attempting to refresh Freebuff token...');\n    if (!currentToken || !currentToken.refresh_token) {\n        console.error('No refresh token available. Initiating new device flow.');\n        return await initiateDeviceFlow();\n    }\n\n    try {\n        const response = await axios.post(`${FREEBUFF_API_BASE}/oauth/token`, {\n            grant_type: 'refresh_token',\n            refresh_token: currentToken.refresh_token,\n            client_id: 'your-client-id-from-freebuff' // IMPORTANT: Replace with your actual client ID\n        }, {\n            headers: { 'Content-Type': 'application/json' }\n        });\n\n        const tokenData = {\n            ...response.data,\n            expires_at: Date.now() + (response.data.expires_in * 1000)\n        };\n        await saveToken(tokenData);\n        console.log('Freebuff token refreshed successfully.');\n        return true;\n    } catch (error) {\n        console.error('Error refreshing Freebuff token:', error.response ? error.response.data : error.message);\n        // This is where you might hit: {\"error\":\"invalid_grant\",\"error_description\":\"Refresh token is invalid or expired.\"}\n        // If that happens, initiate a new device flow.\n        console.log('Refresh failed. Initiating new device flow.');\n        return await initiateDeviceFlow();\n    }\n}\n\nasync function initiateDeviceFlow() {\n    console.log('Initiating Freebuff device code flow...');\n    try {\n        const deviceCodeResponse = await axios.post(`${FREEBUFF_API_BASE}/oauth/device_code`, {\n            client_id: 'your-client-id-from-freebuff', // IMPORTANT: Replace with your actual client ID\n            scope: 'chat' \n        }, {\n            headers: { 'Content-Type': 'application/json' }\n        });\n\n        const { device_code, user_code, verification_uri, interval } = deviceCodeResponse.data;\n        console.log(`Please go to: ${verification_uri}`);\n        console.log(`Enter this code: ${user_code}`);\n        console.log('Waiting for authorization...');\n\n        const pollInterval = interval * 1000 || 5000; // Poll every 'interval' seconds, or 5s default\n\n        return new Promise((resolve, reject) => {\n            const polling = setInterval(async () => {\n                try {\n                    const tokenResponse = await axios.post(`${FREEBUFF_API_BASE}/oauth/token`, {\n                        grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n                        device_code: device_code,\n                        client_id: 'your-client-id-from-freebuff' // IMPORTANT: Replace with your actual client ID\n                    }, {\n                        headers: { 'Content-Type': 'application/json' }\n                    });\n\n                    clearInterval(polling);\n                    const tokenData = {\n                        ...tokenResponse.data,\n                        expires_at: Date.now() + (tokenResponse.data.expires_in * 1000)\n                    };\n                    await saveToken(tokenData);\n                    console.log('Freebuff authorization successful!');\n                    resolve(true);\n                } catch (error) {\n                    if (error.response && error.response.data && error.response.data.error === 'authorization_pending') {\n                        // Still waiting for user authorization. Continue polling.\n                        process.stdout.write('.'); // Indicate activity\n                    } else {\n                        clearInterval(polling);\n                        console.error('\\nError during device code polling:', error.response ? error.response.data : error.message);\n                        reject(new Error('Failed to get Freebuff token.'));\n                    }\n                }\n            }, pollInterval);\n        });\n\n    } catch (error) {\n        console.error('Error initiating device code flow:', error.response ? error.response.data : error.message);\n        return false;\n    }\n}\n\n// Function to get the current valid token\nfunction getFreebuffToken() {\n    if (!currentToken || currentToken.expires_at <= Date.now()) {\n        console.warn('Freebuff token is expired or not available. Attempting refresh/re-init.');\n        // This should ideally trigger a refresh, but for a simple getter, \n        // we'll rely on the scheduled refresh or require manual re-init if needed.\n        return null; \n    }\n    return currentToken.access_token;\n}\n\nasync function startTokenManager() {\n    if (!loadToken()) {\n        await initiateDeviceFlow();\n    } else {\n        // Ensure refresh is scheduled even if token loaded from file\n        scheduleTokenRefresh(currentToken.expires_at - Date.now());\n    }\n}\n\nmodule.exports = { startTokenManager, getFreebuffToken };\n\n// To run this standalone for testing:\n// if (require.main === module) {\n//     startTokenManager();\n// }\n```\n\n**IMPORTANT:** You need to get a `client_id`\n\nfrom Freebuff directly. This isn't publicly documented how to generate one; you usually get it from their team or specific integrations. For the purpose of this guide, assume you have one. If you don't, this blueprint highlights a critical missing piece for broader adoption. Honestly, I don't get why this isn't clearer on their site for dev setups.\n\nRun this script: `node tokenManager.js`\n\n. It will output the `verification_uri`\n\nand `user_code`\n\n. Open the URL, enter the code, and approve. The script will then save `freebuff_token.json`\n\nand keep it refreshed.\n\nNow, set up 9router to proxy requests. Create a `9router.config.js`\n\nfile:\n\n``` js\n// 9router.config.js\nconst { getFreebuffToken } = require('./tokenManager'); // Assuming tokenManager.js is in the same directory\n\nmodule.exports = {\n    port: 3000, // Or any port you want 9router to listen on\n    routes: [\n        {\n            // This route will handle all OpenAI-compatible chat completions requests\n            path: '/v1/chat/completions',\n            method: ['POST'],\n            target: 'https://freebuff.com/api/v1/chat/completions',\n            hooks: {\n                onRequest: async (req, res) => {\n                    const token = getFreebuffToken();\n                    if (!token) {\n                        // If token is not available, maybe respond with a 503 or redirect to auth\n                        console.error('Freebuff token not available for request.');\n                        res.writeHead(503, { 'Content-Type': 'application/json' });\n                        res.end(JSON.stringify({ error: 'Freebuff token unavailable. Please authorize.' }));\n                        return true; // Stop further processing\n                    }\n                    req.headers['Authorization'] = `Bearer ${token}`;\n                    req.headers['Content-Type'] = 'application/json';\n                    // Remove any host headers that might cause issues with Freebuff's proxy\n                    delete req.headers['host'];\n                    delete req.headers['accept-encoding']; // Freebuff might not handle compressed content\n                    return false; // Continue with proxying\n                },\n                onProxyResponse: (proxyRes, req, res) => {\n                    // Optional: You can inspect/modify proxyRes headers here\n                },\n                onError: (err, req, res) => {\n                    console.error('9router proxy error:', err);\n                    res.writeHead(500, { 'Content-Type': 'application/json' });\n                    res.end(JSON.stringify({ error: '9router proxy error', details: err.message }));\n                }\n            }\n        },\n        // Add other OpenAI-compatible routes if Freebuff supports them (e.g., embeddings)\n        // {\n        //     path: '/v1/embeddings',\n        //     method: ['POST'],\n        //     target: 'https://freebuff.com/api/v1/embeddings',\n        //     hooks: {\n        //         onRequest: async (req, res) => {\n        //             const token = getFreebuffToken();\n        //             if (!token) { /* ... handle error ... */ return true; }\n        //             req.headers['Authorization'] = `Bearer ${token}`;\n        //             req.headers['Content-Type'] = 'application/json';\n        //             delete req.headers['host'];\n        //             delete req.headers['accept-encoding'];\n        //             return false;\n        //         }\n        //     }\n        // }\n    ]\n};\n```\n\nTo run 9router:\n\n`tokenManager.js`\n\nis running and has successfully fetched a token.`9router start --config 9router.config.js`\n\nNow your agents can send requests to `http://localhost:3000`\n\n(or whatever port you configured) as if it were the OpenAI API.\n\nExample using `openai`\n\nNode.js client:\n\n``` js\n// agentExample.js\nconst OpenAI = require('openai');\n\nconst openai = new OpenAI({\n    baseURL: 'http://localhost:3000/v1', // Point to your 9router instance\n    apiKey: 'sk-no-key-needed' // Dummy key, 9router handles auth\n});\n\nasync function runAgentTask(prompt) {\n    try {\n        const chatCompletion = await openai.chat.completions.create({\n            model: 'gpt-3.5-turbo', // Or 'llama3', check Freebuff for supported models\n            messages: [{ role: 'user', content: prompt }],\n            max_tokens: 150\n        });\n        console.log('Agent response:', chatCompletion.choices[0].message.content);\n        return chatCompletion.choices[0].message.content;\n    } catch (error) {\n        console.error('Error fetching completion:', error.response ? error.response.data : error.message);\n    }\n}\n\n// Example usage\nrunAgentTask('Explain the concept of quantum entanglement in simple terms.');\n// runAgentTask('Generate a short story about a time-traveling squirrel.');\n```\n\nThis is your **free LLM access 9router** in action. You're using an OpenAI-compatible interface, but routing through your local proxy to a free tier.\n\nHonestly, when I first tried this **freebuff LLM cost** reduction strategy, I hit a wall with rate limits and expired device codes.\n\n`tokenManager.js`\n\ndidn't properly handle `authorization_pending`\n\nand `expired_token`\n\nerrors during the polling phase. I kept getting `{\"error\":\"expired_token\",\"error_description\":\"The device code has expired.\"}`\n\n. Turns out, the `device_code`\n\nhas a limited lifespan (usually a few minutes) to be authorized by the user. If you're too slow, or your polling interval is too long, you miss the window. `interval`\n\nfrom device code response) and clear user instructions. Also, add robust error handling to re-initiate the flow if it expires.`{\"error\":\"invalid_grant\",\"error_description\":\"Refresh token is invalid or expired.\"}`\n\nwas a headache. `refreshToken`\n\nfunction now explicitly checks for this and will trigger a full `initiateDeviceFlow()`\n\nif the refresh token fails. This is crucial for long-running services.`gpt-3.5-turbo`\n\nchat completions over 15 minutes`429 Too Many Requests`\n\nerror, using a batch script sending requests every 5 seconds. This was measured with `max_tokens: 100`\n\nand `temperature: 0.7`\n\n. After hitting the limit, I had to wait roughly 30-45 minutes for the rate limit to reset. This is why it's a `Content-Type`\n\nor extraneous headers being forwarded. My 9router config now explicitly sets `Content-Type: application/json`\n\nand removes `host`\n\nand `accept-encoding`\n\nto prevent issues.For more advanced use cases, like managing different free tiers for different teams or projects (a common scenario for NexusOS agents), you can extend this.\n\n`tokenManager.js`\n\ncould manage an array of `freebuff_token.json`\n\nfiles, perhaps keyed by `client_id`\n\nor user ID. Your 9router `onRequest`\n\nhook could then select the least-rate-limited token or cycle through them. This would require passing a custom header (e.g., `X-Freebuff-Account-ID`\n\n) from your agents for 9router to pick the right token.`onRequest`\n\nhook can significantly reduce calls to Freebuff, helping you stay under their rate limits longer. You could use `node-cache`\n\nor a simple in-memory object.\n\n``` js\n// Example of simple in-memory cache in 9router.config.js (conceptual)\nconst cache = {}; // Simple object for now\n\n// ... inside onRequest hook ...\nconst requestBody = JSON.parse(req.body.toString()); // Assuming body is parsed by 9router or accessible\nconst cacheKey = JSON.stringify(requestBody); // Simple key based on request body\n\nif (cache[cacheKey] && cache[cacheKey].expiry > Date.now()) {\n    console.log('Serving from cache!');\n    res.writeHead(200, { 'Content-Type': 'application/json' });\n    res.end(JSON.stringify(cache[cacheKey].data));\n    return true; // Stop request from going to Freebuff\n}\n\n// ... if not in cache, proceed to Freebuff ...\n// On onProxyResponse, store the response:\n// cache[cacheKey] = { data: JSON.parse(body), expiry: Date.now() + CACHE_TTL_MS };\n```\n\nThis is a decent **LLM free tier routing** optimization for predictable agent behavior.\n\nFreebuff's free tier is great for development, testing, and non-critical background tasks. For production applications needing high availability and strict SLAs, you should always have a fallback to a paid API like OpenAI or Claude. The rate limits mean it's not a substitute for sustained high-volume inference.\n\nYes, 9router is a generic proxy. As long as the free LLM service offers an OpenAI-compatible API and you can manage its authentication (API key, OAuth, etc.) programmatically, you can extend the `9router.config.js`\n\nand `tokenManager.js`\n\nto support it. This makes it a powerful **openai compatible gateway** for various providers.\n\nThe device-code flow is secure for user authorization, as the code is entered directly on the provider's trusted website. However, storing the resulting `access_token`\n\nand `refresh_token`\n\nlocally (like in `freebuff_token.json`\n\n) means anyone with access to that file could impersonate your application. For robust production, use environment variables or a proper secrets manager.\n\nSo, there you have it. You don't need to break the bank to run your AI agents, especially for dev and experimental work. This **free LLM access 9router** setup with Freebuff is a battle-tested way to cut costs while keeping your existing OpenAI API client code. It takes a bit of initial setup, but the savings are real, and the flexibility of managing your own **LLM free tier routing** is powerful. Stop letting LLM bills dictate your innovation. Build smart.", "url": "https://wpnews.pro/news/how-i-wired-free-llm-access-9router-freebuff-blueprint", "canonical_source": "https://dev.to/umair24171/how-i-wired-free-llm-access-9router-freebuff-blueprint-19e3", "published_at": "2026-08-20 04:33:19+00:00", "updated_at": "2026-08-20 05:42:55.895407+00:00", "lang": "en", "topics": ["large-language-models", "ai-infrastructure", "developer-tools"], "entities": ["Freebuff", "9router", "OpenAI", "BuildZn", "FarahGPT", "NexusOS"], "alternates": {"html": "https://wpnews.pro/news/how-i-wired-free-llm-access-9router-freebuff-blueprint", "markdown": "https://wpnews.pro/news/how-i-wired-free-llm-access-9router-freebuff-blueprint.md", "text": "https://wpnews.pro/news/how-i-wired-free-llm-access-9router-freebuff-blueprint.txt", "jsonld": "https://wpnews.pro/news/how-i-wired-free-llm-access-9router-freebuff-blueprint.jsonld"}}