Skip to main content

Documentation Index

Fetch the complete documentation index at: https://chatjs.dev/docs/llms.txt

Use this file to discover all available pages before exploring further.

Keep your model list fresh without redeploying. This pattern fetches available models from an API at runtime, caches the result, and falls back to a generated snapshot when the API is unavailable.

Architecture Overview

Two layers:
  1. Runtime fetch - Live API calls cached for 1 hour
  2. Build-time snapshot - Static fallback when API unavailable

How It Works

1. Define the API Schema

Create a Zod schema for your model provider’s response:
lib/ai/ai-gateway-models-schemas.ts
import { z } from "zod";

const aiGatewayModelSchema = z.object({
  id: z.string(),
  object: z.literal("model"),
  created: z.number(),
  owned_by: z.string(),
  name: z.string(),
  description: z.string(),
  context_window: z.number(),
  max_tokens: z.number(),
  type: z.union([
    z.literal("language"),
    z.literal("embedding"),
    z.literal("image"),
  ]),
  tags: z.array(z.string()).optional(),
  pricing: z.object({
    input: z.string().optional(),
    output: z.string().optional(),
    input_cache_read: z.string().optional(),
    input_cache_write: z.string().optional(),
    web_search: z.string().optional(),
    image: z.string().optional(),
    input_tiers: z
      .array(z.object({ cost: z.string(), min: z.number(), max: z.number().optional() }))
      .optional(),
    output_tiers: z
      .array(z.object({ cost: z.string(), min: z.number(), max: z.number().optional() }))
      .optional(),
  }),
});

export type AiGatewayModel = z.infer<typeof aiGatewayModelSchema>;

export const aiGatewayModelsResponseSchema = z.object({
  object: z.literal("list"),
  data: z.array(aiGatewayModelSchema),
});

2. Create the Fallback Snapshot

Generate an initial empty file that will hold the snapshot:
lib/ai/models.generated.ts
import type { AiGatewayModel } from "./ai-gateway-models-schemas";

export const models = [] as const satisfies readonly AiGatewayModel[];

3. Implement the Fetcher with Fallback

lib/ai/models.ts
import { unstable_cache } from "next/cache";
import { aiGatewayModelsResponseSchema } from "./ai-gateway-models-schemas";
import { models as fallbackModels } from "./models.generated";
import { toModelData, type ModelData } from "./model-data";

const API_URL = "https://ai-gateway.vercel.sh/v1/models";

async function fetchModelsRaw() {
  const apiKey = process.env.AI_GATEWAY_API_KEY;

  if (!apiKey) {
    console.warn("No API key, using fallback models");
    return fallbackModels;
  }

  try {
    const response = await fetch(API_URL, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    if (!response.ok) {
      console.error(`API error: ${response.status}`);
      return fallbackModels;
    }

    const json = await response.json();
    const parsed = aiGatewayModelsResponseSchema.safeParse(json);

    if (!parsed.success) {
      console.error("Schema validation failed:", parsed.error);
      return fallbackModels;
    }

    console.info(`Fetched ${parsed.data.data.length} models from API`);
    return parsed.data.data;
  } catch (error) {
    console.error("Fetch failed:", error);
    return fallbackModels;
  }
}

export const fetchModels = unstable_cache(
  async (): Promise<ModelData[]> => {
    const models = await fetchModelsRaw();
    return models.map(toModelData);
  },
  ["ai-gateway-models"],
  { revalidate: 3600, tags: ["ai-gateway-models"] }
);
Key points:
  • All errors fall back to the snapshot (never throws)
  • Zod validates the response before use
  • unstable_cache stores results for 1 hour
  • Cache tag enables manual invalidation via revalidateTag()

4. Add Computed Fields (Optional)

The API returns capabilities as a tags array (e.g., ["reasoning", "tool-use", "vision"]). You can transform these into boolean flags for easier filtering:
lib/ai/model-data.ts
import type { AiGatewayModel } from "./ai-gateway-models-schemas";

export type ModelData = AiGatewayModel & {
  reasoning: boolean;
  toolCall: boolean;
  input: { image: boolean; text: boolean; pdf: boolean };
  output: { image: boolean; text: boolean };
};

export function toModelData(model: AiGatewayModel): ModelData {
  const tags = model.tags ?? [];
  return {
    ...model,
    reasoning: tags.includes("reasoning"),
    toolCall: tags.includes("tool-use"),
    input: {
      image: tags.includes("vision") || model.type === "image",
      text: model.type === "language",
      pdf: tags.includes("file-input"),
    },
    output: {
      image: tags.includes("image-generation") || model.type === "image",
      text: model.type === "language",
    },
  };
}
This step is optional. If you prefer working with the raw tags array, skip this transformation and return AiGatewayModel[] directly from fetchModels().

5. Create the Sync Script

Write a script to refresh the fallback snapshot:
scripts/fetch-models.ts
const API_URL = "https://ai-gateway.vercel.sh/v1/models";
const OUTPUT_PATH = "lib/ai/models.generated.ts";

async function main() {
  const apiKey = process.env.AI_GATEWAY_API_KEY;
  if (!apiKey) throw new Error("AI_GATEWAY_API_KEY required");

  const response = await fetch(API_URL, {
    headers: { Authorization: `Bearer ${apiKey}` },
  });

  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }

  const json = await response.json();
  const models = json.data;

  const content = `import type { AiGatewayModel } from "./ai-gateway-models-schemas";

export const models = ${JSON.stringify(models, null, 2)} as const satisfies readonly AiGatewayModel[];
`;

  await Bun.write(OUTPUT_PATH, content);
  console.log(`Wrote ${models.length} models to ${OUTPUT_PATH}`);
}

main();
Add to package.json:
package.json
{
  "scripts": {
    "fetch:models": "bun scripts/fetch-models.ts && bun format"
  }
}
Run periodically to keep the fallback fresh:
bun fetch:models

Caching Strategy

LayerCache KeyTTLTag
API response + transform["ai-gateway-models"]1 hourai-gateway-models
To invalidate the cache manually:
import { revalidateTag } from "next/cache";
revalidateTag("ai-gateway-models");

Key Files

FilePurpose
lib/ai/ai-gateway-models-schemas.tsZod schema for API response
lib/ai/models.generated.tsStatic fallback snapshot
lib/ai/models.tsRuntime fetcher with cache
lib/ai/model-data.tsType definitions and transforms
scripts/fetch-models.tsBuild-time sync script

Gotchas

  • Dev vs Prod caching: unstable_cache behaves differently in development. Restart the dev server to see cache changes.
  • Next.js specific: unstable_cache requires Next.js. For other frameworks, use Redis or in-memory caching.
  • Snapshot size: The generated file can be large (40KB+). This is acceptable for build-time inclusion.