Skip to content
Scalekit Docs
Talk to an Engineer Dashboard

Custom tools

Build tools that Scalekit does not provide out of the box by proxying provider API calls through connected accounts.

When you need a connector tool that Scalekit doesn’t offer as a pre-built tool, use API Proxy mode. You define the tool contract and call the provider endpoint through actions.request. Scalekit injects the user’s credentials from their connected account; your agent never handles raw tokens.

OptionBest forWho defines tool schema
Scalekit optimized toolsCommon connector toolsScalekit
Custom tools (API Proxy)Unsupported or app-specific toolsYour application

This page assumes the user has an ACTIVE connected account. If not, see Authorize a user.

The path you pass to actions.request is forwarded directly to the provider’s API; Scalekit only adds authentication headers. Look up the provider’s API reference to get the correct path, method, and request shape.

ConnectorAPI reference
GmailGoogle Gmail API
SlackSlack API methods
GitHubGitHub REST API
SalesforceSalesforce REST API
HubSpotHubSpot API

Design the tool around your agent’s intent, not the provider’s API surface. For example, to list Gmail filters:

  • Tool name: gmail_list_filters (describes the action, not the endpoint)
  • Input: identifier (your app’s user ID)
  • Output: { filters: [...], count: N } (structured, not the raw Gmail response)

Keep schemas focused on what the model needs. Strip provider-specific noise before returning data.

Use actions.request to call any provider endpoint. Scalekit handles credential injection.

GET requests: pass query parameters as a dict:

def gmail_list_filters(identifier: str):
response = actions.request(
connection_name="gmail",
identifier=identifier,
method="GET",
path="/gmail/v1/users/me/settings/filters",
)
data = response.json()
return {"filters": data.get("filter", []), "count": len(data.get("filter", []))}
def gmail_list_unread(identifier: str, max_results: int = 10):
response = actions.request(
connection_name="gmail",
identifier=identifier,
method="GET",
path="/gmail/v1/users/me/messages",
query_params={"q": "is:unread", "maxResults": max_results},
)
return {"messages": response.json().get("messages", [])}

POST requests: pass a body for write operations:

def slack_send_message(identifier: str, channel: str, text: str):
response = actions.request(
connection_name="slack",
identifier=identifier,
method="POST",
path="/api/chat.postMessage",
body={"channel": channel, "text": text},
)
data = response.json()
if not data.get("ok"):
raise ValueError(f"Slack error: {data.get('error')}")
return {"ts": data.get("ts"), "channel": data.get("channel")}

Define a JSON Schema for each custom tool and pass the list to your LLM. The schema format below matches what Scalekit returns for built-in tools, so it works directly with the Anthropic API. For OpenAI, rename input_schema to parameters.

custom_tools = [
{
"name": "gmail_list_filters",
"description": "List Gmail filters configured for the current user",
"input_schema": {
"type": "object",
"properties": {"identifier": {"type": "string"}},
"required": ["identifier"],
},
},
{
"name": "slack_send_message",
"description": "Send a message to a Slack channel on behalf of the user",
"input_schema": {
"type": "object",
"properties": {
"identifier": {"type": "string"},
"channel": {"type": "string"},
"text": {"type": "string"},
},
"required": ["identifier", "channel", "text"],
},
},
]
def route_tool_call(tool_name: str, tool_input: dict):
if tool_name == "gmail_list_filters":
return gmail_list_filters(tool_input["identifier"])
if tool_name == "slack_send_message":
return slack_send_message(tool_input["identifier"], tool_input["channel"], tool_input["text"])
raise ValueError(f"Unknown tool: {tool_name}")

Verify the connected account is ACTIVE before making a proxy call and handle provider errors explicitly:

account = actions.get_or_create_connected_account(
connection_name="gmail",
identifier=identifier,
).connected_account
if account.status != "ACTIVE":
raise ValueError("Connected account is not ACTIVE. Re-authorize the user.")
  • Expose only the fields your model needs; keep schemas small
  • Validate inputs server-side; never trust model-generated parameters
  • Use predictable JSON keys; return stable output across calls
  • Map provider errors to clear tool errors; don’t leak raw provider payloads to prompts