官方流程是:用 API Key 调
POST /v1/tones,再在POST /v1/tasks/{id}/run里传toneId与 contextItems。 这篇提供 mock 服务 +jasper_client.py,在无密钥时仍能看懂请求形状。
先看清成品
- mock 上创建 tone 并跑 task,输出带品牌名的草稿。
- 错误密钥返回 401。
- 2 项 pytest 通过。
合规提醒:Jasper API 需工作区管理员/开发者权限开通;密钥勿进前端或公开仓库。生成营销文案须遵守广告与行业法规,禁止虚假疗效、保证收益等表述。品牌声音与知识库应使用你有权使用的资料。价格与套餐以官网为准,本文不编造。
运行说明:无 Jasper 正式密钥。A/C 为本地校验;B 用本机 mock(X-API-Key)跑通创建 tone + run task。2026-10-23(Asia/Shanghai)pytest 全部通过。对接正式环境时把
--base设为 https://api.jasper.ai 。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
mock_jasper.py |
本地假服务 |
jasper_client.py |
客户端 |
test_97b.py |
2 项测试 |
需要准备哪些环境?
| 项目 | 说明 |
|---|---|
| Python | 3.10+ |
| 密钥 | 正式环境:JASPER_API_KEY;教程 mock:jasper-test-key |
关键事实
| 事实 | 来源 |
|---|---|
鉴权头:X-API-Key: <token>,基址 https://api.jasper.ai |
官方 Authentication |
Agent task run 可传 toneId、contextItems、knowledgeIds 等 |
官方 Run task / Using Agents |
task id 来自你工作区已发布的 Agent;mock 使用占位 agt_mock_blog |
官方 + 教程说明 |
文件结构
97-jasper-B-src/
├── mock_jasper.py
├── jasper_client.py
└── test_97b.py
第 1 步:Mock
#!/usr/bin/env python3
"""Minimal mock of Jasper tones + task run for offline tutorials."""
from __future__ import annotations
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
API_KEY = "jasper-test-key"
TONES: dict[str, dict] = {}
_next = 1
class H(BaseHTTPRequestHandler):
def log_message(self, *a):
pass
def _auth(self) -> bool:
return self.headers.get("X-API-Key") == API_KEY
def _json(self, code: int, obj: dict):
body = json.dumps(obj).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_POST(self):
if not self._auth():
self._json(401, {"error": "Unauthorized"})
return
n = int(self.headers.get("Content-Length", 0))
body = json.loads(self.rfile.read(n) or b"{}")
if self.path == "/v1/tones":
global _next
tid = f"ton_mock_{_next}"
_next += 1
TONES[tid] = {"id": tid, "name": body.get("name"), "value": body.get("value")}
self._json(201, TONES[tid])
return
if self.path.startswith("/v1/tasks/") and self.path.endswith("/run"):
tone = body.get("toneId")
voice = TONES.get(tone, {}).get("name", "default")
ctx = body.get("contextItems") or []
topic = ""
for c in ctx:
topic = c.get("value") or c.get("answer") or topic
self._json(
200,
{
"output": f"[{voice}] 关于「{topic}」的营销草稿:强调价值主张,结尾加明确 CTA。",
"toneId": tone,
},
)
return
self.send_error(404)
def do_GET(self):
if not self._auth():
self._json(401, {"error": "Unauthorized"})
return
if self.path.startswith("/v1/tones"):
self._json(200, {"data": list(TONES.values())})
return
self.send_error(404)
def main():
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("--port", type=int, default=8970)
args = ap.parse_args()
HTTPServer(("127.0.0.1", args.port), H).serve_forever()
if __name__ == "__main__":
main()
第 2 步:客户端
#!/usr/bin/env python3
"""Thin Jasper API client: create tone + run a task (official X-API-Key)."""
from __future__ import annotations
import argparse
import json
import os
import urllib.error
import urllib.request
def request(base: str, key: str, method: str, path: str, payload: dict | None = None) -> dict:
data = None if payload is None else json.dumps(payload).encode()
req = urllib.request.Request(
f"{base.rstrip('/')}{path}",
data=data,
headers={"X-API-Key": key, "Content-Type": "application/json", "Accept": "application/json"},
method=method,
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
raise SystemExit(f"HTTP {e.code}: {e.read().decode(errors='replace')}") from e
def main(argv=None) -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--base", default=os.environ.get("JASPER_BASE_URL", "https://api.jasper.ai"))
ap.add_argument("--key", default=os.environ.get("JASPER_API_KEY", ""))
ap.add_argument("--name", default="Tutorial Voice")
ap.add_argument("--value", default="Professional, concise, benefit-led B2B tone without hype.")
ap.add_argument("--task-id", default="agt_mock_blog")
ap.add_argument("--topic", default="推出 Acme Flow 的产品更新邮件")
args = ap.parse_args(argv)
if not args.key:
print("设置 JASPER_API_KEY", file=__import__("sys").stderr)
return 2
tone = request(
args.base,
args.key,
"POST",
"/v1/tones",
{"name": args.name, "value": args.value},
)
print("tone:", tone.get("id"), tone.get("name"))
out = request(
args.base,
args.key,
"POST",
f"/v1/tasks/{args.task_id}/run",
{
"toneId": tone["id"],
"contextItems": [{"id": "topic", "value": args.topic}],
},
)
print(out.get("output") or out)
return 0
if __name__ == "__main__":
raise SystemExit(main())
第 3 步:测试
import subprocess
import sys
import threading
import time
from pathlib import Path
HERE = Path(__file__).resolve().parent
def test_mock_create_and_run():
port = 18970
proc = subprocess.Popen(
[sys.executable, str(HERE / "mock_jasper.py"), "--port", str(port)],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
try:
time.sleep(0.3)
r = subprocess.run(
[
sys.executable,
str(HERE / "jasper_client.py"),
"--base",
f"http://127.0.0.1:{port}",
"--key",
"jasper-test-key",
"--topic",
"春季促销邮件",
],
capture_output=True,
text=True,
check=True,
)
assert "tone:" in r.stdout
assert "春季促销" in r.stdout or "营销草稿" in r.stdout
finally:
proc.terminate()
proc.wait(timeout=5)
def test_bad_key():
port = 18971
proc = subprocess.Popen(
[sys.executable, str(HERE / "mock_jasper.py"), "--port", str(port)],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
try:
time.sleep(0.3)
r = subprocess.run(
[
sys.executable,
str(HERE / "jasper_client.py"),
"--base",
f"http://127.0.0.1:{port}",
"--key",
"wrong",
],
capture_output=True,
text=True,
)
assert r.returncode != 0
assert "401" in r.stderr or "401" in r.stdout
finally:
proc.terminate()
proc.wait(timeout=5)
第 4 步:运行
.. [100%]
2 passed in 0.72s
tone: ton_mock_1 Tutorial Voice
[Tutorial Voice] 关于「春季促销」的营销草稿:强调价值主张,结尾加明确 CTA。
怎么检查结果?
- 输出含 tone: ton_mock_…。
- 换正式 base/key/task-id 前先在开发者文档试通。
调试示例
正式 task id 不是随便编的。 需在 Jasper 工作区创建/发布 Agent task,再用返回的 agt_… id。mock 只演示 HTTP 形状。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 401 | 密钥/角色 | 检查 Dev Tools token |
| OAuth | 用户代操作 | 服务端脚本优先 API Key |
| 输出不稳定 | Agent 版本 | 可传 version 钉住 |
模板规则
- 只用自己工作区密钥。
- mock 不得暴露为公网开放代理。
- 生成内容需人工审核。
项目资源
| 用途 | 地址 |
|---|---|
| Authentication(官方) | https://developers.jasper.ai/docs/authentication |
| Run an agent task(官方) | https://developers.jasper.ai/reference/runtask |
| Using Agents(官方) | https://developers.jasper.ai/docs/using-agents |
mock 练手,正式环境换 base、key、真实 task id。