Skip to content
Scalekit Docs
Talk to an Engineer Dashboard

Migrate from Nango

Move an agent from Nango to AgentKit: map Nango integrations and connections, export credentials and import them, and update your tool calls.

Use this page if your agent uses Nango today. It covers what’s specific to Nango: how its concepts map to AgentKit, how to export your users’ credentials and import them, and how your tool calls change. The steps every migration shares, such as setting up Scalekit and creating connections, are in Migrate to AgentKit.

NangoAgentKit
Integration (integration ID, provider_config_key)Connection, referred to by its connection name
Connection (connection_id)Connected account: one per user and connection
end_user_id tagIdentifier: your app’s ID for the user, passed on each call
Connect UI and connect sessionAuthorization link
Tools and actions you writeConnector tools, or custom tools that call the API through the proxy
Proxy (nango.proxy(), nango.get())actions.request() through the Scalekit proxy
Agent sessions over MCPVirtual MCP server with a session token per agent run

Nango returns a connection’s stored credentials, so you can import them and your users don’t have to connect again. That works when:

  • OAuth: the Nango integration uses your own OAuth app. Create the Scalekit connection with the same client ID and secret; see Bring your own OAuth app. A refresh token works only with the OAuth app that issued it, so users of an integration on a Nango-managed OAuth app authorize again.
  • API keys and basic auth: always.
  • Your Nango secret key has the environment:connections:read_credentials scope. Without it, Nango leaves the credentials out of the response.

The script below lists the Nango connections of each integration, reads each one’s credentials and creates a connected account in Scalekit. It uses each connection’s end_user_id tag as the Scalekit identifier. If you keep your user ID somewhere else, such as in the connection’s metadata, change that line.

Add your Nango secret key to the .env file from Set up Scalekit:

.env
NANGO_SECRET_KEY=your_nango_secret_key

Install requests too: pip install scalekit-sdk-python python-dotenv requests. The script needs Python 3.11 or later.

import os
from datetime import datetime
import requests
from dotenv import load_dotenv
from scalekit import ScalekitClient
from scalekit.common.exceptions import ScalekitServerException
load_dotenv()
scalekit_client = ScalekitClient(
os.environ["SCALEKIT_ENVIRONMENT_URL"],
os.environ["SCALEKIT_CLIENT_ID"],
os.environ["SCALEKIT_CLIENT_SECRET"],
)
actions = scalekit_client.actions
NANGO_URL = "https://api.nango.dev"
NANGO_HEADERS = {"Authorization": f"Bearer {os.environ['NANGO_SECRET_KEY']}"}
# Your Nango integration ID -> (Scalekit connection name, field for an API key)
CONNECTIONS = {
"google-mail": ("gmail", None), # OAuth
"openai": ("openai", "token"), # API key, sent as a bearer token
"twilio": ("twilio", None), # basic auth
}
def to_authorization_details(credentials, key_field):
kind = credentials["type"]
if kind == "OAUTH2":
token = {"access_token": credentials["access_token"]}
if credentials.get("refresh_token"):
token["refresh_token"] = credentials["refresh_token"]
if credentials.get("expires_at"):
token["expires_at"] = datetime.fromisoformat(credentials["expires_at"])
return {"oauth_token": token}
if kind == "API_KEY" and key_field:
return {"static_auth": {key_field: credentials["apiKey"]}}
if kind == "BASIC":
return {"static_auth": {"username": credentials["username"], "password": credentials["password"]}}
return None
for integration_id, (connection_name, key_field) in CONNECTIONS.items():
page = 0
while True:
listed = requests.get(
f"{NANGO_URL}/connections",
headers=NANGO_HEADERS,
params={"integrationId": integration_id, "limit": 1000, "page": page},
)
listed.raise_for_status()
connections = listed.json()["connections"]
for connection in connections:
identifier = (connection.get("tags") or {}).get("end_user_id")
if not identifier:
print("skipped, no end_user_id tag:", connection["connection_id"])
continue
full = requests.get(
f"{NANGO_URL}/connections/{connection['connection_id']}",
headers=NANGO_HEADERS,
params={"provider_config_key": integration_id, "refresh_token": "true"},
)
full.raise_for_status()
details = to_authorization_details(full.json()["credentials"], key_field)
if details is None:
print("skipped, credential type not mapped:", connection["connection_id"])
continue
try:
created = actions.create_connected_account(
connection_name=connection_name,
identifier=identifier,
authorization_details=details,
)
print(connection_name, identifier, created.connected_account.status)
except ScalekitServerException as e:
print(connection_name, identifier, "failed:", e.error_code, e.message)
if len(connections) < 1000:
break
page += 1

Each imported account comes back ACTIVE. Change CONNECTIONS to your own integration IDs and connection names. For an API key, the field name depends on the Scalekit connector: OpenAI takes a bearer token, Exa takes api_key. See API keys, bearer tokens and basic auth for how to list a connector’s fields.

If a connection already exists for a user, for example because you ran the script twice, creating it again fails and the script prints the error. To replace the credential, use update_connected_account instead.

Nango tools and actions are functions you wrote and deployed to Nango. In AgentKit, use the connector’s built-in tools where one does the same job, and move the rest into your app as custom tools that call the API through the Scalekit proxy.

Before (Nango, Node.js):

const result = await nango.callTool(
{ connectionId: '<CONNECTION-ID>', name: 'create-issue' },
{ input: { owner: 'acme', repo: 'agent', title: 'Bug report' } },
);

After (Scalekit):

result = actions.execute_tool(
tool_name="github_issue_create",
identifier="user_123",
connection_name="github",
tool_input={"owner": "acme", "repo": "agent", "title": "Bug report"},
)
print(result.data)

Nango addresses a user’s account by connectionId. AgentKit addresses it by your user’s identifier plus the connection name, so you don’t need to store a connection ID per user.

Before (Nango, Node.js):

const response = await nango.get({
endpoint: '/gmail/v1/users/me/messages',
providerConfigKey: 'google-mail',
connectionId: '<CONNECTION-ID>',
});

After (Scalekit):

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

If your agent used a Nango agent session’s MCP URL, create a Virtual MCP server with the same connections and tools. It has one static URL for all users; mint a session token for the user before each agent run and pass it as a bearer token.

API reference

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