lowpoly retires Oct 30, 2026. Please switch models before these dates to avoid request errors. See all deprecations

Creative Lab Keycap API

Turn a photo into a full-color custom keycap for a mechanical keyboard in two stages. A prototype task generates an image of the finished keycap from the photo. A build task turns that image into a textured 3D keycap model. It takes the prototype task's ID as input_task_id and the image's candidate ID as candidate_id.

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

Both POST endpoints need a paid plan. A request from a free-plan account returns 402.


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

Create a Keycap prototype task

Generates one image of the finished keycap from a photo. The task's image_urls holds the image, and candidate_ids holds its ID, with one entry in each. If you don't like the design, create another prototype task. Meshy charges for each prototype task separately.

To build the 3D keycap, pass this task's ID to the build endpoint as input_task_id, with the candidate_ids entry as candidate_id. For the task's fields, see The Keycap prototype task object.

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

Parameters

  • Name
    image_url
    Type
    string
    Required
    Description

    The photo to turn into a keycap design, as a public URL or a data URI (data:image/jpeg;base64,<your base64-encoded image data>).

    The image must be a JPG, JPEG, PNG, or WebP file of at most 20 MB (20,000,000 bytes). Each side must be at least 32 pixels, and the image can have at most 178,956,970 pixels in total. An image outside these limits returns 400.

    Meshy detects the format from the image data, not from the file extension in the URL. A URL with no extension works, and Meshy follows HTTP redirects. Meshy applies the EXIF orientation, so a rotated phone photo comes out the way it looks.

    A data URI must be base64-encoded with an image type, such as data:image/png;base64,..., and the decoded image must be JPEG, PNG, or WebP. The 20 MB limit applies to the decoded image, so the base64 text can be about a third larger.

  • Name
    name
    Type
    string
    Description

    A name for the task, up to 100 characters. It comes back in the task's name field. A longer name returns 400.

  • Name
    remove_background
    Type
    boolean
    default false
    Description

    Whether to remove the background from the prototype image. true returns a transparent RGBA PNG, so you can place the keycap on any background. If Meshy can't remove the background, it returns the image with its background, and the task still succeeds.

    The head design that the build turns into 3D keeps its background either way. The build paints the keycap base from the image in image_urls, without its background if you sent true. The build returns that image as process_image_urls.composite.

Returns

The result field holds the ID of the new prototype task. Get the task or stream it until status is SUCCEEDED. Then pass the ID to the build endpoint as input_task_id, with the entry from candidate_ids as candidate_id.

Failure modes

  • Name
    400 - Bad Request
    Description

    The request can't run. For example:

    • image_url is missing.
    • The image isn't a JPG, JPEG, PNG, or WebP file.
    • The image is too small, over 20 MB, or over the maximum pixel count.
    • Meshy can't download the image, for example because the URL returns 404 or times out.
    • The data URI isn't valid base64.
    • The image was flagged as NSFW.

    The error message says which problem it found. Fix the request, then send it again.

  • Name
    401 - Unauthorized
    Description

    The API key is missing or invalid. Check the Authorization header.

  • Name
    402 - Payment Required
    Description

    One of these applies:

    • The account doesn't have enough credits.
    • The API key, workspace, or member reached its credit limit.
    • The account is on the free plan.

    Check your credits and plan, then retry.

  • Name
    403 - Forbidden
    Description

    Your team has IP Filtering turned on, and Meshy flagged the image for third-party intellectual property. Send an image you have the rights to use.

  • Name
    429 - Too Many Requests
    Description

    One of these limits was reached:

    • You sent more requests than your rate limit allows. Wait a few seconds, then retry. See Rate limits.
    • Too many of your tasks are queued or running. Wait for some to finish, then retry.
    • On an unlimited plan with no credits left, you reached the number of tasks that can run without credits. Wait for a task to finish, or add credits.
  • Name
    500 - Internal Server Error
    Description

    Meshy hit a server error, for example because content moderation or the intellectual property check couldn't run, or Meshy couldn't store the image. Meshy doesn't create the task, and refunds any credits it charged. Retry the request.


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

Create a Keycap build task

Generates the textured 3D keycap from a succeeded prototype task and one of its candidates. For its fields, see The Keycap build task object.

One build task runs every step. It generates a 3D model from the design and colors it. Then it seats and cuts the model onto the keycap base in a calibrated default pose, so you don't need to adjust it by hand. Last, it assembles the keycap and exports the files. A build typically takes 3 to 7 minutes.

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

