token.js wraps ten-plus LLM providers in one OpenAI-shaped call, with a compatibility table
Integrate 200+ LLMs with one TypeScript SDK using OpenAI's format.
At a glance
- What is it?
- token.js is a TypeScript SDK that keeps OpenAI's request shape while routing to Anthropic, Bedrock, Gemini, Mistral, Cohere, AI21, Groq, Perplexity, OpenRouter, Requesty, and anything else with an OpenAI compatible API. The interesting parts are the edges: feature support is uneven across providers, extending the model list copies another model's capabilities, and the manifest version sits ahead of the newest release tag.
- Who is it for?
- token.js fits a team that wants to compare or swap models without rewriting call sites, since the provider and model are request fields rather than a different client per vendor, and the compatibility table tells you in advance which provider will silently drop a feature. It does not fit a browser app that must not ship a provider key, because the design has no server component, and it does not fit code that needs every provider to behave identically.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 98 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The call looks like OpenAI's, with one extra required field
Usage is four lines of setup.
npm install token.jsimport { TokenJS } from 'token.js'The client is constructed with `new TokenJS()`, and the request goes through `tokenjs.chat.completions.create`. The body is the familiar OpenAI shape with `messages`, and each entry has a `role` and a `content`, exactly as you would write against the official SDK.
The difference is a required `provider` field alongside `model`. That single addition is what makes the abstraction work: the provider is chosen per request rather than per client instance, so one client can talk to OpenAI for one call and Anthropic for the next without reconfiguration.
That design also means the provider cannot be defaulted silently in a way you forget about. Every call states where it is going, which is an extra line of noise compared with a single vendor SDK but removes an entire category of routing mistake.
Streaming follows the same shape with `stream: true`, and the result is iterated with `for await`, reading `part.choices[0]?.delta?.content` on each chunk, so the delta path is identical to OpenAI's.
Feature support is uneven, and the table is where the truth lives
The project publishes a per-provider matrix across chat completion, streaming, function calling, JSON output, and image input, and the gaps are the story.
OpenAI, Anthropic, Bedrock, Gemini, OpenRouter, Requesty, and any OpenAI compatible endpoint support all five. Mistral supports everything except image input. Cohere has no function calling tool and no JSON output. AI21 supports chat, streaming, and images, but neither function calling nor JSON output.
Groq and Perplexity are the two that are easy to trip over: both give you streaming and JSON output, but not function calling, and neither accepts image input.
Read as a decision aid, the matrix is more useful than the provider count. If your application depends on structured output, Cohere and AI21 and Perplexity are off the table for that feature. If it depends on tool calls, the exclusions are Groq, Perplexity, Cohere, and AI21, which is a longer list than most people expect from a "one SDK" pitch.
The safest usage pattern follows from this: treat the intersection as your floor and only use the extras where you have checked the row for the provider in front of you.
Extending the model list copies another model's features and needs a type cast
Models the predefined list does not know about can be registered at runtime through `extendModelList`, and the mechanism is copy based rather than declarative.
You pass three things: the provider, the new model name, and the name of an existing model from that provider whose feature support should be copied.
tokenjs.extendModelList(
'bedrock',
'us.anthropic.claude-3-5-sonnet-20241022-v2:0',
'anthropic.claude-3-sonnet-20240229-v1:0'
);The documented scenarios are all about time or naming rather than about new capability: adding Bedrock models with regional prefixes, supporting a new model version before it reaches the predefined list, using custom deployments with unique names, and adding experimental or beta models during testing.
The `featureSupport` argument has two forms. It can be a string naming an existing model from the same provider, which is the copy behaviour above, or an object with four booleans describing what the model supports: `streaming`, `json`, `toolCalls`, and `images`.
The catch is stated without softening: when you use an extended model, type casting is required. The request in the example passes the model through `as any`, which is the price of adding a name the type definitions have never seen. It is a small thing, but it means extended models silently opt out of the SDK's type checking for that field.
Credentials are a dozen environment variables with no config file
The recommended approach is environment variables, one per provider, and the set is long enough to be worth copying.
# OpenAI
OPENAI_API_KEY=
# AI21
AI21_API_KEY=
# Anthropic
ANTHROPIC_API_KEY=
# Cohere
COHERE_API_KEY=The full list continues with `GEMINI_API_KEY`, `GROQ_API_KEY`, `MISTRAL_API_KEY`, `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, and `REQUESTY_API_KEY`. AWS Bedrock is different because it needs a region and a key pair rather than one token: `AWS_REGION_NAME`, `AWS_ACCESS_KEY_ID`, and `AWS_SECRET_ACCESS_KEY`. Anything with an OpenAI compatible API reads `OPENAI_COMPATIBLE_API_KEY`.
The shipped `.env.example` is shorter than the documentation. It covers the nine single-key providers plus the Bedrock trio, and does not include `REQUESTY_API_KEY` or `OPENAI_COMPATIBLE_API_KEY`.
That gap is worth knowing about before you file it as a bug: a provider can be fully supported while having no template line, so a fresh copy of the example file is not a complete checklist of what you might need to set.
There is no configuration object and no credentials object passed to the constructor, so the environment is the only wiring.
It is an ESM package with a CommonJS entry, and both are tested
The packaging is dual, and the test setup follows from it.
The manifest declares `type` as `module` while pointing `main` at `dist/index.cjs` and `types` at `dist/index.d.ts`. So consumers on either module system are served, and the published files are only `dist/*`, which keeps the source tree and the tooling out of the tarball.
Three test commands exist because there are three things to check. `test` runs vitest against the `test/automated` directory. `test:commonjs` runs jest against a single CommonJS test file, `test/commonjs/index.test.cjs`, which is the interop check for the `.cjs` entry. And `test:manual` runs a separate directory with a test timeout of 100000, which is a hundred seconds per test, the signature of tests that call live providers rather than fixtures.
The build is two steps, `vite-node` over a build script and then `tsc` against the project config, and the docs are generated and verified by two more `vite-node` scripts under `scripts/docs`, one to update and one to check.
For a package whose entire value is a consistent interface, having an interop test and a live-provider suite is the right allocation.
Client side means there is no server holding your provider key
One of the listed features is that it runs completely on the client side with no proxy server needed, and that claim is doing more work than it first appears.
A proxy server is the usual place to keep a provider key. If the SDK runs entirely in the client, then the key travels from the client to whichever provider receives the request, which means the application is responsible for that key. The documentation's credential section says nothing about a server, a key exchange, or a broker, because there is none in this design.
That is the correct trade for a server-side application and a TypeScript toolchain, where the key already lives in environment variables on a machine you control. It is a different trade for anything running in a browser, where an environment variable is compiled into the bundle unless you do something to prevent it.
The practical reading is that this SDK suits Node and edge runtimes where you already hold provider credentials, and that adding a browser front end requires a server of your own in front of it.
The claim also has an upside for anyone who objects to third-party gateways: requests go straight to the provider rather than through someone else's inference proxy, with the tradeoff that you lose the request logging such a proxy would give you.
The manifest version is ahead of the newest tag and the provider count differs
The package manifest declares version 0.8.1. The newest release in the history is v0.7.1, with v0.7.0 the same day and v0.6.0 a fortnight earlier.
So the version you would install from the registry depends on what has been published, and the version in the repository is ahead of the newest tag. Anyone comparing your lockfile against the repository will see a difference that is not a mistake.
The release dates also place the published line in 2025 while the last push to the main branch is dated 2026-07-05, so the branch has taken work that has not produced a tag.
A second discrepancy is in the counts. The readme leads with 200+ LLMs from 10+ providers and lists ten named providers plus the OpenAI compatible escape hatch, while the manifest description says integrate 9 LLM providers with a single Typescript SDK. The named list has grown since the description was written.
None of this makes the SDK unusable, but it does mean the marketing numbers in the readme and the metadata in the manifest are maintained separately, so quote one or the other and not a blend of both.
Docs are generated from the source and checked in CI
Documentation is not a folder of hand written pages in this repository. Two scripts under `scripts/docs` do the work: one generates the documentation and one checks it.
That pattern, generate then check, is what keeps a documentation site from drifting away from the code it describes. A generation step writes the output, and the check step fails when the committed output no longer matches what the generator produces, so a change to the SDK without a documentation rebuild shows up as a failure rather than as a stale website.
The site itself lives at `docs.tokenjs.ai` and is linked from the readme rather than being described inline, so the readme stays short enough to scan: features, provider list, install, usage, credentials, streaming, function calling, extending models, and the compatibility table.
The remaining repository furniture is conventional for a maintained SDK: a `CHANGELOG.md`, a `CONTRIBUTING.md`, a pull request template, a `.changeset/` directory for changesets, ESLint and Prettier configuration, and both `jest.config.cjs` and `vitest.config.ts` present because two runners are in use.
Licence is MIT and the author field names a single maintainer.
Editorial conclusion
token.js fits a team that wants to compare or swap models without rewriting call sites, since the provider and model are request fields rather than a different client per vendor, and the compatibility table tells you in advance which provider will silently drop a feature. It does not fit a browser app that must not ship a provider key, because the design has no server component, and it does not fit code that needs every provider to behave identically. Before adopting it, check the matrix for the features you actually use, pin the version rather than tracking the manifest, and read the type cast requirement before extending the model list.
Frequently asked questions
How many LLM providers can token.js use?
The readme states 200+ LLMs from 10+ providers and names AI21, Anthropic, AWS Bedrock, Cohere, Gemini, Groq, Mistral, OpenAI, Perplexity, OpenRouter, and Requesty, plus any provider with an OpenAI compatible API. The package manifest description is older and says 9 providers.
How do I call a model with token.js?
Run npm install token.js, import TokenJS from the package, construct it with new TokenJS(), and call tokenjs.chat.completions.create with a messages array plus the required provider and model fields. Set the provider credential as an environment variable such as OPENAI_API_KEY.
Does token.js need a proxy server?
No. It is listed as running completely on the client side with no proxy server needed, which also means there is no server component holding your provider key, so key handling stays with your application.
How do I add a model that is not in the predefined list?
Call extendModelList with the provider, the new model name, and either the name of an existing model from that provider whose feature support should be copied, or an object with streaming, json, toolCalls, and images booleans. Using an extended model requires a type cast in the request.
Which token.js providers support function calling and JSON output?
OpenAI, Anthropic, Bedrock, Gemini, OpenRouter, Requesty, and OpenAI compatible endpoints support both. Cohere and AI21 support neither, while Groq and Perplexity support JSON output but not function calling, and Mistral supports both but not image input.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/token-js-token-js)