使用 Python 生成圖片

使用 Python 標準函式庫提交一個 MaxAPI 圖片任務、輪詢狀態,避免盲目重送造成重複生成。

技術核對日期:

1. 準備伺服器端金鑰

使用 Python 3.10 以上版本。下載檔案只使用 json、time 與 urllib,不需要安裝 pip 套件。在 Console 建立金鑰,確認模型、路由權限及可用餘額。將 MAXAPI_KEY 放在伺服器環境或密鑰管理服務,不要寫入公開筆記本、瀏覽器程式或原始碼倉庫。

2. 從小規模請求開始

下載下方 maxapi_image.py,安全設定 MAXAPI_KEY 後執行。程式只提交一次 POST /v1/images/generations,使用 google/gemini-3.1-flash-lite-image、resolution 1K、aspect_ratio 1:1、n 1 與 async true。這個 Lite 圖片模型只支援 1K,不要直接改成 4K;需要先換成支援該解析度的模型。

python3 maxapi_image.py

3. 區分受理與完成

程式不會自動保存圖片。它將任務 ID 與輪詢網址輸出到 stderr,請存入自己的工作紀錄,成功後才輸出結果網址。HTTP 202 代表受理,不是完成;之後的 HTTP 200 仍可能是執行中或失敗,必須檢查 status。私人圖片網址不要公開,也不要把 MaxAPI 金鑰附在其他主機的圖片下載請求中。

4. 恢復查詢,而非重新提交

每次網路操作有 30 秒 socket 逾時,預設每隔五秒查詢,最多 120 次 GET。由於請求本身也耗時,這不是嚴格的十分鐘期限。輪詢到達上限或按 Ctrl+C 並不會取消任務。將已保存的網址設為 MAXAPI_POLL_URL,用同帳號金鑰恢復查詢,此模式不會再次 POST。若初次 POST 在取得 ID 前逾時,先查使用紀錄,再決定是否重新生成。

MAXAPI_POLL_URL="/v1/images/generations/YOUR_TASK_ID" python3 maxapi_image.py

下載完整範例

maxapi_image.py

maxapi_image.py
"""Python 3.10+. Standard library only. Server-side; local-fixture tested, not a live model test."""
import json
import os
import sys
import time
import urllib.error
import urllib.parse
import urllib.request

EXAMPLE_PAYLOAD = {
    "model": "google/gemini-3.1-flash-lite-image",
    "prompt": "A single blue ceramic cup on a plain cream background, soft studio light.",
    "resolution": "1K", "aspect_ratio": "1:1", "n": 1, "async": True,
}
ACTIVE = {"submitted", "queued", "pending", "running", "processing"}


class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


def safe_url(base, path):
    import re
    origin = urllib.parse.urlsplit(base)
    url = urllib.parse.urlsplit(urllib.parse.urljoin(base + "/", path))
    valid_scheme = origin.scheme == "https" or (origin.scheme == "http" and origin.hostname in {"127.0.0.1", "::1"})
    def address(value):
        return (value.scheme, value.hostname, value.port or (443 if value.scheme == "https" else 80))
    if (not valid_scheme or origin.path not in {"", "/"} or origin.query or origin.fragment
            or origin.username or origin.password or url.username or url.password or url.query or url.fragment
            or address(url) != address(origin) or not re.fullmatch(r"/v1/images/(generations|edits)(/[^/?]+)?", url.path)):
        raise RuntimeError("Unsafe API or polling URL; no credentials sent.")
    return urllib.parse.urlunsplit(url)


def run_image(key, payload=None, base="https://api.maxapi.dev", poll_url=None,
              interval=5, max_polls=120, timeout=30, on_task=lambda task: None):
    if not key:
        raise RuntimeError("Set MAXAPI_KEY in your server environment.")
    if not isinstance(max_polls, int) or not 1 <= max_polls <= 720 or interval < 0 or timeout <= 0:
        raise RuntimeError("Invalid polling limits.")
    opener = urllib.request.build_opener(NoRedirect())

    def request(path, body=None):
        import re
        headers = {"Authorization": "Bearer " + key}
        if body is not None:
            headers["Content-Type"] = "application/json"
        req = urllib.request.Request(safe_url(base, path), headers=headers,
                                     data=json.dumps(body).encode() if body is not None else None,
                                     method="POST" if body is not None else "GET")
        try:
            response = opener.open(req, timeout=timeout)
        except urllib.error.HTTPError as error:
            response = error
        except (urllib.error.URLError, TimeoutError, OSError) as error:
            raise RuntimeError("Network or timeout error; outcome may be unknown. No automatic resubmission.") from error
        with response:
            status = response.code
            try:
                result = json.load(response)
            except (ValueError, UnicodeError) as error:
                raise RuntimeError(f"HTTP {status}: non-JSON response; inspect history before resubmitting.") from error
        if not isinstance(result, dict):
            raise RuntimeError("Invalid JSON response object.")
        if not 200 <= status < 300 or result.get("error"):
            detail = result.get("error")
            code = detail.get("code", "api_error") if isinstance(detail, dict) else "api_error"
            code = code if isinstance(code, str) and re.fullmatch(r"[a-zA-Z0-9_-]{1,80}", code) else "api_error"
            raise RuntimeError(f"HTTP {status}: {code}; no automatic retry.")
        return result

    current = request(poll_url) if poll_url else request("/v1/images/generations", EXAMPLE_PAYLOAD if payload is None else payload)
    path = poll_url or current.get("poll_url")
    if path:
        path = safe_url(base, path)
        on_task({"id": current.get("id"), "poll_url": path})
    count = 0
    while True:
        if current.get("status") == "succeeded" or (not current.get("status") and isinstance(current.get("data"), list) and current["data"]):
            return current
        if current.get("status") not in ACTIVE:
            raise RuntimeError("Task failed, cancelled or returned an unknown state. Check history; no resubmission.")
        if not path:
            raise RuntimeError("Pending task has no polling URL. Check history; do not resubmit automatically.")
        if count >= max_polls:
            raise RuntimeError("Polling limit reached; task was NOT cancelled. Resume using the saved polling URL.")
        time.sleep(interval)
        current = request(path)
        count += 1


if __name__ == "__main__":
    try:
        result = run_image(os.environ.get("MAXAPI_KEY"), poll_url=os.environ.get("MAXAPI_POLL_URL"),
                           on_task=lambda task: print("Save to resume:", json.dumps(task), file=sys.stderr))
        print(json.dumps({"id": result.get("id"), "status": result.get("status", "succeeded"),
                          "urls": [item["url"] for item in result.get("data", []) if item.get("url")]}, indent=2))
    except (RuntimeError, ValueError) as error:
        print(str(error), file=sys.stderr)
        sys.exit(1)

各模型參數

這是公開設定快照,不代表即時可用性。參數、支援路由與參考圖限制請查看各模型頁面。

參考資料

繼續接入

管理 API 金鑰API 文件查看目前價格