Parameters

  • Name
    input_task_id
    Type
    string
    Required
    Description

    The ID of the prototype task to build. It must be a task your account created with POST /openapi/creative-lab/keycap/v1/prototype, with the status SUCCEEDED.

    An ID that isn't one of your Keycap prototype tasks from the API returns 404, including a prototype made in the web app.

  • Name
    candidate_id
    Type
    string
    Required
    Description

    The candidate to build, from the candidate_ids array of the prototype task. A value that isn't in that array returns 400.

  • Name
    name
    Type
    string
    Description

    A name for the task, up to 100 characters. It comes back in the task's name field. A longer name returns 400.

options

Optional geometry settings. Each field has a calibrated default, so send only the fields you want to change.

  • Name
    base_model
    Type
    string
    default cherry-mx-1x1-r1
    Description

    The keycap base to build on. The only value is cherry-mx-1x1-r1, a standard Cherry MX profile 1u keycap. Custom sizes aren't supported, and any other value returns 400.

  • Name
    head_size_mm
    Type
    number
    default 23
    Description

    The size of the head along its longest dimension, in millimeters, from 10 to 40. Meshy scales the head so its longest dimension matches this value. A value outside the range returns 400.

    For a value above about 32.9, Meshy might reduce the size so the head fits the base's protective footprint limit. The head can then come out smaller than you asked. The task object doesn't return the size Meshy applied. To check it, measure the bounding box of the keycap-head mesh in the model.

  • Name
    vertical_offset_mm
    Type
    number
    default 0
    Description

    The vertical offset to apply to the head before Meshy seats it on the base, in millimeters, from -5 to 5. A value outside the range returns 400.

Returns

The result field holds the ID of the new build task. Get the task or stream it until status is SUCCEEDED. Then download the GLB file from model_urls.glb and the OBJ bundle from model_urls.obj_zip.

Failure modes

  • Name
    400 - Bad Request
    Description

    The request can't run. For example:

    • input_task_id is missing or isn't a valid task ID.
    • The prototype task's status isn't SUCCEEDED. If it's still running, wait and retry. If it failed or was canceled, create a new prototype task.
    • The prototype task succeeded but has no candidates. Create a new prototype task.
    • candidate_id is missing, or isn't in the prototype task's candidate_ids. Send a value from that array.
    • A field in options is out of range, or base_model isn't an available value.

    The error message says which problem it found.

  • Name
    401 - Unauthorized
    Description

    The API key is missing or invalid. Check the Authorization header.

  • Name
    402 - Payment Required
    Description

    One of these applies:

    • The account doesn't have enough credits.
    • The API key, workspace, or member reached its credit limit.
    • The account is on the free plan.

    Check your credits and plan, then retry.

  • Name
    404 - Not Found
    Description

    input_task_id doesn't match any Keycap prototype task your account created with the API. A prototype made in the web app also returns 404. Send the result ID from a prototype create response.

  • Name
    429 - Too Many Requests
    Description

    One of these limits was reached:

    • You sent more requests than your rate limit allows. Wait a few seconds, then retry. See Rate limits.
    • Too many of your tasks are queued or running. Wait for some to finish, then retry.
    • On an unlimited plan with no credits left, you reached the number of tasks that can run without credits. Wait for a task to finish, or add credits.
  • Name
    500 - Internal Server Error
    Description

    Meshy couldn't create the task because of a server error. Meshy refunds any credits it charged. Retry the request.


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

Get a Keycap task

Returns a prototype or build task. The path must match the task's stage. A build task requested through /prototype/:id returns 404, and so does a prototype task requested through /build/:id.

For the fields, see The Keycap prototype task object and The Keycap build task object.

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

Parameters

  • Name
    id
    Type
    path
    Description

    The task ID from the create response.

Returns

The prototype task object or the build task object, matching the stage in the path.

Failure modes

  • Name
    400 - Bad Request
    Description

    id isn't a valid task ID. Send the result ID from the create response.

  • Name
    403 - Forbidden
    Description

    Your team has IP Filtering turned on, and Meshy flagged the prototype task's image for third-party intellectual property. Build tasks and teams on the Enterprise plan don't get this error. Create a prototype task from an image you have the rights to use.

  • Name
    404 - Not Found
    Description

    No Keycap task with this ID belongs to your account, or the task's stage doesn't match the path. Check the ID, and use /prototype or /build to match the task.

  • Name
    500 - Internal Server Error
    Description

    Meshy couldn't run the intellectual property check. Retry the request.


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

Delete a Keycap task

Cancels a Keycap task. Canceling a PENDING task refunds its credits. An IN_PROGRESS task is canceled without a refund, because a worker has already started it. A task with a final status (SUCCEEDED, FAILED, or CANCELED) can't be canceled.

The path must match the task's stage. For example, DELETE on /prototype/:id with a build task's ID returns 404.

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).

Path parameters

  • Name
    id
    Type
    path
    Description

    The ID of the task to cancel.

Returns

204 No Content with an empty body.

