Convierte una foto de origen en una keycap (tecla) personalizada de teclado mecánico a todo color en
dos etapas: prototype genera un render de diseño de "keycap terminada"
a partir de tu foto de entrada. Una vez que hayas confirmado ese render, build lo convierte en un
modelo 3D de keycap con textura en una sola ejecución — la generación del modelo blanco, el asentamiento
y corte automáticos sobre una pose predeterminada calibrada, el coloreado completo del modelo y
el ensamblaje final ocurren todos dentro de una única tarea de construcción. Las dos etapas están vinculadas
mediante input_task_id más candidate_id.
POST /openapi/creative-lab/keycap/v1/prototype
POST /openapi/creative-lab/keycap/v1/build
Ambos endpoints POST requieren un plan de suscripción de pago. Las solicitudes de
cuentas del plan gratuito se rechazan con 402 Payment Required.
Genera un render de diseño de keycap terminado a partir de la foto de origen. El resultado de la tarea
incluye un array image_urls (el render de visualización del keycap
terminado) y un array paralelo candidate_ids; ambos contienen una sola entrada.
Llama a este endpoint de nuevo para obtener otro render si el resultado no es lo que
deseas; cada llamada se factura por separado. Pasa el candidate_id junto
con el ID de la tarea de prototipo al endpoint de build.
Consulta
El objeto de tarea de prototipo de Keycap
para conocer la forma de la respuesta.
Parámetros
Name
image_url
Type
string
Requerido
Description
Foto de origen que Meshy convertirá en imágenes de diseño de keycap. Actualmente admitimos los formatos .jpg, .jpeg, .png y .webp.
El formato se detecta decodificando los datos de la imagen, no a partir de la extensión de archivo de la URL; una URL sin extensión, o que redirige, funciona siempre que los bytes se decodifiquen en un formato compatible. Se siguen las redirecciones HTTP. La orientación EXIF se normaliza, por lo que una foto de teléfono rotada se usa tal como se ve.
Límites: al menos 32 píxeles por cada lado, como máximo 178,956,970 píxeles en total, y como máximo 20,000,000 bytes una vez descargada. Para un Data URI, el límite se aplica a los bytes decodificados, por lo que el archivo de origen en sí puede llegar a ese tamaño; es el texto en base64 el que es aproximadamente un tercio más grande, lo cual afecta al cuerpo de tu solicitud, no a este límite. Un Data URI debe declarar un tipo de contenido image/* y ;base64.
Hay dos formas de proporcionar la imagen:
URL de acceso público: Una URL que sea accesible desde internet público.
Data URI: Un Data URI de la imagen codificado en base64. Ejemplo de un Data URI: data:image/jpeg;base64,<tus datos de imagen codificados en base64>.
Name
name
Type
string
Description
Nombre de tarea opcional para fines de visualización. Máximo 100 caracteres.
Name
remove_background
Type
boolean
predeterminado false
Description
Cuando se establece en true, el render de visualización devuelto en image_urls es un PNG RGBA transparente con el fondo eliminado, para que puedas componerlo sobre cualquier fondo.
Esto se aplica únicamente al render de visualización. El candidato que consume el endpoint de build no se ve afectado, por lo que el resultado 3D es idéntico en ambos casos.
Devuelve
La propiedad result de la respuesta contiene el id de tarea de la tarea de prototipo de keycap recién creada. Sondea el endpoint Obtener una tarea o suscríbete al stream hasta que la tarea alcance SUCCEEDED, luego toma la entrada de candidate_ids y pásala, junto con el ID de tarea, al endpoint de build.
Modos de fallo
Name
400 - Bad Request
Description
La solicitud fue inaceptable. Causas comunes:
Falta un parámetro: image_url es obligatorio.
Formato de imagen no válido: El image_url proporcionado no tiene un formato compatible (.jpg, .jpeg, .png, .webp).
Dimensiones de imagen fuera de rango: La imagen es demasiado pequeña, excede el tamaño máximo de archivo, o excede el recuento máximo de píxeles.
URL inaccesible: No se pudo descargar el image_url (404 o timeout).
Data URI no válido: La cadena base64 está mal formada.
Contenido marcado: La imagen de entrada fue marcada por la moderation de NSFW.
Name
401 - Unauthorized
Description
Falló la autenticación. Por favor, verifica tu clave de API.
Name
402 - Payment Required
Description
La cuenta está en el plan gratuito (se requiere un plan de pago para crear tareas) o no tiene créditos suficientes.
Name
403 - Forbidden
Description
La imagen de entrada fue marcada por la moderation de propiedad intelectual.
Name
429 - Too Many Requests
Description
Has excedido tu límite de tasa.
Name
500 - Internal Server Error
Description
Se produjo un error inesperado del lado del servidor; por ejemplo, el servicio de moderation de contenido no estaba disponible, falló la preparación de la imagen de entrada, o no se pudo crear la tarea. En este caso no se crea ninguna tarea, por lo que reintentar es seguro.
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>" }'
Genera el modelo 3D de keycap texturizado final a partir de una tarea de prototipo exitosa y uno de sus candidatos. Una única tarea de construcción ejecuta todo el pipeline de principio a fin: generación del modelo blanco a partir del diseño elegido, asentamiento y corte automáticos sobre la base del keycap usando una pose predeterminada calibrada (sin necesidad de ajuste interactivo), coloreado del modelo completo y ensamblaje y exportación final. Una construcción normalmente tarda 3–7 minutos, acercándose al extremo superior cuando se ejecutan varias construcciones simultáneamente.
Consulta El Objeto de Tarea de Construcción de Keycap
para conocer la forma de la respuesta.
Parámetros
Name
input_task_id
Type
string
Requerido
Description
El ID de tarea de una tarea de prototipo creada mediante este mismo endpoint de OpenAPI. El prototipo debe haber sido creado por la misma cuenta de Meshy, debe haber alcanzado el estado SUCCEEDED y debe haber producido al menos un candidato.
Las tareas de prototipo creadas a través de la aplicación web no se aceptan — el endpoint de construcción solo acepta tareas de prototipo producidas por POST /openapi/creative-lab/keycap/v1/prototype y rechaza cualquier otra fuente con 404.
Name
candidate_id
Type
string
Requerido
Description
El candidato a construir, tomado del array candidate_ids de la tarea de prototipo exitosa. Debe pertenecer a esa tarea; cualquier otro valor se rechaza con 400.
Name
name
Type
string
Description
Nombre de tarea opcional para fines de visualización. Máximo 100 caracteres.
options
Ajuste de geometría opcional. Cada campo tiene un valor predeterminado calibrado — envía solo los que quieras sobrescribir.
Name
base_model
Type
string
predeterminado cherry-mx-1x1-r1
Description
La base de keycap sobre la que construir. Actualmente el único valor disponible es
cherry-mx-1x1-r1 — un keycap estándar de perfil Cherry MX de 1u. Se planean 3–5
tamaños estándar adicionales convencionales; no se admiten tamaños personalizados.
Name
head_size_mm
Type
number
predeterminado 23
Description
Tamaño objetivo de la cabeza esculpida, en milímetros: su dimensión más larga se escala a este valor. Rango: [10, 40]. Los valores por encima de aproximadamente 32.9 pueden reducirse para que la cabeza siga ajustándose al límite de huella protectora de la base, por lo que la dimensión más larga entregada puede ser menor a la solicitada. El valor aplicado no se refleja hoy en el objeto de tarea — si necesitas confirmar el tamaño que realmente recibiste, mide la caja delimitadora de la malla keycap-head en el modelo descargado.
Name
vertical_offset_mm
Type
number
predeterminado 0
Description
Desplazamiento vertical aplicado a la cabeza antes de que se asiente sobre la base, en milímetros. Rango: [-5, 5].
Devuelve
La propiedad result de la respuesta contiene el id de la tarea de la tarea de construcción de keycap recién creada. Consulta periódicamente el endpoint Obtener una Tarea o suscríbete al stream hasta que la tarea alcance SUCCEEDED, y luego descarga los artefactos desde model_urls.glb y model_urls.obj_zip.
Tanto el GLB como el paquete OBJ se exportan a escala real en
milímetros, con un sistema de coordenadas Y arriba (Y-up) y el frente
del keycap orientado hacia +Z.
Modos de fallo
Name
400 - Bad Request
Description
La solicitud fue inaceptable. Causas comunes:
Parámetro faltante: se requieren input_task_id y candidate_id.
UUID inválido: input_task_id no es un UUID válido.
Tarea principal no exitosa: la tarea de prototipo referenciada aún no ha alcanzado SUCCEEDED.
Sin candidatos: la tarea de prototipo tuvo éxito pero no produjo candidatos.
Candidato desconocido: candidate_id no es uno de los candidatos de la tarea de entrada.
Opciones fuera de rango: uno de los campos de options quedó fuera de su rango permitido o conjunto de valores enumerados.
Name
401 - Unauthorized
Description
Falló la autenticación. Por favor, verifica tu clave de API.
Name
402 - Payment Required
Description
La cuenta está en el plan gratuito (se requiere un plan de pago para crear tareas) o no tiene créditos suficientes.
Name
404 - Not Found
Description
La tarea de prototipo referenciada no existe, pertenece a un usuario diferente o fue creada a través de la aplicación web (solo las tareas de prototipo en modo API se encadenan a la construcción).
Name
429 - Too Many Requests
Description
Has excedido tu límite de tasa.
Name
500 - Internal Server Error
Description
Ocurrió un error inesperado del lado del servidor — por ejemplo, el servicio de moderation de contenido no estaba disponible, falló la preparación de la imagen de entrada, o la tarea no pudo crearse. En este caso no se crea ninguna tarea, por lo que es seguro reintentar.
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 } }'
Recupera una tarea de prototipo o de build dado un id de tarea válido. La ruta de la URL
debe coincidir con la etapa de la tarea: una tarea de build obtenida mediante
/prototype/:id devuelve 404, y viceversa.
Cancela una tarea de keycap. Si la tarea todavía está PENDING, los créditos
consumidos en el momento de la creación se reembolsan. Las tareas que ya están
en IN_PROGRESS se cancelan sin reembolso (es posible que el worker ya esté
consumiendo recursos). Las tareas que ya han alcanzado un estado terminal
(SUCCEEDED, FAILED, CANCELED) no se pueden cancelar.
La ruta de la URL debe coincidir con la etapa de la tarea; un DELETE en
/prototype/:buildId devuelve 404.
Parámetros de ruta
Name
id
Type
path
Description
Identificador único de la tarea de keycap que se va a cancelar.
Devuelve
Devuelve 204 No Content cuando se realiza correctamente, con un cuerpo vacío.
Modos de fallo
Name
400 - Bad Request
Description
La tarea ya está en un estado terminal y no se puede cancelar.
Name
404 - Not Found
Description
La tarea no existe, pertenece a otro usuario, o su etapa no coincide con la ruta de la URL.
Name
500 - Internal Server Error
Description
Se produjo un error inesperado del servidor durante la cancelación. Es posible que la tarea se haya cancelado o no; vuelva a leerla para confirmarlo antes de reintentar.
Transmite actualizaciones en tiempo real para una tarea de keycap mediante Server-Sent Events (SSE).
La ruta de la URL debe coincidir con la etapa de la tarea; abrir un stream en
/prototype/:buildId/stream emite un único event: error con
payloadstatus_code: 404 y cierra el stream.
Parámetros
Name
id
Type
path
Description
Identificador único de la tarea de keycap que se va a transmitir.
Devuelve
Devuelve un stream de objetos de tarea Keycap Prototype
o Keycap Build en forma de
Server-Sent Events. Cada frame lleva el objeto de tarea completo correspondiente a la etapa —la misma forma que
devuelve el endpoint Get—, de modo que mientras la tarea está en PENDING o IN_PROGRESS los
campos de salida simplemente aún no están poblados (null, [] o {}) y
finished_at es 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=***" }}
Obtiene una lista paginada de tus tareas de keycap para una sola etapa. La ruta
de la URL selecciona la etapa — /prototype devuelve tareas de prototipo; /build
devuelve tareas de build. Las tareas de la otra etapa no se incluyen en ninguna
de las dos respuestas.
Parámetros de ruta
Name
stage
Type
path
Requerido
Description
Puede ser prototype o build. La colección devuelve solo las tareas
cuya etapa coincide con la URL — obtener /prototype nunca devuelve
tareas de build y viceversa.
Parámetros de consulta
Name
page_num
Type
integer
predeterminado 1
Description
Número de página para la paginación.
Name
page_size
Type
integer
predeterminado 10
Description
Límite de tamaño de página. El máximo permitido es 100 elementos.
Name
sort_by
Type
string
predeterminado -created_at
Description
Campo por el que ordenar. Valores disponibles:
+created_at: Ordenar por hora de creación en orden ascendente.
-created_at: Ordenar por hora de creación en orden descendente.
El objeto de tarea de prototipo de Keycap es una unidad de trabajo que Meshy mantiene para generar una imagen de diseño de keycap terminado a partir de una foto original. El resultado de esta etapa se encadena a la etapa de construcción
mediante input_task_id junto con candidate_id.
Propiedades
Name
id
Type
string
Description
Identificador único de la tarea. Aunque usamos un UUID ordenable por k como detalle de implementación para los ids de tareas, no debes hacer ninguna suposición sobre el formato del id.
Name
type
Type
string
Description
Tipo de la tarea. El valor es creative-lab-keycap-prototype.
Name
name
Type
string
Description
El nombre de la tarea proporcionado al crearla. Cadena vacía si no se proporcionó ningún nombre.
Name
status
Type
string
Description
Estado de la tarea. Los valores posibles son uno de PENDING, IN_PROGRESS, SUCCEEDED, FAILED, CANCELED.
Name
progress
Type
integer
Description
Progreso de la tarea. Si la tarea aún no ha comenzado, esta propiedad será 0. Una vez que la tarea haya finalizado con éxito, será 100.
Name
created_at
Type
timestamp
Description
Marca de tiempo de cuándo se creó la tarea, en milisegundos.
Una marca de tiempo representa el número de milisegundos transcurridos desde el 1 de enero de 1970 UTC, siguiendo
el estándar RFC 3339.
Por ejemplo, el viernes 1 de septiembre de 2023 a las 12:00:00 PM GMT se representa como 1693569600000. Esto se aplica
a todas las marcas de tiempo en Meshy API.
Name
started_at
Type
timestamp
Description
Marca de tiempo de cuándo se inició la tarea, en milisegundos. Si la tarea aún no se ha iniciado, esta propiedad será 0.
Name
finished_at
Type
timestamp
Description
Marca de tiempo de cuándo finalizó la tarea, en milisegundos. Si la tarea aún no ha finalizado, esta propiedad será 0.
Name
expires_at
Type
timestamp
Description
Marca de tiempo de cuándo caduca el resultado de la tarea, en milisegundos.
Name
preceding_tasks
Type
integer
Description
El recuento de tareas precedentes.
El valor de este campo solo tiene sentido si el estado de la tarea es PENDING.
Name
task_error
Type
object
Description
Detalles del error para tareas fallidas. Consulta Errores para la referencia completa del objeto task_error.
Name
consumed_credits
Type
integer
Description
El número de créditos consumidos por esta tarea. Una tarea que alcanza SUCCEEDED se cobra el importe completo correspondiente a su etapa. Una tarea que nunca llega a crearse (un 4xx en el momento de la solicitud, incluido un rechazo de moderation) no se cobra en absoluto. Una tarea que alcanza FAILED devuelve 0: el cargo se reembolsa, incluido un bloqueo de moderation asíncrono. Cancelar mediante DELETE reembolsa solo mientras la tarea sigue en PENDING; una tarea que ya está IN_PROGRESS permanece cobrada, porque el trabajo ya se ha realizado.
Name
image_urls
Type
array of strings
Description
URL descargable del render de diseño de keycap terminado, es decir, cómo se ve el candidato como keycap terminado. Contiene una única entrada; image_urls[i] corresponde a candidate_ids[i]. Vacío hasta que la tarea alcanza SUCCEEDED. La URL es solo para visualización; el endpoint de construcción consume candidate_ids, no estas URLs. Mismo ciclo de vida de URL que model_urls: firmada, sin encabezado Authorization, válida hasta expires_at, y estable al volver a leer la tarea.
Name
candidate_ids
Type
array of strings
Description
Identificadores de candidato opacos, en paralelo con image_urls. Pasa la entrada que corresponda a tu diseño elegido como el candidate_id de la solicitud de construcción. No hagas ninguna suposición sobre el formato de estos ids.
El objeto Keycap Build Task es una unidad de trabajo que Meshy supervisa para
generar el keycap 3D texturizado final a partir de una tarea de prototipo exitosa y un
candidato elegido. Una sola construcción ejecuta el pipeline completo — generación
del modelo blanco, asentado y corte automáticos, coloreado, ensamblaje y exportación.
Propiedades
Name
id
Type
string
Description
Identificador único de la tarea.
Name
type
Type
string
Description
Tipo de la tarea. El valor es creative-lab-keycap-build.
Name
name
Type
string
Description
El nombre de la tarea proporcionado al crearla. Cadena vacía si no se proporcionó ningún nombre.
Name
status
Type
string
Description
Estado de la tarea. Los valores posibles son uno de PENDING, IN_PROGRESS, SUCCEEDED, FAILED, CANCELED.
Name
progress
Type
integer
Description
Progreso de la tarea. Si la tarea aún no ha comenzado, esta propiedad será 0. Una vez que la tarea haya finalizado con éxito, será 100.
Name
created_at
Type
timestamp
Description
Marca de tiempo de cuándo se creó la tarea, en milisegundos.
Name
started_at
Type
timestamp
Description
Marca de tiempo de cuándo se inició la tarea, en milisegundos.
Name
finished_at
Type
timestamp
Description
Marca de tiempo de cuándo finalizó la tarea, en milisegundos.
Name
expires_at
Type
timestamp
Description
Marca de tiempo de cuándo caduca el resultado de la tarea, en milisegundos.
Name
preceding_tasks
Type
integer
Description
El número de tareas precedentes. Solo tiene sentido cuando el estado es PENDING.
Name
task_error
Type
object
Description
Detalles del error para tareas fallidas. Consulta Errores para la referencia completa del objeto task_error.
Name
consumed_credits
Type
integer
Description
El número de créditos consumidos por esta tarea. A una tarea que llega a SUCCEEDED se le cobra el importe completo correspondiente a su etapa. A una tarea que nunca llega a crearse (un 4xx en el momento de la solicitud, incluido un rechazo por moderation) no se le cobra nada en absoluto. Una tarea que llega a FAILED devuelve 0 — el cargo se reembolsa, incluido un bloqueo asíncrono de moderation. Cancelar mediante DELETE solo reembolsa mientras la tarea sigue en PENDING; una tarea que ya está en IN_PROGRESS sigue cobrada, porque el trabajo ya se ha realizado.
Name
model_urls
Type
object
Description
URLs descargables para los artefactos del modelo generado. Tanto el GLB como el paquete OBJ se exportan a escala real en milímetros, con eje Y hacia arriba, con el frente del keycap orientado hacia +Z. Las mallas se llaman keycap-head y keycap-base; cuando la base recurre a un relleno de patrón, también está presente una tercera malla keycap-base-interior para la cavidad del vástago. No asumas que siempre hay exactamente dos mallas.
Estas son URLs firmadas: obténlas sin un encabezado Authorization. Permanecen válidas hasta expires_at, que es 3 días después de finished_at, y volver a leer la tarea dentro de esa ventana devuelve la misma URL en lugar de una recién firmada. Descarga y almacena los archivos tú mismo antes de eso — no hay forma de renovar un enlace caducado.
Name
glb
Type
string
Description
URL descargable al model.glb final texturizado.
Name
obj_zip
Type
string
Description
URL descargable a un paquete zip que contiene model.obj, model.mtl, y los PNG de textura a los que su MTL realmente hace referencia. Una base de color sólido solo incluye keycap-head.png; una base con patrón también incluye keycap-base.png.
Name
process_image_urls
Type
object
Description
URLs descargables para las imágenes intermedias del proceso, indexadas por tipo. Mismo ciclo de vida de URL que model_urls: firmadas, sin encabezado Authorization, válidas hasta expires_at, y estables cuando se vuelve a leer la tarea. Tipos emitidos actualmente:
head_design — la imagen de diseño del candidato elegido que consumió la construcción (siempre presente).
composite — el render de visualización del keycap terminado del candidato elegido (presente cuando está disponible).
base_canvas — el lienzo pintado de la base del keycap (presente cuando está disponible).
Trata el conjunto de claves como abierto; se pueden añadir nuevos tipos sin que constituya un cambio disruptivo.
El flujo completo: crear un prototipo a partir de una foto, sondearlo hasta
SUCCEEDED, elegir un candidato de candidate_ids, crear una compilación con
ese candidato, sondear la compilación hasta SUCCEEDED y luego descargar el GLB y
el paquete OBJ desde model_urls.
El ejemplo elige el primer candidato de forma programática. En una integración
real mostrarías la entrada image_urls al usuario final y
dejarías que eligiera; el índice elegido se corresponde 1:1 con 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"