diff --git a/src/content/docs/cookbooks/fastrouter-agentkit-tool-calling.mdx b/src/content/docs/cookbooks/fastrouter-agentkit-tool-calling.mdx
index 7ee2e1845..9511c022e 100644
--- a/src/content/docs/cookbooks/fastrouter-agentkit-tool-calling.mdx
+++ b/src/content/docs/cookbooks/fastrouter-agentkit-tool-calling.mdx
@@ -1,13 +1,13 @@
---
-title: 'FastRouter + Scalekit AgentKit tool calling'
-description: 'Build a Node.js agent that routes LLM calls through FastRouter and uses Scalekit AgentKit for per-user OAuth tools.'
+title: 'FastRouter + Scalekit tool calling'
+description: 'Build a Node.js agent that routes LLM calls through FastRouter and uses Scalekit for per-user OAuth tools.'
date: 2026-05-26
tags: ['Agent auth', 'Gmail', 'FastRouter', 'Tool calling', 'AgentKit']
sidebar:
label: 'Tool calling with FastRouter'
tableOfContents: true
excerpt: >
- Connect FastRouter's OpenAI-compatible API to per-user OAuth tools via Scalekit AgentKit. The agent discovers available tools, runs an agentic loop through FastRouter, and executes each tool call via Scalekit — no per-integration OAuth code required.
+ Connect FastRouter's OpenAI-compatible API to per-user OAuth tools via Scalekit. The agent discovers available tools, runs an agentic loop through FastRouter, and executes each tool call via Scalekit — no per-integration OAuth code required.
featured: false
authors:
- name: 'Saif'
@@ -18,14 +18,14 @@ authors:
import { Aside, Steps, Tabs, TabItem } from '@astrojs/starlight/components';
-Build an agent that routes LLM calls through [FastRouter](https://fastrouter.ai) 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.
+Build an agent that routes LLM calls through [FastRouter](https://fastrouter.ai). 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 extends that with per-user OAuth tool access, so your agent can read Gmail, create GitHub issues, or post to Slack on behalf of individual users. You can choose from [100+ connectors](/agentkit/connectors/). Scalekit handles OAuth token storage, tool discovery, and tool execution for every connected service.
The sample repository is **[fastrouter-scalekit-demo](https://github.com/scalekit-developers/fastrouter-scalekit-demo)** on GitHub.
## What you are building
- **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 access** — `listScopedTools` returns per-user tool schemas ready to pass directly to FastRouter. `executeTool` runs each tool server-side and returns structured results.
+- **Scalekit for tool access** — `listScopedTools` 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.
@@ -35,6 +35,7 @@ The sample repository is **[fastrouter-scalekit-demo](https://github.com/scaleki
- At least one AgentKit connection configured (Gmail, GitHub, or Slack)
- FastRouter account and API key — [sign up at fastrouter.ai](https://fastrouter.ai)
- Node.js 20 or later
+- For Python code examples: `pip install google-protobuf` (required for tool schema deserialization)
## Clone and run the sample
@@ -58,7 +59,7 @@ The sample repository is **[fastrouter-scalekit-demo](https://github.com/scaleki
```sh
# Scalekit — find these in your Scalekit dashboard under API Keys
- SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.dev
+ SCALEKIT_ENV_URL=https://your-env.scalekit.dev
SCALEKIT_CLIENT_ID=your_client_id
SCALEKIT_CLIENT_SECRET=your_client_secret
@@ -118,6 +119,10 @@ Three pieces connect FastRouter to Scalekit tools.
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.
+
+
@@ -125,6 +130,11 @@ Scalekit handles the full OAuth flow. Your agent calls `getOrCreateConnectedAcco
import { ConnectorStatus } from '@scalekit-sdk/node/lib/pkg/grpc/scalekit/v1/connected_accounts/connected_accounts_pb';
import crypto from 'node:crypto';
+const connectionName = process.env.SCALEKIT_CONNECTION_NAME;
+if (!connectionName) {
+ throw new Error('SCALEKIT_CONNECTION_NAME is required');
+}
+
const userVerifyUrl = 'http://localhost:3000/callback';
// Generate a random state value and store it (e.g. in a secure cookie or session)
@@ -132,14 +142,14 @@ const userVerifyUrl = 'http://localhost:3000/callback';
const state = crypto.randomUUID();
const { connectedAccount } = await scalekit.actions.getOrCreateConnectedAccount({
- connectionName: 'gmail',
+ connectionName,
identifier: 'user_123',
userVerifyUrl,
});
if (connectedAccount?.status !== ConnectorStatus.ACTIVE) {
const { link } = await scalekit.actions.getAuthorizationLink({
- connectionName: 'gmail',
+ connectionName,
identifier: 'user_123',
userVerifyUrl,
state,
@@ -152,22 +162,24 @@ if (connectedAccount?.status !== ConnectorStatus.ACTIVE) {
```python
+import os
import secrets
+connection_name = os.environ["SCALEKIT_CONNECTION_NAME"]
user_verify_url = "http://localhost:3000/callback"
# Generate and store a state value (e.g. in a secure, HTTP-only cookie) for CSRF protection
state = secrets.token_urlsafe(32)
response = scalekit_client.actions.get_or_create_connected_account(
- connection_name="gmail",
+ connection_name=connection_name,
identifier="user_123",
user_verify_url=user_verify_url,
)
if response.connected_account.status != "ACTIVE":
link_resp = scalekit_client.actions.get_authorization_link(
- connection_name="gmail",
+ connection_name=connection_name,
identifier="user_123",
user_verify_url=user_verify_url,
state=state,
@@ -175,62 +187,6 @@ if response.connected_account.status != "ACTIVE":
# Show link_resp.link to the user
```
-
-
-
-```go
-import (
- "context"
- "crypto/rand"
- "encoding/hex"
- "fmt"
- "log"
-)
-
-userVerifyURL := "http://localhost:3000/callback"
-
-// generate state for CSRF protection (store it for callback validation)
-b := make([]byte, 16)
-rand.Read(b)
-state := hex.EncodeToString(b)
-
-resp, err := scalekitClient.Actions.GetOrCreateConnectedAccount(
- context.Background(), "gmail", "user_123",
-)
-if err != nil {
- log.Fatal(err)
-}
-
-if resp.ConnectedAccount.Status != "ACTIVE" {
- link, _ := scalekitClient.Actions.GetAuthorizationLink(
- context.Background(), "gmail", "user_123",
- )
- // Reference state + userVerifyURL so Go sees them as used.
- fmt.Printf("Authorize: %s (state=%s, callback=%s)\n", link.Link, state, userVerifyURL)
-}
-```
-
-
-
-
-```java
-import java.util.UUID;
-
-// Generate state for CSRF protection (store for later validation in callback)
-String state = UUID.randomUUID().toString();
-
-ConnectedAccountResponse response = scalekitClient.actions()
- .getOrCreateConnectedAccount("gmail", "user_123");
-
-ConnectedAccount account = response.getConnectedAccount();
-if (!"ACTIVE".equals(account.getStatus())) {
- AuthorizationLink link = scalekitClient.actions()
- .getAuthorizationLink("gmail", "user_123");
- System.out.println("Authorize: " + link.getLink());
- // Pass state and userVerifyUrl here when the Java SDK overload supports it
-}
-```
-
@@ -283,34 +239,6 @@ result = scalekit_client.actions.verify_connected_account_user(
# redirect to result.post_user_verify_redirect_url
```
-
-
-
-```go
-// In your HTTP handler for the callback:
-// - Read state and auth_request_id from query params
-// - Validate state against the one you generated and stored
-// - Then call verify
-
-resp, err := scalekitClient.Actions.VerifyConnectedAccountUser(
- context.Background(),
- authRequestID,
- "user_123",
-)
-```
-
-
-
-
-```java
-// In your servlet / controller callback handler:
-// 1. Validate state query param matches the stored value
-// 2. Call verify only on success
-
-VerifyConnectedAccountUserResponse resp = scalekitClient.actions()
- .verifyConnectedAccountUser(authRequestId, "user_123");
-```
-
@@ -324,7 +252,7 @@ In a production web app, replace `localhost:3000/callback` with your server's ca
```typescript
const { tools } = await scalekit.tools.listScopedTools('user_123', {
- filter: { connectionNames: ['gmail'] },
+ filter: { connectionNames: [connectionName] },
pageSize: 100,
});
@@ -375,7 +303,7 @@ for (let turn = 0; turn < 8; turn++) {
const result = await scalekit.actions.executeTool({
toolName: call.function.name,
identifier: 'user_123',
- connector: 'gmail',
+ connector: connectionName,
toolInput: JSON.parse(call.function.arguments),
});
@@ -420,7 +348,7 @@ const { tools } = await scalekit.tools.listScopedTools('user_123', {
## Next steps
-- **[Scalekit AgentKit overview](/agentkit/connections)** — Understand connected accounts, tool discovery, and tool execution in depth.
+- **[Scalekit overview](/agentkit/connections)** — Understand connected accounts, tool discovery, and tool execution in depth.
- **[AgentKit connections](/agentkit/connectors)** — Set up Gmail, GitHub, Slack, and other connections.
- **[OpenAI example](/agentkit/examples/openai)** — See the same tool-calling pattern with OpenAI directly.
- **[LiteLLM inbox triage cookbook](/cookbooks/litellm-agentkit-inbox-triage)** — A more complex multi-connection agent with a web approval interface.