> **Building with AI coding agents?** Install the authstack plugin with one command. This equips your agent with accurate Scalekit implementation patterns.
>
> **Recommended** (the `-y` flags skip prompts, so the command runs without an interactive terminal):
> ```bash
> npx -y @scalekit-inc/cli setup -y
> ```
>
> Global:
> ```bash
> npm install -g @scalekit-inc/cli
> scalekit setup -y
> ```
>
> Supports Claude Code, Cursor, GitHub Copilot, Codex + skills for other Agent Skills-compatible agents in `~/.agents/skills`.
> Skills: integrate-agentkit, implement-saaskit, add-mcp-oauth, implement-sso, implement-scim.
> [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# 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:

- [Migrate from Composio](/agentkit/advanced/migrate-from-composio/)
- [Migrate from Nango](/agentkit/advanced/migrate-from-nango/)
- [Migrate from Arcade](/agentkit/advanced/migrate-from-arcade/)

## 1. Set up Scalekit

1. **Create a Scalekit account**

   Sign up at [app.scalekit.com](https://app.scalekit.com) and copy your API credentials from **Developers** > **Settings** > **API Credentials**.

2. **Set environment variables**

   Add them to a `.env` file that you don't commit:

   ```bash title=".env"
   SCALEKIT_CLIENT_ID=your_client_id
   SCALEKIT_CLIENT_SECRET=your_client_secret
   SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com
   ```

3. **Install the SDK and create the client**

   There's no session object: you pass the user's `identifier` on each call. Later blocks reuse this setup.

     **Python**

     ```bash
     pip install scalekit-sdk-python python-dotenv
     ```

     ```python
     import os
     from datetime import datetime

     from dotenv import load_dotenv
     from scalekit import ScalekitClient

     load_dotenv()
     scalekit_client = ScalekitClient(
         os.environ["SCALEKIT_ENVIRONMENT_URL"],
         os.environ["SCALEKIT_CLIENT_ID"],
         os.environ["SCALEKIT_CLIENT_SECRET"],
     )
     actions = scalekit_client.actions
     ```

     **Node.js**

     ```bash
     npm install @scalekit-sdk/node dotenv
     ```

     Save the code in a `.mts` file, such as `migrate.mts`, and run it with `npx tsx migrate.mts`. `@bufbuild/protobuf` comes with the SDK.

     ```typescript
     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;
     ```

     **cURL**

     Save the commands in a script and run it with `bash`. They use `jq`.

     ```bash
     set -a; source .env; set +a

     TOKEN=$(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

For each app your agent uses, create a connection in **AgentKit** > **Connections** > **Add connection**. See [Configure a connection](/agentkit/connections/). 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](/agentkit/advanced/bring-your-own-oauth/). 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

### 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.

  **Python**

  ```python
  link = actions.get_authorization_link(connection_name="gmail", identifier="user_123").link
  print(link)  # send this to the user
  ```

  **Node.js**

  ```typescript
  const { link } = await actions.getAuthorizationLink({
    connectionName: 'gmail',
    identifier: 'user_123',
  });
  console.log(link); // send this to the user
  ```

`user_123` is your app's ID for the user. See [Authorize a user](/agentkit/tools/authorize/) for the full flow.

### 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 under `static_auth`; in Node.js and the REST API, they go one level down, in `details`.

#### 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. |

  **Python**

  ```python
  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)
  ```

  **Node.js**

  ```typescript
  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**

  ```bash
  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

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 |

  **Python**

  ```python
  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)
  ```

  **Node.js**

  ```typescript
  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]);
  }
  ```

  **cURL**

  ```bash
  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:

```bash
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

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](/agentkit/advanced/migrate-from-composio/#move-your-agent-code), [Nango](/agentkit/advanced/migrate-from-nango/#move-your-agent-code) or [Arcade](/agentkit/advanced/migrate-from-arcade/#move-your-agent-code).

### List and execute tools

  **Python**

  ```python
  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)
  ```

  **Node.js**

  ```typescript
  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](/agentkit/connectors/).

### MCP

Scalekit serves MCP through Virtual MCP servers: one static server URL for all users, with a short-lived session token per agent run.

1. **Create a Virtual MCP server** that exposes the connections and tools you choose. This gives you its `mcp_server_url`.
2. **Mint a session token** for the user before each agent run, and pass it as a bearer token.

```json
{
  "mcpServers": {
    "scalekit": {
      "url": "<MCP_SERVER_URL>",
      "headers": {
        "Authorization": "Bearer <SESSION_TOKEN>"
      }
    }
  }
}
```

See [Virtual MCP servers](/agentkit/mcp/overview/) for the full setup.

### 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](/agentkit/tools/custom-tools/#proxy-not-enabled-for-provider).

  **Python**

  ```python
  response = actions.request(
      connection_name="gmail",
      identifier="user_123",
      method="GET",
      path="/gmail/v1/users/me/messages",
  )
  ```

  **Node.js**

  ```typescript
  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](/agentkit/bring-your-own-connector/overview/). It works like a built-in one.

## Next

  - [Quickstart](/agentkit/quickstart/): Make your first tool call as a user.
  - [Choose a framework](/agentkit/examples/): Build the same agent in another framework.

## API reference

The endpoints this page's code calls, with every field and the SDK method for each:

- `POST` [Create a connected account](https://docs.scalekit.com/agentkit/reference/connected-accounts/create-a-connected-account.md)
- `POST` [Get an authorization link](https://docs.scalekit.com/agentkit/reference/authorization/get-an-authorization-link.md)
- `POST` [Execute a tool](https://docs.scalekit.com/agentkit/reference/tools/execute-a-tool.md)
- `GET` [List a user's scoped tools](https://docs.scalekit.com/agentkit/reference/tools/list-scoped-tools.md)


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
