设计实践

Pika API 的接口目录是公开的、不需要 Key

Pika API 的接口目录是公开的、不需要 Key。文档特别强调:目录和每个接口的规格是运行时的事实来源,不要凭模型名去猜接口路径、字段和取值。本篇写一个目录浏览器:列出 Pika 的全部接口,查看某个接口的参数(类型、必填、默认值、取值范围),一键生成 Markdown 速查表。

BINBHO · 图文教程

Pika API 的接口目录是公开的、不需要 Key。文档特别强调:目录和每个接口的规格是运行时的事实来源,不要凭模型名去猜接口路径、字段和取值。本篇写一个目录浏览器:列出 Pika 的全部接口,查看某个接口的参数(类型、必填、默认值、取值范围),一键生成 Markdown 速查表。

先看清成品(全部是 box 上对真实公开接口的调用)

  • 测试: pytest 1 项通过。
  • 列出接口: 目录里共 176 个接口,其中 Pika 自家的 14 个。
  • 查看 Pikaframes(关键帧)参数: images 1–5 张,transition_duration_s 1–10 秒。
  • 速查表: 一次生成了 14 个接口的速查表。
  • 价格: 默认不输出价格(价格会变)。

合规提醒:PIKA_API_KEY 和 webhook 签名密钥只放在服务器端,不要写进浏览器代码、URL、截图或聊天里(Pika 文档原话也是这样要求的)。输入的图片和视频必须是你有权使用的,不要用他人肖像冒充真人,不要侵犯版权或商标。生成的内容发布时要按《人工智能生成合成内容标识办法》加上“AI 生成”标识,并遵守 Pika 的使用条款。Pika API 是预付费的,没有免费的生成额度,也没有测试环境,每次生成都按实时报价扣费。本文不列价格,示例输出里的金额已用“x.xx”代替,请以运行时的报价为准。

做完之后你会得到什么?

成果 说明
pika_catalog.py list(按厂商、类别筛选)/ show(参数表,加 --price 显示带查询时间的价格)/ sheet(Markdown 速查表)
test_catalog.py 参数表格式

需要准备哪些环境?

Python 3.9 以上,pip install requests pytest。不需要 Key。

文件结构

48-pika-A-src/
├── pika_catalog.py
└── test_catalog.py

第 1 步:代码

pika_catalog.py:

"""浏览 Pika API 的公开目录(不需要 API Key):列出某个厂商或类别的接口,查看某个接口的输入参数,生成一份 Markdown 速查表。
文档(dev.pika.art/llms.txt):目录和每个接口的规格是“运行时的事实来源”,不要凭模型名猜接口路径、字段或取值。
  - GET https://api.dev.pika.art/catalog/apis                 全部接口(免费调用)
  - GET https://api.dev.pika.art/catalog/apis/{api_id}?expand=inputs   单个接口,带 input_schema(JSON Schema)
价格字段(display_pricing)会随时变化:本脚本默认不输出价格,加 --price 才显示,并注明查询时间。
用法:python pika_catalog.py list [--vendor pika] [--category video]
      python pika_catalog.py show pika/pika-2.5/image-to-video [--price]
      python pika_catalog.py sheet --vendor pika --out pika接口速查.md"""
import argparse, json, sys
from datetime import datetime
import requests

API = "https://api.dev.pika.art"

def get(path, **params):
    r = requests.get(API + path, params=params, timeout=30)
    if r.status_code != 200: sys.exit(f"✗ HTTP {r.status_code}:{r.text[:200]}")
    return r.json()

def list_apis(vendor=None, category=None):
    return [a for a in get("/catalog/apis")["apis"] if (not vendor or a["vendor"] == vendor) and (not category or a["category"] == category)]

def inputs(api_id):
    """返回 [(参数名, 类型, 是否必填, 默认值, 可选值/范围, 说明)]"""
    s = get(f"/catalog/apis/{api_id}", expand="inputs")
    sch = s.get("input_schema") or {}; req = set(sch.get("required", []))
    rows = []
    for name, p in (sch.get("properties") or {}).items():
        typ = p.get("type") or "/".join(x.get("type", "?") for x in p.get("anyOf", []) if x.get("type") != "null")
        enum = p.get("enum") or next((x.get("enum") for x in p.get("anyOf", []) if x.get("enum")), None)
        rng = [f"{k}={p[k]}" for k in ("minimum", "maximum", "minItems", "maxItems", "maxLength") if k in p]   # 只看 enum 会漏掉范围限制
        if "pattern" in p.get("items", p): rng.append(f"格式 {p.get('items', p)['pattern']}")
        rows.append((name, typ, name in req, p.get("default"), (list(enum) if enum else []) + rng, (p.get("description") or "").replace("\n", " ")))
    return s, rows

def price_lines(s):
    out = []
    for c in (s.get("display_pricing") or {}).get("components", []):
        unit = c.get("unit", {})
        for t in c.get("price_tiers", []):
            out.append(f"{json.dumps(t.get('spec', {}), ensure_ascii=False)}:{t.get('sell_usd')} 美元 / {unit.get('quantity', 1)} {unit.get('type', '')}")
    return out

def table(rows):
    lines = ["| 参数 | 类型 | 必填 | 默认 | 可选值 / 范围 | 说明 |", "| :--- | :--- | :--- | :--- | :--- | :--- |"]
    for n, t, r, d, e, desc in rows:
        lines.append(f"| `{n}` | {t} | {'是' if r else ''} | {'' if d is None else d} | {', '.join(map(str, e)) if e else ''} | {desc} |")
    return "\n".join(lines)

