refactor: use @openrouter/sdk typed client in scripts instead of hand-rolled fetch to eliminate API shape drift
Problem
All scripts (list-models.ts, get-endpoints.ts, compare-models.ts, etc.) share a hand-rolled HTTP layer in lib.ts:
export async function fetchApi(path: string, apiKey?: string): Promise<any> {
const url = `https://openrouter.ai/api/v1${path}`;
const res = await fetch(url, { headers });
// manual switch(res.status) error handling...
return res.json(); // return type: any
}
The return type is any throughout. This means:
- New API fields (
canonical_slug,knowledge_cutoff,default_parameters) go undiscovered until someone manually audits the response and files a docs PR like #10 - Error handling is duplicated across scripts
- There's no compile-time signal when the API shape changes
PR #10 is a direct symptom: it exists because the skill docs drifted from the live API, and the fix was to manually embed JSON shapes in SKILL.md — creating a second source of truth that will drift again.
What to do instead
Replace fetchApi() in each lib.ts with the @openrouter/sdk typed client (@openrouter/sdk covers model listing, chat completions, credits, and OAuth). This gives:
- Typed responses — new/removed fields surface as compile errors, not silent drift
- Standardized error handling — no more per-script
switch (res.status)blocks - Free updates — SDK version bumps propagate API changes to all scripts automatically
The CLI interface, custom formatting logic (formatModel(), pricing per-million conversion, status code → string), and custom algorithms stay in the scripts. Only the HTTP transport layer changes.
Tradeoff to consider
Adding @openrouter/sdk as a dependency increases the install footprint. The current scripts have only tsx as a devDep. If portability across minimal agent environments is a hard constraint, an alternative is generating TypeScript types from openrouter.ai/openapi.yaml at build time (no runtime dependency, types only).
Reviewed by Perry
Source: OpenRouterTeam/skills