Transformez une photo source en un keycap de clavier mécanique personnalisé et en couleurs, en deux étapes : prototype génère un rendu de conception « keycap terminé » à partir de votre photo d'entrée. Une fois ce rendu confirmé, build le transforme en un modèle 3D de keycap texturé en une seule exécution — génération du modèle blanc, positionnement et découpe automatiques selon une pose par défaut calibrée, coloration du modèle complet, et assemblage final, le tout au sein d'une seule tâche de build. Les deux étapes sont liées via input_task_id et candidate_id.
POST /openapi/creative-lab/keycap/v1/prototype
POST /openapi/creative-lab/keycap/v1/build
Les deux endpoints POST nécessitent un plan d'abonnement payant. Les requêtes provenant de comptes en plan gratuit sont rejetées avec 402 Payment Required.
Génère un rendu de design de keycap fini à partir de la photo source. Le résultat de la tâche
contient un tableau image_urls (le rendu d'affichage du keycap fini) et un tableau
parallèle candidate_ids ; les deux contiennent une seule entrée.
Appelez à nouveau ce point de terminaison pour obtenir un autre rendu si le résultat ne vous
convient pas — chaque appel est facturé séparément. Transmettez le candidate_id avec
l'ID de la tâche de prototype au point de terminaison de build.
Reportez-vous à
The Keycap Prototype Task Object
pour connaître la forme de la réponse.
Paramètres
Name
image_url
Type
string
Requis
Description
Photo source que Meshy transforme en images de design de keycap. Nous prenons actuellement en charge les formats .jpg, .jpeg, .png et .webp.
Le format est détecté en décodant les données de l'image, et non à partir de l'extension de fichier de l'URL — une URL sans extension, ou une URL qui redirige, fonctionne tant que les octets se décodent vers un format pris en charge. Les redirections HTTP sont suivies. L'orientation EXIF est normalisée, de sorte qu'une photo de téléphone tournée est utilisée telle qu'elle apparaît visuellement.
Limites : au moins 32 pixels sur chaque côté, au plus 178 956 970 pixels au total, et au plus 20 000 000 octets une fois téléchargée. Pour un Data URI, la limite s'applique aux octets décodés, donc le fichier source lui-même peut atteindre cette taille — c'est le texte base64 qui est environ un tiers plus volumineux, ce qui compte pour le corps de votre requête, pas pour cette limite. Un Data URI doit déclarer un type de contenu image/* et ;base64.
Il existe deux façons de fournir l'image :
URL publiquement accessible : une URL accessible depuis l'internet public.
Data URI : une donnée d'image encodée en base64. Exemple de Data URI : data:image/jpeg;base64,<vos données d'image encodées en base64>.
Name
name
Type
string
Description
Nom de tâche optionnel à des fins d'affichage. 100 caractères maximum.
Name
remove_background
Type
boolean
défaut false
Description
Lorsqu'il est défini sur true, le rendu d'affichage renvoyé dans image_urls est un PNG RGBA transparent avec l'arrière-plan supprimé, ce qui vous permet de le composer sur n'importe quel arrière-plan.
Cela s'applique uniquement au rendu d'affichage. Le candidat consommé par le point de terminaison de build n'est pas affecté, le résultat 3D est donc identique dans les deux cas.
Retours
La propriété result de la réponse contient l'id de la tâche de la tâche de prototype de keycap nouvellement créée. Interrogez le point de terminaison Get a Task ou abonnez-vous au stream jusqu'à ce que la tâche atteigne SUCCEEDED, puis récupérez l'entrée de candidate_ids et transmettez-la, avec l'ID de la tâche, au point de terminaison de build.
Modes d'échec
Name
400 - Bad Request
Description
La requête était inacceptable. Causes courantes :
Paramètre manquant : image_url est requis.
Format d'image invalide : le image_url fourni n'est pas dans un format pris en charge (.jpg, .jpeg, .png, .webp).
Dimensions de l'image hors limites : l'image est trop petite, dépasse la taille de fichier maximale, ou dépasse le nombre maximal de pixels.
URL inaccessible : le image_url n'a pas pu être téléchargé (404 ou timeout).
Data URI invalide : la chaîne base64 est mal formée.
Contenu signalé : l'image d'entrée a été signalée par la moderation NSFW.
Name
401 - Unauthorized
Description
L'authentification a échoué. Veuillez vérifier votre clé API.
Name
402 - Payment Required
Description
Le compte est sur le plan gratuit (un plan payant est requis pour créer des tâches) ou dispose de crédits insuffisants.
Name
403 - Forbidden
Description
L'image d'entrée a été signalée par la moderation de propriété intellectuelle.
Name
429 - Too Many Requests
Description
Vous avez dépassé votre limite de débit.
Name
500 - Internal Server Error
Description
Une erreur serveur inattendue s'est produite — par exemple, le service de moderation de contenu était indisponible, la mise en préparation de l'image d'entrée a échoué, ou la tâche n'a pas pu être créée. Aucune tâche n'est créée dans ce cas, il est donc sûr de réessayer.
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>" }'
Génère le modèle 3D de keycap texturé final à partir d'une tâche de prototype réussie et de l'un de ses candidats. Une seule tâche de build exécute l'ensemble du pipeline de bout en bout — génération du modèle blanc à partir du design choisi, positionnement et découpe automatiques sur la base du keycap à l'aide d'une pose par défaut calibrée (aucun ajustement interactif nécessaire), coloration du modèle complet, puis assemblage final et export. Un build prend généralement 3 à 7 minutes, se rapprochant de la limite supérieure lorsque plusieurs builds s'exécutent simultanément.
Consultez L'objet Tâche de build de keycap
pour connaître la structure de la réponse.
Paramètres
Name
input_task_id
Type
string
Requis
Description
L'ID de tâche d'une tâche de prototype créée via ce même point de terminaison OpenAPI. Le prototype doit avoir été créé par le même compte Meshy, doit avoir atteint SUCCEEDED, et doit avoir produit au moins un candidat.
Les tâches de prototype créées via l'application web ne sont pas acceptées — le point de terminaison de build accepte uniquement les tâches de prototype produites par POST /openapi/creative-lab/keycap/v1/prototype et refuse toute autre source avec 404.
Name
candidate_id
Type
string
Requis
Description
Le candidat à construire, pris dans le tableau candidate_ids de la tâche de prototype réussie. Doit appartenir à cette tâche ; toute autre valeur est rejetée avec 400.
Name
name
Type
string
Description
Nom de tâche facultatif à des fins d'affichage. 100 caractères maximum.
options
Ajustement facultatif de la géométrie. Chaque champ possède une valeur par défaut calibrée — n'envoyez que ceux que vous souhaitez remplacer.
Name
base_model
Type
string
défaut cherry-mx-1x1-r1
Description
La base de keycap sur laquelle construire. Actuellement, la seule valeur disponible est
cherry-mx-1x1-r1 — un keycap 1u de profil Cherry MX standard. 3 à 5
tailles standard grand public supplémentaires sont prévues ; les tailles
personnalisées ne sont pas prises en charge.
Name
head_size_mm
Type
number
défaut 23
Description
Taille cible de la tête sculptée, en millimètres : sa dimension la plus longue est mise à l'échelle sur cette valeur. Plage : [10, 40]. Les valeurs supérieures à environ 32.9 peuvent être réduites afin que la tête respecte toujours la limite d'emprise de protection de la base, de sorte que la dimension la plus longue livrée peut être inférieure à celle demandée. La valeur appliquée n'est pas encore renvoyée sur l'objet tâche — si vous devez confirmer la taille réellement obtenue, mesurez la boîte englobante du maillage keycap-head dans le modèle téléchargé.
Name
vertical_offset_mm
Type
number
défaut 0
Description
Décalage vertical appliqué à la tête avant qu'elle ne soit positionnée sur la base, en millimètres. Plage : [-5, 5].
Retours
La propriété result de la réponse contient l'id de tâche de la tâche de build de keycap nouvellement créée. Interrogez le point de terminaison Obtenir une tâche ou abonnez-vous au flux jusqu'à ce que la tâche atteigne SUCCEEDED, puis téléchargez les artefacts depuis model_urls.glb et model_urls.obj_zip.
Le GLB et l'archive OBJ sont tous deux exportés à l'échelle réelle en
millimètres, avec un système de coordonnées Y-up et l'avant du
keycap orienté vers +Z.
Modes d'échec
Name
400 - Bad Request
Description
La requête était inacceptable. Causes courantes :
Paramètre manquant : input_task_id et candidate_id sont requis.
UUID invalide : input_task_id n'est pas un UUID valide.
Parent non réussi : la tâche de prototype référencée n'a pas encore atteint SUCCEEDED.
Aucun candidat : la tâche de prototype a réussi mais n'a produit aucun candidat.
Candidat inconnu : candidate_id ne fait pas partie des candidats de la tâche d'entrée.
Options hors limites : l'un des champs options se situait en dehors de sa plage autorisée ou de son ensemble d'énumération.
Name
401 - Unauthorized
Description
L'authentification a échoué. Veuillez vérifier votre clé API.
Name
402 - Payment Required
Description
Le compte est sur le plan gratuit (un plan payant est requis pour créer des tâches) ou dispose de crédits insuffisants.
Name
404 - Not Found
Description
La tâche de prototype référencée n'existe pas, appartient à un autre utilisateur, ou a été créée via l'application web (seules les tâches de prototype en mode API peuvent enchaîner vers un build).
Name
429 - Too Many Requests
Description
Vous avez dépassé votre limite de débit.
Name
500 - Internal Server Error
Description
Une erreur serveur inattendue s'est produite — par exemple, le service de moderation de contenu était indisponible, la mise en préparation de l'image d'entrée a échoué, ou la tâche n'a pas pu être créée. Aucune tâche n'est créée dans ce cas, il est donc sûr de réessayer.
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 } }'
Récupère une tâche de prototype ou de build à partir d'un id de tâche valide. Le chemin de l'URL
doit correspondre à l'étape de la tâche — une tâche de build récupérée via
/prototype/:id renvoie 404, et inversement.
Annule une tâche keycap. Si la tâche est encore PENDING, les crédits
consommés au moment de la création sont remboursés. Les tâches déjà
IN_PROGRESS sont annulées sans remboursement (le worker peut déjà être
en train de consommer des ressources). Les tâches ayant déjà atteint un
état final (SUCCEEDED, FAILED, CANCELED) ne peuvent pas être annulées.
Le chemin de l'URL doit correspondre à l'étape de la tâche — un DELETE
sur /prototype/:buildId renvoie 404.
Paramètres de chemin
Name
id
Type
path
Description
Identifiant unique de la tâche keycap à annuler.
Retours
Renvoie 204 No Content en cas de succès, avec un corps vide.
Modes d'échec
Name
400 - Bad Request
Description
La tâche est déjà dans un état final et ne peut pas être annulée.
Name
404 - Not Found
Description
La tâche n'existe pas, appartient à un autre utilisateur, ou son étape ne correspond pas au chemin de l'URL.
Name
500 - Internal Server Error
Description
Une erreur serveur inattendue s'est produite lors de l'annulation. La tâche a pu être annulée ou non — relisez-la pour le confirmer avant de réessayer.
Diffuse en temps réel les mises à jour d'une tâche keycap via Server-Sent Events (SSE).
Le chemin de l'URL doit correspondre à l'étape de la tâche — ouvrir un flux à
/prototype/:buildId/stream émet une seule charge utile event: error avec
status_code: 404 puis ferme le flux.
Paramètres
Name
id
Type
path
Description
Identifiant unique de la tâche keycap à diffuser en flux continu.
Retours
Renvoie un flux d'objets de tâche Keycap Prototype
ou Keycap Build sous forme
d'événements SSE (Server-Sent Events). Chaque trame contient l'objet de tâche complet correspondant à l'étape — la même forme que celle renvoyée par le
point de terminaison Get — ainsi, tant que la tâche est PENDING ou IN_PROGRESS, les
champs de sortie ne sont simplement pas encore renseignés (null, [] ou {}) et
finished_at vaut 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=***" }}
Récupère une liste paginée de vos tâches de keycap pour une seule étape. Le
chemin de l'URL sélectionne l'étape — /prototype renvoie les tâches de
prototype ; /build renvoie les tâches de build. Les tâches de l'autre
étape ne sont incluses dans aucune des deux réponses.
Paramètres de chemin
Name
stage
Type
path
Requis
Description
Soit prototype, soit build. La collection ne renvoie que les
tâches dont l'étape correspond à l'URL — récupérer /prototype ne
renvoie jamais de tâches de build, et vice versa.
Paramètres de requête
Name
page_num
Type
integer
défaut 1
Description
Numéro de page pour la pagination.
Name
page_size
Type
integer
défaut 10
Description
Limite de taille de page. Le maximum autorisé est de 100 éléments.
Name
sort_by
Type
string
défaut -created_at
Description
Champ selon lequel trier. Valeurs disponibles :
+created_at : Trier par heure de création dans l'ordre croissant.
-created_at : Trier par heure de création dans l'ordre décroissant.
L'objet de tâche de prototype de touche (Keycap Prototype Task) est une unité de travail que Meshy suit afin de
générer une image de conception de touche finalisée à partir d'une photo source. Le
résultat de cette étape est enchaîné vers l'étape de build
via input_task_id et candidate_id.
Propriétés
Name
id
Type
string
Description
Identifiant unique de la tâche. Bien que nous utilisions un UUID triable en k comme détail d'implémentation pour les identifiants de tâche, vous ne devez pas faire d'hypothèses sur le format de l'id.
Name
type
Type
string
Description
Type de la tâche. La valeur est creative-lab-keycap-prototype.
Name
name
Type
string
Description
Le nom de la tâche fourni lors de sa création. Chaîne vide si aucun nom n'a été fourni.
Name
status
Type
string
Description
Statut de la tâche. Les valeurs possibles sont PENDING, IN_PROGRESS, SUCCEEDED, FAILED, CANCELED.
Name
progress
Type
integer
Description
Progression de la tâche. Si la tâche n'a pas encore démarré, cette propriété sera 0. Une fois la tâche réussie, elle deviendra 100.
Name
created_at
Type
timestamp
Description
Horodatage de la création de la tâche, en millisecondes.
Un horodatage représente le nombre de millisecondes écoulées depuis le 1er janvier 1970 UTC, selon
la norme RFC 3339.
Par exemple, le vendredi 1er septembre 2023 à 12h00m00s GMT est représenté par 1693569600000. Cela s'applique
à tous les horodatages de l'API Meshy.
Name
started_at
Type
timestamp
Description
Horodatage du démarrage de la tâche, en millisecondes. Si la tâche n'a pas encore démarré, cette propriété sera 0.
Name
finished_at
Type
timestamp
Description
Horodatage de la fin de la tâche, en millisecondes. Si la tâche n'est pas encore terminée, cette propriété sera 0.
Name
expires_at
Type
timestamp
Description
Horodatage de l'expiration du résultat de la tâche, en millisecondes.
Name
preceding_tasks
Type
integer
Description
Le nombre de tâches précédentes.
La valeur de ce champ n'est significative que si le statut de la tâche est PENDING.
Name
task_error
Type
object
Description
Détails d'erreur pour les tâches échouées. Consultez Erreurs pour la référence complète de l'objet task_error.
Name
consumed_credits
Type
integer
Description
Le nombre de crédits consommés par cette tâche. Une tâche qui atteint SUCCEEDED est facturée du montant total de son étape. Une tâche qui n'est jamais créée (une erreur 4xx au moment de la requête, y compris un rejet par la modération) n'est pas facturée du tout. Une tâche qui atteint FAILED renvoie 0 — les frais sont remboursés, y compris en cas de blocage par la modération asynchrone. L'annulation via DELETE ne rembourse que si la tâche est encore PENDING ; une tâche déjà IN_PROGRESS reste facturée, car le travail a déjà été effectué.
Name
image_urls
Type
array of strings
Description
URL téléchargeable du rendu de la conception de touche finalisée — l'apparence du candidat en tant que touche finie. Contient une seule entrée ; image_urls[i] correspond à candidate_ids[i]. Vide jusqu'à ce que la tâche atteigne SUCCEEDED. L'URL est uniquement destinée à l'affichage ; le point de terminaison de build consomme les candidate_ids, pas ces URL. Même cycle de vie d'URL que model_urls : signée, sans en-tête Authorization, valide jusqu'à expires_at, et stable lors d'une nouvelle lecture de la tâche.
Name
candidate_ids
Type
array of strings
Description
Identifiants de candidats opaques, en parallèle de image_urls. Transmettez l'entrée correspondant à la conception choisie en tant que candidate_id de la requête de build. Ne faites pas d'hypothèses sur le format de ces identifiants.
L'objet Keycap Build Task est une unité de travail que Meshy suit pour
générer le modèle 3D de keycap final texturé à partir d'une tâche de prototype réussie et d'un
candidat choisi. Une seule génération exécute le pipeline complet — génération du
modèle blanc, positionnement et découpe automatiques, coloration, assemblage et export.
Propriétés
Name
id
Type
string
Description
Identifiant unique de la tâche.
Name
type
Type
string
Description
Type de la tâche. La valeur est creative-lab-keycap-build.
Name
name
Type
string
Description
Le nom de la tâche fourni lors de sa création. Chaîne vide si aucun nom n'a été fourni.
Name
status
Type
string
Description
Statut de la tâche. Les valeurs possibles sont PENDING, IN_PROGRESS, SUCCEEDED, FAILED, CANCELED.
Name
progress
Type
integer
Description
Progression de la tâche. Si la tâche n'a pas encore démarré, cette propriété vaudra 0. Une fois la tâche réussie, elle deviendra 100.
Name
created_at
Type
timestamp
Description
Horodatage de la création de la tâche, en millisecondes.
Name
started_at
Type
timestamp
Description
Horodatage du démarrage de la tâche, en millisecondes.
Name
finished_at
Type
timestamp
Description
Horodatage de la fin de la tâche, en millisecondes.
Name
expires_at
Type
timestamp
Description
Horodatage de l'expiration du résultat de la tâche, en millisecondes.
Name
preceding_tasks
Type
integer
Description
Le nombre de tâches précédentes. Pertinent uniquement lorsque le statut est PENDING.
Name
task_error
Type
object
Description
Détails d'erreur pour les tâches échouées. Voir Erreurs pour la référence complète de l'objet task_error.
Name
consumed_credits
Type
integer
Description
Le nombre de crédits consommés par cette tâche. Une tâche qui atteint SUCCEEDED est facturée au montant complet pour son étape. Une tâche qui n'est jamais créée (un 4xx au moment de la requête, y compris un rejet de modération) n'est pas facturée du tout. Une tâche qui atteint FAILED renvoie 0 — le montant est remboursé, y compris en cas de blocage de modération asynchrone. L'annulation via DELETE ne rembourse que si la tâche est encore PENDING ; une tâche déjà IN_PROGRESS reste facturée, car le travail a déjà été effectué.
Name
model_urls
Type
object
Description
URL téléchargeables pour les fichiers du modèle généré. Le GLB et le bundle OBJ sont tous deux exportés à l'échelle réelle en millimètres, avec Y vers le haut, l'avant du keycap étant orienté vers +Z. Les maillages sont nommés keycap-head et keycap-base ; lorsque la base se rabat sur un remplissage à motif, un troisième maillage keycap-base-interior est également présent pour la cavité de la tige. Ne pas supposer qu'il y a exactement deux maillages.
Ce sont des URL signées : récupérez-les sans en-tête Authorization. Elles restent valides jusqu'à expires_at, soit 3 jours après finished_at, et relire la tâche pendant cette période renvoie l'URL identique plutôt qu'une nouvelle URL signée. Téléchargez et stockez vous-même les fichiers avant cela — il n'existe aucun moyen de rafraîchir un lien expiré.
Name
glb
Type
string
Description
URL téléchargeable vers le model.glb final texturé.
Name
obj_zip
Type
string
Description
URL téléchargeable vers une archive zip contenant model.obj, model.mtl, et les textures PNG que son MTL référence réellement. Une base de couleur unie ne fournit que keycap-head.png ; une base à motif fournit également keycap-base.png.
Name
process_image_urls
Type
object
Description
URL téléchargeables pour les images de processus intermédiaires, indexées par type. Même cycle de vie d'URL que model_urls : signées, sans en-tête Authorization, valides jusqu'à expires_at, et stables lorsque la tâche est relue. Types actuellement émis :
head_design — l'image de design du candidat choisi consommée par la génération (toujours présente).
composite — le rendu d'affichage du keycap terminé pour le candidat choisi (présent si disponible).
base_canvas — le canevas peint de la base du keycap (présent si disponible).
Considérez cet ensemble de clés comme ouvert ; de nouveaux types peuvent être ajoutés sans rupture de compatibilité.
Le flux complet : créer un prototype à partir d'une photo, l'interroger jusqu'à
SUCCEEDED, choisir un candidat parmi candidate_ids, créer un build avec
ce candidat, interroger le build jusqu'à SUCCEEDED, puis télécharger le GLB et
le bundle OBJ depuis model_urls.
L'exemple choisit le premier candidat de manière programmatique. Dans une
intégration réelle, vous afficheriez l'entrée image_urls à l'utilisateur final et
le laisseriez choisir ; l'index choisi correspond 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"