Skip to main content

Use OceanToken with the Vercel AI SDK

OceanToken's API is OpenAI-compatible, so the AI SDK's OpenAI Compatible provider, @ai-sdk/openai-compatible, works with a base URL and your key. There is no OceanToken package to install. Claude models also work with @ai-sdk/anthropic, the AI SDK's Anthropic provider (below).

Every sample on this page is a file we run against the service before publishing. They were tested on 2026-10-19 with ai 7.0.142, @ai-sdk/openai-compatible 3.0.70, @ai-sdk/anthropic 4.0.81 and zod 4.6.7.

Install​

npm install ai @ai-sdk/openai-compatible zod
export OCEANTOKEN_API_KEY=sk-...

Create an API key in the console and give it a Credit limit if you want a cap on what this app can spend. Use the key on the server only: in a route handler, a server action or a backend job, never in code that runs in the browser.

Create the provider​

Create the provider once and import it wherever you call a model:

oceantoken.ts
// npm install ai @ai-sdk/openai-compatible
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";

export const oceantoken = createOpenAICompatible({
name: "oceantoken",
baseURL: process.env.OCEANTOKEN_BASE_URL ?? "https://api.oceantoken.ai/v1",
apiKey: process.env.OCEANTOKEN_API_KEY,
// Token counts in streamed responses, as in non-streamed ones.
includeUsage: true,
// Send your schema with structured-output requests (Output.object).
supportsStructuredOutputs: true,
});
  • baseURL is https://api.oceantoken.ai/v1. The samples read OCEANTOKEN_BASE_URL first only so that we can run them against a test environment; you can leave it out.
  • apiKey is sent as Authorization: Bearer <key>.
  • includeUsage: true adds token counts to streamed responses.
  • supportsStructuredOutputs: true sends your schema with structured output requests. Without it the SDK asks only for JSON, with no schema.

Pass a model ID exactly as the Models page writes it, with the publisher in front: openai/gpt-5.5, anthropic/claude-sonnet-5.5, deepseek/deepseek-v4.1-flash, google/gemini-3.5-flash. GET https://api.oceantoken.ai/v1/models with your key lists every model the key can use, and the full catalog with prices is also published as JSON at https://api.oceantoken.ai/public/model_hub.

Generate text​

generate-text.ts
import { generateText } from "ai";
import { oceantoken } from "./oceantoken";

const { text, usage } = await generateText({
model: oceantoken("openai/gpt-5.5"),
prompt: "Explain what an API gateway does in one sentence.",
});

console.log(text);
console.log(usage.inputTokens, "input tokens,", usage.outputTokens, "output tokens");

usage has the token counts the request was billed for. The same request appears in the console under Logs with its cost.

Stream text​

stream-text.ts
import { streamText } from "ai";
import { oceantoken } from "./oceantoken";

const result = streamText({
model: oceantoken("deepseek/deepseek-v4.1-flash"),
prompt: "Write a four-line poem about a lighthouse.",
});

for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}

const usage = await result.usage; // needs includeUsage: true
console.log("\n", usage.inputTokens, "input tokens,", usage.outputTokens, "output tokens");

Stream anything a person is waiting on: tokens arrive as they are generated, and long answers do not run into the request timeout described in Limits.

Call tools​

tools.ts
// npm install zod
import { generateText, stepCountIs, tool } from "ai";
import { z } from "zod";
import { oceantoken } from "./oceantoken";

const { text, steps } = await generateText({
model: oceantoken("google/gemini-3.5-flash"),
tools: {
weather: tool({
description: "Get the current weather in a city",
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => ({ city, temperatureC: 18, sky: "clear" }),
}),
},
// Let the model call the tool, read the result, then answer.
stopWhen: stepCountIs(5),
prompt: "What is the weather in Lisbon right now?",
});

console.log(text);
console.log(steps.flatMap((step) => step.toolCalls.map((call) => call.toolName)));

The model asks for weather, the SDK runs execute and sends the result back, and the model answers with it. stopWhen: stepCountIs(5) allows up to five such steps; without it, generation stops after the first tool call. Tool calling also works with streamText. Use a model whose page on Models lists tool calling; the four models named above were tested with both generateText and streamText.

Structured output​

structured-output.ts
import { generateText, Output } from "ai";
import { z } from "zod";
import { oceantoken } from "./oceantoken";

const { output } = await generateText({
model: oceantoken("openai/gpt-5.5"),
output: Output.object({
schema: z.object({
name: z.string(),
ingredients: z.array(z.object({ item: z.string(), amount: z.string() })),
minutes: z.number(),
}),
}),
prompt: "A simple recipe for pancakes.",
});