Failure modes

  • Name
    400 - Bad Request
    Description

    One of these applies:

    • id isn't a valid task ID. Send the result ID from the create response.
    • The task already has a final status (SUCCEEDED, FAILED, or CANCELED), so it can't be canceled.
  • Name
    404 - Not Found
    Description

    No Keycap task with this ID belongs to your account, or the task's stage doesn't match the path. Check the ID, and use /prototype or /build to match the task.

  • Name
    500 - Internal Server Error
    Description

    Meshy hit a server error while canceling the task, so the task might or might not be canceled. Get the task to check its status before you retry.


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

Stream a Keycap task

Streams a Keycap task's updates as Server-Sent Events (SSE). The path must match the task's stage. If it doesn't, for example a build task's ID at /prototype/:id/stream, the stream sends one error event with status_code: 404 and closes.

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.
// Every frame is the full task object; fields not yet populated are null / empty.
// The following PENDING frame is shortened to the fields that change.
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=***"
  }
}

Parameters

  • Name
    id
    Type
    path
    Description

    The task ID from the create response.

Returns

A stream of prototype or build task objects. Each event has the full task object, as Get a Keycap task returns it. While the task is PENDING or IN_PROGRESS, its output fields are still empty (null, [], or {}), and finished_at is null.

If IP Filtering blocks the task's result, the stream sends one error event in its place, with the status_code that a get request would return.


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

List Keycap tasks

Returns your Keycap tasks of one stage, one page at a time. The path selects the stage. /prototype returns only prototype tasks, and /build returns only build tasks.

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

Path parameters

  • Name
    stage
    Type
    path
    Required
    Description

    The stage to list, prototype or build.

Query parameters

  • Name
    page_num
    Type
    integer
    default 1
    Description

    The page to return, from 1 to 255. 0 returns the first page. A value outside 0 to 255 returns 400.

  • Name
    page_size
    Type
    integer
    default 10
    Description

    Number of tasks per page, up to 100. A value from 101 to 255 returns 100 tasks, and 0 uses the default. A value outside 0 to 255 returns 400.

  • Name
    sort_by
    Type
    string
    default -created_at
    Description

    The sort order.

    Available values:

    • -created_at: newest first
    • +created_at: oldest first

Returns

An array of prototype task objects or build task objects, matching the stage in the path.


The Keycap prototype task object

A prototype task generates one image of the finished keycap from a photo. To turn the design into a 3D keycap, pass the task's ID to the build endpoint as input_task_id, with its candidate_ids entry as candidate_id.

Properties

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"
  ]
}
  • Name
    id
    Type
    string
    Description

    The task ID. Don't rely on its format.

  • Name
    type
    Type
    string
    Description

    The task type, always creative-lab-keycap-prototype.

  • Name
    name
    Type
    string
    Description

    The name you sent when you created the task, or "" if you didn't send one.

  • Name
    status
    Type
    string
    Description

    The task's status.

    Available values:

    • PENDING: waiting in the queue
    • IN_PROGRESS: generating the result
    • SUCCEEDED: final, with the result
    • FAILED: final, with the reason in task_error
    • CANCELED: final, with no result
  • Name
    progress
    Type
    integer
    Description

    Percentage of the work done, from 0 to 100. It's 0 until the task starts and 100 when it succeeds.

  • Name
    created_at
    Type
    timestamp
    Description

    Time the task was created, in Unix milliseconds.

  • Name
    started_at
    Type
    timestamp
    Description

    Time a worker started the task, in Unix milliseconds. null until then.

  • Name
    finished_at
    Type
    timestamp
    Description

    Time the task reached a final status, in Unix milliseconds. null until then.

  • Name
    expires_at
    Type
    timestamp
    Description

    Time the task's result expires, in Unix milliseconds. It's 3 days after finished_at, or 3 days after created_at until the task finishes. For Enterprise accounts, which keep API results indefinitely, it's about 100 years later. See Asset retention.

  • Name
    preceding_tasks
    Type
    integer
    Description

    The task's position in the queue while it's PENDING. 1 means it starts next. 0 after the task leaves the queue, and always 0 in list results.

  • Name
    task_error
    Type
    object
    Description

    Why the task failed. null unless status is FAILED. See Errors.

  • Name
    consumed_credits
    Type
    integer
    Description

    Number of credits charged for the task, after any refund. A request that returns an error isn't charged. A failed task is 0, because Meshy refunds it. Canceling refunds a task only while it's PENDING. If Meshy can't read the charge, a get or stream response leaves the field out, and a list request returns 500. In that case, retry the request.

  • Name
    image_urls
    Type
    array of strings
    Description

    Download URLs for the image of the finished keycap. The array has one URL after the task succeeds, and is empty until then. image_urls[i] shows the candidate in candidate_ids[i].

    These URLs are for display. The build endpoint takes an entry from candidate_ids, not a URL. They're signed URLs, so fetch them without an Authorization header. They stay valid until expires_at, and getting the task again returns the same URLs.

  • Name
    candidate_ids
    Type
    array of strings
    Description

    IDs of the candidates, in the same order as image_urls. To build a candidate, send its ID as candidate_id in the build request. Don't rely on the format of these IDs.


