원본 사진을 완전 컬러의 커스텀 기계식 키보드 키캡으로 변환하는 과정은
두 단계로 이루어집니다. prototype 단계는 입력한 사진으로부터 "완성된
키캡" 디자인 렌더를 생성합니다. 해당 렌더를 확인한 후, build 단계는 이를
텍스처가 적용된 3D 키캡 모델로 한 번의 실행으로 변환합니다 — 화이트 모델
생성, 보정된 기본 포즈에서의 자동 시팅 및 커팅, 전체 모델 컬러링, 최종
조립까지 모든 과정이 하나의 빌드 작업 안에서 이루어집니다. 두 단계는
input_task_id와 candidate_id를 통해 연결됩니다.
POST /openapi/creative-lab/keycap/v1/prototype
POST /openapi/creative-lab/keycap/v1/build
두 POST 엔드포인트 모두 유료 구독 플랜이 필요합니다. 무료 플랜 계정의
요청은 402 Payment Required 오류로 거부됩니다.
소스 사진으로부터 완성된 키캡 디자인 렌더를 생성합니다. 작업 결과에는 image_urls 배열(완성된 키캡의 디스플레이 렌더)과 이에 대응하는 candidate_ids 배열이 포함되며, 둘 다 항목을 하나씩만 담고 있습니다.
원하는 결과가 아니라면 다른 렌더를 얻기 위해 이 엔드포인트를 다시 호출하세요 — 각 호출은 별도로 청구됩니다. candidate_id를 프로토타입 작업 ID와 함께 빌드 엔드포인트에 전달하세요.
응답 형태에 대해서는
키캡 프로토타입 작업 객체를
참고하세요.
파라미터
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 텍스트는 약 3분의 1 정도 더 커지지만, 이는 요청 본문에 관한 문제일 뿐 이 제한과는 무관합니다. 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가 되어, 원하는 배경 위에 합성할 수 있습니다.
이는 디스플레이 렌더에만 적용됩니다. 빌드 엔드포인트가 사용하는 candidate에는 영향을 주지 않으므로, 3D 결과는 어느 쪽이든 동일합니다.
반환값
응답의 result 속성에는 새로 생성된 키캡 프로토타입 작업의 id가 포함됩니다. 작업 가져오기 엔드포인트를 폴링하거나 스트림을 구독하여 작업이 SUCCEEDED 상태에 도달할 때까지 기다린 다음, candidate_ids에서 항목을 가져와 작업 ID와 함께 빌드 엔드포인트에 전달하세요.
실패 모드
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
인증에 실패했습니다. API 키를 확인해 주세요.
Name
402 - Payment Required
Description
계정이 무료 플랜을 사용 중이거나(작업 생성에는 유료 플랜이 필요합니다) 크레딧이 부족합니다.
Name
403 - Forbidden
Description
입력 이미지가 지적 재산권 moderation에 의해 플래그 처리되었습니다.
Name
429 - Too Many Requests
Description
속도 제한을 초과했습니다.
Name
500 - Internal Server Error
Description
예기치 않은 서버 측 오류가 발생했습니다 — 예를 들어 콘텐츠 moderation 서비스를 사용할 수 없었거나, 입력 이미지를 스테이징하는 데 실패했거나, 작업을 생성할 수 없었던 경우입니다. 이 경우 작업이 생성되지 않으므로 재시도해도 안전합니다.
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분이 소요되며,
여러 빌드가 동시에 실행될 경우 상한선에 가까워집니다.
응답 형태에 대해서는 키캡 빌드 태스크 객체를 참고하세요.
파라미터
Name
input_task_id
Type
string
필수
Description
동일한 OpenAPI 엔드포인트를 통해 생성된 프로토타입 태스크의 태스크 ID입니다. 프로토타입은 동일한 Meshy 계정에 의해 생성되어야 하며, SUCCEEDED 상태에 도달해야 하고, 최소 하나 이상의 후보를 생성했어야 합니다.
웹앱을 통해 생성된 프로토타입 태스크는 허용되지 않습니다 — 빌드 엔드포인트는 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 프로파일 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축이 위쪽인
좌표계를 사용하고, 키캡의 앞면은 +Z 방향을 향합니다.
실패 모드
Name
400 - Bad Request
Description
요청이 허용되지 않았습니다. 일반적인 원인:
파라미터 누락: input_task_id와 candidate_id가 필요합니다.
유효하지 않은 UUID: input_task_id가 유효한 UUID가 아닙니다.
부모가 성공하지 않음: 참조된 프로토타입 태스크가 아직 SUCCEEDED에 도달하지 않았습니다.
후보 없음: 프로토타입 태스크는 성공했지만 후보를 생성하지 못했습니다.
알 수 없는 후보: candidate_id가 입력 태스크의 후보 중 하나가 아닙니다.
옵션 범위 초과: options 필드 중 하나가 허용된 범위나 열거값 집합을 벗어났습니다.
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
예기치 않은 서버 측 오류가 발생했습니다 — 예를 들어 콘텐츠 moderation 서비스를 사용할 수 없었거나, 입력 이미지를 스테이징하는 데 실패했거나, 태스크를 생성할 수 없었습니다. 이 경우 태스크가 생성되지 않으므로 재시도해도 안전합니다.
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 } }'
키캡 작업을 취소합니다. 작업이 아직 PENDING 상태라면 생성 시 소모된
크레딧이 환불됩니다. 이미 IN_PROGRESS 상태인 작업은 환불 없이
취소됩니다(워커가 이미 리소스를 소모하고 있을 수 있습니다). 이미 최종
상태(SUCCEEDED, FAILED, CANCELED)에 도달한 작업은 취소할 수 없습니다.
Server-Sent Events(SSE)를 통해 키캡 작업의 실시간 업데이트를 스트리밍합니다.
URL 경로는 작업의 단계와 일치해야 합니다 — /prototype/:buildId/stream에서 스트림을
열면 status_code: 404가 포함된 단일 event: error 페이로드를 내보내고
스트림이 닫힙니다.
매개변수
Name
id
Type
path
Description
스트리밍할 키캡 작업의 고유 식별자입니다.
반환값
Server-Sent Events로 Keycap Prototype
또는 Keycap Build 작업 객체의 스트림을 반환합니다.
모든 프레임은 해당 단계의 전체 작업 객체를 담고 있으며 — 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=***" }}
키캡 프로토타입 작업 객체는 소스 사진으로부터 완성된 키캡 디자인 이미지 하나를 생성하기 위해 Meshy가 추적하는 작업 단위입니다. 이 단계의 출력은 input_task_id와 candidate_id를 통해 빌드 단계로 연결됩니다.
속성
Name
id
Type
string
Description
작업의 고유 식별자입니다. 구현 세부사항으로 작업 id에 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
작업이 생성된 시각의 타임스탬프이며, 밀리초 단위입니다.
타임스탬프는 RFC 3339 표준을 따라
1970년 1월 1일 UTC 이후 경과한 밀리초 수를 나타냅니다.
예를 들어, 2023년 9월 1일 금요일 12:00:00 PM GMT는 1693569600000으로 표현됩니다. 이는
Meshy API의 모든 타임스탬프에 적용됩니다.
Name
started_at
Type
timestamp
Description
작업이 시작된 시각의 타임스탬프이며, 밀리초 단위입니다. 작업이 아직 시작되지 않은 경우 이 속성은 0이 됩니다.
Name
finished_at
Type
timestamp
Description
작업이 완료된 시각의 타임스탬프이며, 밀리초 단위입니다. 작업이 아직 완료되지 않은 경우 이 속성은 0이 됩니다.
Name
expires_at
Type
timestamp
Description
작업 결과가 만료되는 시각의 타임스탬프이며, 밀리초 단위입니다.
Name
preceding_tasks
Type
integer
Description
선행 작업의 수입니다.
이 필드의 값은 작업 상태가 PENDING일 때만 의미가 있습니다.
Name
task_error
Type
object
Description
실패한 작업에 대한 오류 세부정보입니다. task_error 객체 전체 참조는 오류를 참고하세요.
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은 표시 전용이며, 빌드 엔드포인트는 이 URL이 아니라 candidate_ids를 사용합니다. model_urls와 동일한 URL 수명 주기를 가집니다: 서명되어 있고, Authorization 헤더가 없으며, expires_at까지 유효하고, 작업을 다시 읽어도 안정적으로 유지됩니다.
Name
candidate_ids
Type
array of strings
Description
image_urls와 병렬로 대응하는 불투명한 후보 식별자입니다. 선택한 디자인에 해당하는 항목을 빌드 요청의 candidate_id로 전달하세요. 이 id들의 형식에 대해 어떠한 가정도 하지 마세요.
Keycap 빌드 태스크 객체는 성공한 프로토타입 태스크와 선택된 candidate로부터 최종 텍스처가 적용된 3D keycap을 생성하기 위해 Meshy가 추적하는 작업 단위입니다. 하나의 빌드는 화이트 모델 생성, 자동 시팅 및 커팅, 컬러링, 조립, 익스포트로 이루어진 전체 파이프라인을 실행합니다.
속성
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, moderation 거부 포함)는 전혀 청구되지 않습니다. FAILED에 도달한 태스크는 0을 반환하며, 이는 비동기 moderation 차단을 포함해 요금이 환불됨을 의미합니다. DELETE를 통한 취소는 태스크가 아직 PENDING 상태일 때만 환불되며, 이미 IN_PROGRESS인 태스크는 작업이 이미 소비되었기 때문에 계속 청구된 상태로 유지됩니다.
Name
model_urls
Type
object
Description
생성된 모델 아티팩트를 다운로드할 수 있는 URL입니다. GLB와 OBJ 번들 모두 실제 세계 밀리미터 단위 스케일, Y-up으로 익스포트되며, keycap의 정면은 +Z 방향을 향합니다. 메시는 keycap-head와 keycap-base로 명명되며, 베이스가 패턴 채우기로 대체되는 경우 스템 캐비티를 위한 세 번째 메시 keycap-base-interior도 함께 존재합니다. 메시가 정확히 두 개라고 가정하지 마세요.
이는 서명된 URL이므로 Authorization 헤더 없이 가져와야 합니다. 이 URL은 expires_at까지 유효하며, 이는 finished_at으로부터 3일 후이고, 그 기간 내에 태스크를 다시 읽으면 새로 서명된 URL이 아니라 동일한 URL이 반환됩니다. 만료되기 전에 파일을 직접 다운로드하여 저장하세요. 만료된 링크를 새로고침할 방법은 없습니다.
Name
glb
Type
string
Description
최종 텍스처가 적용된 model.glb를 다운로드할 수 있는 URL입니다.
Name
obj_zip
Type
string
Description
model.obj, model.mtl, 그리고 해당 MTL이 실제로 참조하는 텍스처 PNG 파일들을 포함하는 zip 번들을 다운로드할 수 있는 URL입니다. 단색 베이스는 keycap-head.png만 포함하며, 패턴 베이스는 keycap-base.png도 함께 포함합니다.
Name
process_image_urls
Type
object
Description
종류별로 키가 지정된 중간 프로세스 이미지를 다운로드할 수 있는 URL입니다. model_urls와 동일한 URL 라이프사이클을 가지며, 서명되어 있고 Authorization 헤더가 필요 없으며, expires_at까지 유효하고, 태스크를 다시 읽어도 값이 안정적으로 유지됩니다. 현재 생성되는 종류는 다음과 같습니다:
head_design — 빌드가 소비한, 선택된 candidate의 디자인 이미지입니다(항상 존재).
composite — 선택된 candidate의 완성된 keycap 디스플레이 렌더입니다(사용 가능한 경우 존재).
base_canvas — 채색된 keycap 베이스 캔버스입니다(사용 가능한 경우 존재).
이 키 집합은 열려 있는 것으로 간주하세요. 이후 호환성을 깨지 않으면서 새로운 종류가 추가될 수 있습니다.
전체 흐름은 다음과 같습니다: 사진으로부터 프로토타입을 생성하고, SUCCEEDED
상태가 될 때까지 폴링한 다음, candidate_ids에서 후보를 선택하고, 해당
후보로 빌드를 생성한 후, 빌드가 SUCCEEDED 상태가 될 때까지 폴링하고,
마지막으로 model_urls에서 GLB와 OBJ 번들을 다운로드합니다.
이 예제에서는 프로그래밍 방식으로 첫 번째 후보를 선택합니다. 실제
통합에서는 최종 사용자에게 image_urls 항목을 표시하여 직접 선택하도록
하며, 선택된 인덱스는 candidate_ids에 1:1로 매핑됩니다.
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"