Ubah foto sumber menjadi keycap keyboard mekanik kustom full-color dalam
dua tahap: prototype menghasilkan render desain "keycap jadi"
dari foto input Anda. Setelah Anda mengonfirmasi render tersebut, build mengubahnya menjadi
model keycap 3D bertekstur dalam satu kali proses — pembuatan white-model, pendudukan
dan pemotongan otomatis pada pose default yang terkalibrasi, pewarnaan model penuh, dan
perakitan akhir semuanya terjadi dalam satu tugas build. Kedua tahap ini terhubung
melalui input_task_id plus candidate_id.
POST /openapi/creative-lab/keycap/v1/prototype
POST /openapi/creative-lab/keycap/v1/build
Kedua endpoint POST memerlukan paket langganan berbayar. Permintaan dari
akun paket gratis akan ditolak dengan 402 Payment Required.
Menghasilkan render desain keycap jadi dari foto sumber. Hasil task
membawa array image_urls (render tampilan dari keycap jadi) dan array
candidate_ids yang sejajar; keduanya masing-masing berisi satu entri.
Panggil endpoint ini lagi untuk mendapatkan render lain jika hasilnya bukan
yang Anda inginkan — setiap panggilan ditagih secara terpisah. Kirimkan
candidate_id bersama dengan ID task prototipe ke
build endpoint.
Lihat
The Keycap Prototype Task Object
untuk bentuk responsnya.
Parameter
Name
image_url
Type
string
Wajib
Description
Foto sumber yang akan diubah Meshy menjadi gambar desain keycap. Kami saat ini mendukung format .jpg, .jpeg, .png, dan .webp.
Format dideteksi dengan mendekode data gambar, bukan dari ekstensi file pada URL — URL tanpa ekstensi, atau yang melakukan redirect, tetap berfungsi selama byte-nya terdekode menjadi format yang didukung. HTTP redirect akan diikuti. Orientasi EXIF dinormalisasi, sehingga foto ponsel yang berputar digunakan sesuai tampilannya.
Batasan: minimal 32 piksel pada setiap sisi, maksimal 178.956.970 piksel secara total, dan maksimal 20.000.000 byte setelah diunduh. Untuk data URI, batasan berlaku pada byte yang sudah didekode, sehingga file sumber itu sendiri boleh mencapai ukuran tersebut — teks base64-nya sekitar sepertiga lebih besar, yang berpengaruh pada isi permintaan Anda, bukan pada batasan ini. Data URI harus mendeklarasikan tipe konten image/* dan ;base64.
Ada dua cara untuk menyediakan gambar:
URL yang dapat diakses publik: URL yang dapat diakses dari internet publik.
Data URI: Data URI gambar yang dikodekan base64. Contoh data URI: data:image/jpeg;base64,<data gambar Anda yang dikodekan base64>.
Name
name
Type
string
Description
Nama task opsional untuk keperluan tampilan. Maksimal 100 karakter.
Name
remove_background
Type
boolean
default false
Description
Jika diatur ke true, render tampilan yang dikembalikan dalam image_urls berupa PNG RGBA transparan dengan latar belakang dihapus, sehingga Anda dapat menggabungkannya dengan latar belakang apa pun.
Ini hanya berlaku untuk render tampilan. Kandidat yang dikonsumsi oleh build endpoint tidak terpengaruh, sehingga hasil 3D-nya identik dalam kedua kasus.
Returns
Properti result pada respons berisi id task dari task prototipe keycap yang baru dibuat. Lakukan polling pada endpoint Get a Task atau berlangganan stream hingga task mencapai SUCCEEDED, lalu ambil entri dari candidate_ids dan kirimkan bersama ID task ke build endpoint.
Mode Kegagalan
Name
400 - Bad Request
Description
Permintaan tidak dapat diterima. Penyebab umum:
Parameter hilang: image_url wajib diisi.
Format gambar tidak valid: image_url yang diberikan bukan format yang didukung (.jpg, .jpeg, .png, .webp).
Dimensi gambar di luar rentang: Gambar terlalu kecil, melebihi ukuran file maksimum, atau melebihi jumlah piksel maksimum.
URL tidak dapat dijangkau: image_url tidak dapat diunduh (404 atau timeout).
Data URI tidak valid: String base64 tidak valid formatnya.
Konten ditandai: Gambar input ditandai oleh moderation NSFW.
Name
401 - Unauthorized
Description
Autentikasi gagal. Silakan periksa kunci API Anda.
Name
402 - Payment Required
Description
Akun menggunakan paket gratis (paket berbayar diperlukan untuk membuat task) atau kreditnya tidak mencukupi.
Name
403 - Forbidden
Description
Gambar input ditandai oleh moderation kekayaan intelektual.
Name
429 - Too Many Requests
Description
Anda telah melampaui batas laju Anda.
Name
500 - Internal Server Error
Description
Terjadi kesalahan tak terduga di sisi server — misalnya layanan moderation konten tidak tersedia, penyiapan gambar input gagal, atau task tidak dapat dibuat. Dalam kasus ini tidak ada task yang dibuat, sehingga aman untuk mencoba lagi.
Request
POST
/openapi/creative-lab/keycap/v1/prototype
# Stage 1: generate a finished-keycap design rendercurlhttps://api.meshy.ai/openapi/creative-lab/keycap/v1/prototype \-XPOST \-H"Authorization: Bearer ${YOUR_API_KEY}" \-H'Content-Type: application/json' \-d'{ "image_url": "<your publicly accessible image url or base64-encoded data URI>" }'
Menghasilkan model keycap 3D bertekstur final dari task prototipe yang berhasil dan salah satu kandidatnya. Satu task build menjalankan seluruh pipeline dari awal hingga akhir — pembuatan model putih (white-model) dari desain yang dipilih, penempatan dan pemotongan otomatis ke dasar keycap menggunakan pose default yang telah dikalibrasi (tanpa perlu penyesuaian interaktif), pewarnaan model penuh, serta perakitan dan ekspor akhir. Sebuah build biasanya memakan waktu 3–7 menit, mendekati batas atas ketika beberapa build berjalan bersamaan.
Lihat The Keycap Build Task Object
untuk bentuk responsnya.
Parameter
Name
input_task_id
Type
string
Wajib
Description
ID task dari task prototipe yang dibuat melalui endpoint OpenAPI yang sama ini. Prototipe tersebut harus dibuat oleh akun Meshy yang sama, harus telah mencapai status SUCCEEDED, dan harus telah menghasilkan setidaknya satu kandidat.
Task prototipe yang dibuat melalui webapp tidak diterima — endpoint build hanya menerima task prototipe yang dihasilkan oleh POST /openapi/creative-lab/keycap/v1/prototype dan menolak sumber lain mana pun dengan 404.
Name
candidate_id
Type
string
Wajib
Description
Kandidat yang akan dibangun, diambil dari array candidate_ids milik task prototipe yang berhasil. Harus termasuk dalam task tersebut; nilai lain mana pun akan ditolak dengan 400.
Name
name
Type
string
Description
Nama task opsional untuk keperluan tampilan. Maksimum 100 karakter.
options
Penyesuaian geometri opsional. Setiap field memiliki nilai default yang telah dikalibrasi — kirim hanya yang ingin Anda timpa.
Name
base_model
Type
string
default cherry-mx-1x1-r1
Description
Dasar keycap yang akan digunakan untuk build. Saat ini satu-satunya nilai yang tersedia adalah
cherry-mx-1x1-r1 — keycap 1u profil Cherry MX standar. 3–5
ukuran standar mainstream tambahan sedang direncanakan; ukuran kustom
tidak didukung.
Name
head_size_mm
Type
number
default 23
Description
Ukuran target dari kepala yang dipahat, dalam milimeter: dimensi terpanjangnya diskalakan ke nilai ini. Rentang: [10, 40]. Nilai di atas kira-kira 32.9 dapat dikurangi agar kepala tetap sesuai dengan batas jejak (footprint) pelindung dasar, sehingga dimensi terpanjang yang dihasilkan bisa lebih kecil dari yang diminta. Nilai yang diterapkan belum ditampilkan kembali pada objek task saat ini — jika Anda perlu memastikan ukuran yang sebenarnya Anda terima, ukur kotak pembatas mesh keycap-head pada model yang diunduh.
Name
vertical_offset_mm
Type
number
default 0
Description
Offset vertikal yang diterapkan pada kepala sebelum ditempatkan pada dasar, dalam milimeter. Rentang: [-5, 5].
Returns
Properti result pada respons berisi id task dari task build keycap yang baru dibuat. Poll endpoint Get a Task atau berlangganan ke stream hingga task mencapai SUCCEEDED, lalu unduh artifak dari model_urls.glb dan model_urls.obj_zip.
Baik GLB maupun bundel OBJ diekspor pada skala milimeter
dunia nyata, dengan sistem koordinat Y-up dan bagian depan
keycap menghadap +Z.
Failure Modes
Name
400 - Bad Request
Description
Permintaan tidak dapat diterima. Penyebab umum:
Parameter hilang: input_task_id dan candidate_id wajib diisi.
UUID tidak valid: input_task_id bukan UUID yang valid.
Induk belum berhasil: Task prototipe yang dirujuk belum mencapai SUCCEEDED.
Tidak ada kandidat: Task prototipe berhasil tetapi tidak menghasilkan kandidat apa pun.
Kandidat tidak dikenal: candidate_id bukan salah satu kandidat dari task input.
Options di luar rentang: Salah satu field options berada di luar rentang yang diizinkan atau di luar kumpulan enum-nya.
Name
401 - Unauthorized
Description
Autentikasi gagal. Silakan periksa kunci API Anda.
Name
402 - Payment Required
Description
Akun berada pada paket gratis (diperlukan paket berbayar untuk membuat task) atau kredit tidak mencukupi.
Name
404 - Not Found
Description
Task prototipe yang dirujuk tidak ada, dimiliki oleh pengguna lain, atau dibuat melalui webapp (hanya task prototipe mode API yang dapat dilanjutkan ke build).
Name
429 - Too Many Requests
Description
Anda telah melampaui batas laju Anda.
Name
500 - Internal Server Error
Description
Terjadi kesalahan sisi server yang tidak terduga — misalnya layanan moderation konten tidak tersedia, penyiapan (staging) gambar input gagal, atau task tidak dapat dibuat. Dalam kasus ini tidak ada task yang dibuat, sehingga mencoba ulang aman dilakukan.
Request
POST
/openapi/creative-lab/keycap/v1/build
# Stage 2: build the chosen candidate into a 3D keycapcurlhttps://api.meshy.ai/openapi/creative-lab/keycap/v1/build \-XPOST \-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 } }'
Mengambil sebuah tugas prototype atau build berdasarkan id tugas yang valid. Path URL
harus sesuai dengan tahap tugas tersebut — sebuah tugas build yang diambil melalui
/prototype/:id akan mengembalikan 404, begitu pula sebaliknya.
Batalkan tugas keycap. Jika tugas masih PENDING, kredit
yang digunakan pada saat pembuatan akan dikembalikan. Tugas yang sudah
IN_PROGRESS akan dibatalkan tanpa pengembalian dana (worker mungkin sudah
menggunakan sumber daya). Tugas yang sudah mencapai status akhir
(SUCCEEDED, FAILED, CANCELED) tidak dapat dibatalkan.
Jalur URL harus sesuai dengan tahap tugas — DELETE pada
/prototype/:buildId mengembalikan 404.
Parameter Jalur
Name
id
Type
path
Description
Pengenal unik untuk tugas keycap yang akan dibatalkan.
Nilai Kembalian
Mengembalikan 204 No Content jika berhasil dengan isi yang kosong.
Mode Kegagalan
Name
400 - Bad Request
Description
Tugas sudah berada pada status akhir dan tidak dapat dibatalkan.
Name
404 - Not Found
Description
Tugas tidak ada, milik pengguna lain, atau tahapnya tidak sesuai dengan jalur URL.
Name
500 - Internal Server Error
Description
Terjadi kesalahan tak terduga di sisi server saat membatalkan. Tugas mungkin sudah atau belum dibatalkan — baca ulang untuk memastikan sebelum mencoba lagi.
Melakukan streaming pembaruan secara real-time untuk task keycap melalui Server-Sent Events (SSE).
Path URL harus sesuai dengan tahap task tersebut — membuka stream di
/prototype/:buildId/stream akan mengirimkan satu event: error payload dengan
status_code: 404 dan menutup stream tersebut.
Parameter
Name
id
Type
path
Description
Pengenal unik untuk task keycap yang akan di-stream.
Returns
Mengembalikan stream objek task Keycap Prototype
atau Keycap Build sebagai
Server-Sent Events. Setiap frame membawa objek task lengkap untuk tahap tersebut — bentuk yang sama seperti yang
dikembalikan oleh endpoint Get — sehingga selama task berstatus PENDING atau IN_PROGRESS
kolom output belum terisi (null, [] atau {}) dan
finished_at bernilai null.
// Error event example (wrong stage or task not found)event: errordata: {"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 PENDING frame below is abbreviated to the fields that change.event: messagedata: {"id": "019c9a52-7d18-7e2b-9f01-6e3b98c4d2af","progress": 0,"status": "PENDING"}event: messagedata: {"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=***" }}
Mengambil daftar tugas keycap Anda yang dipaginasi untuk satu tahap. Path
URL memilih tahap tersebut — /prototype mengembalikan tugas prototype;
/build mengembalikan tugas build. Tugas dari tahap lainnya tidak
disertakan pada respons keduanya.
Path Parameters
Name
stage
Type
path
Wajib
Description
Baik prototype maupun build. Koleksi ini hanya mengembalikan tugas
yang tahapnya cocok dengan URL — mengambil /prototype tidak pernah
mengembalikan tugas build, begitu juga sebaliknya.
Query Parameters
Name
page_num
Type
integer
default 1
Description
Nomor halaman untuk paginasi.
Name
page_size
Type
integer
default 10
Description
Batas ukuran halaman. Maksimum yang diizinkan adalah 100 item.
Name
sort_by
Type
string
default -created_at
Description
Field untuk mengurutkan. Nilai yang tersedia:
+created_at: Urutkan berdasarkan waktu pembuatan secara menaik.
-created_at: Urutkan berdasarkan waktu pembuatan secara menurun.
Objek Keycap Prototype Task adalah unit kerja yang dilacak oleh Meshy untuk
menghasilkan satu gambar render desain keycap yang sudah jadi dari foto sumber. Output
dari tahap ini dirangkai ke tahap build
melalui input_task_id beserta candidate_id.
Properti
Name
id
Type
string
Description
Pengenal unik untuk task. Meskipun kami menggunakan UUID yang dapat diurutkan secara k (k-sortable) untuk id task sebagai detail implementasi, Anda tidak boleh membuat asumsi apa pun tentang format id tersebut.
Name
type
Type
string
Description
Tipe dari task. Nilainya adalah creative-lab-keycap-prototype.
Name
name
Type
string
Description
Nama task yang diberikan saat task dibuat. String kosong jika tidak ada nama yang diberikan.
Name
status
Type
string
Description
Status dari task. Nilai yang mungkin adalah salah satu dari PENDING, IN_PROGRESS, SUCCEEDED, FAILED, CANCELED.
Name
progress
Type
integer
Description
Progress dari task. Jika task belum dimulai, properti ini akan bernilai 0. Setelah task berhasil, nilai ini akan menjadi 100.
Name
created_at
Type
timestamp
Description
Stempel waktu saat task dibuat, dalam milidetik.
Stempel waktu merepresentasikan jumlah milidetik yang telah berlalu sejak 1 Januari 1970 UTC, mengikuti
standar RFC 3339.
Sebagai contoh, Jumat, 1 September 2023 pukul 12:00:00 GMT direpresentasikan sebagai 1693569600000. Hal ini berlaku
untuk semua stempel waktu di Meshy API.
Name
started_at
Type
timestamp
Description
Stempel waktu saat task dimulai, dalam milidetik. Jika task belum dimulai, properti ini akan bernilai 0.
Name
finished_at
Type
timestamp
Description
Stempel waktu saat task selesai, dalam milidetik. Jika task belum selesai, properti ini akan bernilai 0.
Name
expires_at
Type
timestamp
Description
Stempel waktu saat hasil task kedaluwarsa, dalam milidetik.
Name
preceding_tasks
Type
integer
Description
Jumlah task yang mendahului.
Nilai bidang ini hanya bermakna jika status task adalah PENDING.
Name
task_error
Type
object
Description
Detail kesalahan untuk task yang gagal. Lihat Kesalahan untuk referensi lengkap objek task_error.
Name
consumed_credits
Type
integer
Description
Jumlah kredit yang dikonsumsi oleh task ini. Task yang mencapai SUCCEEDED akan dikenakan biaya penuh untuk tahapnya. Task yang tidak pernah berhasil dibuat (sebuah 4xx pada saat permintaan, termasuk penolakan moderation) tidak dikenakan biaya sama sekali. Task yang mencapai FAILED mengembalikan 0 — biaya dikembalikan (refund), termasuk pemblokiran moderation asinkron. Membatalkan melalui DELETE hanya mengembalikan biaya selama task masih PENDING; task yang sudah IN_PROGRESS tetap dikenakan biaya, karena pekerjaannya sudah dilakukan.
Name
image_urls
Type
array of strings
Description
URL yang dapat diunduh dari render desain keycap yang sudah jadi — seperti apa tampilan kandidat sebagai keycap jadi. Menyimpan satu entri; image_urls[i] berkorespondensi dengan candidate_ids[i]. Kosong hingga task mencapai SUCCEEDED. URL ini hanya untuk tampilan; endpoint build mengonsumsi candidate_ids, bukan URL ini. Siklus hidup URL sama seperti model_urls: bertanda tangan (signed), tanpa header Authorization, berlaku hingga expires_at, dan stabil saat task dibaca ulang.
Name
candidate_ids
Type
array of strings
Description
Pengenal kandidat yang bersifat opak, sejajar dengan image_urls. Kirimkan entri yang sesuai dengan desain pilihan Anda sebagai candidate_id pada permintaan build. Jangan membuat asumsi apa pun tentang format id ini.
Objek Keycap Build Task adalah unit kerja yang dilacak oleh Meshy untuk
menghasilkan keycap 3D bertekstur final dari sebuah prototype task yang berhasil dan
kandidat yang dipilih. Satu build menjalankan seluruh pipeline — pembuatan
white-model, seating dan cutting otomatis, pewarnaan, perakitan, dan ekspor.
Properti
Name
id
Type
string
Description
Pengenal unik untuk task ini.
Name
type
Type
string
Description
Jenis task. Nilainya adalah creative-lab-keycap-build.
Name
name
Type
string
Description
Nama task yang diberikan saat task dibuat. String kosong jika tidak ada nama yang diberikan.
Name
status
Type
string
Description
Status task. Kemungkinan nilainya adalah salah satu dari PENDING, IN_PROGRESS, SUCCEEDED, FAILED, CANCELED.
Name
progress
Type
integer
Description
Progress task. Jika task belum dimulai, properti ini akan bernilai 0. Setelah task berhasil, ini akan menjadi 100.
Name
created_at
Type
timestamp
Description
Stempel waktu saat task dibuat, dalam milidetik.
Name
started_at
Type
timestamp
Description
Stempel waktu saat task dimulai, dalam milidetik.
Name
finished_at
Type
timestamp
Description
Stempel waktu saat task selesai, dalam milidetik.
Name
expires_at
Type
timestamp
Description
Stempel waktu saat hasil task kedaluwarsa, dalam milidetik.
Name
preceding_tasks
Type
integer
Description
Jumlah task yang mendahului. Hanya bermakna ketika status adalah PENDING.
Name
task_error
Type
object
Description
Detail kesalahan untuk task yang gagal. Lihat Kesalahan untuk referensi lengkap objek task_error.
Name
consumed_credits
Type
integer
Description
Jumlah kredit yang dikonsumsi oleh task ini. Task yang mencapai SUCCEEDED akan dikenakan biaya penuh untuk tahapannya. Task yang tidak pernah dibuat (4xx pada saat request, termasuk penolakan moderation) sama sekali tidak dikenakan biaya. Task yang mencapai FAILED mengembalikan 0 — biaya dikembalikan, termasuk blokir moderation asinkron. Membatalkan melalui DELETE hanya mengembalikan biaya selama task masih PENDING; task yang sudah IN_PROGRESS tetap dikenakan biaya, karena pekerjaannya telah digunakan.
Name
model_urls
Type
object
Description
URL yang dapat diunduh untuk artefak model yang dihasilkan. Baik bundel GLB maupun OBJ diekspor pada skala milimeter dunia nyata, Y-up, dengan bagian depan keycap menghadap +Z. Mesh diberi nama keycap-head dan keycap-base; ketika base beralih ke pola isian (pattern fill), mesh ketiga keycap-base-interior juga tersedia untuk rongga stem. Jangan berasumsi bahwa hanya ada tepat dua mesh.
Ini adalah URL yang ditandatangani (signed): ambil tanpa header Authorization. URL ini tetap valid hingga expires_at, yaitu 3 hari setelah finished_at, dan membaca ulang task dalam rentang waktu tersebut akan mengembalikan URL yang identik, bukan URL yang baru ditandatangani. Unduh dan simpan file tersebut sendiri sebelum waktu itu — tidak ada cara untuk memperbarui tautan yang sudah kedaluwarsa.
Name
glb
Type
string
Description
URL yang dapat diunduh untuk model.glb bertekstur final.
Name
obj_zip
Type
string
Description
URL yang dapat diunduh untuk bundel zip yang berisi model.obj, model.mtl, dan PNG tekstur yang benar-benar direferensikan oleh MTL-nya. Base berwarna solid hanya menyertakan keycap-head.png; base berpola juga menyertakan keycap-base.png.
Name
process_image_urls
Type
object
Description
URL yang dapat diunduh untuk gambar proses perantara, dikelompokkan berdasarkan jenisnya (kind). Siklus hidup URL sama seperti model_urls: ditandatangani (signed), tanpa header Authorization, valid hingga expires_at, dan stabil ketika task dibaca ulang. Jenis yang saat ini dihasilkan:
head_design — gambar desain kandidat terpilih yang digunakan oleh build (selalu ada).
composite — render tampilan keycap jadi dari kandidat terpilih (ada jika tersedia).
base_canvas — kanvas base keycap yang telah dicat (ada jika tersedia).
Perlakukan kumpulan key ini sebagai terbuka (open-ended); jenis baru dapat ditambahkan tanpa perubahan yang bersifat breaking change.
Alur lengkapnya: membuat prototipe dari sebuah foto, melakukan polling hingga
SUCCEEDED, memilih kandidat dari candidate_ids, membuat build dengan
kandidat tersebut, melakukan polling pada build hingga SUCCEEDED, lalu mengunduh GLB dan
bundel OBJ dari model_urls.
Contoh ini memilih kandidat pertama secara terprogram. Dalam integrasi
yang sesungguhnya, Anda akan menampilkan entri image_urls kepada pengguna akhir dan
membiarkan mereka memilih; indeks yang dipilih dipetakan 1:1 ke candidate_ids.
Complete flow
POST
/openapi/creative-lab/keycap/v1
#!/usr/bin/env bashset-euopipefail# 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:-}" ]]; thenecho"export IMAGE_PATH (local file) or IMAGE_URL (public url) first">&2exit1fiBASE="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 bodyshift2 out=$(curl--silent--show-error--max-time60--write-out$'\n%{http_code}' \-X"$method""$url"-H"$AUTH""$@") ||return1 http_code=${out##*$'\n'} body=${out%$'\n'*}if ((http_code >=400)); thenecho"HTTP $http_code for $url: $body">&2return1fiprintf'%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:; doif (($(date +%s) >= deadline)); thenecho"gave up waiting for $kind $id">&2return1fi task_status=$(apiGET"$BASE/$kind/$id"|jq-r'.status')echo"$kind: $task_status"case"$task_status"inSUCCEEDED)return0 ;;FAILED|CANCELED)return1 ;;esacsleep"$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"'EXITif [[ -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')"inpng) 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"elseprintf'{"image_url":"%s"}'"$IMAGE_URL">"$BODY"fi# 1. Create the prototype taskPROTO_ID=$(apiPOST"$BASE/prototype" \-H'Content-Type: application/json'--data-binary@"$BODY"|jq-r'.result')# 2. Wait for the design renderpollprototype"$PROTO_ID"# 3. Pick a candidate (first one here; show image_urls to a user in production)CANDIDATE_ID=$(apiGET"$BASE/prototype/$PROTO_ID"|jq-r'.candidate_ids[0]')# 4. Create the build taskjq-n--argp"$PROTO_ID"--argc"$CANDIDATE_ID" \'{input_task_id: $p, candidate_id: $c}'>"$BODY"BUILD_ID=$(apiPOST"$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)pollbuild"$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=$(apiGET"$BASE/build/$BUILD_ID")curl--silent--show-error--fail--max-time900 \-okeycap.glb"$(jq-r '.model_urls.glb' <<<"$TASK")"curl--silent--show-error--fail--max-time900 \-okeycap-obj.zip"$(jq-r '.model_urls.obj_zip' <<<"$TASK")"echo"Done: keycap.glb + keycap-obj.zip"