Skip to content
Scalekit Docs
Talk to an Engineer Dashboard

Route LLM calls through FastRouter with Scalekit AgentKit tools

Build a Node.js agent that uses FastRouter as its LLM provider and Scalekit AgentKit for per-user OAuth-connected tools — Gmail, GitHub, Slack, and more — without writing OAuth code per integration.

Build an agent that routes LLM calls through FastRouter and executes OAuth-connected tools through Scalekit AgentKit. FastRouter provides an OpenAI-compatible chat completions API, so the integration requires only one configuration change: point the OpenAI SDK’s baseURL at FastRouter. Scalekit handles OAuth token storage, tool discovery, and tool execution for every connected service.

The sample repository is fastrouter-scalekit-demo on GitHub.

  • FastRouter as the LLM provider — All chat completions go through FastRouter’s OpenAI-compatible endpoint. Switch models by changing one environment variable.
  • Scalekit AgentKit for tool accesslistScopedTools returns per-user tool schemas ready to pass directly to FastRouter. executeTool runs each tool server-side and returns structured results.
  • B2B OAuth without custom OAuth code — Scalekit handles the OAuth flow, token storage, and refresh for each connected service. Your agent gets an auth link, waits for the user to authorize, and receives a verified, active connected account.
  • Agentic loop — The agent calls FastRouter, receives tool calls, executes them through Scalekit, and feeds results back — repeating until FastRouter returns a final answer.
  1. Clone the repository and install dependencies.

    Terminal window
    git clone https://github.com/scalekit-developers/fastrouter-scalekit-demo
    cd fastrouter-scalekit-demo
    npm install
  2. Copy the example environment file and fill in your credentials.

    Terminal window
    cp .env.example .env

    Open .env and set these values:

    Terminal window
    # Scalekit — find these in your Scalekit dashboard under API Keys
    SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.dev
    SCALEKIT_CLIENT_ID=your_client_id
    SCALEKIT_CLIENT_SECRET=your_client_secret
    # The AgentKit connection to use — must match a connection name in your dashboard
    SCALEKIT_CONNECTION_NAME=gmail
    # FastRouter — find your API key at fastrouter.ai/dashboard
    FASTROUTER_API_KEY=sk-v1-...
    FASTROUTER_MODEL=openai/gpt-4o-mini

    SCALEKIT_CONNECTION_NAME must match the exact connection name in your Scalekit dashboard under AgentKit → Connections.

  3. Run the agent.

    Terminal window
    npm start
  4. Authorize the connection on first run.

    The agent prints an authorization link if the connected account is not yet active:

    Authorization required.
    Open this link and complete the flow:
    https://your-env.scalekit.dev/magicLink/...
    Waiting for callback on http://localhost:3000/callback ...

    Open the link in your browser and complete the OAuth flow. The agent detects the callback automatically and continues — no manual step required.

After authorization, the agent loads tools, calls FastRouter, and prints a final answer:

Connected account is now active.
Loaded 17 scoped tools from Scalekit.
Model requested 1 tool call(s).
→ Executing gmail_list_messages
args: {"maxResults":5,"q":"is:unread"}
Final answer:
Here are your 5 most recent unread emails: ...

Three pieces connect FastRouter to Scalekit tools.

B2B OAuth connects user accounts without custom token code

Section titled “B2B OAuth connects user accounts without custom token code”

Scalekit handles the full OAuth flow. Your agent calls getOrCreateConnectedAccount to check whether the user’s account is already connected, then calls getAuthorizationLink to get an auth URL if it isn’t.

const userVerifyUrl = 'http://localhost:3000/callback';
const { connectedAccount } = await scalekit.actions.getOrCreateConnectedAccount({
connectionName: 'gmail',
identifier: 'user_123',
userVerifyUrl,
});
if (connectedAccount?.status !== ConnectorStatus.ACTIVE) {
const { link } = await scalekit.actions.getAuthorizationLink({
connectionName: 'gmail',
identifier: 'user_123',
userVerifyUrl,
});
// Show link to user, wait for callback
}