console.log(output.name, output.minutes, output.ingredients.length);

The SDK validates the answer against your schema, and output is typed from it. This needs supportsStructuredOutputs: true on the provider. In AI SDK 7, generateObject is deprecated in favour of generateText with output; it still works with the same provider.

Embeddings​

embeddings.ts
import { cosineSimilarity, embedMany } from "ai";
import { oceantoken } from "./oceantoken";

const { embeddings, usage } = await embedMany({
model: oceantoken.embeddingModel("openai/text-embedding-3-small"),
values: [
"The lighthouse keeper rowed out before dawn.",
"At first light the keeper took the boat out.",
"Quarterly revenue rose by four percent.",
],
});

console.log(embeddings[0].length, usage);
console.log(cosineSimilarity(embeddings[0], embeddings[1])); // close in meaning
console.log(cosineSimilarity(embeddings[0], embeddings[2])); // unrelated

Use oceantoken.embeddingModel() with any embedding model from the Models page, for example openai/text-embedding-3-small or baai/bge-m3. embed takes one value and embedMany several.

Generate images​

image.ts
import { randomUUID } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { generateImage } from "ai";
import { oceantoken } from "./oceantoken";

const { image } = await generateImage({
model: oceantoken.imageModel("openai/gpt-image-2"),
prompt: "A small lighthouse on a calm sea at dawn, watercolor",
size: "1024x1024",
// A retry with the same key, including the SDK's own retries, gets the
// first result back instead of running (and charging) a second time.
headers: { "Idempotency-Key": randomUUID() },
});

await writeFile("lighthouse.png", image.uint8Array);
console.log(image.mediaType, image.uint8Array.length, "bytes");

Use oceantoken.imageModel() with any image model from the Models page. The SDK downloads the result, so image holds the file itself. Image requests are charged when they run, and the SDK retries failed requests twice by default; the Idempotency-Key header makes a retry return the first result instead of running again (Errors).

Claude with @ai-sdk/anthropic​

Claude models work through the OpenAI-compatible provider above like any other model. To use the AI SDK's Anthropic provider instead, give it the same base URL:

npm install ai @ai-sdk/anthropic
claude.ts
// npm install ai @ai-sdk/anthropic
// Claude models also answer on the Anthropic Messages API. Unlike the
// Anthropic SDKs, this package's baseURL includes /v1.
import { createAnthropic } from "@ai-sdk/anthropic";
import { generateText } from "ai";

const anthropic = createAnthropic({
baseURL: process.env.OCEANTOKEN_BASE_URL ?? "https://api.oceantoken.ai/v1",
apiKey: process.env.OCEANTOKEN_API_KEY,
});

const { text, usage } = await generateText({
model: anthropic("anthropic/claude-sonnet-5.5"),
prompt: "Explain what an API gateway does in one sentence.",
});

console.log(text);
console.log(usage.inputTokens, "input tokens,", usage.outputTokens, "output tokens");

@ai-sdk/anthropic takes https://api.oceantoken.ai/v1, with /v1. The Anthropic SDKs for Python and TypeScript take https://api.oceantoken.ai and add /v1 themselves (Quickstart).

Text, streaming and tool calling work as above. For structured output, set the provider's JSON-tool mode:

const { output } = await generateText({
model: anthropic("anthropic/claude-sonnet-5.5"),
output: Output.object({ schema }),
providerOptions: { anthropic: { structuredOutputMode: "jsonTool" } },
prompt: "...",
});

Without it, @ai-sdk/anthropic sends the schema as output_config.format, which OceanToken does not apply yet: the model answers in prose and the SDK reports that no object was generated. The OpenAI-compatible provider needs no such setting for Claude.

Troubleshooting​

What you seeWhat to do
Invalid API key. Check the key, or create one in the OceanToken console.The key is wrong, revoked or has a stray space. Check OCEANTOKEN_API_KEY, or create a new key.
Unknown model '…'. GET /v1/models lists the models your key can use.Use the model ID exactly as the Models page writes it, with the publisher in front (deepseek/deepseek-v4.1-flash, not deepseek-v4.1-flash).
Insufficient balance - please add credit to continue.Add credits.
No object generated with Output.objectOn createOpenAICompatible, set supportsStructuredOutputs: true. With @ai-sdk/anthropic, set structuredOutputMode: "jsonTool" (above).
usage is empty after streamTextSet includeUsage: true on the provider.

The SDK raises an APICallError whose statusCode and responseBody carry OceanToken's error; Errors lists every code and what to do about it.

Resources​