> ## 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 动态代理 API：生成住宅与移动代理

> 使用 Crc 与 KeyName 调用 MiyaIP 动态住宅和移动代理生成接口，设置地区、会话、协议与输出格式并读取连接信息。

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>;
};

生成指定国家或城市的动态代理连接信息。动态住宅与移动产品复用此接口，但使用各自对应的产品凭据。

## 准备生成请求

1. 从后台取得 `Crc` 与产品密钥。住宅代理使用 `mainKey`；移动代理使用 `mobileMainKey`，并设置 `mealType=mobile`。
2. 按下方参数选择地区、数量、协议、输出格式与会话方式。保留查询参数名称的大小写。
3. 发送请求后检查 HTTP 状态。成功时按选定格式读取纯文本连接列表，再在客户端核验连接。

`SessionTime=0` 表示每次请求轮换，`SessionTime=-1` 表示手动轮换。来源未说明正数值的单位与完整支持范围，不要仅凭数值推断时长。

表单数值范围来自当前公开界面。国家、城市与入口线路示例不是完整选项列表，请使用当前后台提供的值。

产品配置可参阅[动态住宅快速开始](/zh/proxy/dynamic-residential/quick-start)或[移动代理快速开始](/zh/proxy/mobile/quick-start)。生成失败时参阅[响应处理说明](/zh/api-reference/overview)。

## 在线调试

<MiyaApiPlayground specUrl="/api-reference/openapi.zh.json" method="GET" path="/api/ProxyLogic/Generate" locale="zh" key="zh:dynamic-generate" />

[来源：官方动态生成接口参考](https://miyaip.com/zh/developer-center?endpoint=dynamic-generate)。


## OpenAPI

````yaml api-reference/openapi.zh.json GET /api/ProxyLogic/Generate
openapi: 3.0.3
info:
  title: MiyaIP API 接入
  version: '2026-10-13'
  description: >-
    本规范整理开发者中心公开的 8 个接口。响应结构按来源示例展示，未声明全部可返回字段。在线表单中的最小值、最大值及初始值是客户端行为，保留于
    x-miya-playground，不作为未核实的后端约束或默认值。
servers:
  - url: https://miyaip.com
    description: 生产 API 基础地址
security: []
tags:
  - name: authentication
    description: 身份认证
    x-displayName: 身份认证
  - name: proxy-generation
    description: 代理生成
    x-displayName: 代理生成
  - name: account-data
    description: 账户与用量
    x-displayName: 账户与用量
  - name: proxy-inventory
    description: 代理资产
    x-displayName: 代理资产
externalDocs:
  description: 开发者中心来源
  url: https://miyaip.com/zh/developer-center
paths:
  /api/ProxyLogic/Generate:
    get:
      tags:
        - proxy-generation
      summary: 获取动态国家/城市代理
      description: |-
        按国家、州省、城市和协议生成动态住宅或移动代理凭证。

        Crc 与 KeyName 是查询凭证，请按敏感信息保护，不要公开生成后的完整 URL。
      operationId: dynamic-generate
      parameters:
        - name: Num
          in: query
          required: true
          description: 返回的代理行数量。
          schema:
            type: integer
            example: 1
        - name: Country
          in: query
          required: false
          description: 国家代码；留空时随机选择国家。
          schema:
            type: string
            example: US
        - name: State
          in: query
          required: false
          description: 可选的州或省。
          schema:
            type: string
        - name: City
          in: query
          required: false
          description: 可选的城市。
          schema:
            type: string
        - name: SessionTime
          in: query
          required: true
          description: 0 表示每次请求轮换；-1 表示手动切换。
          schema:
            type: integer
            example: -1
        - name: Server
          in: query
          required: true
          description: 网关节点，例如 as、us 或 eu。
          schema:
            type: string
            example: us
        - name: Format
          in: query
          required: true
          description: 控制返回的代理凭证排列格式。
          schema:
            type: string
            enum:
              - '0'
              - '1'
              - '2'
            example: '0'
        - name: Crc
          in: query
          required: true
          description: 代理账户对应的密码值。
          schema:
            type: string
            format: password
        - name: Pool
          in: query
          required: true
          description: 代理线路池编号。
          schema:
            type: integer
            example: 1
        - name: KeyName
          in: query
          required: true
          description: 住宅代理使用 mainKey；移动代理使用 mobileMainKey。
          schema:
            type: string
            format: password
        - name: mealType
          in: query
          required: false
          description: 移动代理填写 mobile；普通动态住宅代理留空。
          schema:
            type: string
        - name: GenType
          in: query
          required: false
          description: 可选的代理协议覆盖。
          schema:
            type: string
            enum:
              - http
              - socks5
      responses:
        '200':
          description: 生成的代理行
          content:
            text/plain:
              schema:
                type: string
              example: gateway.miyaip.com:10000:username-zone-us:********
        '400':
          description: >-
            业务或参数错误


            来源将此示例标为 HTTP 400，示例中的业务 code 为 500。HTTP 状态与 JSON 业务 code
            是不同字段，示例不代表完整错误状态或错误码清单。
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                  message:
                    type: string
                  body:
                    description: 来源示例为 null，未定义此字段的完整值类型。
              example:
                code: 500
                message: Invalid request
                body: null
      security:
        - proxyCrc: []
          proxyKey: []
      externalDocs:
        description: 此接口的来源说明
        url: https://miyaip.com/zh/developer-center?endpoint=dynamic-generate
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl --get 'https://miyaip.com/api/ProxyLogic/Generate' \
              -H 'Accept: text/plain' \
              --data-urlencode 'Num=1' \
              --data-urlencode 'Country=US' \
              --data-urlencode 'SessionTime=-1' \
              --data-urlencode 'Server=us' \
              --data-urlencode 'Format=0' \
              --data-urlencode 'Crc=<YOUR_PASSWORD>' \
              --data-urlencode 'Pool=1' \
              --data-urlencode 'KeyName=<YOUR_MAIN_KEY>'
        - lang: javascript
          label: JavaScript
          source: |-
            const params = new URLSearchParams({
              "Num": "1",
              "Country": "US",
              "SessionTime": "-1",
              "Server": "us",
              "Format": "0",
              "Crc": "<YOUR_PASSWORD>",
              "Pool": "1",
              "KeyName": "<YOUR_MAIN_KEY>"
            });
            const url = `https://miyaip.com/api/ProxyLogic/Generate?${params}`;

            const response = await fetch(url, {
              method: 'GET',
              headers: {
                "Accept": "text/plain"
              },
            });

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


            url = "https://miyaip.com/api/ProxyLogic/Generate"

            headers = {
              "Accept": "text/plain"
            }

            params = {
              "Num": 1,
              "Country": "US",
              "SessionTime": -1,
              "Server": "us",
              "Format": "0",
              "Crc": "<YOUR_PASSWORD>",
              "Pool": 1,
              "KeyName": "<YOUR_MAIN_KEY>"
            }


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

            response.raise_for_status()

            print(response.text)
components:
  securitySchemes:
    proxyCrc:
      type: apiKey
      in: query
      name: Crc
      description: 代理账户的密码值；查询字符串中的敏感凭据。
    proxyKey:
      type: apiKey
      in: query
      name: KeyName
      description: 对应产品的主 Key；与 Crc 同时提供。

````

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