Migrate to AgentKit
Move to AgentKit from your in-house solution, Composio, Nango, Pipedream Connect or another agent integration platform.
Use this guide to move an agent from another integration platform, such as your current in-house solution, Composio, Nango, Pipedream Connect or Arcade, to AgentKit. You set up Scalekit, create a connection for each app, move your users’ access, and then move your agent’s tool calls.
There are two ways to move your users. Choose per connection:
| Users authorize again | Import existing credentials | |
|---|---|---|
| What users do | Connect each app once more, through an authorization link | Nothing |
| What you need | Nothing from the old platform | Each user’s credential, exported from the old platform |
| OAuth apps | Any connection | Your own OAuth app, with the same client ID that issued the tokens |
| Best for | Tokens you can’t export | Credentials you can export |
A refresh token works only with the OAuth app that issued it. If the old platform signed users in with its own OAuth app, those users authorize again.
Coming from one of these platforms? Its guide maps the platform’s concepts to AgentKit, says which credentials you can export, and shows your agent code before and after:
1. Set up Scalekit
Section titled “1. Set up Scalekit”-
Create a Scalekit account
Sign up at app.scalekit.com and copy your API credentials from Developers > Settings > API Credentials.
-
Set environment variables
Add them to a
.envfile that you don’t commit:.env SCALEKIT_CLIENT_ID=your_client_idSCALEKIT_CLIENT_SECRET=your_client_secretSCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com -
Install the SDK and create the client
There’s no session object: you pass the user’s
identifieron each call. Later blocks reuse this setup.Terminal window pip install scalekit-sdk-python python-dotenvimport osfrom datetime import datetimefrom dotenv import load_dotenvfrom scalekit import ScalekitClientload_dotenv()scalekit_client = ScalekitClient(os.environ["SCALEKIT_ENVIRONMENT_URL"],os.environ["SCALEKIT_CLIENT_ID"],os.environ["SCALEKIT_CLIENT_SECRET"],)actions = scalekit_client.actionsTerminal window npm install @scalekit-sdk/node dotenvSave the code in a
.mtsfile, such asmigrate.mts, and run it withnpx tsx migrate.mts.@bufbuild/protobufcomes with the SDK.import 'dotenv/config';import { ScalekitClient, ConnectorStatus } from '@scalekit-sdk/node';import { timestampFromDate } from '@bufbuild/protobuf/wkt';const scalekit = new ScalekitClient(process.env.SCALEKIT_ENVIRONMENT_URL!,process.env.SCALEKIT_CLIENT_ID!,process.env.SCALEKIT_CLIENT_SECRET!,);const actions = scalekit.actions;Save the commands in a script and run it with
bash. They usejq.Terminal window set -a; source .env; set +aTOKEN=$(curl -sS "$SCALEKIT_ENVIRONMENT_URL/oauth/token" \-d grant_type=client_credentials \-d client_id="$SCALEKIT_CLIENT_ID" \-d client_secret="$SCALEKIT_CLIENT_SECRET" | jq -r .access_token)
2. Create connections
Section titled “2. Create connections”For each app your agent uses, create a connection in AgentKit > Connections > Add connection. See Configure a connection. The examples on this page use connections named gmail, openai, twilio and exa.
| Old platform’s auth | Connection in Scalekit |
|---|---|
| OAuth with the platform’s own OAuth app | OAuth. Use Scalekit credentials to start, or bring your own OAuth app. Users authorize again |
| OAuth with your own OAuth app | OAuth with the same OAuth app (client ID and secret). You can import the tokens |
| API key, bearer token or basic auth | The same type. You can import the credential |
3. Move your users
Section titled “3. Move your users”Option A: users authorize again
Section titled “Option A: users authorize again”Send each user an authorization link. It creates the user’s connected account if there isn’t one. Once they approve, the account is ACTIVE and your agent can call tools.
link = actions.get_authorization_link(connection_name="gmail", identifier="user_123").linkprint(link) # send this to the userconst { link } = await actions.getAuthorizationLink({ connectionName: 'gmail', identifier: 'user_123',});console.log(link); // send this to the useruser_123 is your app’s ID for the user. See Authorize a user for the full flow.
Option B: import existing credentials
Section titled “Option B: import existing credentials”Create each user’s connected account with the credential they already have. Scalekit imports it when your server calls the API with your environment’s client credentials, and the account is ACTIVE straight away.
Send the credential in authorization_details:
- OAuth tokens go in
oauth_token. - API keys, bearer tokens and basic auth go in
static_auth. In Python, the fields go directly understatic_auth; in Node.js and the REST API, they go one level down, indetails.
OAuth tokens
Section titled “OAuth tokens”| Field | Required | Description |
|---|---|---|
access_token | Yes | The user’s current access token. |
refresh_token | No, recommended | Lets Scalekit get a new access token when the current one expires. |
expires_at | No, recommended | When the access token expires, in UTC. Needs Python SDK 2.25.0 or Node.js SDK 2.22.0 or later. |
response = actions.create_connected_account( connection_name="gmail", identifier="user_123", authorization_details={ "oauth_token": { "access_token": "ya29.a0...", "refresh_token": "1//0g...", "expires_at": datetime.fromisoformat("2026-12-01T12:00:00+00:00"), } },)print(response.connected_account.status)const gmail = await actions.createConnectedAccount({ connectionName: 'gmail', identifier: 'user_123', authorizationDetails: { details: { case: 'oauthToken', value: { accessToken: 'ya29.a0...', refreshToken: '1//0g...', expiresAt: timestampFromDate(new Date('2026-12-01T12:00:00Z')), }, }, },});console.log(gmail.connectedAccount && ConnectorStatus[gmail.connectedAccount.status]);curl -sS -X POST "$SCALEKIT_ENVIRONMENT_URL/api/v1/connected_accounts" \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "connector": "gmail", "identifier": "user_123", "connected_account": { "authorization_details": { "oauth_token": { "access_token": "ya29.a0...", "refresh_token": "1//0g...", "expires_at": "2026-12-01T12:00:00Z" } } } }' | jq '.connected_account | {status, token_expires_at}'API keys, bearer tokens and basic auth
Section titled “API keys, bearer tokens and basic auth”Each connector names the fields it needs. Most use these:
| Type | Fields | Example connector |
|---|---|---|
| Bearer token | token | OpenAI: the API key |
| Basic auth | username and password | Twilio: the Account SID and Auth Token |
| API key | api_key | Exa: the API key |
credentials = { "openai": {"token": "sk-proj-..."}, # bearer "twilio": {"username": "AC...", "password": "..."}, # basic auth "exa": {"api_key": "..."}, # API key}for connection_name, fields in credentials.items(): response = actions.create_connected_account( connection_name=connection_name, identifier="user_123", authorization_details={"static_auth": fields}, ) print(connection_name, response.connected_account.status)const credentials = { openai: { token: 'sk-proj-...' }, // bearer twilio: { username: 'AC...', password: '...' }, // basic auth exa: { api_key: '...' }, // API key};for (const [connectionName, fields] of Object.entries(credentials)) { const created = await actions.createConnectedAccount({ connectionName, identifier: 'user_123', authorizationDetails: { details: { case: 'staticAuth', value: { details: fields } } }, }); console.log(connectionName, created.connectedAccount && ConnectorStatus[created.connectedAccount.status]);}create() { # connection name, static_auth.details JSON curl -sS -X POST "$SCALEKIT_ENVIRONMENT_URL/api/v1/connected_accounts" \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d "{\"connector\": \"$1\", \"identifier\": \"user_123\", \"connected_account\": {\"authorization_details\": {\"static_auth\": {\"details\": $2}}}}" \ | jq -c '.connected_account | {connector, status}'}create openai '{"token": "sk-proj-..."}'create twilio '{"username": "AC...", "password": "..."}'create exa '{"api_key": "..."}'Each account comes back ACTIVE. Some connectors use other field names, such as domain or one field per header. To see a connector’s fields, list its auth patterns:
curl -sS -G "$SCALEKIT_ENVIRONMENT_URL/api/v1/providers" \ -H "Authorization: Bearer $TOKEN" \ --data-urlencode identifier=TWILIO \ | jq '.providers[].auth_patterns[] | {type, fields: [.fields[] | {field_name, required}]}'A request that leaves out a required field, or sends one the connector doesn’t define, fails with 400, such as unknown field 'api_key' for provider ALGOLIAMONITORING. An update replaces the whole credential, so send every required field when you rotate a key; that also makes an EXPIRED account ACTIVE again.
4. Move your agent code
Section titled “4. Move your agent code”Your agent lists the user’s tools, then executes them. Each call names the user with identifier and the connection by its name. For the old platform’s code side by side with this, see the guide for Composio, Nango or Arcade.
List and execute tools
Section titled “List and execute tools”tools_response, _ = scalekit_client.actions.tools.list_scoped_tools( identifier="user_123", # Required: list every connection whose tools the agent should see filter={"connection_names": ["gmail"]}, page_size=100, # up to 100 tools per page; pass next_page_token back as page_token for more)
result = actions.execute_tool( tool_name="gmail_fetch_mails", identifier="user_123", connection_name="gmail", tool_input={"query": "is:unread", "max_results": 5},)print(result.data)const { tools } = await scalekit.tools.listScopedTools('user_123', { // Required: list every connection whose tools the agent should see filter: { connectionNames: ['gmail'] }, pageSize: 100, // up to 100 tools per page; pass nextPageToken back as pageToken for more});
const result = await actions.executeTool({ toolName: 'gmail_fetch_mails', identifier: 'user_123', connector: 'gmail', toolInput: { query: 'is:unread', max_results: 5 },});console.log(result.data);Pass identifier and the connection name on every executeTool call: connection_name in Python, connector in Node.js.
Tool names and inputs can differ from the old platform’s. Find each tool’s exact name and input in the connector catalog.
Scalekit serves MCP through Virtual MCP servers: one static server URL for all users, with a short-lived session token per agent run.
- Create a Virtual MCP server that exposes the connections and tools you choose. This gives you its
mcp_server_url. - Mint a session token for the user before each agent run, and pass it as a bearer token.
{ "mcpServers": { "scalekit": { "url": "<MCP_SERVER_URL>", "headers": { "Authorization": "Bearer <SESSION_TOKEN>" } } }}See Virtual MCP servers for the full setup.
Custom tools
Section titled “Custom tools”Define custom tools in your app code and call the app’s API through the Scalekit proxy with actions.request(), which adds the user’s credentials. If a call returns proxy not enabled for provider, see Call any API.
response = actions.request( connection_name="gmail", identifier="user_123", method="GET", path="/gmail/v1/users/me/messages",)const response = await actions.request({ connectionName: 'gmail', identifier: 'user_123', method: 'GET', path: '/gmail/v1/users/me/messages',});For an app that isn’t in the catalog, add your own connector. It works like a built-in one.
API reference
The endpoints this page's code calls, with every field and the SDK method for each: