Creative Lab — 键帽 API

将一张原始照片分两个阶段转换为全彩定制机械键盘键帽:prototype(原型) 阶段会根据你输入的照片生成 一张“成品键帽”设计效果图。在确认该效果图之后,build(构建) 阶段会在一次运行中将其转换为 带贴图的 3D 键帽模型——白模生成、在标定的默认姿态上自动就位与切割、全模型上色, 以及最终组装,均在同一个构建任务内完成。这两个阶段通过 input_task_idcandidate_id 关联在一起。

  • POST /openapi/creative-lab/keycap/v1/prototype
  • POST /openapi/creative-lab/keycap/v1/build

POST/openapi/creative-lab/keycap/v1/prototype

创建键帽原型任务

根据源照片生成成品键帽设计渲染图。任务结果携带一个 image_urls 数组(成品键帽的展示渲染图)和一个对应的 candidate_ids 数组;两者都只包含一个条目。如果结果不是你想要的,可以再次调用此 endpoint 来获取另一张渲染图——每次调用都是单独计费的。将 candidate_id 与原型任务 ID 一起传给构建 endpoint。 有关响应结构,请参阅 键帽原型任务对象

参数

  • Name
    image_url
    Type
    string
    必选
    Description

    供 Meshy 转换为键帽设计图片的源照片。我们目前支持 .jpg.jpeg.png.webp 格式。

    格式是通过解码图片数据来检测的,而不是根据 URL 的文件扩展名——没有扩展名的 URL,或会重定向的 URL,只要字节能解码为受支持的格式即可正常使用。系统会跟随 HTTP 重定向。EXIF 方向信息会被规范化,因此旋转过的手机照片会按其看起来的方向被使用。

    限制条件:每边至少 32 像素,总像素数最多 178,956,970,下载后大小最多 20,000,000 字节。对于 data URI,该限制适用于解码后的字节数,因此源文件本身可以达到该大小上限——base64 文本会大约多出三分之一,这会影响你的请求体大小,但不影响此项限制。data URI 必须声明 image/* 内容类型和 ;base64

    提供图片的方式有两种:

    • 可公开访问的 URL:可从公共互联网访问的 URL。
    • Data URI:图片的 base64 编码 data URI。data URI 示例:data:image/jpeg;base64,<your base64-encoded image data>
  • Name
    name
    Type
    string
    Description

    用于显示目的的可选任务名称。最多 100 个字符。

  • Name
    remove_background
    Type
    boolean
    默认值 false
    Description

    设置为 true 时,image_urls 中返回的展示渲染图是已移除背景的透明 RGBA PNG,因此你可以将其合成到任意背景上。

    此设置仅适用于展示渲染图。构建 endpoint 所使用的候选项不受影响,因此无论如何设置,3D 结果都是相同的。

返回值

响应的 result 属性包含新创建的键帽原型任务的任务 id。轮询获取任务 endpoint 或订阅流式接口,直到任务达到 SUCCEEDED 状态,然后取出 candidate_ids 中的条目,将其与任务 ID 一起传给构建 endpoint

失败模式

  • Name
    400 - Bad Request
    Description

    请求无法被接受。常见原因:

    • 缺少参数image_url 是必填项。
    • 图片格式无效:提供的 image_url 不是受支持的格式(.jpg.jpeg.png.webp)。
    • 图片尺寸超出范围:图片过小、超过最大文件大小,或超过最大像素数。
    • URL 无法访问:无法下载 image_url(404 或 timeout)。
    • Data URI 无效:base64 字符串格式有误。
    • 内容被标记:输入图片被 NSFW moderation 标记。
  • Name
    401 - Unauthorized
    Description

    Authentication 失败。请检查你的 API key。

  • Name
    402 - Payment Required
    Description

    该账户处于免费计划中(创建任务需要付费计划),或 credits 不足。

  • Name
    403 - Forbidden
    Description

    输入图片被知识产权 moderation 标记。

  • Name
    429 - Too Many Requests
    Description

    你已超出速率限制。

  • Name
    500 - Internal Server Error
    Description

    发生了意外的服务器端错误——例如内容审核服务不可用、暂存输入图片失败,或任务无法创建。在这种情况下不会创建任务,因此重试是安全的。

Request

POST
/openapi/creative-lab/keycap/v1/prototype
# Stage 1: generate a finished-keycap design render
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"
}

POST/openapi/creative-lab/keycap/v1/build

创建键帽构建任务

从一个成功的原型任务及其某个候选方案生成最终的带贴图 3D 键帽模型。单个构建任务会端到端运行整个流程——根据所选设计生成白模、使用经过校准的默认姿态自动将其放置并裁切到键帽底座上(无需交互式调整)、全模型上色,以及最终装配与导出。构建通常需要 3–7 分钟,在多个构建并发运行时会更接近上限。 响应结构请参阅键帽构建任务对象

参数

  • Name
    input_task_id
    Type
    string
    必选
    Description

    通过同一个 OpenAPI endpoint 创建的原型任务的任务 ID。该原型必须由同一个 Meshy 账户创建,必须已到达 SUCCEEDED 状态,并且必须至少产生一个候选方案。

    通过 webapp 创建的原型任务不被接受——构建 endpoint 仅接受由 POST /openapi/creative-lab/keycap/v1/prototype 生成的原型任务,任何其他来源都会被拒绝并返回 404

  • Name
    candidate_id
    Type
    string
    必选
    Description

    要构建的候选方案,取自成功的原型任务的 candidate_ids 数组。必须属于该任务;任何其他值都会被拒绝并返回 400

  • Name
    name
    Type
    string
    Description

    可选的任务名称,用于显示目的。最多 100 个字符。

options

可选的 geometry 调整。每个字段都有经过校准的默认值——只需发送你想要覆盖的字段。

  • 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 mesh 的包围盒。

  • Name
    vertical_offset_mm
    Type
    number
    默认值 0
    Description

    在头部安置到底座之前施加的垂直偏移量,以毫米为单位。范围:[-5, 5]

返回值

响应的 result 属性包含新创建的键帽构建任务的任务 id。轮询获取任务 endpoint 或订阅,直到任务到达 SUCCEEDED,然后从 model_urls.glbmodel_urls.obj_zip 下载产物。

失败模式

  • Name
    400 - Bad Request
    Description

    请求不可接受。常见原因:

    • 缺少参数input_task_idcandidate_id 是必需的。
    • 无效的 UUIDinput_task_id 不是有效的 UUID。
    • 父任务未成功:所引用的原型任务尚未到达 SUCCEEDED
    • 没有候选方案:原型任务成功但未产生任何候选方案。
    • 未知的候选方案candidate_id 不是输入任务的候选方案之一。
    • 参数超出范围options 中的某个字段超出了其允许的范围或枚举集合。
  • Name
    401 - Unauthorized
    Description

    身份验证失败。请检查你的 API key。

  • Name
    402 - Payment Required
    Description

    该账户处于免费计划(创建任务需要付费计划)或 credits 不足。

  • Name
    404 - Not Found
    Description

    所引用的原型任务不存在、属于其他用户,或是通过 webapp 创建的(只有 API 模式的原型任务才能链接到构建)。

  • Name
    429 - Too Many Requests
    Description

    你已超出速率限制。

  • Name
    500 - Internal Server Error
    Description

    发生了意外的服务器端错误——例如内容 moderation 服务不可用、暂存输入图像失败,或无法创建任务。这种情况下不会创建任何任务,因此重试是安全的。

Request

POST
/openapi/creative-lab/keycap/v1/build
# Stage 2: build the chosen candidate into a 3D keycap
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"
}

GET/openapi/creative-lab/keycap/v1/(prototype|build)/:id

获取 Keycap 任务

根据有效的任务 id 获取原型(prototype)或构建(build)任务。URL 路径必须与任务所处的阶段匹配——通过 /prototype/:id 获取的构建任务会返回 404,反之亦然。

有关响应结构,请参阅 Keycap 原型任务对象Keycap 构建任务对象

参数

  • Name
    id
    Type
    path
    Description

    要获取的 keycap 任务的唯一标识符。

返回

响应包含 keycap 任务对象。其结构取决于所请求的具体阶段。

Request

GET
/openapi/creative-lab/keycap/v1/prototype/019c9a4e-2b31-7f6a-8c44-5d2a87b3c1ef
# 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=***"
  }
}

DELETE/openapi/creative-lab/keycap/v1/(prototype|build)/:id

删除 Keycap 任务

取消一个 keycap 任务。如果任务仍处于 PENDING 状态,则会退还创建时消耗的 credits。已经处于 IN_PROGRESS 状态的任务会被取消但不予退款(worker 可能已经在 消耗资源)。已经达到终态(SUCCEEDEDFAILEDCANCELED)的任务无法取消。

URL 路径必须与任务所处的阶段相匹配 —— 对 /prototype/:buildId 执行 DELETE 会返回 404

路径参数

  • Name
    id
    Type
    path
    Description

    要取消的 keycap 任务的唯一标识符。

返回值

成功时返回 204 No Content,响应体为空。

失败模式

  • Name
    400 - Bad Request
    Description

    该任务已处于终态,无法取消。

  • Name
    404 - Not Found
    Description

    该任务不存在、属于其他用户,或其所处阶段与 URL 路径不匹配。

  • Name
    500 - Internal Server Error
    Description

    取消过程中发生了意外的服务器端错误。该任务可能已被取消,也可能未被取消 —— 请在重试前重新读取以确认。

Request

DELETE
/openapi/creative-lab/keycap/v1/prototype/019c9a4e-2b31-7f6a-8c44-5d2a87b3c1ef
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

// Returns 204 No Content on success (empty body).

GET/openapi/creative-lab/keycap/v1/(prototype|build)/:id/stream

流式获取 Keycap 任务

通过 Server-Sent Events(SSE)流式获取 keycap 任务的实时更新。 URL 路径必须与任务所处的阶段匹配 —— 在 /prototype/:buildId/stream 处打开流会发出一个带有 status_code: 404event: error payload,随后关闭该流。

参数

  • Name
    id
    Type
    path
    Description

    要进行流式传输的 keycap 任务的唯一标识符。

返回

以 Server-Sent Events 的形式返回一个由 Keycap PrototypeKeycap Build 任务对象组成的流。 对于 PENDINGIN_PROGRESS 状态的任务,响应流中将只包含必要的 progressstatus 字段。

Request

GET
/openapi/creative-lab/keycap/v1/build/019c9a52-7d18-7e2b-9f01-6e3b98c4d2af/stream
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 example (wrong stage or task not found)
event: error
data: {
  "status_code": 404,
  "message": "Task not found"
}

// Message event examples illustrate task progress.
// For PENDING or IN_PROGRESS tasks, the response stream will not include all fields.
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=***"
  }
}

GET/openapi/creative-lab/keycap/v1/(prototype|build)

List Keycap Tasks

获取单个阶段中你的 keycap 任务的分页列表。URL 路径决定了阶段——/prototype 返回原型(prototype)任务;/build 返回构建(build)任务。另一个阶段的任务不会包含在任何一个响应中。

路径参数

  • Name
    stage
    Type
    path
    必选
    Description

    prototypebuild。该集合只返回阶段与 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 时返回 keycap 原型任务对象, 列出 /build 时返回 keycap 构建任务对象

Request

GET
/openapi/creative-lab/keycap/v1/prototype
# List prototype tasks
curl https://api.meshy.ai/openapi/creative-lab/keycap/v1/prototype?page_size=10 \
  -H "Authorization: Bearer ${YOUR_API_KEY}"

# List build tasks
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"
    ]
  }
]

键帽原型任务对象

键帽原型任务(Keycap Prototype Task)对象是 Meshy 用于跟踪的一个工作单元,用于根据源照片生成一张成品键帽设计图。该阶段的输出会通过 input_task_idcandidate_id 串联到构建阶段

属性

  • Name
    id
    Type
    string
    Description

    任务的唯一标识符。虽然我们在实现细节上使用 k-可排序的 UUID 作为任务 id,但你不应对 id 的格式做任何假设。

  • Name
    type
    Type
    string
    Description

    任务的类型。此值为 creative-lab-keycap-prototype

  • Name
    name
    Type
    string
    Description

    创建任务时提供的任务名称。如果未提供名称,则为空字符串。

  • Name
    status
    Type
    string
    Description

    任务的状态。可能的取值为 PENDINGIN_PROGRESSSUCCEEDEDFAILEDCANCELED 之一。

  • Name
    progress
    Type
    integer
    Description

    任务的进度。如果任务尚未开始,此属性为 0。任务成功后,此值将变为 100

  • Name
    created_at
    Type
    timestamp
    Description

    任务创建时的时间戳,单位为毫秒。

  • 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

    前置任务的数量。

  • Name
    task_error
    Type
    object
    Description

    失败任务的错误详情。完整的 task_error 对象参考请参见 Errors

  • Name
    consumed_credits
    Type
    integer
    Description

    此任务消耗的 credits 数量。达到 SUCCEEDED 状态的任务将按该阶段的全额收费。从未成功创建的任务(请求时出现 4xx,包括 moderation 拒绝)完全不收费。达到 FAILED 状态的任务返回 0——费用将被退还,包括异步 moderation 拦截的情况。通过 DELETE 取消任务仅在任务仍为 PENDING 状态时退款;已处于 IN_PROGRESS 状态的任务仍会被收费,因为相应的算力已经消耗。

  • Name
    image_urls
    Type
    array of strings
    Description

    成品键帽设计渲染图的可下载 URL——即该候选项作为成品键帽的效果图。仅包含一个条目;image_urls[i] 对应于 candidate_ids[i]。在任务达到 SUCCEEDED 之前为空。该 URL 仅用于展示;构建 endpoint 使用的是 candidate_ids,而非这些 URL。其 URL 生命周期与 model_urls 相同:经过签名、无需 Authorization 请求头、在 expires_at 之前有效,且在重新读取任务时保持不变。

  • Name
    candidate_ids
    Type
    array of strings
    Description

    不透明的候选项标识符,与 image_urls 一一对应。请将与你所选设计相匹配的条目作为构建请求的 candidate_id 传入。请勿对这些 id 的格式做任何假设。

Example Keycap Prototype Task Object

{
  "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"
  ]
}

键帽构建任务对象

键帽构建任务对象是 Meshy 用于跟踪的一个工作单元,用来根据成功的原型任务和选定的候选方案生成最终的带 texture 3D 键帽。单次构建会运行完整的流水线——白模生成、自动落座与切割、上色、装配以及导出。

属性

  • Name
    id
    Type
    string
    Description

    任务的唯一标识符。

  • Name
    type
    Type
    string
    Description

    任务的类型。其值为 creative-lab-keycap-build

  • Name
    name
    Type
    string
    Description

    创建任务时提供的任务名称。如果未提供名称,则为空字符串。

  • Name
    status
    Type
    string
    Description

    任务的状态。可能的值为 PENDINGIN_PROGRESSSUCCEEDEDFAILEDCANCELED 之一。

  • 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

    此任务消耗的 credits 数量。达到 SUCCEEDED 状态的任务将按其阶段收取全额费用。从未成功创建的任务(请求时返回 4xx,包括 moderation 拒绝)完全不收费。达到 FAILED 状态的任务返回 0——费用会被退还,包括异步 moderation 拦截的情况。通过 DELETE 取消任务仅在任务仍处于 PENDING 状态时才会退款;已处于 IN_PROGRESS 状态的任务仍会被收费,因为相关工作已经耗费。

  • Name
    model_urls
    Type
    object
    Description

    生成的模型文件的可下载 URL。GLB 和 OBJ 压缩包均以真实世界的毫米级比例Y 轴朝上导出,键帽正面朝向 +Z 方向。mesh 分别命名为 keycap-headkeycap-base;当底座回退为图案填充时,还会存在第三个 mesh keycap-base-interior,用于表示卡柱空腔。请勿假定一定只有两个 mesh。

    这些是签名 URL:获取时无需携带 Authorization 请求头。它们在 expires_at(即 finished_at 之后 3 天)之前一直有效,在此期间重新读取任务将返回相同的 URL,而不是重新签名的 URL。请在此之前自行下载并保存这些文件——过期的链接无法刷新。

    • Name
      glb
      Type
      string
      Description

      最终带 texture 的 model.glb 的可下载 URL。

    • Name
      obj_zip
      Type
      string
      Description

      指向包含 model.objmodel.mtl 以及其 MTL 实际引用的 texture PNG 文件的压缩包的可下载 URL。纯色底座只提供 keycap-head.png;带图案的底座还会提供 keycap-base.png

  • Name
    process_image_urls
    Type
    object
    Description

    按种类分类的中间过程图像的可下载 URL。与 model_urls 具有相同的 URL 生命周期:已签名、无需 Authorization 请求头、在 expires_at 之前有效,并且在重新读取任务时保持不变。目前会输出以下种类:

    • head_design — 构建所使用的所选候选方案的设计图(始终存在)。
    • composite — 所选候选方案的成品键帽展示渲染图(在可用时存在)。
    • base_canvas — 绘制完成的键帽底座画布(在可用时存在)。

    请将该键集合视为开放式的;未来可能会新增种类,且不会构成破坏性变更。

Example Keycap Build Task Object

{
  "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=***"
  }
}

端到端示例

完整流程:从一张照片创建原型,轮询直到状态变为 SUCCEEDED,从 candidate_ids 中选择一个候选项,使用该候选项创建构建任务, 轮询构建任务直到状态变为 SUCCEEDED,然后从 model_urls 下载 GLB 和 OBJ 压缩包。

该示例以编程方式选择了第一个候选项。在实际集成中,你应当将 image_urls 中的条目展示给最终用户,让他们进行选择;所选的索引与 candidate_ids 一一对应。

Complete flow

POST
/openapi/creative-lab/keycap/v1
#!/usr/bin/env bash
set -euo pipefail

# Requires curl and jq. Point IMAGE_PATH at a local photo, or IMAGE_URL at a public one:
#   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 args...] -> prints the response body, non-zero on failure.
# Note we do not use -f/--fail: it discards the body, and the body is the only
# place the reason appears.
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"
}

# Each task gets its own 40-minute budget.
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
}

# Build the request body in a file. A base64 data URI must never go on the
# command line or into an exported variable - a photo of any real size will
# exceed the OS argument limit.
BODY=$(mktemp)
trap 'rm -f "$BODY"' EXIT
if [[ -n "${IMAGE_PATH:-}" ]]; then
  # Declare the real type: the API accepts JPEG, PNG and 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. Create the prototype task
PROTO_ID=$(api POST "$BASE/prototype" \
  -H 'Content-Type: application/json' --data-binary @"$BODY" | jq -r '.result')

# 2. Wait for the design render
poll prototype "$PROTO_ID"

# 3. Pick a candidate (first one here; show image_urls to a user in production)
CANDIDATE_ID=$(api GET "$BASE/prototype/$PROTO_ID" | jq -r '.candidate_ids[0]')

# 4. Create the build task
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. Wait for the model (a build usually takes 3-7 minutes)
poll build "$BUILD_ID"

# 6. Download the artifacts. These are signed URLs: no Authorization header,
#    and they stay valid for 3 days after the task finishes.
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"