The Keycap build task object

A build task generates the textured 3D keycap from a succeeded prototype task and one of its candidates. It generates the 3D model, colors it, seats and cuts it onto the keycap base, and then assembles and exports the keycap.

Properties

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=***"
  }
}
  • Name
    id
    Type
    string
    Description

    The task ID. Don't rely on its format.

  • Name
    type
    Type
    string
    Description

    The task type, always creative-lab-keycap-build.

  • Name
    name
    Type
    string
    Description

    The name you sent when you created the task, or "" if you didn't send one.

  • Name
    status
    Type
    string
    Description

    The task's status.

    Available values:

    • PENDING: waiting in the queue
    • IN_PROGRESS: generating the result
    • SUCCEEDED: final, with the result
    • FAILED: final, with the reason in task_error
    • CANCELED: final, with no result
  • Name
    progress
    Type
    integer
    Description

    Percentage of the work done, from 0 to 100. It's 0 until the task starts and 100 when it succeeds.

  • Name
    created_at
    Type
    timestamp
    Description

    Time the task was created, in Unix milliseconds.

  • Name
    started_at
    Type
    timestamp
    Description

    Time a worker started the task, in Unix milliseconds. null until then.

  • Name
    finished_at
    Type
    timestamp
    Description

    Time the task reached a final status, in Unix milliseconds. null until then.

  • Name
    expires_at
    Type
    timestamp
    Description

    Time the task's result expires, in Unix milliseconds. It's 3 days after finished_at, or 3 days after created_at until the task finishes. For Enterprise accounts, which keep API results indefinitely, it's about 100 years later. See Asset retention.

  • Name
    preceding_tasks
    Type
    integer
    Description

    The task's position in the queue while it's PENDING. 1 means it starts next. 0 after the task leaves the queue, and always 0 in list results.

  • Name
    task_error
    Type
    object
    Description

    Why the task failed. null unless status is FAILED. See Errors.

  • Name
    consumed_credits
    Type
    integer
    Description

    Number of credits charged for the task, after any refund. A request that returns an error isn't charged. A failed task is 0, because Meshy refunds it. Canceling refunds a task only while it's PENDING. If Meshy can't read the charge, a get or stream response leaves the field out, and a list request returns 500. In that case, retry the request.

  • Name
    model_urls
    Type
    object
    Description

    Download URLs for the 3D model. Meshy exports the GLB file and the OBJ bundle at real-world scale in millimeters, Y-up, with the front of the keycap facing +Z.

    The model has meshes named keycap-head and keycap-base. When Meshy falls back to a pattern fill for the base, the model also has a keycap-base-interior mesh for the stem cavity. Don't assume the model has exactly two meshes.

    These are signed URLs, so fetch them without an Authorization header. They stay valid until expires_at, and getting the task again before then returns the same URLs. An expired URL can't be renewed, so download and store the files before then.

    • Name
      glb
      Type
      string
      Description

      Download URL for the textured GLB file, model.glb.

    • Name
      obj_zip
      Type
      string
      Description

      Download URL for the OBJ bundle, a ZIP file with model.obj, model.mtl, and the PNG textures that model.mtl uses. With a solid-color base, the only texture is keycap-head.png. With a patterned base, the ZIP file also has keycap-base.png.

  • Name
    process_image_urls
    Type
    object
    Description

    Download URLs for images from the build's steps, keyed by kind. Like model_urls, they're signed URLs, so fetch them without an Authorization header. They stay valid until expires_at, and getting the task again returns the same URLs.

    It can have these keys:

    • head_design: the design image of the chosen candidate, which the build turned into 3D. Always present.
    • composite: the image of the finished keycap for the chosen candidate, which the build painted the base from. Can be missing.
    • base_canvas: the painted keycap base. Can be missing.

    Meshy may add keys, so ignore a key you don't recognize.


End-to-end example

The example runs the whole flow:

  1. Create a prototype task from a photo.
  2. Get the task until its status is SUCCEEDED.
  3. Pick a candidate from candidate_ids.
  4. Create a build task with that candidate.
  5. Get the build task until its status is SUCCEEDED.
  6. Download the GLB file and the OBJ bundle from model_urls.

The example picks the first candidate. In your app, show the image_urls entries to your users and let them choose. A chosen image's index in image_urls is the index of its ID in 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"