n4nAI

Custom baseURL setup: Vercel AI SDK meets n4n.ai

Configure Vercel AI SDK to route requests through n4n.ai with a custom baseURL, including provider fallback, streaming, and usage metering.

n4n Team4 min read918 words

Audio narration

Coming soon — every post will get a voice note here.

The Vercel AI SDK expects an OpenAI-compatible endpoint by default, but production workloads often need a gateway that handles model routing, fallback, and observability in one place. This guide walks through wiring the SDK’s baseURL option to n4n.ai so you can call 240-plus models through a single endpoint while keeping your application code unchanged. You’ll set up the client, configure routing directives, enable streaming, and verify the integration end to end.

Step 1: Install the AI SDK and dependencies

Start with a fresh or existing Node.js project. The AI SDK v4 splits providers into separate packages, so install the OpenAI-compatible client alongside the core library.

npm install ai @ai-sdk/openai zod

If you’re using pnpm or yarn, adjust the command accordingly. The zod dependency is optional but recommended for schema validation in tool calls.

Step 2: Create the gateway client with a custom baseURL

The @ai-sdk/openai package exposes a createOpenAI factory that accepts a baseURL parameter. Point it at the n4n.ai endpoint and pass your API key through the standard apiKey field.

// lib/gateway.ts
import { createOpenAI } from '@ai-sdk/openai';

export const gateway = createOpenAI({
  baseURL: 'https://api.n4n.ai/v1',
  apiKey: process.env.N4N_API_KEY,
});

Set N4N_API_KEY in your environment. The gateway honors the same authentication scheme as OpenAI, so no custom headers are required.

Step 3: Call a model through the gateway

With the client configured, you can invoke any supported model by its n4n.ai slug. The model identifier maps to the underlying provider automatically.

// app/chat/route.ts
import { streamText } from 'ai';
import { gateway } from '@/lib/gateway';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: gateway('anthropic/claude-3-5-sonnet-20241022'),
    messages,
    temperature: 0.3,
    maxTokens: 1024,
  });

  return result.toDataStreamResponse();
}

The streamText helper returns a ReadableStream compatible with the AI SDK’s useChat hook on the frontend. No changes to your UI components are necessary.

Step 4: Use routing directives for model selection

n4n.ai accepts an x-n4n-route header that lets you steer requests without hardcoding model IDs in your application. Pass the directive through the headers option on the model call.

// lib/gateway.ts (extended)
import { createOpenAI } from '@ai-sdk/openai';

export const gateway = createOpenAI({
  baseURL: 'https://api.n4n.ai/v1',
  apiKey: process.env.N4N_API_KEY,
});

export function routedModel(slug: string, directive?: string) {
  const model = gateway(slug);
  if (!directive) return model;

  return model.withSettings({
    headers: {
      'x-n4n-route': directive,
    },
  });
}

Now your route handler can stay generic while the directive controls fallback order, cost tier, or latency preference.

// app/chat/route.ts
import { streamText } from 'ai';
import { routedModel } from '@/lib/gateway';

export async function POST(req: Request) {
  const { messages, route } = await req.json();

  const result = streamText({
    model: routedModel('openai/gpt-4o', route),
    messages,
  });

  return result.toDataStreamResponse();
}

Send {"route": "prefer:cost"} to favor cheaper providers, or {"route": "require:anthropic"} to pin a specific vendor.

Step 5: Enable provider cache-control hints

When a downstream provider returns cache metadata (for example, Anthropic’s prompt caching), n4n.ai forwards the cache-control header in the response. The AI SDK surfaces this through the response.headers property on the stream result.

// app/chat/route.ts
import { streamText } from 'ai';
import { routedModel } from '@/lib/gateway';

export async function POST(req: Request) {
  const { messages, route } = await req.json();

  const result = streamText({
    model: routedModel('anthropic/claude-3-5-sonnet-20241022', route),
    messages,
    onFinish: async ({ response }) => {
      const cacheControl = response.headers.get('cache-control');
      if (cacheControl) {
        console.log('Cache hint:', cacheControl);
        // Persist or metric as needed
      }
    },
  });

  return result.toDataStreamResponse();
}

This lets you build dashboards around cache hit rates without parsing provider-specific response bodies.

Step 6: Track per-token usage for cost attribution

n4n.ai includes usage metadata in the final chunk of a streaming response. The AI SDK’s onFinish callback receives a usage object with promptTokens, completionTokens, and totalTokens.

// app/chat/route.ts
import { streamText } from 'ai';
import { routedModel } from '@/lib/gateway';

export async function POST(req: Request) {
  const { messages, route, userId } = await req.json();

  const result = streamText({
    model: routedModel('openai/gpt-4o', route),
    messages,
    onFinish: async ({ usage, response }) => {
      await fetch('https://your-billing-api/record', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          userId,
          model: response.modelId,
          promptTokens: usage.promptTokens,
          completionTokens: usage.completionTokens,
          totalTokens: usage.totalTokens,
          timestamp: new Date().toISOString(),
        }),
      });
    },
  });

  return result.toDataStreamResponse();
}

The response.modelId reflects the actual provider model that served the request, which is useful when routing directives result in fallback.

Step 7: Handle automatic fallback in your error flow

When a provider returns 429 or 5xx, n4n.ai retries the request against the next provider in the routing chain. The AI SDK sees a single successful response. If the entire chain exhausts, you receive a standard APIError with a statusCode of 502.

// app/chat/route.ts
import { streamText, APIError } from 'ai';
import { routedModel } from '@/lib/gateway';

