Call any API
Call any endpoint of a connector's API as the user through the Scalekit API proxy, then wrap it as a tool for your agent. Python, Node.js and cURL.
When no built-in tool does what you need, call the app’s API directly through the Scalekit proxy. You send the path, method and body; Scalekit adds the user’s credentials, calls the app at its base URL and returns the raw response. Then you can wrap the call as your own tool. The proxy is on for most connectors.
Search the built-in tools first: they come with schemas and structured output, and cover most of each app’s API.
Before you start
Section titled “Before you start”-
An
ACTIVEconnected account for the user. Authorize a user shows how to get one. -
The connection name from AgentKit > Connections. These examples use a Slack connection named
slack. -
A client set up as in the Quickstart:
actionsin Python,scalekitin Node.js. For cURL, get an access token and keep it inTOKEN:Terminal window 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) -
The app’s API reference for the endpoint you want:
Connector API reference Gmail Gmail API Slack Slack API methods GitHub GitHub REST API Salesforce Salesforce REST API HubSpot HubSpot API
-
Check the connected account is active
Section titled “Check the connected account is active”The proxy only works for an
ACTIVEconnected account. Check before you call, and send a new authorization link if it isn’t.account = actions.get_connected_account_details(connection_name="slack",identifier="user_123",).connected_accountif account.status != "ACTIVE":raise SystemExit(f"Slack is {account.status}. Send the user a new authorization link.")import { ConnectorStatus } from '@scalekit-sdk/node';const { connectedAccount: account } = await scalekit.actions.getConnectedAccount({connectionName: 'slack',identifier: 'user_123',});if (account?.status !== ConnectorStatus.ACTIVE) {throw new Error('Slack is not ACTIVE. Send the user a new authorization link.');}Terminal window curl -sS -G "$SCALEKIT_ENVIRONMENT_URL/api/v1/connected_accounts/details" \-H "Authorization: Bearer $TOKEN" \--data-urlencode "connector=slack" \--data-urlencode "identifier=user_123" |jq -r '.connected_account.status' -
Call the endpoint
Section titled “Call the endpoint”Pass the path from the app’s API reference, without the base URL. Query parameters go in
query_params, and a JSON body goes inbody. This reads the custom profile fields of the user’s Slack workspace, which no built-in Slack tool covers:from scalekit.common.exceptions import ScalekitServerExceptiontry:response = actions.request(connection_name="slack",identifier="user_123",method="GET",path="/api/team.profile.get",query_params={"visibility": "visible"},)print(response.status_code, response.json())except ScalekitServerException as e:print("Request failed:", e.error_code, e.message)import { ScalekitServerException } from '@scalekit-sdk/node';try {const response = await scalekit.actions.request({connectionName: 'slack',identifier: 'user_123',method: 'GET',path: '/api/team.profile.get',queryParams: { visibility: 'visible' },});console.log(response.status, response.data);} catch (err) {if (!(err instanceof ScalekitServerException)) throw err;console.error('Request failed:', err.errorCode, err.message);}Terminal window curl -sS -G "$SCALEKIT_ENVIRONMENT_URL/proxy/api/team.profile.get" \-H "Authorization: Bearer $TOKEN" \-H "connection_name: slack" \-H "identifier: user_123" \--data-urlencode "visibility=visible"A write works the same way with a body. This sets a field on the user’s Slack profile:
response = actions.request(connection_name="slack",identifier="user_123",method="POST",path="/api/users.profile.set",body={"profile": {"title": "Support engineer"}},)print(response.status_code, response.json())const response = await scalekit.actions.request({connectionName: 'slack',identifier: 'user_123',method: 'POST',path: '/api/users.profile.set',body: { profile: { title: 'Support engineer' } },});console.log(response.status, response.data);Terminal window curl -sS -X POST "$SCALEKIT_ENVIRONMENT_URL/proxy/api/users.profile.set" \-H "Authorization: Bearer $TOKEN" \-H "connection_name: slack" \-H "identifier: user_123" \-H "Content-Type: application/json" \-d '{"profile": {"title": "Support engineer"}}'The proxy returns the app’s response as is: its status code, headers and body. In Python,
requestreturns arequestsresponse (status_code,json()) for every status, so checkstatus_code. In Node.js, it returns an Axios response (status,data) for a2xxstatus, and throws aScalekitServerExceptionfor any other status, so wrap the call intry/catch. -
Wrap it as a tool
Section titled “Wrap it as a tool”Give your model a tool that matches what the agent needs to do, not the shape of the app’s endpoint. Keep the input schema small, validate model input before you send it, and return only the fields the model needs.
list_profile_fields_tool = {"name": "slack_list_profile_fields","description": "List the custom profile fields defined in the user's Slack workspace, ""such as Title or Pronouns. Returns each field's ID, label and type.","input_schema": {"type": "object", "properties": {}},}def slack_list_profile_fields(identifier: str) -> dict:response = actions.request(connection_name="slack",identifier=identifier,method="GET",path="/api/team.profile.get",)data = response.json()if not data.get("ok"):return {"error": data.get("error")}fields = data.get("profile", {}).get("fields", [])return {"fields": [{"id": f["id"], "label": f["label"], "type": f["type"]} for f in fields]}print(slack_list_profile_fields("user_123"))const listProfileFieldsTool = {name: 'slack_list_profile_fields',description:"List the custom profile fields defined in the user's Slack workspace, " +"such as Title or Pronouns. Returns each field's ID, label and type.",input_schema: { type: 'object', properties: {} },};type ProfileField = { id: string; label: string; type: string };async function slackListProfileFields(identifier: string) {const response = await scalekit.actions.request({connectionName: 'slack',identifier,method: 'GET',path: '/api/team.profile.get',});const data = response.data as { ok: boolean; error?: string; profile?: { fields?: ProfileField[] } };if (!data.ok) return { error: data.error };const fields = data.profile?.fields ?? [];return { fields: fields.map(({ id, label, type }) => ({ id, label, type })) };}console.log(listProfileFieldsTool.name, await slackListProfileFields('user_123'));Pass the tool definition to your model with the built-in tools, and run the function when the model calls it.
Check it worked
Section titled “Check it worked”The call returns the app’s own response. For Slack, a 200 with "ok": true and the fields you asked for. Slack reports errors in the body with "ok": false, while most other APIs use a 4xx status.
Common problems
Section titled “Common problems”proxy not enabled for provider
Section titled “proxy not enabled for provider”The proxy is off for that connector. Contact support@scalekit.com to turn it on.
authorization details are missing for connected account identifier
Section titled “authorization details are missing for connected account identifier”No active connected account matches this identifier and connection. Check both values, and authorize the user if the account isn’t ACTIVE.
Node.js: request() throws a 401 on the first call
Section titled “Node.js: request() throws a 401 on the first call”Make another SDK call before your first request(), such as the account check in step 1. The Node.js client fetches its access token on its first regular SDK call, and request() then sends that token. In a long-running process, if request() later throws a 401 because the token expired, make another SDK call and retry.
The app returns 401 or 403
Section titled “The app returns 401 or 403”The user’s token is missing a scope the endpoint needs. Add the scope to the connection, then have the user authorize again.
The app only accepts traffic from known IP addresses
Section titled “The app only accepts traffic from known IP addresses”Add Scalekit’s outbound IP addresses for your region to the app’s allowlist.
API reference
The endpoints this page's code calls, with every field and the SDK method for each: