创意工坊 — 键帽 API
把你的照片变成全彩定制机械键盘键帽。整个流程分两段:prototype 用源照片
生成 1 张「成品键帽」设计效果图,确认后 build 一条龙生成带贴图的
3D 键帽模型 —— 白模生成、按校准好的默认姿态自动落座与切割、整体上色、组装导出
全部在一个 build 任务内完成。两段通过 input_task_id 加 candidate_id
串联。
POST /openapi/creative-lab/keycap/v1/prototypePOST /openapi/creative-lab/keycap/v1/build
两个 POST 端点均要求付费订阅套餐。免费套餐账号的请求会被拒绝并返回
402 Payment Required。
创建键帽 Prototype 任务
从源照片生成 1 张成品键帽设计效果图。任务结果携带 image_urls
数组(成品键帽的展示渲染图)以及与之平行的 candidate_ids 数组,各含 1 项。
若效果不满意,可再次调用本接口重新生成(每次调用单独计费)。把图片展示
给用户挑选后,将对应的 candidate_id 与 prototype 任务 ID 一起传给
build 端点。响应结构详见
Prototype 任务对象。
参数
- Name
- image_url
- Type
- string
- 必选
- Description
提供给 Meshy 用于生成键帽设计图的源照片。目前支持
.jpg、.jpeg、.png、.webp格式。格式由解码图片数据判定,不看 URL 的文件扩展名 —— 没有扩展名、或会发生跳转的 URL 都可以,只要字节能解码成受支持的格式。HTTP 跳转会被跟随。EXIF 方向会被归一化,所以手机拍的旋转照片会按看起来的方向使用。
限制:每边至少
32像素、总像素不超过178,956,970、下载后不超过20,000,000字节。data URI 的上限按 base64 解码后的字节计算,所以源文件本身可以达到该上限 —— 变大约三分之一的是 base64 文本,那影响的是请求体大小,不是这个上限。data URI 必须声明image/*内容类型与;base64。可通过两种方式提供图片:
- 公网可访问 URL:可从公网访问的 URL。
- Data URI:图片的 base64 编码 data URI,示例:
data:image/jpeg;base64,<your base64-encoded image data>。
- Name
- name
- Type
- string
- Description
可选的任务名称,仅用于展示。最长 100 个字符。
返回值
响应的 result 字段返回新创建的键帽 prototype 任务的 id。轮询
查询任务 或订阅 流式更新
直到任务状态变为 SUCCEEDED,再从 candidate_ids 中选定一个候选,
连同任务 ID 一起传给 build 端点。
失败情形
- Name
400 - Bad Request- Description
请求不可接受。常见原因:
- 缺少参数:
image_url为必填。 - 图片格式不支持:
image_url不是受支持的格式(.jpg、.jpeg、.png、.webp)。 - 图片尺寸超限:图片过小、超出最大文件体积或超出最大像素数。
- URL 无法访问:
image_url下载失败(404 或超时)。 - Data URI 无效:base64 字符串格式错误。
- 内容被标记:输入图片被 NSFW 审核标记。
- 缺少参数:
- Name
401 - Unauthorized- Description
鉴权失败。请检查你的 API key。
- Name
402 - Payment Required- Description
账号处于免费套餐(创建任务需要付费套餐),或积分不足。
- Name
403 - Forbidden- Description
输入图片被知识产权审核标记。
- Name
429 - Too Many Requests- Description
超出速率限制。
- Name
500 - Internal Server Error- Description
服务端发生未预期的错误,例如内容审核服务不可用、输入图片暂存失败,或任务创建失败。此时不会创建任务,可安全重试。
Request
# 第一段:生成 1 张成品键帽设计效果图
curl https://api.meshy.ai/openapi/creative-lab/keycap/v1/prototype \
-X POST \
-H "Authorization: Bearer ${YOUR_API_KEY}" \
-H 'Content-Type: application/json' \
-d '{
"image_url": "<your publicly accessible image url or base64-encoded data URI>"
}'
Response
{
"result": "019c9a4e-2b31-7f6a-8c44-5d2a87b3c1ef"
}
创建键帽 Build 任务
从一个已成功的 prototype 任务及其选定候选生成最终的带贴图 3D 键帽模型。 单个 build 任务一条龙跑完整条流水线 —— 用选定设计图生成白模、按校准好的 默认姿态自动落座并切割到键帽底座(无需交互调整)、整体上色、最终组装导出。 一次 build 通常需要 3–7 分钟,多个 build 并发时接近上限。响应结构详见 Build 任务对象。
参数
- Name
- input_task_id
- Type
- string
- 必选
- Description
通过本 OpenAPI 端点创建的 prototype 任务 ID。该 prototype 必须由同一个 Meshy 账号创建、已达到
SUCCEEDED状态,且至少产出一个候选。通过网页版创建的 prototype 任务不被接受 —— build 端点只接受由
POST /openapi/creative-lab/keycap/v1/prototype产出的 prototype 任务,其他来源一律返回404。
- Name
- candidate_id
- Type
- string
- 必选
- Description
要构建的候选,取自已成功 prototype 任务的
candidate_ids数组。必须是该任务的候选之一,否则返回400。
- Name
- name
- Type
- string
- Description
可选的任务名称,仅用于展示。最长 100 个字符。
options
可选的几何调节参数。每个字段都有校准好的默认值 —— 只发送需要覆盖的字段即可。
- Name
- base_model
- Type
- string
- 默认值 cherry-mx-1x1-r1
- Description
构建所用的键帽底座。当前唯一可用值为
cherry-mx-1x1-r1—— 标准 Cherry MX 高度的 1u 键帽。后续计划扩充 3–5 种主流标准尺寸;不支持自定义尺寸。
- Name
- head_size_mm
- Type
- number
- 默认值 23
- Description
雕塑头部的目标尺寸,单位毫米:头部的最长边会被缩放到该数值。范围:
[10, 40]。超过约32.9的取值可能会被下调,以确保头部仍符合底座的保护性尺寸上限,因此实际交付的最长边可能小于请求值。目前任务对象不会回传实际采用的取值 —— 如需确认真正拿到的尺寸,请量下载模型里keycap-head网格的包围盒。
- Name
- vertical_offset_mm
- Type
- number
- 默认值 0
- Description
头部落座到底座前施加的垂直偏移,单位毫米。范围:
[-5, 5]。
返回值
响应的 result 字段返回新创建的键帽 build 任务的 id。轮询
查询任务 或订阅 流式更新
直到任务状态变为 SUCCEEDED,再从 model_urls.glb 与 model_urls.obj_zip
下载产物。
GLB 与 OBJ 产物均按真实毫米尺度导出,坐标系为 Y 轴向上,键帽 正面朝向 +Z。
失败情形
- Name
400 - Bad Request- Description
请求不可接受。常见原因:
- 缺少参数:
input_task_id与candidate_id为必填。 - UUID 无效:
input_task_id不是合法的 UUID。 - 父任务未成功:引用的 prototype 任务尚未达到
SUCCEEDED。 - 没有候选:prototype 任务虽成功但未产出候选。
- 候选不存在:
candidate_id不是该 prototype 任务的候选之一。 - 参数超出范围:
options中某字段超出允许范围或枚举集合。
- 缺少参数:
- Name
401 - Unauthorized- Description
鉴权失败。请检查你的 API key。
- Name
402 - Payment Required- Description
账号处于免费套餐(创建任务需要付费套餐),或积分不足。
- Name
404 - Not Found- Description
引用的 prototype 任务不存在、属于其他用户,或由网页版创建(只有 API 模式的 prototype 任务才能串联 build)。
- Name
429 - Too Many Requests- Description
超出速率限制。
- Name
500 - Internal Server Error- Description
服务端发生未预期的错误,例如内容审核服务不可用、输入图片暂存失败,或任务创建失败。此时不会创建任务,可安全重试。
Request
# 第二段:把选定候选构建成 3D 键帽
curl https://api.meshy.ai/openapi/creative-lab/keycap/v1/build \
-X POST \
-H "Authorization: Bearer ${YOUR_API_KEY}" \
-H 'Content-Type: application/json' \
-d '{
"input_task_id": "019c9a4e-2b31-7f6a-8c44-5d2a87b3c1ef",
"candidate_id": "0198c1b2-33f5-7d21-a4b7-8e9d0c1f2a3b",
"options": {
"base_model": "cherry-mx-1x1-r1",
"head_size_mm": 23,
"vertical_offset_mm": 0
}
}'
Response
{
"result": "019c9a52-7d18-7e2b-9f01-6e3b98c4d2af"
}
查询键帽任务
通过有效的任务 id 查询 prototype 或 build 任务。URL 路径必须与任务所属
阶段匹配 —— 通过 /prototype/:id 查询 build 任务会返回 404,反之亦然。
响应结构详见 Prototype 任务对象 与 Build 任务对象。
参数
- Name
- id
- Type
- path
- Description
要查询的键帽任务的唯一标识符。
返回值
响应包含键帽任务对象,具体结构取决于所查询的阶段。
Request
# Prototype
curl https://api.meshy.ai/openapi/creative-lab/keycap/v1/prototype/019c9a4e-2b31-7f6a-8c44-5d2a87b3c1ef \
-H "Authorization: Bearer ${YOUR_API_KEY}"
# Build
curl https://api.meshy.ai/openapi/creative-lab/keycap/v1/build/019c9a52-7d18-7e2b-9f01-6e3b98c4d2af \
-H "Authorization: Bearer ${YOUR_API_KEY}"
Prototype Response
{
"id": "019c9a4e-2b31-7f6a-8c44-5d2a87b3c1ef",
"type": "creative-lab-keycap-prototype",
"name": "",
"status": "SUCCEEDED",
"progress": 100,
"created_at": 1753142456000,
"started_at": 1753142460000,
"finished_at": 1753142516000,
"expires_at": 1753401716000,
"preceding_tasks": 0,
"task_error": null,
"consumed_credits": 12,
"image_urls": [
"https://assets.meshy.ai/***/design-1.png?Expires=***"
],
"candidate_ids": [
"0198c1b2-33f5-7d21-a4b7-8e9d0c1f2a3b"
]
}
Build Response
{
"id": "019c9a52-7d18-7e2b-9f01-6e3b98c4d2af",
"type": "creative-lab-keycap-build",
"name": "",
"status": "SUCCEEDED",
"progress": 100,
"created_at": 1753142600000,
"started_at": 1753142610000,
"finished_at": 1753143050000,
"expires_at": 1753402250000,
"preceding_tasks": 0,
"task_error": null,
"consumed_credits": 50,
"model_urls": {
"glb": "https://assets.meshy.ai/***/tasks/019c9a52-7d18-7e2b-9f01-6e3b98c4d2af/output/model.glb?Expires=***",
"obj_zip": "https://assets.meshy.ai/***/tasks/019c9a52-7d18-7e2b-9f01-6e3b98c4d2af/output/model-obj.zip?Expires=***"
},
"process_image_urls": {
"head_design": "https://assets.meshy.ai/***/head-design.png?Expires=***",
"composite": "https://assets.meshy.ai/***/composite.png?Expires=***",
"base_canvas": "https://assets.meshy.ai/***/base-canvas.png?Expires=***"
}
}
删除键帽任务
取消一个键帽任务。若任务仍处于 PENDING,创建时消耗的积分将退还;已进入
IN_PROGRESS 的任务会被取消但不退积分(worker 可能已经在消耗资源);已到达
终态(SUCCEEDED、FAILED、CANCELED)的任务无法取消。
URL 路径必须与任务所属阶段匹配 —— 对 /prototype/:buildId 执行 DELETE
会返回 404。
路径参数
- Name
- id
- Type
- path
- Description
要取消的键帽任务的唯一标识符。
返回值
成功时返回 204 No Content,响应体为空。
失败情形
- Name
400 - Bad Request- Description
任务已处于终态,无法取消。
- Name
404 - Not Found- Description
任务不存在、属于其他用户,或所属阶段与 URL 路径不匹配。
- Name
500 - Internal Server Error- Description
取消过程中服务端发生未预期的错误。任务可能已取消也可能未取消,重试前请重新读取任务确认状态。
Request
curl --request DELETE \
--url https://api.meshy.ai/openapi/creative-lab/keycap/v1/prototype/019c9a4e-2b31-7f6a-8c44-5d2a87b3c1ef \
-H "Authorization: Bearer ${YOUR_API_KEY}"
Response
// 成功时返回 204 No Content(空响应体)。
流式订阅键帽任务
通过 Server-Sent Events(SSE)实时订阅键帽任务的状态更新。URL 路径必须与
任务所属阶段匹配 —— 在 /prototype/:buildId/stream 上打开流会发出一条
event: error(status_code: 404)后关闭。
参数
- Name
- id
- Type
- path
- Description
要订阅的键帽任务的唯一标识符。
返回值
以 Server-Sent Events 形式返回
Prototype 任务对象 或
Build 任务对象 的流。对 PENDING 或
IN_PROGRESS 的任务,流中只包含必要的 progress 与 status 字段。
Request
curl -N https://api.meshy.ai/openapi/creative-lab/keycap/v1/build/019c9a52-7d18-7e2b-9f01-6e3b98c4d2af/stream \
-H "Authorization: Bearer ${YOUR_API_KEY}"
Response Stream
// error 事件示例(阶段不匹配或任务不存在)
event: error
data: {
"status_code": 404,
"message": "Task not found"
}
// message 事件示例(任务进度)。
// 对 PENDING 或 IN_PROGRESS 的任务,流中不会包含全部字段。
event: message
data: {
"id": "019c9a52-7d18-7e2b-9f01-6e3b98c4d2af",
"progress": 0,
"status": "PENDING"
}
event: message
data: {
"id": "019c9a52-7d18-7e2b-9f01-6e3b98c4d2af",
"type": "creative-lab-keycap-build",
"status": "SUCCEEDED",
"progress": 100,
"created_at": 1753142600000,
"started_at": 1753142610000,
"finished_at": 1753143050000,
"expires_at": 1753402250000,
"task_error": null,
"consumed_credits": 50,
"model_urls": {
"glb": "https://assets.meshy.ai/***/tasks/019c9a52-7d18-7e2b-9f01-6e3b98c4d2af/output/model.glb?Expires=***",
"obj_zip": "https://assets.meshy.ai/***/tasks/019c9a52-7d18-7e2b-9f01-6e3b98c4d2af/output/model-obj.zip?Expires=***"
},
"process_image_urls": {
"head_design": "https://assets.meshy.ai/***/head-design.png?Expires=***"
}
}
列出键帽任务
分页列出你在单一阶段下的键帽任务。URL 路径决定阶段 —— /prototype 只返回
prototype 任务;/build 只返回 build 任务。两个响应都不会包含另一阶段的任务。
路径参数
- Name
- stage
- Type
- path
- 必选
- Description
prototype或build。集合只返回与 URL 匹配阶段的任务 —— 请求/prototype永远不会返回 build 任务,反之亦然。
查询参数
- Name
- page_num
- Type
- integer
- 默认值 1
- Description
分页页码。
- Name
- page_size
- Type
- integer
- 默认值 10
- Description
每页条数上限。最大允许
100条。
- Name
- sort_by
- Type
- string
- 默认值 -created_at
- Description
排序字段。可用值:
+created_at:按创建时间升序。-created_at:按创建时间降序。
返回值
返回对应阶段任务对象的分页列表 —— 列出 /prototype 时为
Prototype 任务对象,列出 /build
时为 Build 任务对象。
Request
# 列出 prototype 任务
curl https://api.meshy.ai/openapi/creative-lab/keycap/v1/prototype?page_size=10 \
-H "Authorization: Bearer ${YOUR_API_KEY}"
# 列出 build 任务
curl https://api.meshy.ai/openapi/creative-lab/keycap/v1/build?page_size=10 \
-H "Authorization: Bearer ${YOUR_API_KEY}"
Response (List Prototype Tasks)
[
{
"id": "019c9a4e-2b31-7f6a-8c44-5d2a87b3c1ef",
"type": "creative-lab-keycap-prototype",
"name": "",
"status": "SUCCEEDED",
"progress": 100,
"created_at": 1753142456000,
"started_at": 1753142460000,
"finished_at": 1753142516000,
"expires_at": 1753401716000,
"preceding_tasks": 0,
"task_error": null,
"consumed_credits": 12,
"image_urls": [
"https://assets.meshy.ai/***/design-1.png?Expires=***"
],
"candidate_ids": [
"0198c1b2-33f5-7d21-a4b7-8e9d0c1f2a3b"
]
}
]
Prototype 任务对象
键帽 Prototype 任务对象是 Meshy 跟踪的一个工作单元,用于从源照片生成 1 张
成品键帽设计效果图。此阶段的产出通过 input_task_id 加 candidate_id
串联进 build 阶段。
属性
- Name
- id
- Type
- string
- Description
任务的唯一标识符。实现细节上我们使用 k-sortable UUID 作为任务 id,但你不应对 id 的格式做任何假设。
- Name
- type
- Type
- string
- Description
任务类型。取值为
creative-lab-keycap-prototype。
- Name
- name
- Type
- string
- Description
创建任务时提供的任务名称。未提供时为空字符串。
- Name
- status
- Type
- string
- Description
任务状态。取值为
PENDING、IN_PROGRESS、SUCCEEDED、FAILED、CANCELED之一。
- Name
- progress
- Type
- integer
- Description
任务进度。任务尚未开始时为
0;任务成功后为100。
- Name
- created_at
- Type
- timestamp
- Description
任务创建时间的时间戳,单位毫秒。
时间戳表示自 1970 年 1 月 1 日 UTC 以来经过的毫秒数,遵循 RFC 3339
标准。 例如 2023 年 9 月 1 日星期五 12:00:00 PM GMT 表示为1693569600000。 这适用于 Meshy API 中的所有时间戳。
- Name
- started_at
- Type
- timestamp
- Description
任务开始时间的时间戳,单位毫秒。任务尚未开始时为
0。
- Name
- finished_at
- Type
- timestamp
- Description
任务结束时间的时间戳,单位毫秒。任务尚未结束时为
0。
- Name
- expires_at
- Type
- timestamp
- Description
任务结果过期时间的时间戳,单位毫秒。
- Name
- preceding_tasks
- Type
- integer
- Description
排在前面的任务数量。
仅当任务状态为
PENDING时此字段才有意义。
- Name
- task_error
- Type
- object
- Description
失败任务的错误详情。完整的
task_error对象参考见 Errors。
- Name
- consumed_credits
- Type
- integer
- Description
此任务消耗的积分数。进入
SUCCEEDED的任务按该阶段全额计费。从未被创建的任务(请求时即返回4xx,包括内容审核拒绝)完全不计费。进入FAILED的任务返回0—— 费用会退还,异步的内容审核拦截也一样。通过DELETE取消仅在任务仍为PENDING时退款;已进入IN_PROGRESS的任务不退款,因为算力已经花掉了。
- Name
- image_urls
- Type
- array of strings
- Description
成品键帽设计效果图的下载 URL —— 展示该候选做成成品键帽后的效果。数组仅含 1 项,
image_urls[i]对应candidate_ids[i]。任务达到SUCCEEDED前为空数组。该 URL 仅用于展示;build 端点消费的是candidate_ids,而不是这些 URL。URL 生命周期与model_urls相同:签名链接、不需要Authorization头、有效至expires_at,重复读取任务时保持不变。
- Name
- candidate_ids
- Type
- array of strings
- Description
与
image_urls平行的不透明候选标识符。把你选中设计对应的条目作为 build 请求的candidate_id传入。不要对这些 id 的格式做任何假设。
Prototype 任务对象示例
{
"id": "019c9a4e-2b31-7f6a-8c44-5d2a87b3c1ef",
"type": "creative-lab-keycap-prototype",
"name": "",
"status": "SUCCEEDED",
"progress": 100,
"created_at": 1753142456000,
"started_at": 1753142460000,
"finished_at": 1753142516000,
"expires_at": 1753401716000,
"preceding_tasks": 0,
"task_error": null,
"consumed_credits": 12,
"image_urls": [
"https://assets.meshy.ai/***/design-1.png?Expires=***"
],
"candidate_ids": [
"0198c1b2-33f5-7d21-a4b7-8e9d0c1f2a3b"
]
}
Build 任务对象
键帽 Build 任务对象是 Meshy 跟踪的一个工作单元,用于从已成功的 prototype 任务及选定候选生成最终的带贴图 3D 键帽。单个 build 一条龙跑完整条流水线 —— 白模生成、自动落座切割、上色、组装、导出。
属性
- Name
- id
- Type
- string
- Description
任务的唯一标识符。
- Name
- type
- Type
- string
- Description
任务类型。取值为
creative-lab-keycap-build。
- Name
- name
- Type
- string
- Description
创建任务时提供的任务名称。未提供时为空字符串。
- Name
- status
- Type
- string
- Description
任务状态。取值为
PENDING、IN_PROGRESS、SUCCEEDED、FAILED、CANCELED之一。
- Name
- progress
- Type
- integer
- Description
任务进度。任务尚未开始时为
0;任务成功后为100。
- Name
- created_at
- Type
- timestamp
- Description
任务创建时间的时间戳,单位毫秒。
- Name
- started_at
- Type
- timestamp
- Description
任务开始时间的时间戳,单位毫秒。
- Name
- finished_at
- Type
- timestamp
- Description
任务结束时间的时间戳,单位毫秒。
- Name
- expires_at
- Type
- timestamp
- Description
任务结果过期时间的时间戳,单位毫秒。
- Name
- preceding_tasks
- Type
- integer
- Description
排在前面的任务数量。仅当状态为
PENDING时有意义。
- Name
- task_error
- Type
- object
- Description
失败任务的错误详情。完整的
task_error对象参考见 Errors。
- Name
- consumed_credits
- Type
- integer
- Description
此任务消耗的积分数。进入
SUCCEEDED的任务按该阶段全额计费。从未被创建的任务(请求时即返回4xx,包括内容审核拒绝)完全不计费。进入FAILED的任务返回0—— 费用会退还,异步的内容审核拦截也一样。通过DELETE取消仅在任务仍为PENDING时退款;已进入IN_PROGRESS的任务不退款,因为算力已经花掉了。
- Name
- model_urls
- Type
- object
- Description
生成模型产物的下载 URL。GLB 与 OBJ 产物均按真实毫米尺度导出,坐标系为 Y 轴向上,键帽正面朝向 +Z。网格命名为
keycap-head与keycap-base;当底座回退为图案填充时,还会多出第三个网格keycap-base-interior(轴孔内壁)。请勿假设固定为两个网格。这些是签名链接:请求时不要带
Authorization头。它们在expires_at之前有效,也就是finished_at之后 3 天;在这个窗口内重复读取任务会拿到完全相同的链接,而不是重新签名的新链接。请在此之前自行下载并保存文件 —— 过期的链接无法刷新。- Name
glb- Type
- string
- Description
最终带贴图
model.glb的下载 URL。
- Name
obj_zip- Type
- string
- Description
zip 包的下载 URL,内含
model.obj、model.mtl,以及 MTL 实际引用的贴图 PNG。纯色底座仅含keycap-head.png;带图案的底座还会包含keycap-base.png。
- Name
- process_image_urls
- Type
- object
- Description
过程图的下载 URL,按 kind 作为键。URL 生命周期与
model_urls相同:签名链接、不需要Authorization头、有效至expires_at,重复读取任务时保持不变。当前会出现的 kind:head_design—— build 实际消费的选定候选设计图(始终存在)。composite—— 选定候选的成品键帽展示渲染图(存在时返回)。base_canvas—— 上色后的键帽底座画布(存在时返回)。
请把键集合视为开放集合;后续可能在不破坏兼容的前提下新增 kind。
Build 任务对象示例
{
"id": "019c9a52-7d18-7e2b-9f01-6e3b98c4d2af",
"type": "creative-lab-keycap-build",
"name": "",
"status": "SUCCEEDED",
"progress": 100,
"created_at": 1753142600000,
"started_at": 1753142610000,
"finished_at": 1753143050000,
"expires_at": 1753402250000,
"preceding_tasks": 0,
"task_error": null,
"consumed_credits": 50,
"model_urls": {
"glb": "https://assets.meshy.ai/***/tasks/019c9a52-7d18-7e2b-9f01-6e3b98c4d2af/output/model.glb?Expires=***",
"obj_zip": "https://assets.meshy.ai/***/tasks/019c9a52-7d18-7e2b-9f01-6e3b98c4d2af/output/model-obj.zip?Expires=***"
},
"process_image_urls": {
"head_design": "https://assets.meshy.ai/***/head-design.png?Expires=***",
"composite": "https://assets.meshy.ai/***/composite.png?Expires=***",
"base_canvas": "https://assets.meshy.ai/***/base-canvas.png?Expires=***"
}
}
端到端完整示例
完整流程:用照片创建 prototype,轮询到 SUCCEEDED,从 candidate_ids
中选定一个候选,用它创建 build,轮询 build 到 SUCCEEDED,最后从
model_urls 下载 GLB 与 OBJ 包。
示例中程序化地选择了第一个候选。实际接入时应把 image_urls 里的那一项
展示给最终用户挑选;选中的下标与 candidate_ids 一一对应。
Complete flow
#!/usr/bin/env bash
set -euo pipefail
# 需要 curl 与 jq。把 IMAGE_PATH 指向本地照片,或把 IMAGE_URL 指向公网图片:
# export MESHY_API_KEY=msy_...
# export IMAGE_PATH=./portrait.jpg # or: export IMAGE_URL=https://...
: "${MESHY_API_KEY:?export MESHY_API_KEY first}"
if [[ -z "${IMAGE_PATH:-}" && -z "${IMAGE_URL:-}" ]]; then
echo "export IMAGE_PATH (local file) or IMAGE_URL (public url) first" >&2
exit 1
fi
BASE="https://api.meshy.ai/openapi/creative-lab/keycap/v1"
AUTH="Authorization: Bearer $MESHY_API_KEY"
# api METHOD URL [curl 参数...] -> 打印响应体,失败时返回非 0。
# 注意这里不用 -f/--fail:它会把响应体丢掉,而原因只出现在响应体里。
api() {
local method=$1 url=$2 out http_code body
shift 2
out=$(curl --silent --show-error --max-time 60 --write-out $'\n%{http_code}' \
-X "$method" "$url" -H "$AUTH" "$@") || return 1
http_code=${out##*$'\n'}
body=${out%$'\n'*}
if ((http_code >= 400)); then
echo "HTTP $http_code for $url: $body" >&2
return 1
fi
printf '%s' "$body"
}
# 每个任务各有自己的 40 分钟预算。
poll() {
local kind=$1 id=$2 delay=5 task_status deadline
deadline=$(($(date +%s) + 2400))
while :; do
if (($(date +%s) >= deadline)); then
echo "gave up waiting for $kind $id" >&2
return 1
fi
task_status=$(api GET "$BASE/$kind/$id" | jq -r '.status')
echo "$kind: $task_status"
case "$task_status" in
SUCCEEDED) return 0 ;;
FAILED | CANCELED) return 1 ;;
esac
sleep "$delay"
delay=$((delay * 2 > 30 ? 30 : delay * 2))
done
}
# 把请求体写进文件。base64 data URI 绝对不能出现在命令行或导出的环境变量里 ——
# 任何真实尺寸的照片都会超过操作系统的参数长度上限。
BODY=$(mktemp)
trap 'rm -f "$BODY"' EXIT
if [[ -n "${IMAGE_PATH:-}" ]]; then
# 声明真实类型:接口支持 JPEG、PNG、WebP。
case "$(printf '%s' "${IMAGE_PATH##*.}" | tr 'A-Z' 'a-z')" in
png) MIME=image/png ;;
webp) MIME=image/webp ;;
*) MIME=image/jpeg ;;
esac
{
printf '{"image_url":"data:%s;base64,' "$MIME"
base64 <"$IMAGE_PATH" | tr -d '\n'
printf '"}'
} >"$BODY"
else
printf '{"image_url":"%s"}' "$IMAGE_URL" >"$BODY"
fi
# 1. 创建 prototype 任务
PROTO_ID=$(api POST "$BASE/prototype" \
-H 'Content-Type: application/json' --data-binary @"$BODY" | jq -r '.result')
# 2. 等待设计效果图
poll prototype "$PROTO_ID"
# 3. 选一个候选(这里取第一个;生产环境应把 image_urls 展示给用户选)
CANDIDATE_ID=$(api GET "$BASE/prototype/$PROTO_ID" | jq -r '.candidate_ids[0]')
# 4. 创建 build 任务
jq -n --arg p "$PROTO_ID" --arg c "$CANDIDATE_ID" \
'{input_task_id: $p, candidate_id: $c}' >"$BODY"
BUILD_ID=$(api POST "$BASE/build" \
-H 'Content-Type: application/json' --data-binary @"$BODY" | jq -r '.result')
# 5. 等待模型(build 通常需要 3-7 分钟)
poll build "$BUILD_ID"
# 6. 下载产物。这些是签名链接:不需要 Authorization 头,
# 任务完成后 3 天内有效。
TASK=$(api GET "$BASE/build/$BUILD_ID")
curl --silent --show-error --fail --max-time 900 \
-o keycap.glb "$(jq -r '.model_urls.glb' <<<"$TASK")"
curl --silent --show-error --fail --max-time 900 \
-o keycap-obj.zip "$(jq -r '.model_urls.obj_zip' <<<"$TASK")"
echo "Done: keycap.glb + keycap-obj.zip"