{
  "components": {
    "responses": {
      "Error": {
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "错误响应，形状固定为 {error, code}"
      },
      "RateLimited": {
        "content": {
          "application/json": {
            "example": {
              "code": "rate_limited",
              "error": "请求过于频繁，请稍后再试"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "触发限流；响应头 Retry-After 指示退避秒数",
        "headers": {
          "Retry-After": {
            "description": "建议等待秒数",
            "schema": {
              "type": "integer"
            }
          }
        }
      },
      "Unauthorized": {
        "content": {
          "application/json": {
            "example": {
              "code": "unauthorized",
              "error": "API Key 无效"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "凭据缺失或无效"
      }
    },
    "schemas": {
      "CaptchaRequest": {
        "description": "任务类型 + 该类型的参数。只填业务参数即可，认证与其它字段由网关处理。完整参数见 /docs#captcha。",
        "properties": {
          "body": {
            "description": "ImageToText：Base64",
            "type": "string"
          },
          "image": {
            "description": "图像类：Base64（前缀可带可不带）",
            "type": "string"
          },
          "proxy": {
            "description": "CloudFlareTaskS2 必填：自带代理",
            "type": "string"
          },
          "queries": {
            "description": "hCaptcha / AWS / bCaptcha 图像识别：Base64 或数组"
          },
          "question": {
            "description": "图像识别的问题 ID，如 /m/0k4j",
            "type": "string"
          },
          "type": {
            "enum": [
              "NoCaptchaTaskProxyless",
              "RecaptchaV2EnterpriseTaskProxyless",
              "RecaptchaV3TaskProxyless",
              "RecaptchaV3EnterpriseTask",
              "HCaptchaTaskProxyless",
              "TurnstileTaskProxyless",
              "CloudFlareTaskS2",
              "RecaptchaV2TaskProxyless",
              "RecaptchaV3TaskProxylessM1",
              "RecaptchaV3TaskProxylessM1S7",
              "RecaptchaV3EnterpriseTaskM1",
              "NoCaptchaTask",
              "RecaptchaV2EnterpriseTask",
              "HCaptchaTask",
              "TurnstileTask",
              "FunCaptchaTask",
              "FunCaptchaTaskProxyless",
              "ImageToTextTaskMuggle",
              "ImageToTextTaskM1",
              "ImageToTextTask",
              "ReCaptchaV2Classification",
              "HCaptchaClassification",
              "FunCaptchaClassification",
              "AwsClassification",
              "bCaptchaClassification"
            ],
            "type": "string"
          },
          "websiteKey": {
            "description": "协议类必填：站点 key",
            "type": "string"
          },
          "websiteURL": {
            "description": "协议类必填：验证码所在页面地址",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "type": "object"
      },
      "CaptchaSolution": {
        "properties": {
          "balance": {
            "type": "integer"
          },
          "cookies": {
            "description": "CloudFlareTaskS2 的 cookie",
            "type": "object"
          },
          "objects": {
            "description": "图像识别分类结果"
          },
          "ok": {
            "type": "boolean"
          },
          "price": {
            "description": "本次扣除积分",
            "type": "integer"
          },
          "ready": {
            "type": "boolean"
          },
          "solution": {
            "description": "原始结果",
            "type": "object"
          },
          "status": {
            "enum": [
              "ready",
              "processing"
            ],
            "type": "string"
          },
          "task_id": {
            "type": "string"
          },
          "text": {
            "description": "OCR 结果",
            "type": "string"
          },
          "token": {
            "description": "协议类任务的结果 token",
            "type": "string"
          },
          "took_ms": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "Credits": {
        "properties": {
          "batches": {
            "description": "按批次记账，每批各自从入账时间起算有效期",
            "items": {
              "properties": {
                "created_at": {
                  "type": "string"
                },
                "expires_at": {
                  "type": "string"
                },
                "granted": {
                  "type": "integer"
                },
                "id": {
                  "type": "integer"
                },
                "note": {
                  "type": "string"
                },
                "remaining": {
                  "type": "integer"
                },
                "source": {
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "next_expire_at": {
            "type": "string"
          },
          "ticket_price": {
            "description": "单张票价格",
            "type": "integer"
          },
          "total": {
            "description": "可用积分合计",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "Error": {
        "properties": {
          "balance": {
            "description": "积分不足时附带当前余额",
            "type": "integer"
          },
          "code": {
            "description": "机器可读的稳定错误码，**请按这个分支**",
            "enum": [
              "bad_request",
              "unauthorized",
              "forbidden",
              "not_found",
              "proxy_not_configured",
              "insufficient_credits",
              "mint_failed",
              "rate_limited",
              "internal_error"
            ],
            "type": "string"
          },
          "error": {
            "description": "人类可读的描述，文案可能调整",
            "type": "string"
          },
          "price": {
            "description": "积分不足时附带单张票价",
            "type": "integer"
          },
          "shortage": {
            "description": "积分不足时附带还差多少",
            "type": "integer"
          }
        },
        "required": [
          "error",
          "code"
        ],
        "type": "object"
      },
      "Key": {
        "properties": {
          "calls": {
            "type": "integer"
          },
          "created_at": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "id": {
            "type": "integer"
          },
          "last_used_at": {
            "type": "string"
          },
          "masked": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "Me": {
        "properties": {
          "credits": {
            "type": "integer"
          },
          "email": {
            "type": "string"
          },
          "proxy_configured": {
            "type": "boolean"
          },
          "role": {
            "enum": [
              "user",
              "admin"
            ],
            "type": "string"
          },
          "tickets_total": {
            "type": "integer"
          },
          "user_id": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "ProxyConfig": {
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "template": {
            "description": "代理模板，密码已打码",
            "type": "string"
          }
        },
        "type": "object"
      },
      "Ticket": {
        "description": "一张可用的 ARP 票及其绑定的出口。",
        "properties": {
          "balance": {
            "description": "扣费后余额",
            "type": "integer"
          },
          "elapsed_ms": {
            "description": "本次铸票耗时（毫秒，纯算通常 1–2 秒）",
            "type": "integer"
          },
          "fp": {
            "description": "纯算路径选用的指纹特征（仅 `mint_mode=pure` 时返回；老票没有）。",
            "properties": {
              "cfp": {
                "description": "CFP / 指纹标识（样本没有该字段时等于 fp_id）",
                "type": "string"
              },
              "cores": {
                "description": "navigator.hardwareConcurrency（样本没有则为 0）",
                "type": "integer"
              },
              "enhanced_fp": {
                "description": "enhanced_fp 项数",
                "type": "integer"
              },
              "fe": {
                "description": "fe 项数",
                "type": "integer"
              },
              "file": {
                "description": "指纹池里的文件名",
                "type": "string"
              },
              "fp_id": {
                "description": "指纹内容摘要（16 hex）",
                "type": "string"
              },
              "gpu": {
                "description": "WebGL 渲染器",
                "type": "string"
              },
              "memory": {
                "description": "navigator.deviceMemory（样本没有则为 0）",
                "type": "integer"
              },
              "resolution": {
                "description": "窗口内尺寸，如 2560x1440",
                "type": "string"
              },
              "screen": {
                "description": "窗口外尺寸",
                "type": "string"
              }
            },
            "type": "object"
          },
          "id": {
            "description": "取票记录 ID（使用日志里的一行）",
            "type": "integer"
          },
          "mint_mode": {
            "description": "这张票由哪条路径铸出。默认 `pure`（纯算，不启动浏览器）；`browser` 仅供内部 A/B。老票可能没有这个字段。",
            "enum": [
              "pure",
              "browser"
            ],
            "type": "string"
          },
          "minted_at": {
            "format": "date-time",
            "type": "string"
          },
          "next_expire_at": {
            "description": "最近一批积分到期时间",
            "type": "string"
          },
          "price": {
            "description": "本次扣除的积分",
            "type": "integer"
          },
          "proxy_url": {
            "description": "**必须用这个出口提交**，换了出口票即作废（Adobe 回 408）",
            "type": "string"
          },
          "sid": {
            "description": "本次生成的代理会话 ID，已填进 proxy_url",
            "type": "string"
          },
          "ticket": {
            "description": "票据本体，放进请求头 `x-arp-session-id`",
            "type": "string"
          },
          "took_sec": {
            "description": "本次铸票耗时（秒）",
            "type": "integer"
          },
          "usage": {
            "description": "用法提示",
            "type": "string"
          }
        },
        "required": [
          "id",
          "sid",
          "ticket",
          "proxy_url"
        ],
        "type": "object"
      },
      "TicketHistoryItem": {
        "properties": {
          "id": {
            "type": "integer"
          },
          "minted_at": {
            "type": "string"
          },
          "proxy_url": {
            "description": "密码已打码",
            "type": "string"
          },
          "sid": {
            "type": "string"
          },
          "ticket": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "TicketJob": {
        "description": "取票任务的状态。轮询它是**免费**的，按 `poll_after_sec` 的节奏来即可。",
        "properties": {
          "attempt": {
            "description": "当前是第几个出口",
            "type": "integer"
          },
          "attempts": {
            "description": "一共允许多少个出口",
            "type": "integer"
          },
          "balance": {
            "type": "integer"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "done_at": {
            "description": "进终态的时间（未结束为空）",
            "format": "date-time",
            "type": "string"
          },
          "elapsed_sec": {
            "description": "已耗时（秒）。它在涨就说明任务在推进，不是卡死。",
            "type": "integer"
          },
          "error": {
            "description": "失败原因（仅 failed / canceled 有）",
            "type": "string"
          },
          "job_id": {
            "type": "string"
          },
          "message": {
            "description": "给人看的一句话，**不要拿它做判断**",
            "type": "string"
          },
          "ok": {
            "type": "boolean"
          },
          "poll_after_sec": {
            "type": "integer"
          },
          "price": {
            "type": "integer"
          },
          "refunded": {
            "description": "积分是否已退回（仅 failed / canceled 有意义）",
            "type": "boolean"
          },
          "result": {
            "$ref": "#/components/schemas/Ticket",
            "description": "**status=ready 时才有**：一张完整的票"
          },
          "stage": {
            "description": "进行到哪一步：排队 → 启动浏览器 → 打开页面 → 等安全校验（最久）→ 换出口重试 → 完成。",
            "enum": [
              "queued",
              "browser",
              "page",
              "waiting",
              "retry",
              "done"
            ],
            "type": "string"
          },
          "status": {
            "description": "`ready` = 出票且已扣费；`failed` = 失败且**已全额退回**；`canceled` = 已取消并退回。",
            "enum": [
              "queued",
              "running",
              "ready",
              "failed",
              "canceled"
            ],
            "type": "string"
          }
        },
        "required": [
          "ok",
          "job_id",
          "status",
          "price",
          "poll_after_sec"
        ],
        "type": "object"
      },
      "TicketJobAccepted": {
        "description": "异步取票任务已受理。**积分此时已被冻结**（balance 已反映），铸票成功才真扣。",
        "properties": {
          "balance": {
            "description": "冻结后余额",
            "type": "integer"
          },
          "expires_in_sec": {
            "description": "任务记录保留时长（秒）",
            "type": "integer"
          },
          "job_id": {
            "description": "任务 ID，用它轮询 `GET /v1/ticket/{job_id}`",
            "type": "string"
          },
          "message": {
            "description": "给人看的一句话，**不要拿它做判断**",
            "type": "string"
          },
          "ok": {
            "type": "boolean"
          },
          "poll_after_sec": {
            "description": "建议的轮询间隔（秒）",
            "type": "integer"
          },
          "poll_url": {
            "description": "建议的轮询路径",
            "type": "string"
          },
          "price": {
            "description": "本次冻结的积分",
            "type": "integer"
          },
          "stage": {
            "description": "阶段标识，取值见 TicketJob.stage",
            "type": "string"
          },
          "status": {
            "description": "刚受理时固定是 queued",
            "enum": [
              "queued"
            ],
            "type": "string"
          }
        },
        "required": [
          "ok",
          "job_id",
          "status",
          "price",
          "balance",
          "poll_url",
          "poll_after_sec"
        ],
        "type": "object"
      },
      "TicketJobSummary": {
        "description": "任务列表里的一条（不含票本身，避免一次响应带几十份票据）。",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "done_at": {
            "format": "date-time",
            "type": "string"
          },
          "elapsed_sec": {
            "type": "integer"
          },
          "error": {
            "type": "string"
          },
          "job_id": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "price": {
            "type": "integer"
          },
          "refunded": {
            "type": "boolean"
          },
          "stage": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "ticket_id": {
            "description": "已出票时指向使用日志里的那一行",
            "type": "integer"
          }
        },
        "type": "object"
      }
    },
    "securitySchemes": {
      "ApiKey": {
        "description": "`Authorization: Bearer arp_xxx`，程序调用推荐用这个。",
        "scheme": "bearer",
        "type": "http"
      },
      "Session": {
        "description": "登录后自动下发的会话 Cookie，给浏览器页面用。",
        "in": "cookie",
        "name": "arp_session",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "contact": {
      "name": "ARP 取票服务"
    },
    "description": "平台提供两类能力：**Firefly 取票**（用你自己的住宅代理换一张会话票据）与 **打码识别**（reCaptcha / hCaptcha / Turnstile / 图像识别）。\n\n两类能力都按次扣积分，**调用失败全额退回**；积分为**长期有效**。\n\n## 契约约定\n- 对接请只用 `/v1/*`；`/api/*` 是本站前端自用，不保证兼容。\n- `/v1` 是冻结契约：字段只增不改不删，破坏性变更走 `/v2`。\n- 所有响应带 `X-API-Version: 1`。\n- 错误响应形状固定为 `{\"error\":\"...\",\"code\":\"...\"}`，请按 `code` 分支。",
    "title": "ARP 取票服务 API",
    "version": "1.0.0"
  },
  "openapi": "3.0.3",
  "paths": {
    "/healthz": {
      "get": {
        "operationId": "healthz",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "ok": true
                },
                "schema": {
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "服务正常"
          }
        },
        "security": [],
        "summary": "健康检查",
        "tags": [
          "系统"
        ]
      }
    },
    "/v1/captcha/prices": {
      "get": {
        "description": "返回全部任务类型的单价（积分）。调之前建议先看这个。",
        "operationId": "captchaPrices",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "default": {
                      "type": "integer"
                    },
                    "note": {
                      "type": "string"
                    },
                    "price": {
                      "additionalProperties": {
                        "type": "integer"
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "价格表"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "打码价格表",
        "tags": [
          "打码"
        ]
      }
    },
    "/v1/captcha/solve": {
      "post": {
        "description": "创建识别任务并等待结果。协议类通常 10–60 秒，图像类 1–5 秒。\n\n**计费**：先按类型单价预扣，识别成功才真扣，失败/超时**全额退回**。",
        "operationId": "captchaSolve",
        "parameters": [
          {
            "description": "等待上限（秒）",
            "in": "query",
            "name": "timeout",
            "required": false,
            "schema": {
              "default": 180,
              "maximum": 600,
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaptchaRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaptchaSolution"
                }
              }
            },
            "description": "识别成功（已扣费）"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "积分不足。**未扣费。**"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "识别失败，**已全额退回积分**，可安全重试"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        },
        "summary": "同步打码识别",
        "tags": [
          "打码"
        ]
      }
    },
    "/v1/captcha/task": {
      "post": {
        "description": "只创建任务立刻返回 task_id，后续用 GET /v1/captcha/task/{id} **免费**查询。\n\n**建任务就会扣费**（识别失败仍会退回）。",
        "operationId": "captchaCreateTask",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaptchaRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaptchaSolution"
                }
              }
            },
            "description": "识别成功（已扣费）"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "积分不足。**未扣费。**"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "识别失败，**已全额退回积分**，可安全重试"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        },
        "summary": "异步建打码任务",
        "tags": [
          "打码"
        ]
      }
    },
    "/v1/captcha/task/{id}": {
      "get": {
        "description": "查询异步任务结果。**不额外扣费。**",
        "operationId": "captchaGetTask",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaptchaSolution"
                }
              }
            },
            "description": "查询结果"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "查询异步打码结果",
        "tags": [
          "打码"
        ]
      }
    },
    "/v1/credits": {
      "get": {
        "description": "返回可用总额、单张票价、最近到期时间，以及每个批次的明细。积分按批次记账，**每批各自从入账时间起算有效期**，先后充值不会互相续命。",
        "operationId": "getCredits",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Credits"
                }
              }
            },
            "description": "余额"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "积分余额",
        "tags": [
          "积分"
        ]
      }
    },
    "/v1/credits/ledger": {
      "get": {
        "operationId": "getLedger",
        "responses": {
          "200": {
            "description": "流水列表"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "积分流水",
        "tags": [
          "积分"
        ]
      }
    },
    "/v1/fp/pool": {
      "get": {
        "description": "**内部接口**：只允许服务器本机（127.0.0.1/::1）或带 `X-Browser-Mint-Token` 的请求。指纹池是目录热插拔的 —— 往 `fp_pool_dir` 里放一个 `real_fp_*.json` 立刻可用、删掉立刻失效，不需要重启或 reload。",
        "operationId": "fpPoolList",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "pool": {
                      "items": {
                        "properties": {
                          "api_type": {
                            "type": "string"
                          },
                          "cfp": {
                            "type": "string"
                          },
                          "cores": {
                            "type": "integer"
                          },
                          "enhanced_fp": {
                            "type": "integer"
                          },
                          "error": {
                            "type": "string"
                          },
                          "fe": {
                            "type": "integer"
                          },
                          "file": {
                            "type": "string"
                          },
                          "fp_id": {
                            "type": "string"
                          },
                          "gpu": {
                            "type": "string"
                          },
                          "jsbd_len": {
                            "type": "integer"
                          },
                          "last_used": {
                            "type": "number"
                          },
                          "name": {
                            "type": "string"
                          },
                          "resolution": {
                            "type": "string"
                          },
                          "screen": {
                            "type": "string"
                          },
                          "used_count": {
                            "type": "integer"
                          },
                          "valid": {
                            "type": "boolean"
                          },
                          "wh": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "summary": {
                      "properties": {
                        "dir": {
                          "type": "string"
                        },
                        "invalid": {
                          "type": "integer"
                        },
                        "strategy": {
                          "type": "string"
                        },
                        "total": {
                          "type": "integer"
                        },
                        "total_mints": {
                          "type": "integer"
                        },
                        "unique_cfp": {
                          "type": "integer"
                        },
                        "unique_fp_id": {
                          "type": "integer"
                        },
                        "usage_file": {
                          "type": "string"
                        },
                        "valid": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "池子清单 + 汇总"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        },
        "summary": "列出指纹池（内部）",
        "tags": [
          "指纹池"
        ]
      }
    },
    "/v1/fp/reload": {
      "post": {
        "description": "**内部接口**：只允许服务器本机（127.0.0.1/::1）或带 `X-Browser-Mint-Token` 的请求。指纹池是目录热插拔的 —— 往 `fp_pool_dir` 里放一个 `real_fp_*.json` 立刻可用、删掉立刻失效，不需要重启或 reload。\n\n正常加/删文件不需要它；这个接口只是让运维立刻自检一遍（清解析缓存 + 逐份校验 + 清理已删除文件的计数）。",
        "operationId": "fpPoolReload",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "invalid": {
                      "type": "integer"
                    },
                    "invalid_files": {
                      "items": {
                        "type": "string"
                      },
                      "type": "array"
                    },
                    "note": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    },
                    "total": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "重扫结果"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          }
        },
        "summary": "重扫指纹池（内部）",
        "tags": [
          "指纹池"
        ]
      }
    },
    "/v1/fp/stats": {
      "get": {
        "description": "**内部接口**：只允许服务器本机（127.0.0.1/::1）或带 `X-Browser-Mint-Token` 的请求。指纹池是目录热插拔的 —— 往 `fp_pool_dir` 里放一个 `real_fp_*.json` 立刻可用、删掉立刻失效，不需要重启或 reload。\n\n返回总数 / 唯一 CFP 数 / GPU、分辨率、核数分布 / 使用次数分布。",
        "operationId": "fpPoolStats",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "cores": {
                      "items": {
                        "properties": {
                          "count": {
                            "type": "integer"
                          },
                          "value": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "dir": {
                      "type": "string"
                    },
                    "gpu": {
                      "items": {
                        "properties": {
                          "count": {
                            "type": "integer"
                          },
                          "value": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "invalid": {
                      "type": "integer"
                    },
                    "ok": {
                      "type": "boolean"
                    },
                    "resolution": {
                      "items": {
                        "properties": {
                          "count": {
                            "type": "integer"
                          },
                          "value": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "screen": {
                      "items": {
                        "properties": {
                          "count": {
                            "type": "integer"
                          },
                          "value": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "strategy": {
                      "type": "string"
                    },
                    "total": {
                      "type": "integer"
                    },
                    "total_mints": {
                      "type": "integer"
                    },
                    "unique_cfp": {
                      "type": "integer"
                    },
                    "unique_fp_id": {
                      "type": "integer"
                    },
                    "usage": {
                      "description": "使用次数分布：never_used / used_once / used_2_5 / used_6_10 / used_over_10 / max_used",
                      "type": "object"
                    },
                    "usage_file": {
                      "type": "string"
                    },
                    "valid": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "统计结果"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          }
        },
        "summary": "指纹池统计（内部）",
        "tags": [
          "指纹池"
        ]
      }
    },
    "/v1/keys": {
      "get": {
        "operationId": "listKeys",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "keys": {
                      "items": {
                        "$ref": "#/components/schemas/Key"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Key 列表（仅显示打码后的值）"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "列出 API Key",
        "tags": [
          "API Key"
        ]
      },
      "post": {
        "description": "**完整 Key 只在这里返回一次**，之后不再显示。请立即保存。",
        "operationId": "createKey",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "name": {
                    "description": "备注名，可省略",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "key": {
                      "$ref": "#/components/schemas/Key"
                    },
                    "note": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "创建成功"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "新建 API Key",
        "tags": [
          "API Key"
        ]
      }
    },
    "/v1/keys/{id}/toggle": {
      "post": {
        "operationId": "toggleKey",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "enabled"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "操作成功"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "启用/停用 API Key",
        "tags": [
          "API Key"
        ]
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "getMe",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            },
            "description": "当前账号"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "账号信息与用量",
        "tags": [
          "账号"
        ]
      }
    },
    "/v1/proxy": {
      "get": {
        "description": "返回当前配置的代理模板，密码部分已打码。",
        "operationId": "getProxy",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "template": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "当前模板"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "读取代理模板",
        "tags": [
          "代理"
        ]
      },
      "put": {
        "description": "模板**必须**包含 `{sid}` 占位符，否则返回 400。\n\n每次取票会用新生成的 12 位随机 sid 替换它，并把填好后的完整 URL 作为 `proxy_url` 返回。\n\n**务必带上粘性时长参数**（如 `-t-30`），否则代理每次轮换出口 IP，票会立刻失效。",
        "operationId": "putProxy",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "template": {
                    "example": "http://用户名-region-US-sid-{sid}-t-30:密码@us.1024proxy.io:3000",
                    "type": "string"
                  }
                },
                "required": [
                  "template"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProxyConfig"
                }
              }
            },
            "description": "保存成功"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "code": "bad_request",
                  "error": "代理模板里必须包含 {sid} 占位符"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "模板不合法（缺少 {sid} 占位符）"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "设置代理模板",
        "tags": [
          "代理"
        ]
      }
    },
    "/v1/redeem": {
      "post": {
        "operationId": "redeem",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "code": {
                    "type": "string"
                  }
                },
                "required": [
                  "code"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "兑换成功"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "兑换码充值",
        "tags": [
          "积分"
        ]
      }
    },
    "/v1/ticket": {
      "post": {
        "description": "默认用**纯算**铸票（不启动浏览器，通常 1–2 秒）。响应里的 `mint_mode` 告诉你这张票是哪条路径铸的，`fp` 告诉你纯算用了哪份指纹（便于 A/B 排查）。`?mode=browser` 仅供内部对比测试，公网请求会被 403 拒绝。\n\n用你配置的代理出口现铸一张 ARP 票。\n\n**强烈建议用异步**（`?async=1` 或 `Prefer: respond-async`）：铸票要 20–150 秒（最坏约 6 分钟），同步请求得一直挂着这条连接，链路上任何一层反向代理的空闲超时都会把它掐断 —— 而**掐断不等于失败**：票可能照样铸出来了、积分照样扣了，你却什么都拿不到。异步把\"发起\"和\"取结果\"拆开，只依赖你的耐心。\n\n**计费**：受理/发起时先冻结额度，成功才真扣；失败或超时**全额退回**，退回后该批积分有效期不变。所以本接口虽然不是幂等的（每次成功都会铸新票并扣费），但**失败重试是安全的**。\n\n**失败重试**：`502 mint_failed` 属于正常现象（铸票是概率性的），建议退避 3–5 秒重试 2–3 次。",
        "operationId": "createTicket",
        "parameters": [
          {
            "description": "传 `1` 走异步：立刻返回 `202` + `job_id`，之后轮询 `GET /v1/ticket/{job_id}`。等效写法是请求头 `Prefer: respond-async`。",
            "in": "query",
            "name": "async",
            "required": false,
            "schema": {
              "default": "0",
              "enum": [
                "1",
                "true"
              ],
              "type": "string"
            }
          },
          {
            "description": "铸票模式。**默认 `pure`（纯算，不启动浏览器，1–2 秒）**。`browser` 保留给内部 A/B：只允许服务器本机（127.0.0.1/::1）或带 `X-Browser-Mint-Token` 的请求；公网请求即使显式带 `?mode=browser` 也会返回 `403 forbidden`。",
            "in": "query",
            "name": "mode",
            "required": false,
            "schema": {
              "enum": [
                "pure",
                "browser"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ticket"
                }
              }
            },
            "description": "铸票成功（同步入口）"
          },
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TicketJobAccepted"
                }
              }
            },
            "description": "已受理（异步）。积分已冻结，之后用 `GET /v1/ticket/{job_id}` 轮询。"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "code": "proxy_not_configured",
                  "error": "还没配置代理，请先填写代理模板（含 {sid} 占位符）"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "还没配置代理模板（code=proxy_not_configured）或参数不合法"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "balance": 500,
                  "code": "insufficient_credits",
                  "error": "积分不足，请先充值",
                  "price": 2000,
                  "shortage": 1500
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "积分不足。**此时代码未铸票，也未扣费。**"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "code": "forbidden",
                  "error": "浏览器铸票仅限服务器本机或携带内部令牌的请求；对外请使用默认的纯算模式（不要带 ?mode=browser）"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`?mode=browser` 被拒绝（公网请求且没有内部令牌）。不影响纯算模式：去掉 `mode` 参数重试即可。"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          },
          "502": {
            "content": {
              "application/json": {
                "example": {
                  "code": "mint_failed",
                  "error": "铸票失败，已全额退回 2000 积分：连续 3 个出口都铸票失败"
                },
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "铸票失败，**已全额退回积分**，可安全重试"
          }
        },
        "summary": "取一张票（默认同步；`?async=1` 异步）",
        "tags": [
          "取票"
        ]
      }
    },
    "/v1/ticket/jobs": {
      "get": {
        "description": "列出最近的任务（时间倒序）。用途是**页面刷新/客户端重启后接着看** —— `job_id` 丢了也能在这里找回来。任务记录保留 24 小时。",
        "operationId": "listTicketJobs",
        "parameters": [
          {
            "description": "返回条数上限",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "active": {
                      "description": "进行中的任务数",
                      "type": "integer"
                    },
                    "jobs": {
                      "items": {
                        "$ref": "#/components/schemas/TicketJobSummary"
                      },
                      "type": "array"
                    },
                    "max_active": {
                      "description": "允许同时进行的上限",
                      "type": "integer"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "任务列表"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "我最近的取票任务",
        "tags": [
          "取票"
        ]
      }
    },
    "/v1/ticket/{id}": {
      "delete": {
        "description": "取消一个**进行中**的任务，预扣积分随即全额退回。\n\n**幂等**：任务已经结束时会返回现状（`canceled:false`）而不是报错。\n\n极少数情况下任务已经铸成、正在收尾，取消不掉会如实回 `409 conflict` —— 这时别当成已退费，以轮询到的终态为准。",
        "operationId": "cancelTicketJob",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "canceled": {
                      "type": "boolean"
                    },
                    "job_id": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    },
                    "status": {
                      "description": "`canceling` 表示取消请求已送达，终态以轮询为准",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "已请求取消（`canceled:true`），或任务已结束（`canceled:false`）"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "任务不存在、已过期，或者不是你的"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "任务不在本实例运行（可能刚刚重启或已在收尾），**取消未生效**"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "取消进行中的取票任务",
        "tags": [
          "取票"
        ]
      },
      "get": {
        "description": "**免费、幂等，可以随便轮。** 按响应里的 `poll_after_sec`（3 秒）的节奏来。\n\n`status=ready` 时，票会连在 `result` 里一起返回，不用再查别的接口。\n\n服务重启时，中断的任务会被标成 `failed` 并**立刻退回预扣积分**，所以不会出现一个永远不动的 `running`。",
        "operationId": "getTicketJob",
        "parameters": [
          {
            "description": "受理时返回的 job_id",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TicketJob"
                }
              }
            },
            "description": "任务状态（可能是 queued / running / ready / failed / canceled）"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "任务不存在、已过期（保留 24 小时），或者**不是你的** —— 三种情况返回同一个响应，避免被用来枚举别人的 job_id。"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "查询取票任务（轮询）",
        "tags": [
          "取票"
        ]
      }
    },
    "/v1/tickets": {
      "get": {
        "description": "返回最近 20 次取票明细（使用日志），proxy_url 中的密码已打码。",
        "operationId": "listTickets",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "tickets": {
                      "items": {
                        "$ref": "#/components/schemas/TicketHistoryItem"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "使用日志"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "使用日志（取票明细）",
        "tags": [
          "取票"
        ]
      }
    }
  },
  "security": [
    {
      "ApiKey": []
    },
    {
      "Session": []
    }
  ],
  "servers": [
    {
      "description": "当前站点",
      "url": "/"
    }
  ],
  "tags": [
    {
      "description": "核心接口",
      "name": "取票"
    },
    {
      "description": "账号信息与用量",
      "name": "账号"
    },
    {
      "description": "余额与流水",
      "name": "积分"
    },
    {
      "description": "取票用的出口代理配置",
      "name": "代理"
    },
    {
      "description": "凭证管理",
      "name": "API Key"
    },
    {
      "description": "健康检查",
      "name": "系统"
    },
    {
      "description": "验证码识别，按次扣积分，失败全额退回",
      "name": "打码"
    },
    {
      "description": "纯算铸票的指纹池运维（内部）：目录热插拔，加/删文件立刻生效",
      "name": "指纹池"
    }
  ]
}