> ## Documentation Index
> Fetch the complete documentation index at: https://help.miyaip.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MiyaIP residential assets API: filter and paginate proxy IPs

> Query MiyaIP residential proxy assets with a Bearer token, pagination, status, product type, location, keyword, and sorting filters.

export const MiyaApiPlayground = ({specUrl, method, path, locale = "en"}) => {
  const miyaApiOrigin = "https://miyaip.com";
  const miyaResponseLimit = 200 * 1024;
  const miyaAllowedOperations = ["POST /api/SysLogin/Login", "GET /api/ProxyLogic/Generate", "POST /api/UserShort/GenerateIps", "GET /api/ProxyLogic/UnlimitedGenerate", "GET /api/SysLogin/GetUserInfo", "GET /api/ProxyLogic/BandwidthAnalysis", "GET /api/UserIsps/PageList", "GET /api/DatacenterIps/PageList"];
  const miyaAllowedSpecs = ["/api-reference/openapi.zh.json", "/api-reference/openapi.en.json"];
  const miyaSensitiveKey = /password|passwd|pwd|token|secret|authorization|^crc$|keyname|key$|api.?key|user.?name|proxy.?user|proxy.?pass|full.?proxy/i;
  const miyaLabels = locale => locale === "zh" ? {
    title: "在线调试",
    loading: "正在加载接口配置…",
    loadError: "无法加载接口配置。请刷新页面后重试。",
    unsupported: "此调试器仅支持文档中列出的 MiyaIP 接口。",
    request: "请求预览（凭据已隐藏）",
    send: "发送请求",
    sending: "请求中…",
    cancel: "取消请求",
    clear: "清空",
    optional: "可选",
    required: "必填",
    requiredError: "请填写此项。",
    integerError: "请输入整数。",
    emailError: "请输入有效的邮箱地址。",
    enumError: "请选择文档列出的选项。",
    minimum: "最小值为",
    maximum: "最大值为",
    live: "点击发送会向 MiyaIP 发送实际请求。凭据和响应仅保存在本页面的内存中。",
    cancelNote: "超时或取消只会停止浏览器等待，不能保证服务器撤销请求；调试器不会自动重试。",
    response: "响应",
    elapsed: "耗时",
    status: "HTTP 状态",
    contentType: "响应类型",
    businessCode: "业务 code",
    httpSuccess: "HTTP 请求完成",
    businessSuccess: "业务 code 表示成功",
    businessError: "接口返回业务错误",
    httpError: "接口返回 HTTP 错误",
    networkError: "请求失败。请检查网络、跨域策略及连接状态。",
    timeout: "等待超过 30 秒，请求已停止等待。",
    cancelled: "已取消等待响应。",
    truncated: "响应超过 200 KiB，展示内容已截断；不能据此判断完整 JSON 或业务结果。",
    invalidJson: "响应无法解析为 JSON，请检查响应类型和原始返回内容。",
    jsonHidden: "为避免泄露敏感字段，不展示截断或无法解析的 JSON 正文。",
    empty: "响应正文为空。",
    showProxy: "显示代理连接信息",
    hideProxy: "隐藏代理连接凭据",
    token: "登录 Token",
    showToken: "显示 Token",
    hideToken: "隐藏 Token",
    copyToken: "复制 Token",
    copied: "已复制 Token。",
    copyFailed: "复制失败，请显示 Token 后手动复制。",
    masked: "敏感字段默认隐藏。",
    proxyMasked: "代理连接凭据默认隐藏，可主动显示完整原文。"
  } : {
    title: "Try this API",
    loading: "Loading the API configuration…",
    loadError: "The API configuration could not be loaded. Refresh this page and try again.",
    unsupported: "This form only supports the MiyaIP endpoints listed in these docs.",
    request: "Request preview (credentials hidden)",
    send: "Send request",
    sending: "Sending…",
    cancel: "Cancel request",
    clear: "Clear",
    optional: "Optional",
    required: "Required",
    requiredError: "Enter a value.",
    integerError: "Enter a whole number.",
    emailError: "Enter a valid email address.",
    enumError: "Select one of the documented options.",
    minimum: "The minimum is",
    maximum: "The maximum is",
    live: "Sending makes a real request to MiyaIP. Credentials and responses remain in memory on this page only.",
    cancelNote: "A timeout or cancellation stops the browser from waiting; it does not guarantee that the server cancels the request. This form never retries automatically.",
    response: "Response",
    elapsed: "Elapsed",
    status: "HTTP status",
    contentType: "Content type",
    businessCode: "Business code",
    httpSuccess: "HTTP request completed",
    businessSuccess: "The business code indicates success",
    businessError: "The API returned a business error",
    httpError: "The API returned an HTTP error",
    networkError: "The request failed. Check the network, CORS policy, and connection status.",
    timeout: "The 30-second wait expired. The browser stopped waiting.",
    cancelled: "Stopped waiting for the response.",
    truncated: "The response exceeds 200 KiB and has been truncated. A partial response cannot confirm a complete JSON or business result.",
    invalidJson: "The response could not be parsed as JSON. Check its content type and returned content.",
    jsonHidden: "A truncated or unparseable JSON body is not displayed, so its sensitive fields cannot be exposed.",
    empty: "The response body is empty.",
    showProxy: "Show proxy credentials",
    hideProxy: "Hide proxy credentials",
    token: "Login token",
    showToken: "Show token",
    hideToken: "Hide token",
    copyToken: "Copy token",
    copied: "Token copied.",
    copyFailed: "Copy failed. Show the token and copy it manually.",
    masked: "Sensitive fields are hidden by default.",
    proxyMasked: "Proxy credentials are hidden by default. You can explicitly show the original text."
  };
  const miyaActiveFields = (fields, values) => fields.filter(field => !field.showWhen || Object.entries(field.showWhen).every(([name, expected]) => String(values[name]) === String(expected)));
  const miyaDefaultValues = fields => Object.fromEntries(fields.map(field => [field.name, field.defaultValue === undefined || field.defaultValue === null ? "" : String(field.defaultValue)]));
  const miyaValidate = (fields, values, labels) => {
    const errors = {};
    miyaActiveFields(fields, values).forEach(field => {
      const value = String(values[field.name] ?? "").trim();
      if (!value) {
        if (field.required) errors[field.name] = labels.requiredError;
        return;
      }
      if (field.type === "integer") {
        const number = Number(value);
        if (!(/^-?\d+$/).test(value) || !Number.isSafeInteger(number)) errors[field.name] = labels.integerError; else if (field.min !== undefined && number < field.min) errors[field.name] = `${labels.minimum} ${field.min}.`; else if (field.max !== undefined && number > field.max) errors[field.name] = `${labels.maximum} ${field.max}.`;
      }
      if (field.input === "email" && !(/^[^\s@]+@[^\s@]+\.[^\s@]+$/).test(value)) errors[field.name] = labels.emailError;
      if (field.options?.length && !field.options.some(option => String(option.value) === value)) errors[field.name] = labels.enumError;
    });
    return errors;
  };
  const miyaBearerToken = value => String(value).trim().replace(/^Bearer\s+/i, "").trim();
  const miyaKnownSecrets = (fields, values) => miyaActiveFields(fields, values).filter(field => field.sensitive || field.in === "header" || miyaSensitiveKey.test(field.name)).flatMap(field => {
    const raw = String(values[field.name] ?? "");
    return field.in === "header" ? [raw, raw.trim(), miyaBearerToken(raw)] : [raw];
  }).filter(Boolean).sort((a, b) => b.length - a.length);
  const miyaMaskString = (text, secrets = []) => {
    let masked = String(text);
    secrets.forEach(secret => {
      const formEncoded = new URLSearchParams([["value", secret]]).toString().slice(("value=").length);
      [...new Set([secret, encodeURIComponent(secret), formEncoded])].sort((a, b) => b.length - a.length).forEach(variant => {
        masked = masked.split(variant).join("[hidden]");
      });
    });
    return masked.replace(/((?:https?|socks5?h?):\/\/)[^\s/@]+(?::[^\s/@]*)?@/gi, "$1[hidden]@").replace(/(^|\n)(\s*)[^\r\n\s@]+@([^\r\n]+)/g, "$1$2[hidden]@$3").replace(/(^|\n)(\s*)[^:\s]+:[^:\s]+:(\[[^\]\s]+\]|[^:\s]+):(\d+)(?=\r?\n|$)/g, "$1$2[hidden]:[hidden]:$3:$4").replace(/(^|\n)(\s*(?:\[[^\]\s]+\]|[^\s:]+):\d+:)[^\r\n]+/g, "$1$2[hidden]:[hidden]");
  };
  const miyaRedactJson = (value, secrets = []) => {
    if (Array.isArray(value)) return value.map(item => miyaRedactJson(item, secrets));
    if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, miyaSensitiveKey.test(key) && item !== null && item !== "" && ["string", "object"].includes(typeof item) ? "[hidden]" : miyaRedactJson(item, secrets)]));
    return typeof value === "string" ? miyaMaskString(value, secrets) : value;
  };
  const miyaMaskProxyText = (text, format, secrets) => String(text).split("\n").map(line => {
    if (!line.trim()) return line;
    let masked;
    if (String(format) === "0") masked = line.replace(/^(\s*(?:\[[^\]\s]+\]|[^:\s]+):\d+):[^\r\n]+$/, "$1:[hidden]:[hidden]"); else if (String(format) === "1") masked = line.replace(/^(\s*)[^\r\n]+@([^\r\n]+)$/, "$1[hidden]@$2"); else if (String(format) === "2") masked = line.replace(/^(\s*)[^\r\n]+:(\[[^\]\s]+\]|[^:\s]+):(\d+)\s*$/, "$1[hidden]:[hidden]:$2:$3");
    return masked && masked !== line ? miyaMaskString(masked, secrets) : "[hidden]";
  }).join("\n");
  const miyaFindToken = value => {
    if (!value || typeof value !== "object") return "";
    for (const [key, item] of Object.entries(value)) {
      if ((/^(?:token|access_?token|jwt)$/i).test(key) && typeof item === "string" && item) return item;
      if (item && typeof item === "object") {
        const found = miyaFindToken(item);
        if (found) return found;
      }
    }
    return "";
  };
  const miyaBuildRequest = (method, path, fields, values, redacted = false) => {
    if (!miyaAllowedOperations.includes(`${method} ${path}`)) throw new Error("Unsupported operation");
    const url = new URL(path, miyaApiOrigin);
    const headers = {
      Accept: "application/json, text/plain"
    };
    const body = {};
    miyaActiveFields(fields, values).forEach(field => {
      const raw = String(values[field.name] ?? "");
      if (!raw.trim()) return;
      const sensitive = field.sensitive || field.in === "header" || miyaSensitiveKey.test(field.name);
      const value = redacted && sensitive ? "[hidden]" : field.type === "integer" ? Number(raw) : raw;
      if (field.in === "header") {
        headers.Authorization = redacted ? "Bearer [hidden]" : `Bearer ${miyaBearerToken(raw)}`;
      } else if (field.in === "query") url.searchParams.set(field.name, String(value)); else if (field.in === "body") body[field.name] = value;
    });
    const init = {
      method,
      headers,
      credentials: "omit",
      mode: "cors",
      redirect: "error"
    };
    if (method === "POST") {
      headers["Content-Type"] = "application/json";
      init.body = JSON.stringify(body);
    }
    return {
      url: url.toString(),
      init
    };
  };
  const miyaReadResponse = async (response, limit = miyaResponseLimit) => {
    if (!response.body?.getReader) {
      const bytes = new TextEncoder().encode(await response.text());
      return {
        text: new TextDecoder().decode(bytes.subarray(0, limit)),
        truncated: bytes.length > limit
      };
    }
    const reader = response.body.getReader();
    const chunks = [];
    let size = 0;
    let truncated = false;
    try {
      while (true) {
        const {value, done} = await reader.read();
        if (done) break;
        const remaining = limit - size;
        if (value.byteLength > remaining) {
          if (remaining > 0) chunks.push(value.subarray(0, remaining));
          size = limit;
          truncated = true;
          await reader.cancel();
          break;
        }
        chunks.push(value);
        size += value.byteLength;
      }
    } finally {
      reader.releaseLock();
    }
    const bytes = new Uint8Array(size);
    let offset = 0;
    chunks.forEach(chunk => {
      bytes.set(chunk, offset);
      offset += chunk.byteLength;
    });
    return {
      text: new TextDecoder().decode(bytes),
      truncated
    };
  };
  const miyaParseResponse = (response, payload, method, path, secrets, outputFormat) => {
    const contentType = response.headers.get("content-type") || "";
    const expectsText = method === "GET" && ["/api/ProxyLogic/Generate", "/api/ProxyLogic/UnlimitedGenerate"].includes(path);
    let parsed = null;
    let invalidJson = false;
    if (!payload.truncated && payload.text.trim() && (contentType.includes("json") || !expectsText)) {
      try {
        parsed = JSON.parse(payload.text);
      } catch {
        invalidJson = true;
      }
    }
    const code = parsed && !Array.isArray(parsed) && typeof parsed === "object" ? parsed.code : undefined;
    const codeSuccess = code === 0 || code === 200 || code === "0" || code === "200";
    const kind = !response.ok ? "httpError" : payload.truncated ? "truncated" : invalidJson ? "invalidJson" : code !== undefined ? codeSuccess ? "businessSuccess" : "businessError" : "httpSuccess";
    const token = path === "/api/SysLogin/Login" && response.ok && !payload.truncated && kind !== "businessError" ? miyaFindToken(parsed) : "";
    const textResponse = parsed === null && expectsText && !contentType.includes("json");
    const hiddenJson = !textResponse && (payload.truncated || invalidJson);
    return {
      status: response.status,
      contentType,
      kind,
      code,
      truncated: payload.truncated,
      rawText: payload.text,
      textResponse,
      hiddenJson,
      token,
      maskedText: hiddenJson ? "" : parsed !== null ? JSON.stringify(miyaRedactJson(parsed, [...secrets, token].filter(Boolean)), null, 2) : textResponse ? miyaMaskProxyText(payload.text, outputFormat, secrets) : miyaMaskString(payload.text, [...secrets, token].filter(Boolean))
    };
  };
  const labels = useMemo(() => miyaLabels(locale), [locale]);
  const [config, setConfig] = useState(null);
  const [values, setValues] = useState({});
  const [errors, setErrors] = useState({});
  const [loading, setLoading] = useState(true);
  const [loadError, setLoadError] = useState("");
  const [pending, setPending] = useState(false);
  const [result, setResult] = useState(null);
  const [showToken, setShowToken] = useState(false);
  const [showProxy, setShowProxy] = useState(false);
  const [notice, setNotice] = useState("");
  const requestRef = useRef(null);
  const sequenceRef = useRef(0);
  const liveRef = useRef(false);
  useEffect(() => {
    liveRef.current = true;
    sequenceRef.current += 1;
    requestRef.current?.controller.abort();
    requestRef.current = null;
    setConfig(null);
    setValues({});
    setErrors({});
    setResult(null);
    setShowToken(false);
    setShowProxy(false);
    setNotice("");
    setPending(false);
    setLoading(true);
    setLoadError("");
    const specController = new AbortController();
    let current = true;
    const load = async () => {
      if (!miyaAllowedSpecs.includes(specUrl) || !miyaAllowedOperations.includes(`${method} ${path}`)) {
        if (current) {
          setLoadError(labels.unsupported);
          setLoading(false);
        }
        return;
      }
      try {
        const response = await fetch(specUrl, {
          credentials: "omit",
          signal: specController.signal
        });
        if (!response.ok) throw new Error("Configuration unavailable");
        const spec = await response.json();
        const metadata = spec.paths?.[path]?.[method.toLowerCase()]?.["x-miya-playground"];
        if (!metadata || !Array.isArray(metadata.fields) || !metadata.fields.every(field => typeof field.name === "string" && ["query", "body", "header"].includes(field.in))) throw new Error("Invalid configuration");
        if (current) {
          setConfig(metadata);
          setValues(miyaDefaultValues(metadata.fields));
        }
      } catch (error) {
        if (current && !specController.signal.aborted) setLoadError(labels.loadError);
      } finally {
        if (current) setLoading(false);
      }
    };
    load();
    return () => {
      current = false;
      liveRef.current = false;
      sequenceRef.current += 1;
      specController.abort();
      requestRef.current?.controller.abort();
      requestRef.current = null;
    };
  }, [specUrl, method, path, locale]);
  const fields = config?.fields || [];
  const activeFields = miyaActiveFields(fields, values);
  const preview = useMemo(() => {
    if (!config) return "";
    try {
      const request = miyaBuildRequest(method, path, config.fields, values, true);
      return `${method} ${request.url}\n${Object.entries(request.init.headers).map(([key, value]) => `${key}: ${value}`).join("\n")}${request.init.body ? `\n\n${JSON.stringify(JSON.parse(request.init.body), null, 2)}` : ""}`;
    } catch {
      return "";
    }
  }, [config, values, method, path]);
  const updateValue = (name, value) => {
    setValues(previous => ({
      ...previous,
      [name]: value
    }));
    setErrors(previous => {
      const next = {
        ...previous
      };
      delete next[name];
      return next;
    });
  };
  const cancel = () => {
    const active = requestRef.current;
    if (!active) return;
    sequenceRef.current += 1;
    active.controller.abort();
    requestRef.current = null;
    setPending(false);
    setResult({
      kind: "cancelled",
      elapsed: Math.round(performance.now() - active.started)
    });
    setShowToken(false);
    setShowProxy(false);
    setNotice("");
  };
  const clear = () => {
    sequenceRef.current += 1;
    requestRef.current?.controller.abort();
    requestRef.current = null;
    setValues(miyaDefaultValues(fields));
    setErrors({});
    setResult(null);
    setShowToken(false);
    setShowProxy(false);
    setNotice("");
    setPending(false);
  };
  const submit = async event => {
    event.preventDefault();
    if (!config || requestRef.current || !liveRef.current) return;
    const nextErrors = miyaValidate(fields, values, labels);
    setErrors(nextErrors);
    if (Object.keys(nextErrors).length) return;
    const controller = new AbortController();
    const serial = ++sequenceRef.current;
    const started = performance.now();
    requestRef.current = {
      controller,
      serial,
      started
    };
    setPending(true);
    setResult(null);
    setShowToken(false);
    setShowProxy(false);
    setNotice("");
    let timedOut = false;
    let status;
    let contentType;
    const timeout = setTimeout(() => {
      timedOut = true;
      controller.abort();
    }, 30000);
    try {
      const request = miyaBuildRequest(method, path, fields, values);
      const response = await fetch(request.url, {
        ...request.init,
        signal: controller.signal
      });
      status = response.status;
      contentType = response.headers.get("content-type") || "";
      const payload = await miyaReadResponse(response);
      const parsed = miyaParseResponse(response, payload, method, path, miyaKnownSecrets(fields, values), values.Format);
      if (liveRef.current && sequenceRef.current === serial) setResult({
        ...parsed,
        elapsed: Math.round(performance.now() - started)
      });
    } catch {
      if (liveRef.current && sequenceRef.current === serial) setResult({
        kind: timedOut ? "timeout" : controller.signal.aborted ? "cancelled" : "networkError",
        status,
        contentType,
        elapsed: Math.round(performance.now() - started)
      });
    } finally {
      clearTimeout(timeout);
      if (requestRef.current?.serial === serial) requestRef.current = null;
      if (liveRef.current && sequenceRef.current === serial) setPending(false);
    }
  };
  const copyToken = async () => {
    if (!result?.token) return;
    const serial = sequenceRef.current;
    try {
      await navigator.clipboard.writeText(result.token);
      if (liveRef.current && sequenceRef.current === serial) setNotice(labels.copied);
    } catch {
      if (liveRef.current && sequenceRef.current === serial) setNotice(labels.copyFailed);
    }
  };
  const border = "1px solid rgba(128, 128, 128, 0.35)";
  const inputStyle = {
    width: "100%",
    minWidth: 0,
    border,
    borderRadius: "8px",
    padding: "8px 10px",
    background: "transparent",
    color: "inherit",
    font: "inherit",
    colorScheme: "inherit"
  };
  const buttonStyle = {
    border,
    borderRadius: "8px",
    padding: "8px 12px",
    background: "transparent",
    color: "inherit",
    cursor: "pointer",
    font: "inherit",
    fontSize: "14px"
  };
  const preStyle = {
    border,
    borderRadius: "8px",
    padding: "12px",
    maxHeight: "420px",
    overflow: "auto",
    whiteSpace: "pre-wrap",
    overflowWrap: "anywhere",
    fontSize: "13px",
    lineHeight: 1.6,
    background: "rgba(128, 128, 128, 0.06)",
    color: "inherit"
  };
  const hasResponseBody = result && result.rawText !== undefined;
  const responseText = hasResponseBody ? result.hiddenJson ? labels.jsonHidden : result.textResponse && showProxy ? result.rawText : result.maskedText : "";
  return <section data-miya-api-playground="true" aria-label={labels.title} style={{
    border,
    borderRadius: "12px",
    padding: "20px",
    margin: "24px 0",
    color: "inherit"
  }}>
      <h3 style={{
    marginTop: 0
  }}>{labels.title}</h3>
      {loading && <p role="status">{labels.loading}</p>}
      {loadError && <p role="alert">{loadError}</p>}
      {config && !loading && <>
          <p style={{
    fontSize: "14px",
    lineHeight: 1.6
  }}>{labels.live}</p>
          {config.warning && <p style={{
    borderLeft: "3px solid #ef7025",
    paddingLeft: "12px",
    fontSize: "14px"
  }}>{config.warning}</p>}
          <form onSubmit={submit} noValidate>
            <div style={{
    display: "grid",
    gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 240px), 1fr))",
    gap: "16px",
    margin: "18px 0"
  }}>
              {activeFields.map(field => <label key={field.name} style={{
    display: "block",
    minWidth: 0
  }}>
                  <span style={{
    display: "block",
    marginBottom: "6px",
    fontSize: "14px",
    fontWeight: 600
  }}>
                    {field.label || field.name} <span style={{
    fontWeight: 400,
    opacity: 0.7
  }}>({field.required ? labels.required : labels.optional})</span>
                  </span>
                  {field.input === "select" || field.options?.length ? <select name={field.name} value={values[field.name] ?? ""} disabled={pending} style={inputStyle} aria-invalid={Boolean(errors[field.name])} onChange={event => updateValue(field.name, event.target.value)}>
                      {!field.options?.some(option => String(option.value) === "") && <option value="">—</option>}
                      {(field.options || []).map(option => <option key={String(option.value)} value={String(option.value)}>{option.label || String(option.value)}</option>)}
                    </select> : <input name={field.name} type={field.sensitive || field.in === "header" ? "password" : field.input || (field.type === "integer" ? "number" : "text")} value={values[field.name] ?? ""} placeholder={field.placeholder || ""} min={field.min} max={field.max} step={field.type === "integer" ? 1 : undefined} disabled={pending} autoComplete="off" spellCheck={false} aria-invalid={Boolean(errors[field.name])} onChange={event => updateValue(field.name, event.target.value)} style={inputStyle} />}
                  {field.description && <span style={{
    display: "block",
    fontSize: "12px",
    lineHeight: 1.5,
    opacity: 0.75,
    marginTop: "5px"
  }}>{field.description}</span>}
                  {errors[field.name] && <span role="alert" style={{
    display: "block",
    fontSize: "13px",
    color: "#ef7025",
    marginTop: "4px"
  }}>{errors[field.name]}</span>}
                </label>)}
            </div>
            <details style={{
    margin: "16px 0"
  }}>
              <summary style={{
    cursor: "pointer",
    fontSize: "14px"
  }}>{labels.request}</summary>
              <pre data-miya-request-preview="true" style={preStyle}>{preview}</pre>
            </details>
            <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: "8px"
  }}>
              <button type="submit" disabled={pending} style={{
    ...buttonStyle,
    background: "#ef7025",
    color: "#fff",
    borderColor: "#ef7025",
    opacity: pending ? 0.6 : 1
  }}>
                {pending ? labels.sending : config.submitLabel || labels.send}
              </button>
              {pending && <button type="button" onClick={cancel} style={buttonStyle}>{labels.cancel}</button>}
              <button type="button" onClick={clear} style={buttonStyle}>{labels.clear}</button>
            </div>
          </form>
          <p style={{
    fontSize: "12px",
    lineHeight: 1.6,
    opacity: 0.75
  }}>{labels.cancelNote}</p>
          {result && <div data-miya-response="true" aria-live="polite" style={{
    marginTop: "20px"
  }}>
              <h4 style={{
    marginBottom: "8px"
  }}>{labels.response}</h4>
              <p role={["networkError", "timeout", "httpError", "businessError", "invalidJson"].includes(result.kind) ? "alert" : "status"}>
                {labels[result.kind] || labels.httpSuccess}
              </p>
              <p style={{
    fontSize: "13px",
    display: "flex",
    flexWrap: "wrap",
    gap: "12px"
  }}>
                {result.status !== undefined && <span>{labels.status}: <strong>{result.status}</strong></span>}
                <span>{labels.elapsed}: {result.elapsed} ms</span>
                {result.code !== undefined && <span>{labels.businessCode}: <strong>{String(result.code)}</strong></span>}
                {result.contentType && <span>{labels.contentType}: {result.contentType}</span>}
              </p>
              {result.truncated && result.kind !== "truncated" && <p>{labels.truncated}</p>}
              {result.token && <div style={{
    border,
    borderRadius: "8px",
    padding: "12px",
    margin: "12px 0"
  }}>
                  <p style={{
    margin: "0 0 8px",
    fontSize: "14px"
  }}>{labels.token}: <code style={{
    overflowWrap: "anywhere"
  }}>{showToken ? result.token : "••••••••"}</code></p>
                  <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: "8px"
  }}>
                    <button type="button" onClick={() => setShowToken(shown => !shown)} style={buttonStyle}>{showToken ? labels.hideToken : labels.showToken}</button>
                    <button type="button" onClick={copyToken} style={buttonStyle}>{labels.copyToken}</button>
                  </div>
                  {notice && <p role="status" style={{
    fontSize: "13px",
    marginBottom: 0
  }}>{notice}</p>}
                </div>}
              {hasResponseBody && <>
                  <p style={{
    fontSize: "12px",
    opacity: 0.75
  }}>{result.textResponse ? labels.proxyMasked : labels.masked}</p>
                  {result.textResponse && <button type="button" onClick={() => setShowProxy(shown => !shown)} style={buttonStyle}>{showProxy ? labels.hideProxy : labels.showProxy}</button>}
                  <pre data-miya-response-body="true" style={preStyle}>{responseText || labels.empty}</pre>
                </>}
            </div>}
        </>}
    </section>;
};

