From API Chaos to Clean Code: Why I Built a TypeScript SDK That Actually Works
Sometimes the best tools come from solving your own problems.
The Problem Every Developer Knows
You know that feeling when you’re building something cool, but you keep getting stuck on the boring stuff? That’s exactly what happened while working on server automation at Rackoona.
Every time I talked to the TenantOS API, I ended up writing the same tedious code: authentication headers, retry logic, parsing error responses, handling timeouts — it was like Groundhog Day, but with more frustration and less Bill Murray.
The TenantOS API is actually pretty solid for managing bare-metal servers, but using it felt like trying to eat soup with a fork. Technically possible — why would you want to?
When Copy-Paste Becomes a Code Smell
After copying the same authentication logic for the third time in one week, something clicked. I wasn’t solving the real problem — I was just getting better at recreating the same mess.
Instead of writing “better boilerplate,” I decided to kill the boilerplate entirely.
What Actually Changed
Here’s the before and after that made me realize this was worth sharing.
Get Salim Shadman’s stories in your inbox
Join Medium for free to get updates from this writer.
The old way (yes, this was real code):
const axios = require('axios');
async function getServers() {
let retries = 3;
while (retries > 0) {
try {
const response = await axios.get(
'https://your-tenant.tenantos.com/api/servers',
{
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
'Content-Type': 'application/json',
},
timeout: 30_000,
}
);
if (response.status === 200) {
return response.data;
}
} catch (error) {
retries--;
if (error.response?.status === 429) {
await new Promise((r) => setTimeout(r, 2000));
continue;
}
if (retries === 0) throw error;
}
}
}The new way:
import { TenantosClient } from 'tenantos-api';
const client = new TenantosClient({
baseUrl: 'https://your-tenant.tenantos.com',
apiKey: process.env.TENANTOS_API_KEY!,
});
const servers = await client.servers.list();That’s it. No retry logic. No error-handling ceremony. No auth dance. It just works.
Why This Actually Matters
Your IDE Becomes Your Friend
No more guessing what fields an API response might have:
const server = await client.servers.get(123);
console.log(server.servername); // ✅ your IDE knows this exists
console.log(server.typoField); // ❌ TypeScript catches this immediatelyError Messages That Help Instead of Confuse
Get context-aware errors:
try {
const server = await client.servers.get(999);
} catch (error: any) {
if (error.isStatus?.(404)) {
console.log('Server not found — check the ID');
} else if (error.isStatus?.(401)) {
console.log('API key expired or invalid');
} else {
console.error('Unexpected error', error);
}
}No More Retry Logic Headaches
Configure it once, forget about it:
const client = new TenantosClient({
baseUrl: 'https://your-tenant.tenantos.com',
apiKey: 'your-api-key',
retry: { attempts: 5, delay: 2000 },
timeout: 60_000,
});APIs That Make Sense
The SDK is shaped the way you think about the resources:
// Power management
await client.servers.power.on(123);
await client.servers.power.reset(456);
// Provisioning
await client.servers.provisioning(123).startReinstallation({
os_id: 1,
type: 'pxe',
});
// Network
const switches = await client.networkDevices.list({
filters: { deviceType: 'switch' },
});Real Impact (Not Just Theory)
Since switching to this SDK at Rackoona, a few things changed dramatically:
- Time to ship: what used to take an afternoon now takes ~20 minutes
- Bug reports: fewer “the automation broke again” messages
- Onboarding: new devs build on day 1 instead of re-learning wrapper patterns
Here’s actual code from our monitoring system:
async function checkServerHealth() {
const client = new TenantosClient({
baseUrl: process.env.TENANTOS_URL!,
apiKey: process.env.TENANTOS_API_KEY!,
});
const servers = await client.servers.list({
filters: { tags: ['production'] },
});
for (const server of servers) {
const stats = await client.servers.statistics(server.id).getNetworkStats('daily');
const bmcUsers = await client.servers.bmcUsers(server.id).listUsers();
console.log(
`${server.servername}: ${stats.length} network events, ${bmcUsers.length} BMC users`
);
}
}This replaced ~200 lines of error-prone boilerplate with 15 lines that actually describe the work.
Getting Started
The whole thing is open source and available right now:
npm install tenantos-apiimport { TenantosClient } from 'tenantos-api';
const client = new TenantosClient({
baseUrl: 'https://your-tenant.tenantos.com',
apiKey: 'your-api-key',
});
// See what you're working with
const version = await client.system.ui.getVersion();
console.log('TenantOS version:', version);
// Get your servers
const servers = await client.servers.list();
console.log(`Managing ${servers.length} servers`);What’s Actually In the Box
- Complete API coverage: every TenantOS endpoint, fully typed
- Lightweight: under 50 KB, only depends on
axios - Modern: Node.js 18+, current JS features
- Documented: auto-generated docs with real examples
- Debuggable: built-in logging when things go wrong
What’s Missing (And What’s Next)
This SDK is great for day-to-day automation, and there’s still room to grow:
- Webhook handling for real-time event
- Bulk operations for large fleets
- Smarter caching for read-heavy workloads
- More examples for common patterns
Contributions and ideas are welcome!
Worth Trying?
If you’re working with TenantOS APIs and tired of writing the same boilerplate code, this might save you time (and sanity). If you’re not using TenantOS but deal with similar API frustrations, maybe the approach sparks ideas for your stack.
Either way, the code’s on GitHub if you want to poke around or suggest improvements.
Links
- 📦 Install:
npm install tenantos-api - 📚 Docs: https://shadmanzero.github.io/tenantos-api/
- 🐛 Issues: https://github.com/shadmanZero/tenantos-api/issues
- ⭐ Source: https://github.com/shadmanZero/tenantos-api
Tags: #TypeScript #SDK #API #DevTools #Infrastructure #Automation #OpenSource #TenantOS #DeveloperTools #Rackoona




