Перетворіть вихідну фотографію на повнокольоровий кастомний ковпачок клавіші
механічної клавіатури у два етапи: prototype генерує рендер дизайну
"готового ковпачка" з вашого вхідного фото. Після того, як ви підтвердили
цей рендер, build перетворює його на текстуровану 3D-модель ковпачка
клавіші за один запуск — генерація білої моделі, автоматичне посадження та
обрізка за каліброваною позою за замовчуванням, повне розфарбування моделі
та фінальне складання — усе це відбувається в межах одного завдання
побудови. Два етапи пов'язані через input_task_id разом із candidate_id.
POST /openapi/creative-lab/keycap/v1/prototype
POST /openapi/creative-lab/keycap/v1/build
Обидва POST endpoints вимагають платного плану підписки. Запити від
акаунтів на безкоштовному плані відхиляються з кодом 402 Payment Required.
Створює рендер готового дизайну кейкапа на основі вихідного фото. Результат
завдання містить масив image_urls (відображуваний рендер готового
кейкапа) та паралельний масив candidate_ids; обидва містять по одному
елементу. Викличте цю кінцеву точку ще раз, щоб отримати інший рендер,
якщо результат вас не влаштовує — кожен виклик оплачується окремо.
Передайте candidate_id разом з ID завдання прототипу до
endpoint для build.
Дивіться
Об'єкт завдання прототипу кейкапа
щодо структури відповіді.
Параметри
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-текст приблизно на третину більший, що має значення для тіла вашого запиту, але не для цього обмеження. Data URI має вказувати тип вмісту image/* та ;base64.
Є два способи надати зображення:
Загальнодоступний URL: URL, доступний з публічного інтернету.
Data URI: зображення, закодоване в base64 як data URI. Приклад data URI: data:image/jpeg;base64,<ваші base64-закодовані дані зображення>.
Name
name
Type
string
Description
Необов'язкова назва завдання для відображення. Максимум 100 символів.
Name
remove_background
Type
boolean
за замовчуванням false
Description
Якщо встановлено значення true, відображуваний рендер, повернутий у image_urls, є прозорим RGBA PNG з видаленим фоном, щоб ви могли накласти його на будь-який фон.
Це стосується лише відображуваного рендера. Кандидат, який використовує endpoint для build, залишається незмінним, тому 3D-результат буде однаковим в обох випадках.
Повертає
Властивість result відповіді містить id завдання новоствореного прототипу кейкапа. Опитуйте endpoint Отримати завдання або підпишіться на потік, доки завдання не досягне статусу SUCCEEDED, після чого візьміть елемент з candidate_ids і передайте його разом з ID завдання до endpoint для build.
Розміри зображення поза межами діапазону: зображення занадто мале, перевищує максимальний розмір файлу або максимальну кількість пікселів.
Недоступний URL: не вдалося завантажити image_url (404 або timeout).
Недійсний Data URI: рядок base64 некоректний.
Контент позначено: вхідне зображення було позначено системою moderation NSFW-контенту.
Name
401 - Unauthorized
Description
Помилка автентифікації. Перевірте свій API-ключ.
Name
402 - Payment Required
Description
Обліковий запис перебуває на безкоштовному тарифі (для створення завдань потрібен платний тариф) або має недостатньо кредитів.
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 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>" }'
Згенеруйте фінальну текстуровану 3D-модель ковпачка клавіші з успішного завдання-прототипу та одного з його кандидатів. Одне завдання на побудову виконує весь конвеєр від початку до кінця — генерацію білої моделі з обраного дизайну, автоматичне позиціонування та обрізання на основі ковпачка з використанням каліброваної позиції за замовчуванням (без потреби в інтерактивному налаштуванні), розфарбовування повної моделі, а також фінальне складання та експорт. Побудова зазвичай триває 3–7 хвилин, ближче до верхньої межі, коли одночасно виконується кілька побудов.
Зверніться до розділу Об'єкт завдання на побудову Keycap
для опису форми відповіді.
Параметри
Name
input_task_id
Type
string
Обов'язковий
Description
Ідентифікатор завдання-прототипу, створеного через цю саму кінцеву точку OpenAPI. Прототип має бути створений тим самим обліковим записом Meshy, повинен досягти статусу SUCCEEDED і має мати щонайменше одного кандидата.
Завдання-прототипи, створені через веб-застосунок, не приймаються — кінцева точка build приймає лише завдання-прототипи, створені через POST /openapi/creative-lab/keycap/v1/prototype, і відхиляє будь-яке інше джерело з кодом 404.
Name
candidate_id
Type
string
Обов'язковий
Description
Кандидат, який потрібно побудувати, взятий з масиву 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 profile 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 відповіді містить id завдання новоствореного завдання на побудову ковпачка клавіші. Опитуйте кінцеву точку Отримання завдання або підпишіться на потік, доки завдання не досягне статусу SUCCEEDED, а потім завантажте артефакти з model_urls.glb та model_urls.obj_zip.
Як GLB, так і пакет OBJ експортуються в реальному масштабі
в міліметрах, із системою координат Y-up та лицьовою стороною
ковпачка клавіші, спрямованою у бік +Z.
Режими збою
Name
400 - Bad Request
Description
Запит неприйнятний. Поширені причини:
Відсутній параметр: input_task_id та candidate_id є обов'язковими.
Недійсний UUID: input_task_id не є дійсним UUID.
Батьківське завдання не завершено успішно: вказане завдання-прототип ще не досягло статусу SUCCEEDED.
Немає кандидатів: завдання-прототип завершилось успішно, але не створило жодного кандидата.
Невідомий кандидат: candidate_id не є одним із кандидатів вхідного завдання.
Параметри поза допустимим діапазоном: одне з полів options вийшло за межі дозволеного діапазону або набору значень enum.
Name
401 - Unauthorized
Description
Помилка автентифікації. Будь ласка, перевірте свій API-ключ.
Name
402 - Payment Required
Description
Обліковий запис на безкоштовному плані (для створення завдань потрібен платний план) або має недостатньо кредитів.
Name
404 - Not Found
Description
Вказане завдання-прототип не існує, належить іншому користувачу або було створене через веб-застосунок (тільки завдання-прототипи, створені у режимі API, можуть переходити до побудови).
Name
429 - Too Many Requests
Description
Ви перевищили обмеження частоти.
Name
500 - Internal Server Error
Description
Сталася неочікувана помилка на боці сервера — наприклад, служба модерації контенту була недоступна, не вдалося підготувати вхідне зображення, або завдання не вдалося створити. У цьому випадку завдання не створюється, тому повторити спробу безпечно.
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 } }'
Отримати завдання прототипу або збірки за дійсним id завдання. Шлях URL
має відповідати етапу завдання — якщо завдання збірки запитати через
/prototype/:id, буде повернено 404, і навпаки.
Скасувати завдання keycap. Якщо завдання ще перебуває у стані PENDING,
кредити, витрачені під час створення, повертаються. Завдання, що вже мають статус
IN_PROGRESS, скасовуються без повернення коштів (обробник може вже
витрачати ресурси). Завдання, що вже досягли термінального стану
(SUCCEEDED, FAILED, CANCELED), скасувати не можна.
Шлях URL повинен відповідати етапу завдання — виклик DELETE за адресою
/prototype/:buildId поверне 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
Під час скасування сталася непередбачена помилка на стороні сервера. Завдання могло або не могло бути скасовано — перечитайте його, щоб переконатися, перш ніж повторювати спробу.
Транслює оновлення в реальному часі для завдання keycap через Server-Sent Events (SSE).
Шлях URL повинен відповідати етапу завдання — відкриття потоку за адресою
/prototype/:buildId/stream видає одне корисне навантаження event: error зі
значенням status_code: 404 і закриває потік.
Параметри
Name
id
Type
path
Description
Унікальний ідентифікатор завдання keycap для стрімінгу.
Повертає
Повертає потік об'єктів завдання Keycap Prototype
або Keycap Build у вигляді
Server-Sent Events. Кожен кадр містить повний об'єкт завдання для відповідного етапу — та сама форма, яку
повертає кінцева точка Get — тому поки завдання перебуває у стані PENDING або IN_PROGRESS,
вихідні поля просто ще не заповнені (null, [] або {}), а
finished_at дорівнює 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=***" }}
Отримайте список ваших завдань з клавішами (keycap) для одного етапу з пагінацією. Шлях URL
визначає етап — /prototype повертає завдання прототипу; /build
повертає завдання збірки. Завдання з іншого етапу не включаються в жодну з
відповідей.
Параметри шляху
Name
stage
Type
path
Обов'язковий
Description
Або prototype, або build. Колекція повертає лише завдання,
етап яких відповідає URL — запит до /prototype ніколи не поверне
завдання збірки, і навпаки.
Параметри запиту
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: Сортування за часом створення в порядку спадання.
Об'єкт Keycap Prototype Task — це одиниця роботи, яку Meshy відстежує для
створення одного зображення дизайну готового ковпачка клавіші з вихідної фотографії. Результат
цього етапу передається на етап побудови
за допомогою input_task_id разом із candidate_id.
Властивості
Name
id
Type
string
Description
Унікальний ідентифікатор завдання. Хоча як деталь реалізації ми використовуємо для ідентифікаторів завдань k-сортований UUID, вам не слід робити жодних припущень щодо формату цього 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
Progress завдання. Якщо завдання ще не почалося, ця властивість матиме значення 0. Коли завдання успішно завершиться, воно стане 100.
Name
created_at
Type
timestamp
Description
Мітка часу створення завдання, у мілісекундах.
Мітка часу представляє кількість мілісекунд, що минули з 1 січня 1970 UTC, відповідно до
стандарту RFC 3339. Наприклад, п'ятниця, 1 вересня 2023 12:00:00 за Гринвічем представлена як 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.
Name
consumed_credits
Type
integer
Description
Кількість кредитів, витрачених на це завдання. Завдання, що досягає статусу 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-адреса призначена лише для відображення; кінцева точка побудови використовує candidate_ids, а не ці URL-адреси. Той самий життєвий цикл URL-адреси, що й у model_urls: підписана, без заголовка Authorization, дійсна до expires_at, і стабільна при повторному читанні завдання.
Name
candidate_ids
Type
array of strings
Description
Непрозорі ідентифікатори кандидатів, паралельні до image_urls. Передайте елемент, що відповідає обраному вами дизайну, як candidate_id у запиті на побудову. Не робіть жодних припущень щодо формату цих ідентифікаторів.
Об'єкт задачі побудови Keycap — це робоча одиниця, яку Meshy відстежує для
генерації фінальної текстурованої 3D-клавіші (keycap) з успішно виконаної
прототипної задачі та обраного кандидата. Одна побудова виконує повний
конвеєр — генерацію білої моделі, автоматичне посадження та обрізку,
розфарбовування, збірку та експорт.
Властивості
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.
Name
consumed_credits
Type
integer
Description
Кількість кредитів, витрачених на цю задачу. Задача, що досягла статусу SUCCEEDED, стягується повною сумою за свій етап. Задача, яка так і не була створена (4xx під час запиту, включно з відхиленням модерацією), не стягується взагалі. Задача, що досягла статусу FAILED, повертає 0 — плата повертається, включно з випадком асинхронного блокування модерацією. Скасування через DELETE повертає кошти лише поки задача ще перебуває у статусі PENDING; задача, яка вже перебуває у статусі IN_PROGRESS, залишається оплаченою, оскільки робота вже виконана.
Name
model_urls
Type
object
Description
URL-адреси для завантаження згенерованих артефактів моделі. Як GLB, так і пакет OBJ експортуються у реальному масштабі в міліметрах, з орієнтацією Y-up, при цьому передня частина клавіші звернена до +Z. Сітки (meshes) названі keycap-head та keycap-base; коли основа переходить до заповнення за шаблоном (pattern fill), також присутня третя сітка keycap-base-interior для порожнини під стрижень. Не припускайте, що завжди буде рівно дві сітки.
Це підписані URL-адреси: отримуйте їх без заголовка Authorization. Вони залишаються дійсними до expires_at, що становить 3 дні після finished_at, і повторне читання задачі протягом цього періоду повертає ідентичний URL, а не заново підписаний. Завантажте та збережіть файли самостійно до цього моменту — способу оновити застарілу URL-адресу не існує.
Name
glb
Type
string
Description
URL-адреса для завантаження фінального текстурованого файлу model.glb.
Name
obj_zip
Type
string
Description
URL-адреса для завантаження zip-пакету, що містить model.obj, model.mtl та PNG-текстури, на які фактично посилається його MTL. Однотонна (solid-color) основа постачається лише з keycap-head.png; основа з візерунком (patterned) також постачається з keycap-base.png.
Name
process_image_urls
Type
object
Description
URL-адреси для завантаження проміжних зображень процесу, згруповані за типом. Той самий життєвий цикл URL, що й у model_urls: підписані, без заголовка Authorization, дійсні до expires_at, і стабільні при повторному читанні задачі. Наразі формуються такі типи:
head_design — зображення дизайну обраного кандидата, яке було використано побудовою (завжди присутнє).
composite — фінальне відображення готової клавіші (keycap) для обраного кандидата (присутнє, коли доступне).
base_canvas — розфарбоване полотно основи клавіші (присутнє, коли доступне).
Вважайте набір ключів відкритим; нові типи можуть додаватися без критичних змін.
Повний потік: створення прототипу з фотографії, опитування його стану до
SUCCEEDED, вибір кандидата з candidate_ids, створення білда з цим
кандидатом, опитування білда до SUCCEEDED, а потім завантаження GLB та
пакета OBJ з model_urls.
У прикладі програмно обирається перший кандидат. У реальній
інтеграції ви б показали значення image_urls кінцевому користувачу та
дали йому змогу вибрати; обраний індекс відображається 1:1 на 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"