Read a paginated list of residential proxy assets for the authenticated account. This endpoint retrieves existing assets; it does not purchase, renew, or generate proxies.

## Query existing assets

1. Obtain a token from [Log in](/api-reference/login) and enter it as the Bearer credential.
2. Set pagination and any required status, product type, location, keyword, or sorting filters using the parameters below.
3. Send the request, check the HTTP and business results, then inspect the returned page and total before requesting the next page with the same filters.

Choose `Vid` and `IspType` from their documented enumerations. Keep `IspType=0` when you intend that filter value; zero is a valid choice, not an empty field. Sorting supports the documented fields and `asc` or `desc` order. Omitting an optional filter differs from supplying a filter value.

The form's pagination bounds come from the current public interface. Use the [static residential quick start](/proxy/static-residential/quick-start) to manage purchased IPs in the dashboard, and see [API response handling](/api-reference/overview) for errors.

## Online debugging

<MiyaApiPlayground specUrl="/api-reference/openapi.en.json" method="GET" path="/api/UserIsps/PageList" locale="en" key="en:isp-list" />

[Source: official residential asset reference](https://miyaip.com/developer-center?endpoint=isp-list).


## OpenAPI

````yaml api-reference/openapi.en.json GET /api/UserIsps/PageList
openapi: 3.0.3
info:
  title: MiyaIP API integration
  version: '2026-10-13'
  description: >-
    This specification documents the eight endpoints published in the Developer
    Center. Response structures follow the source examples and do not claim to
    define every returned field. Form minimums, maximums, and initial values
    describe client behavior and are retained in x-miya-playground rather than
    asserted as unverified server constraints or defaults.
servers:
  - url: https://miyaip.com
    description: Production API base URL
security: []
tags:
  - name: authentication
    description: Authentication
    x-displayName: Authentication
  - name: proxy-generation
    description: Proxy generation
    x-displayName: Proxy generation
  - name: account-data
    description: Account and usage
    x-displayName: Account and usage
  - name: proxy-inventory
    description: Proxy inventory
    x-displayName: Proxy inventory
externalDocs:
  description: Developer Center source
  url: https://miyaip.com/developer-center
paths:
  /api/UserIsps/PageList:
    get:
      tags:
        - proxy-inventory
      summary: List static ISP proxies
      description: >-
        List the authenticated user’s static residential ISP proxies with
        filters and pagination.


        Requires a Bearer token returned by the login API.
      operationId: isp-list
      parameters:
        - name: PageNo
          in: query
          required: true
          description: Page number starting at 1.
          schema:
            type: integer
            example: 1
        - name: PageSize
          in: query
          required: true
          description: Number of records per page.
          schema:
            type: integer
            example: 10
        - name: IspType
          in: query
          required: false
          description: '-1 returns all; 0 is exclusive; 1 is shared.'
          schema:
            type: integer
            enum:
              - -1
              - 0
              - 1
            example: -1
        - name: Vid
          in: query
          required: false
          description: Inventory status filter.
          schema:
            type: string
            enum:
              - all
              - new
              - valid
              - exp
              - expiringsoon
            example: all
        - name: IspLocation
          in: query
          required: false
          description: Optional ISP location filter.
          schema:
            type: string
        - name: KeyWord
          in: query
          required: false
          description: Search by IP or related identifier.
          schema:
            type: string
        - name: SortField
          in: query
          required: false
          description: Optional created-time or expiry-time sorting.
          schema:
            type: string
            enum:
              - sysCreateTime
              - expireTime
        - name: SortOrder
          in: query
          required: false
          description: Optional. Omit it for the current default ascending behavior.
          schema:
            type: string
            enum:
              - asc
              - desc
      responses:
        '200':
          description: Paged ISP inventory
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                  message:
                    type: string
                  body:
                    type: object
                    properties:
                      pageNo:
                        type: integer
                      pageSize:
                        type: integer
                      totalPage:
                        type: integer
                      totalRows:
                        type: integer
                      records:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: integer
                            ipNo:
                              type: string
                            ispType:
                              type: integer
                            ip:
                              type: string
                            directIp:
                              type: string
                            ispLocation:
                              type: string
                            expireTime:
                              type: string
              example:
                code: 200
                message: Success
                body:
                  pageNo: 1
                  pageSize: 10
                  totalPage: 1
                  totalRows: 1
                  records:
                    - id: 12001
                      ipNo: <IP_NO>
                      ispType: 0
                      ip: proxy.example:10000:user:********
                      directIp: 203.0.113.10:10000:user:********
                      ispLocation: US
                      expireTime: '2026-12-31 23:59:59'
        '400':
          description: >-
            Business or validation error


            The source labels this example as HTTP 400 while its JSON business
            code is 500. HTTP status and the JSON business code are distinct
            values; this example is not a complete catalog of error statuses or
            codes.
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                  message:
                    type: string
                  body:
                    description: >-
                      The source example is null; the source does not define
                      this field's complete value type.
              example:
                code: 500
                message: Invalid request
                body: null
      security:
        - bearerAuth: []
      externalDocs:
        description: Source reference for this endpoint
        url: https://miyaip.com/developer-center?endpoint=isp-list
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl --get 'https://miyaip.com/api/UserIsps/PageList' \
              -H 'Accept: application/json' \
              -H 'Authorization: Bearer <YOUR_TOKEN>' \
              --data-urlencode 'PageNo=1' \
              --data-urlencode 'PageSize=10' \
              --data-urlencode 'IspType=-1' \
              --data-urlencode 'Vid=all'
        - lang: javascript
          label: JavaScript
          source: |-
            const params = new URLSearchParams({
              "PageNo": "1",
              "PageSize": "10",
              "IspType": "-1",
              "Vid": "all"
            });
            const url = `https://miyaip.com/api/UserIsps/PageList?${params}`;

            const response = await fetch(url, {
              method: 'GET',
              headers: {
                "Accept": "application/json",
                "Authorization": "Bearer <YOUR_TOKEN>"
              },
            });

            const result = await response.text();
            console.log(result);
        - lang: python
          label: Python
          source: >-
            import requests


            url = "https://miyaip.com/api/UserIsps/PageList"

            headers = {
              "Accept": "application/json",
              "Authorization": "Bearer <YOUR_TOKEN>"
            }

            params = {
              "PageNo": 1,
              "PageSize": 10,
              "IspType": -1,
              "Vid": "all"
            }


            response = requests.get(url, headers=headers, params=params,
            timeout=30)

            response.raise_for_status()

            print(response.text)
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        Use token from the login response in the Authorization: Bearer <token>
        header. The source does not specify token lifetime or refresh behavior.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.