You can ship a working AI service today without renting a server or buying tokens. This walkthrough takes you from an empty directory to a deployed HTTP endpoint that summarizes pull requests — using a free server and a free model tier. The whole thing is about 60 lines of code.
Most side projects die at the same stage: the code works locally, and then... where does it live? A $5 VPS feels wasteful for a weekend project. Serverless functions want a credit card. And every "free tier" I've tried has a catch buried somewhere in the docs.
So when the open-source MonkeyCode project started bundling free model access with a free server option, I decided to test the workflow end to end. Not with a benchmark — with an actual deploy.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Here's the honest version of what I built, the exact steps, and the point where this approach stops being a good idea.
What we're building
A PR summary bot. It takes a diff, sends it to the free model endpoint, and returns a short summary. Small enough to read in one sitting. Useful enough to actually run.
Why a PR bot? Because reviewing is the new bottleneck. AI writes more code, so humans review more of it. A summary doesn't replace the reviewer — it gives the reviewer a starting point. You still read the diff. You just don't start from zero.
Step 0: What you need
- Node.js 18+ (
node -vto check) - A diff to test with (grab one from any public PR)
- A MonkeyCode account for the API key and the free server
One warning before we start: the current free quota is 10 million tokens. That's what the project states today — quotas change, so verify it in the docs before you build anything on top of it.
Step 1: Scaffold the service
mkdir pr-summary-bot
cd pr-summary-bot
npm init -y
npm install express
Create index.js:
const express = require('express');
const app = express();
app.use(express.json());
const PORT = process.env.PORT || 3000;
app.get('/health', (req, res) => {
res.json({ ok: true });
});
app.post('/summarize', async (req, res) => {
const { diff } = req.body;
if (!diff) return res.status(400).json({ error: 'missing diff' });
const summary = await summarizeDiff(diff);
res.json({ summary });
});
app.listen(PORT, () => {
console.log(`listening on ${PORT}`);
});
Two endpoints. /health for the deploy check, /summarize for the actual work. Nothing clever, and that's the point.
Step 2: Wire the model call
Add the function that talks to the free model. Everything reads from env vars, because endpoints and model names change:
async function summarizeDiff(diff) {
const response = await fetch(process.env.MONKEYCODE_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MONKEYCODE_API_KEY}`
},
body: JSON.stringify({
model: process.env.MONKEYCODE_MODEL,
messages: [
{ role: 'system', content: 'Summarize this diff in under 80 words. Focus on behavior changes, not style.' },
{ role: 'user', content: diff.slice(0, 6000) }
]
})
});
const data = await response.json();
return data.choices?.[0]?.message?.content ?? 'no summary returned';
}
The diff.slice(0, 6000) is intentional. A huge diff will eat your token quota fast, and 6,000 characters covers most PRs. You're building a draft, not a full code review.
Step 3: Run it locally and verify
export MONKEYCODE_ENDPOINT="<paste from docs>"
export MONKEYCODE_MODEL="<paste from docs>"
export MONKEYCODE_API_KEY="<your key>"
node index.js
In a second terminal:
curl http://localhost:3000/health
# {"ok":true}
curl -X POST http://localhost:3000/summarize \
-H "Content-Type: application/json" \
-d '{"diff":"+function add(a, b) { return a + b; }\n+console.log(add(2, 3));"}'
What should you see? A short summary mentioning a new add function and a log call. If you get no summary returned, check the response format in the docs — some providers nest the text differently.
Test with a real diff before deploying. I used a PR from one of my own repos and compared the summary against the actual changes. It caught the two behavior changes I cared about and missed one edge case. That's the right failure mode for a free-tier tool: it saves you time, but it never gets the final say. Good enough for a draft. Not good enough to skip the human.
Step 4: Deploy to the free server
This is where the free server option does the heavy lifting. Add a start script to package.json first:
"scripts": {
"start": "node index.js"
}
Push the project to a Git repository, connect it through MonkeyCode's deploy flow, set the same three env vars, and deploy. You'll get a public URL back.
Verify the deploy the same way you verified locally:
curl https://<your-app>.example.dev/health
If that returns {"ok":true}, your bot is live. Now send it a real diff:
curl -X POST https://<your-app>.example.dev/summarize \
-H "Content-Type: application/json" \
-d @pr-diff.json
Step 5: Make it a habit
The bot is only useful if it runs. Point a cron job at it once a day, or paste a diff into it when you're about to review a big PR. I keep mine open in a terminal tab. It's faster than scrolling through 40 changed files.
If something breaks
-
{"ok":true}but an empty summary → the model returned nothing; check the quota and the response format. - 401 on the API call → wrong key, or the key isn't activated for the free tier.
- Cold start timeout → the free server was idle; hit
/healthfirst, then retry. - Token error → you hit the cap; check the usage page before assuming a code bug.
Where this approach breaks
Let me be clear about the limits, because I've seen too many "free tier" articles skip this part.
Don't send private code. A free shared model endpoint is the wrong place for proprietary source or customer data. If your repo isn't public, this bot should not see it. This is the line I refuse to cross, and you should too.
10 million tokens is not infinite. One 6,000-character summary costs a few thousand tokens. Do the math: roughly 3,000 summaries on the free quota. Fine for a side project. Gone in a week for a busy team.
Free servers have cold starts. The first request after idle might take seconds, not milliseconds. If your bot needs to answer instantly at 3am, rent a box.
Quotas and endpoints change. The 10M number, the model name, the endpoint — all of it can shift. That's why the code reads everything from env vars. When something changes, you update the config, not the code.
The takeaway
The bot itself is not the point. The path is: free tokens, a free server, and 60 lines of code are enough to ship something real. Next time someone asks where to host a side project, you'll have an answer that costs nothing. The next deploy is up to you — what are you going to build?
Top comments (0)