Overview
The customer API lets scripts and AI agents use your IPMusk account. Typical clients are your own code and agents such as Claude, Codex and Cursor.
The base URL is https://ipmusk.com/api/v1. Requests and responses are JSON, except the Static ISP export, which returns a file.
An MCP server at https://ipmusk.com/mcp uses the same API keys and the same scopes. See /docs/mcp.
https://ipmusk.com/api/v1Create an API key
- You can have up to 10 active keys.
- A key that is not used for 90 days expires.
- You can revoke a key at any time. It stops working immediately.
- 1Open API keys
Sign in to the dashboard and go to Settings, then API keys (/app/settings/api-keys).
- 2Name the key and pick scopes
Give the key a name you will recognise, such as the script or agent that uses it. Select only the scopes it needs.
- 3Copy the key
The full key is shown once. Store it in a secret manager or an environment variable. If you lose it, revoke it and create a new one.
Authentication
Send the key in the Authorization header as a Bearer token. Keys start with ipm_live_.
Never put a key in a URL or query string. The API does not read cookies, so a dashboard session cannot call it.
export IPMUSK_API_KEY="ipm_live_YOUR_KEY"
curl https://ipmusk.com/api/v1/me \
-H "Authorization: Bearer $IPMUSK_API_KEY"The catalogue endpoints (products, plans, locations, quotes) are public and need no key.
Scopes
Each key has one or more scopes. A request outside the key's scopes returns 403 forbidden.
- read: account details, order history, usage, the Static ISP list and support tickets.
- connect: proxy credentials, proxy URLs and Static ISP connection details. These responses contain passwords.
- support: create tickets and add replies.
- order: create orders and renewals that end in a payment link. Nothing is charged unless a person pays. Grant it only to code or an assistant you trust to place orders.
Rate limits
Limits apply per key, per minute, and depend on the endpoint's scope.
- read: 60 requests per minute.
- connect: 20 requests per minute.
- support: 30 requests per minute.
- order: 10 requests per minute.
When you go over a limit the API returns 429 rate_limited. Wait for the next minute before retrying, and back off if it happens again.
Responses and errors
A successful response wraps the result in data and includes a request_id. Quote the request_id when you contact support.
An error response has an error object and the same request_id. field_errors appears on validation errors and names the fields that failed.
- validation_error (400): the request body or query is not valid.
- authentication_required (401): the key is missing, wrong, revoked or expired.
- forbidden (403): the key lacks the scope, the email is not verified or the account is not active.
- capability_unavailable (403 or 422): the API is switched off, or the feature is not available for this request.
- not_found (404): the resource does not exist or is not yours.
- idempotency_conflict (409): the Idempotency-Key was already used with a different request.
- invalid_state_transition (409): the resource is not in a state that allows this, for example an order that is already paid, or a renewal whose price changed.
- checkout_unavailable (422): ordering is switched off on this deployment, or no payment provider is available.
- rate_limited (429): too many requests for this key.
- upstream_unavailable (503): a dependency is down. Retry later.
- internal_error (500): something failed on our side. Quote the request_id.
// Success
{
"data": { },
"request_id": "req_..."
}
// Error
{
"error": {
"code": "validation_error",
"message": "The request is not valid.",
"field_errors": { "country": ["Required"] }
},
"request_id": "req_..."
}Catalogue
No key is needed. Prices use amount_minor and currency, where amount_minor is in the smallest currency unit.
GET /products lists the two products, their billing model and the Static ISP categories.
GET /plans lists plans. Filter with product (rotating_residential or static_isp) and category (native_isp, isp or datacenter). Static plans include stock status.
GET /locations?product= lists country codes and availability for a product.
POST /quotes returns a price preview. It does not create an order. Residential quotes accept billing_mode one_time only. Static quotes take resource_days and quantity.
curl https://ipmusk.com/api/v1/products
curl "https://ipmusk.com/api/v1/plans?product=static_isp&category=isp"
curl "https://ipmusk.com/api/v1/locations?product=rotating_residential"
# Residential quote
curl -X POST https://ipmusk.com/api/v1/quotes \
-H "Content-Type: application/json" \
-d '{"product":"rotating_residential","plan_id":"PLAN_ID","billing_mode":"one_time"}'
# Static ISP quote
curl -X POST https://ipmusk.com/api/v1/quotes \
-H "Content-Type: application/json" \
-d '{"product":"static_isp","plan_id":"PLAN_ID","resource_days":30,"quantity":1}'Account
GET /me needs the read scope. It returns your customer id, email, display name, language and time zone, plus the id and scopes of the key you used.
curl https://ipmusk.com/api/v1/me \
-H "Authorization: Bearer $IPMUSK_API_KEY"Rotating Residential
GET /residential/credentials needs the connect scope. It returns the gateway host and port, your username and password, and your remaining traffic. It returns 404 not_found if you have no residential credentials yet.
POST /residential/proxy-url needs the connect scope. It builds ready-to-use proxy URLs for a country and, optionally, a state or city.
Set session to rotating to get a new IP for each connection. Set session to sticky to keep one IP for sticky_minutes (1, 3, 10 or 30).
count (1 to 100) sets how many URLs to return. It only applies to sticky sessions.
protocol is http or socks5. The response items contain url, host, port, username and password.
GET /residential/usage?days=7 or 30 needs the read scope. It returns your traffic usage overview.
curl https://ipmusk.com/api/v1/residential/credentials \
-H "Authorization: Bearer $IPMUSK_API_KEY"
# Three sticky sessions in California, 10 minutes each
curl -X POST https://ipmusk.com/api/v1/residential/proxy-url \
-H "Authorization: Bearer $IPMUSK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"country":"US","state":"california","session":"sticky","sticky_minutes":10,"protocol":"http","count":3}'
curl "https://ipmusk.com/api/v1/residential/usage?days=30" \
-H "Authorization: Bearer $IPMUSK_API_KEY"Static ISP and Datacenter
GET /static-proxies needs the read scope. It lists your Static ISP and Datacenter proxies without credentials. Filter with category (native_isp, isp or datacenter).
GET /static-proxies/{proxyId} needs the connect scope. It returns one proxy with host, port, username and password. A proxy that is not yours returns 404 not_found.
POST /static-proxies/export needs the connect scope. Send proxy_ids and a format of txt, csv or json. The response is a file, not the JSON envelope.
curl "https://ipmusk.com/api/v1/static-proxies?category=isp" \
-H "Authorization: Bearer $IPMUSK_API_KEY"
curl https://ipmusk.com/api/v1/static-proxies/PROXY_ID \
-H "Authorization: Bearer $IPMUSK_API_KEY"
curl -X POST https://ipmusk.com/api/v1/static-proxies/export \
-H "Authorization: Bearer $IPMUSK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"proxy_ids":["PROXY_ID"],"format":"txt"}' \
-o proxies.txtOrders
Both endpoints need the read scope.
GET /orders lists your orders. Page through the list with cursor and limit.
GET /orders/{orderId} returns one order. An order that is not yours returns 404 not_found.
curl "https://ipmusk.com/api/v1/orders?limit=20" \
-H "Authorization: Bearer $IPMUSK_API_KEY"
curl https://ipmusk.com/api/v1/orders/ORDER_ID \
-H "Authorization: Bearer $IPMUSK_API_KEY"Orders and renewals
These endpoints need the order scope. They create an order and return a payment link. They never take payment: a person opens checkout_url in a browser and pays there.
POST /orders, POST /orders/{orderId}/checkout and POST /static-proxies/renew need an Idempotency-Key header. Send the same value when you retry, and the order is created only once.
POST /orders creates a one-time order. For product rotating_residential, send plan_id and an optional promo_code. For product static_isp, send allocations (each with plan_id and quantity) and resource_days (30 or 90), plus an optional promo_code. Get the price first with POST /quotes and show it to the person.
provider is optional: stripe, paypal or infini. If you leave it out, the first provider available on this deployment is used.
The response is { order_id, amount_minor, currency, provider, checkout_url, expires_at }. Give checkout_url to the person. Poll GET /orders/{orderId} to see when the order is paid.
POST /orders/{orderId}/checkout returns a new payment link for an order that is not paid yet.
POST /static-proxies/renew-quote prices a renewal for proxy_ids and resource_days (30 or 90). It creates nothing.
POST /static-proxies/renew creates the renewal order and returns the payment link. Send the same body as the quote, and optionally expected_amount_minor from the quote. If the current price differs, the call returns 409 invalid_state_transition with the new price in field_errors and creates no order. Confirm the new price with the person, then retry.
A renewal covers one location and kind at a time. Proxies that cannot be renewed (expired, released or discontinued) return 409.
If ordering is switched off on this deployment, these endpoints return 422 checkout_unavailable and create no order.
# Residential order
curl -X POST https://ipmusk.com/api/v1/orders -H "Authorization: Bearer $IPMUSK_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" -d '{"product":"rotating_residential","plan_id":"PLAN_ID","promo_code":"WELCOME10"}'
# Static ISP order: 2 proxies for 30 days
curl -X POST https://ipmusk.com/api/v1/orders -H "Authorization: Bearer $IPMUSK_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" -d '{"product":"static_isp","allocations":[{"plan_id":"PLAN_ID","quantity":2}],"resource_days":30}'
# Response (the person opens checkout_url and pays)
# {"data":{"order_id":"...","amount_minor":1800,"currency":"USD","provider":"...","checkout_url":"https://...","expires_at":"..."},"request_id":"req_..."}
# New payment link for an unpaid order
curl -X POST https://ipmusk.com/api/v1/orders/ORDER_ID/checkout -H "Authorization: Bearer $IPMUSK_API_KEY" -H "Idempotency-Key: $(uuidgen)"
# Price a renewal, then renew
curl -X POST https://ipmusk.com/api/v1/static-proxies/renew-quote -H "Authorization: Bearer $IPMUSK_API_KEY" -H "Content-Type: application/json" -d '{"proxy_ids":["PROXY_ID"],"resource_days":30}'
curl -X POST https://ipmusk.com/api/v1/static-proxies/renew -H "Authorization: Bearer $IPMUSK_API_KEY" -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" -d '{"proxy_ids":["PROXY_ID"],"resource_days":30,"expected_amount_minor":1200}'
# Poll until the order is paid
curl https://ipmusk.com/api/v1/orders/ORDER_ID -H "Authorization: Bearer $IPMUSK_API_KEY"GET /orders/{orderId} needs the read scope. Give the key both order and read if your code polls the status.
Support tickets
GET /tickets and GET /tickets/{ticketId} need the read scope.
POST /tickets and POST /tickets/{ticketId}/replies need the support scope and an Idempotency-Key header. Use a new unique value for each new request. If you retry the same request, send the same value, and the ticket or reply is created only once.
type is technical, billing or other. subject is 3 to 160 characters. message is up to 10,000 characters. order_id is optional. credential_id or static_proxy_id can link a resource, but then order_id is required too.
curl https://ipmusk.com/api/v1/tickets \
-H "Authorization: Bearer $IPMUSK_API_KEY"
curl https://ipmusk.com/api/v1/tickets/TICKET_ID \
-H "Authorization: Bearer $IPMUSK_API_KEY"
curl -X POST https://ipmusk.com/api/v1/tickets \
-H "Authorization: Bearer $IPMUSK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"type":"technical","subject":"Sticky session drops after 2 minutes","message":"Request ID req_... returned a 407 on the third request."}'
curl -X POST https://ipmusk.com/api/v1/tickets/TICKET_ID/replies \
-H "Authorization: Bearer $IPMUSK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"message":"Still happening after I restarted the client."}'Never put a password, API key or full proxy URL in a ticket.
OAuth sign-in (for MCP clients)
MCP clients such as claude.ai, Claude Desktop and ChatGPT can sign in with OAuth instead of an API key. The person approves the permissions in the browser. The client never sees a password.
The server follows the standard discovery documents, so a client finds everything from https://ipmusk.com/mcp.
- Register: the client calls /oauth/register and gets a client_id. No secret is used. Redirect URIs must be https or localhost.
- Authorize: the client sends the person to /oauth/authorize with a PKCE S256 code_challenge. PKCE is required. The person signs in to IPMusk and ticks the scopes (read, connect, support, order) to grant.
- Token: the client exchanges the code at /oauth/token. Access tokens (ipm_oat_) last 1 hour. Refresh tokens last 90 days, and each one works once and is replaced by a new one.
- Revoke: the client can call /oauth/revoke. The person can also disconnect the app in Settings, then API keys, then Connected AI apps. Access ends at once.
https://ipmusk.com/.well-known/oauth-protected-resource
https://ipmusk.com/.well-known/oauth-authorization-server
https://ipmusk.com/oauth/register (dynamic client registration)
https://ipmusk.com/oauth/authorize
https://ipmusk.com/oauth/token
https://ipmusk.com/oauth/revokeSend an access token to /api/v1 and /mcp the same way as an API key: Authorization: Bearer. Scopes, rate limits and errors are the same.
OpenAPI
The full API description is published as OpenAPI 3.1. It lists every endpoint, request body and response.
Use it to generate a client, or import it into ChatGPT custom actions, Codex tools or Postman. Set the Bearer token to your API key.
https://ipmusk.com/api/v1/openapi.jsonSecurity notes
- Responses from connect endpoints contain proxy passwords. Treat them as secrets and do not log them.
- Never paste an API key into a ticket, a chat or a public repository.
- If a key leaks, revoke it in Settings, then API keys, and create a new one.
- Give each script or agent its own key with the fewest scopes it needs.
- The same source-country rules as the website apply to the API.
- Orders and renewals only produce a payment link. A person pays in the browser, and the API never handles payment details.
- Grant the order scope only to code or assistants you trust to place orders. Use expected_amount_minor on renewals so a price change cannot slip through.