Who this is for
Developers who call DeepSeek (深度求索), Qwen through DashScope (通义千问), Moonshot Kimi (月之暗面), Zhipu GLM (智谱) or MiniMax through their OpenAI-compatible chat APIs.
The same code works for any other OpenAI-compatible API that supports tool calling.
What you need
- An IPMusk account with a verified email address.
- An IPMusk API key. Create it in Settings → API keys (/app/settings/api-keys). For typical use tick read and connect. Add support only if the assistant should open tickets, and order only if it should create orders that end in a payment link.
- The key in an environment variable named IPMUSK_API_KEY, for example export IPMUSK_API_KEY="ipm_live_YOUR_KEY" in your shell profile. The examples below read it from there.
- Python 3.9 or later and pip install openai requests. From your model provider: an API key, the base URL and a model name that supports tool calling.
- IPMusk applies the same source-country rules to the API and MCP as to the website, so connect from a region where ipmusk.com is available.
Set it up
The model decides which tool to call. Your code sends the HTTPS request to IPMusk and passes the result back. This example defines two tools: get_account (GET /api/v1/me) and build_proxy_url (POST /api/v1/residential/proxy-url).
- Set LLM_API_KEY, LLM_BASE_URL and LLM_MODEL to your provider's key, base URL and model name. Base URLs and model names change, so check your provider's current docs.
- get_account needs the read scope. build_proxy_url needs connect.
- The loop stops after five rounds so a confused model cannot call tools forever.
import json
import os
import requests
from openai import OpenAI
IPMUSK = "https://ipmusk.com/api/v1"
IPMUSK_HEADERS = {"Authorization": f"Bearer {os.environ['IPMUSK_API_KEY']}"}
# Your model provider. Example base URLs:
# DeepSeek https://api.deepseek.com
# Qwen https://dashscope.aliyuncs.com/compatible-mode/v1
# Kimi https://api.moonshot.cn/v1
# GLM https://open.bigmodel.cn/api/paas/v4
# MiniMax https://api.minimaxi.com/v1
client = OpenAI(api_key=os.environ["LLM_API_KEY"], base_url=os.environ["LLM_BASE_URL"])
MODEL = os.environ["LLM_MODEL"]
TOOLS = [
{
"type": "function",
"function": {
"name": "get_account",
"description": "Get the IPMusk account that the API key belongs to.",
"parameters": {"type": "object", "properties": {}},
},
},
{
"type": "function",
"function": {
"name": "build_proxy_url",
"description": "Build an IPMusk residential proxy URL.",
"parameters": {
"type": "object",
"properties": {
"country": {"type": "string", "description": "Two-letter code, e.g. US"},
"session": {"type": "string", "enum": ["rotating", "sticky"]},
"sticky_minutes": {"type": "integer", "enum": [1, 3, 10, 30]},
},
"required": ["country", "session"],
},
},
},
]
def run_tool(name: str, args: dict) -> dict:
if name == "get_account":
response = requests.get(f"{IPMUSK}/me", headers=IPMUSK_HEADERS, timeout=30)
elif name == "build_proxy_url":
response = requests.post(
f"{IPMUSK}/residential/proxy-url", headers=IPMUSK_HEADERS, json=args, timeout=30
)
else:
return {"error": f"unknown tool {name}"}
return response.json()
messages = [
{"role": "user", "content": "Which IPMusk account am I on? Then give me a rotating US proxy URL."}
]
for _ in range(5):
reply = client.chat.completions.create(model=MODEL, messages=messages, tools=TOOLS)
message = reply.choices[0].message
if not message.tool_calls:
print(message.content)
break
messages.append(
{
"role": "assistant",
"content": message.content,
"tool_calls": [call.model_dump() for call in message.tool_calls],
}
)
for call in message.tool_calls:
result = run_tool(call.function.name, json.loads(call.function.arguments or "{}"))
messages.append(
{"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)}
)Node.js
The same loop with fetch and no SDK. It needs Node.js 18 or later; save it as agent.mjs.
const IPMUSK = "https://ipmusk.com/api/v1";
const ipmuskHeaders = {
Authorization: `Bearer ${process.env.IPMUSK_API_KEY}`,
"Content-Type": "application/json",
};
const TOOLS = [
{
type: "function",
function: {
name: "build_proxy_url",
description: "Build an IPMusk residential proxy URL.",
parameters: {
type: "object",
properties: {
country: { type: "string", description: "Two-letter code, e.g. US" },
session: { type: "string", enum: ["rotating", "sticky"] },
},
required: ["country", "session"],
},
},
},
];
async function runTool(name, args) {
if (name !== "build_proxy_url") return { error: `unknown tool ${name}` };
const response = await fetch(`${IPMUSK}/residential/proxy-url`, {
method: "POST",
headers: ipmuskHeaders,
body: JSON.stringify(args),
});
return response.json();
}
const messages = [{ role: "user", content: "Give me a rotating US proxy URL." }];
for (let round = 0; round < 5; round++) {
const response = await fetch(`${process.env.LLM_BASE_URL}/chat/completions`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LLM_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ model: process.env.LLM_MODEL, messages, tools: TOOLS }),
});
const message = (await response.json()).choices[0].message;
if (!message.tool_calls?.length) {
console.log(message.content);
break;
}
messages.push(message);
for (const call of message.tool_calls) {
const result = await runTool(call.function.name, JSON.parse(call.function.arguments || "{}"));
messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) });
}
}Generate every tool
Two tools are enough to start. To give the model every endpoint, generate the tool list from the OpenAPI schema: each operation becomes one function named after its operationId, such as me_get, with its parameters and request body as the function's parameters.
The full reference, with every endpoint, scope and error code, is at /docs/api.
https://ipmusk.com/api/v1/openapi.jsonCheck it works
Run python agent.py. It should print your email address and one proxy URL.
Try these prompts
- Which IPMusk account am I connected as?
- How much residential traffic do I have left?
- Build a rotating US residential proxy URL.
If it fails
- KeyError: 'IPMUSK_API_KEY' or 'LLM_API_KEY': set the variable in the shell that runs the script.
- The model answers without calling a tool: pick a model that supports tool calling (function calling).
- A tool result contains 401 or 403: the IPMusk key is wrong, revoked or lacks the scope. Create a new key with read and connect.
- 400 from the model provider after a tool call: it rejected the message history. Check its docs for how it wants tool calls and results passed back.
- 429 rate_limited: too many calls in one minute (read 60, connect 20, support 30, order 10). Wait a minute and try again.
Remove access
- Revoke the key in Settings → API keys (/app/settings/api-keys). It stops working at once.
- Then remove the key from the environment or secret store your code runs with.