export async function POST(req: Request) {
  const { messages, route } = await req.json();

  try {
    const result = streamText({
      model: routedModel('openai/gpt-4o', route),
      messages,
    });

    return result.toDataStreamResponse();
  } catch (error) {
    if (error instanceof APIError && error.statusCode === 502) {
      return new Response(
        JSON.stringify({ error: 'All providers unavailable' }),
        { status: 503, headers: { 'Content-Type': 'application/json' } }
      );
    }
    throw error;
  }
}

This keeps your error surface small while still surfacing actionable status codes to the client.

Step 8: Verify the integration with a curl smoke test

Before wiring the frontend, confirm the gateway responds correctly from the command line.

curl -s -X POST https://api.n4n.ai/v1/chat/completions \
  -H "Authorization: Bearer $N4N_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "messages": [{"role": "user", "content": "Say hello in one word"}],
    "stream": false
  }' | jq '.choices[0].message.content'

Expected output: a short JSON string like "Hello". If you see an authentication error, double-check that N4N_API_KEY is set and valid.

Step 9: Verify streaming from your application

Run your Next.js dev server and hit the route directly.

curl -N -X POST http://localhost:3000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "Count to three"}]}'

The -N flag disables curl’s output buffering so you see chunks as they arrive. You should observe SSE-formatted lines:

data: {"type":"text-delta","textDelta":"One"}

data: {"type":"text-delta","textDelta":" two"}

data: {"type":"text-delta","textDelta":" three"}

If the stream closes cleanly with a finish message, the integration works end to end.

Step 10: Confirm routing directives change provider behavior

Test that the x-n4n-route header influences which provider serves the request. Use a model available from multiple vendors, such as meta-llama/llama-3.1-70b-instruct.

curl -s -X POST https://api.n4n.ai/v1/chat/completions \
  -H "Authorization: Bearer $N4N_API_KEY" \
  -H "Content-Type: application/json" \
  -H "x-n4n-route: require:together" \
  -d '{
    "model": "meta-llama/llama-3.1-70b-instruct",
    "messages": [{"role": "user", "content": "Who are you?"}],
    "stream": false
  }' | jq '.model'

The response model field should reflect the Together AI variant (for example, meta-llama/llama-3.1-70b-instruct:together). Repeat with require:fireworks to see the Fireworks variant. This confirms the directive passes through your gateway client correctly.

Step 11: Add request logging for observability

Production systems need visibility into latency, token counts, and fallback events. Wrap the gateway client with a lightweight logger.

// lib/gateway.ts
import { createOpenAI } from '@ai-sdk/openai';

const base = createOpenAI({
  baseURL: 'https://api.n4n.ai/v1',
  apiKey: process.env.N4N_API_KEY,
});

export const gateway = new Proxy(base, {
  get(target, prop) {
    const original = target[prop];
    if (typeof original !== 'function') return original;

    return (...args: unknown[]) => {
      const start = Date.now();
      const modelId = args[0] as string;
      console.log(`[gateway] request model=${modelId}`);

      const result = original(...args);
      if (result?.then) {
        return result.then((r: any) => {
          console.log(`[gateway] response model=${modelId} ms=${Date.now() - start} usage=${JSON.stringify(r.usage)}`);
          return r;
        });
      }
      return result;
    };
  },
});

This logs every model call with latency and usage without modifying your route handlers.

Step 12: Deploy and monitor

Deploy to Vercel (or your platform of choice) with the N4N_API_KEY environment variable configured. In the n4n.ai dashboard, you’ll see per-request logs showing the resolved provider, latency percentiles, and token totals. Correlate these with your application logs using the request ID that n4n.ai returns in the x-request-id response header.

// app/chat/route.ts (final)
import { streamText } from 'ai';
import { routedModel } from '@/lib/gateway';

export async function POST(req: Request) {
  const { messages, route, userId } = await req.json();
  const requestId = crypto.randomUUID();

  const result = streamText({
    model: routedModel('openai/gpt-4o', route),
    messages,
    headers: {
      'x-request-id': requestId,
    },
    onFinish: async ({ usage, response }) => {
      await fetch('https://your-billing-api/record', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          requestId,
          userId,
          model: response.modelId,
          promptTokens: usage.promptTokens,
          completionTokens: usage.completionTokens,
          totalTokens: usage.totalTokens,
          timestamp: new Date().toISOString(),
        }),
      });
    },
  });

  return result.toDataStreamResponse({
    headers: {
      'x-request-id': requestId,
    },
  });
}

The x-request-id header threads through to n4n.ai and back, letting you trace a single chat turn from frontend to provider and back.

Troubleshooting checklist

  • 401 Unauthorized: Verify N4N_API_KEY is set in the deployment environment, not just locally.
  • 404 Model not found: Use the exact slug from the n4n.ai model catalog (for example, anthropic/claude-3-5-sonnet-20241022, not claude-3.5-sonnet).
  • Stream hangs: Ensure your platform supports streaming responses (Vercel, AWS Lambda with response streaming, Cloudflare Workers). Some serverless platforms buffer by default.
  • Fallback not triggering: Confirm the routing directive allows fallback (prefer:cost vs require:anthropic). A require directive pins to one provider and disables fallback.
  • Usage missing in onFinish: The usage object is only populated on the final chunk. If you transform the stream before onFinish, ensure you don’t drop the last chunk.

Next steps

You now have a production-ready Vercel AI SDK integration that routes through a single gateway endpoint. From here you can:

  • Add edge caching for repeated prompts using the cache-control hints.
  • Build a model router UI that lets users pick cost, speed, or quality tiers.
  • Implement per-tenant budgets by gating requests in onFinish before they reach the gateway.
  • Explore n4n.ai’s batch endpoint for offline workloads — same baseURL, different path.

The pattern stays the same: one client, one baseURL, many models.

Tagsvercel-ai-sdkn4n-aibaseurlconfiguration

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All vercel ai sdk getting started with n4n.ai posts →