userVerifyUrl is where Scalekit redirects after the OAuth flow completes. The sample runs a minimal HTTP server on localhost:3000 to catch that redirect, extract the auth_request_id parameter, and call verifyConnectedAccountUser to mark the account active:

async function waitForCallback(port: number): Promise<string> {
return new Promise((resolve, reject) => {
const server = http.createServer((req, res) => {
const url = new URL(req.url ?? '/', `http://localhost:${port}`);
const authRequestId = url.searchParams.get('auth_request_id');
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<html><body><h2>Authorization complete — return to your terminal.</h2></body></html>');
server.close();
if (authRequestId) resolve(authRequestId);
else reject(new Error('No auth_request_id in callback'));
});
server.listen(port);
});
}
const authRequestId = await waitForCallback(3000);
await scalekit.actions.verifyConnectedAccountUser({
authRequestId,
identifier: 'user_123',
});

Tool discovery returns schemas in FastRouter’s expected format

Section titled “Tool discovery returns schemas in FastRouter’s expected format”

listScopedTools returns only the tools the connected account has permission to use. Map each tool’s input_schema to the parameters field FastRouter expects:

const { tools } = await scalekit.tools.listScopedTools('user_123', {
filter: { connectionNames: ['gmail'] },
pageSize: 100,
});
const fastRouterTools = tools
.map((t) => t.tool?.definition)
.filter((def): def is NonNullable<typeof def> => Boolean(def?.name))
.map((def) => ({
type: 'function' as const,
function: {
name: String(def.name),
description: String(def.description ?? ''),
parameters: def.input_schema ?? { type: 'object', properties: {} },
},
}));

FastRouter uses the same function-calling format as OpenAI. No additional schema transformation is needed.

The agentic loop runs until the model stops requesting tools

Section titled “The agentic loop runs until the model stops requesting tools”

Pass the tool list to FastRouter and execute each tool call through Scalekit until the model returns a response with no tool calls:

const messages: OpenAI.ChatCompletionMessageParam[] = [
{ role: 'system', content: 'You are a helpful assistant. Use tools when they help. Do not invent tool results.' },
{ role: 'user', content: 'Fetch my last 5 unread emails and summarize them.' },
];
for (let turn = 0; turn < 8; turn++) {
const response = await fastRouter.chat.completions.create({
model: 'openai/gpt-4o-mini',
messages,
tools: fastRouterTools,
tool_choice: 'auto',
});
const message = response.choices[0].message;
messages.push(message);
// No tool calls means a final answer
if (!message.tool_calls?.length) {
console.log(message.content);
return;
}
// Execute each tool call and append the result
for (const call of message.tool_calls) {
const result = await scalekit.actions.executeTool({
toolName: call.function.name,
identifier: 'user_123',
connector: 'gmail',
toolInput: JSON.parse(call.function.arguments),
});
messages.push({
role: 'tool',
tool_call_id: call.id,
content: JSON.stringify(result.data ?? {}),
});
}
}

executeTool runs the tool server-side using the connected account’s stored OAuth tokens. Your agent never handles raw access tokens.

Change the connection. Set SCALEKIT_CONNECTION_NAME to any connection configured in your Scalekit dashboard:

ValueWhat it connects
gmailGmail read/send
githubRepositories, issues, pull requests
slackChannels, messages, users

Change the model. Set FASTROUTER_MODEL in .env to any model FastRouter supports. The agent uses the same code regardless of which model you choose.

Change the prompt. Pass a prompt as a CLI argument to override the default:

Terminal window
npm start "List all GitHub pull requests assigned to me"

Or set USER_PROMPT in .env to change the default.

Support multiple connections. Call listScopedTools with multiple connection names to give the model tools from all of them at once:

const { tools } = await scalekit.tools.listScopedTools('user_123', {
filter: { connectionNames: ['gmail', 'github', 'slack'] },
});