def main():
    ap = argparse.ArgumentParser(); ap.add_argument("cmd", choices=["list", "show", "sheet"]); ap.add_argument("api_id", nargs="?")
    ap.add_argument("--vendor"); ap.add_argument("--category"); ap.add_argument("--price", action="store_true"); ap.add_argument("--out")
    a = ap.parse_args()
    if a.cmd == "list":
        apis = list_apis(a.vendor, a.category)
        for x in apis: print(f"{x['api_id']:<45} {x['category']:<6} {x['call']['method']} {x['call']['path']}")
        print(f"共 {len(apis)} 个")
    elif a.cmd == "show":
        s, rows = inputs(a.api_id); print(f"# {s['name']}\n{s.get('description', '')}\n调用:{s['call']['method']} {API}{s['call']['path']}\n\n{table(rows)}")
        if a.price: print(f"\n价格({datetime.now():%Y-%m-%d %H:%M} 查询,以官网为准):\n" + "\n".join(price_lines(s)))
    else:
        md = [f"# Pika API 接口速查({a.vendor or '全部'})", "", f"- 生成时间:{datetime.now():%Y-%m-%d %H:%M},数据来自公开目录 {API}/catalog/apis", "- 价格会变化,本表不列价格;提交前用 quote 接口报价(见 48-pika-B)", ""]
        for x in list_apis(a.vendor, a.category):
            s, rows = inputs(x["api_id"])
            md += [f"## {s['name']}(`{x['api_id']}`)", "", f"`{s['call']['method']} {s['call']['path']}`", "", table(rows), ""]
        open(a.out, "w", encoding="utf-8").write("\n".join(md)); print(f"✓ 已写入 {a.out}({len(list_apis(a.vendor, a.category))} 个接口)")

if __name__ == "__main__":
    main()

第 2 步:运行

$ python pika_catalog.py list --vendor pika
pika/pika-2.5/image-to-video                  video  POST /v1/media/pika/pika-2.5/image-to-video
pika/pika-2.5/text-to-video                   video  POST /v1/media/pika/pika-2.5/text-to-video
pika/pikadditions/video-to-video              video  POST /v1/media/pika/pikadditions/video-to-video
…(共 14 行)
pika/dynamic-captions/caption-video           video  POST /v1/media/pika/dynamic-captions/caption-video
共 14 个

$ python pika_catalog.py show pika/pikaframes/image-to-video
# Pika 2.5 Keyframe
Animate 1-5 ordered keyframes with individual transition prompts, durations, and optional looping. A single image requires looping.
调用:POST https://api.dev.pika.art/v1/media/pika/pikaframes/image-to-video

| 参数 | 类型 | 必填 | 默认 | 可选值 / 范围 | 说明 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `resolution` | string |  | 1080p | 720p, 1080p | Output resolution. |
| `images` | array | 是 |  | minItems=1, maxItems=5, 格式 ^https?:// | 1-5 ordered keyframe image URLs. A single image requires loop=true. |
| `transition_duration_s` | integer |  | 5 | minimum=1, maximum=10 | Default seconds per transition. Multiple transitions are limited to 5 seconds each. |
| `loop` | boolean |  | False |  | Add a closing transition from the last image to the first. |
…

$ python pika_catalog.py sheet --vendor pika --out pika接口速查.md
✓ 已写入 pika接口速查.md(14 个接口)

检查点:注意 images 的说明“A single image requires loop=true”,以及 transition_duration_s 的“Multiple transitions are limited to 5 seconds each”。这两条是跨字段的规则,Schema 本身表达不了,B 篇会专门处理。

调试示例:只看 enum,漏掉了范围限制

第一版参数表的“可选值”一列只读取 enum。查看 Pikaframes 时,images 和 transition_duration_s 这两列都是空的,看起来像是“随便填”。打印原始 schema 才发现,限制写在别的字段里:

"images": {"type": "array", "minItems": 1, "maxItems": 5, "items": {"type": "string", "pattern": "^https?://"}},
"transition_duration_s": {"type": "integer", "minimum": 1, "maximum": 10, "default": 5}

现在 minimum、maximum、minItems、maxItems、maxLength 和 pattern 都会显示出来。另外,可以为空的字段在 schema 里写成 anyOf: [{"type": "string"}, {"type": "null"}],代码会把它显示为 string。

常见问题

现象 原因 处理
接口列表和文章里的不一样 目录是实时的 以运行结果为准
价格和上周不同 价格会变 用 --price 查询时会注明查询时间;提交前报价(B 篇)
参数说明是英文 目录原文就是英文 速查表保留原文,避免翻译出错

模板规则

  • 接口路径和参数以目录为准,不凭印象写。
  • 速查表要注明生成时间。
  • 价格不写进文档,提交前实时报价。

项目资源

用途 地址
Pika API 首页(llms.txt) https://dev.pika.art/llms.txt
Pika API 开发者站点 https://dev.pika.art/
公开目录(JSON) https://api.dev.pika.art/catalog/apis
Pikaframes 规格 https://dev.pika.art/llms/pika/pikaframes/image-to-video

先查清楚目录,再写调用代码,Pika 的文档也是这么要求的。