Förvandla ett källfoto till en fullfärgad, anpassad mekanisk tangentbordskeycap i
två steg: prototype genererar en design-rendering av en "färdig keycap"
utifrån ditt källfoto. När du har bekräftat den renderingen omvandlar build
den till en texturerad 3D-keycapmodell i en enda körning — generering av
vitmodell, automatisk placering och kapning i en kalibrerad standardpose,
färgsättning av hela modellen och slutlig sammansättning sker allt inom en
enda byggtask. De två stegen kopplas samman via input_task_id plus
candidate_id.
POST /openapi/creative-lab/keycap/v1/prototype
POST /openapi/creative-lab/keycap/v1/build
Båda POST-endpointerna kräver en betald prenumerationsplan. Förfrågningar
från konton på gratisplanen avvisas med 402 Payment Required.
Genererar en färdig renderad keycap-design utifrån källfotot. Uppgiftens resultat innehåller en image_urls-array (den visade renderingen av den färdiga keycapen) och en parallell candidate_ids-array; båda innehåller en enda post.
Anropa detta endpoint igen för att få en ny rendering om resultatet inte blev som du ville — varje anrop debiteras separat. Skicka candidate_id tillsammans med prototypuppgiftens ID till build-endpointen.
Se
The Keycap Prototype Task Object
för svarets format.
Parametrar
Name
image_url
Type
string
Obligatorisk
Description
Källfoto som Meshy ska omvandla till keycap-designbilder. Vi stöder för närvarande formaten .jpg, .jpeg, .png och .webp.
Formatet identifieras genom att avkoda bilddatan, inte utifrån URL:ens filändelse — en URL utan filändelse, eller en som omdirigerar, fungerar så länge byte-datan går att avkoda till ett format som stöds. HTTP-omdirigeringar följs. EXIF-orientering normaliseras, så ett roterat mobilfoto används på det sätt det ser ut.
Begränsningar: minst 32 pixlar på varje sida, högst 178 956 970 pixlar totalt, och högst 20 000 000 byte efter nedladdning. För en Data URI gäller gränsen de avkodade byten, så själva källfilen kan vara upp till den storleken — det är base64-texten som är cirka en tredjedel större, vilket har betydelse för din request-body, inte för denna gräns. En Data URI måste ange en image/*-innehållstyp och ;base64.
Det finns två sätt att ange bilden på:
Publikt tillgänglig URL: En URL som är tillgänglig från det publika internet.
Data URI: En base64-kodad data-URI för bilden. Exempel på en Data URI: data:image/jpeg;base64,<din base64-kodade bilddata>.
Name
name
Type
string
Description
Valfritt uppgiftsnamn för visningsändamål. Maximalt 100 tecken.
Name
remove_background
Type
boolean
standard false
Description
När detta sätts till true är den visade renderingen som returneras i image_urls en transparent RGBA PNG med bakgrunden borttagen, så att du kan sätta ihop den med valfri bakgrund.
Detta gäller endast den visade renderingen. Kandidaten som build-endpointen använder påverkas inte, så 3D-resultatet blir identiskt oavsett.
Returnerar
Svarets result-egenskap innehåller uppgifts-id:t för den nyligen skapade keycap-prototypuppgiften. Fråga Get a Task-endpointen eller prenumerera på streamen tills uppgiften når SUCCEEDED, och hämta sedan posten från candidate_ids och skicka den, tillsammans med uppgifts-ID:t, till build-endpointen.
Felscenarier
Name
400 - Bad Request
Description
Begäran kunde inte accepteras. Vanliga orsaker:
Saknad parameter: image_url krävs.
Ogiltigt bildformat: Den angivna image_url har inte ett format som stöds (.jpg, .jpeg, .png, .webp).
Bilddimensioner utanför tillåtet intervall: Bilden är för liten, överskrider den maximala filstorleken, eller överskrider det maximala antalet pixlar.
Onåbar URL: image_url kunde inte hämtas (404 eller timeout).
Ogiltig Data URI: Base64-strängen är felaktigt formaterad.
Innehåll flaggat: Den angivna bilden flaggades av NSFW-moderation.
Name
401 - Unauthorized
Description
Autentiseringen misslyckades. Kontrollera din API-nyckel.
Name
402 - Payment Required
Description
Kontot använder den kostnadsfria planen (en betald plan krävs för att skapa uppgifter) eller har otillräckligt med credits.
Name
403 - Forbidden
Description
Den angivna bilden flaggades av moderation för immateriella rättigheter.
Name
429 - Too Many Requests
Description
Du har överskridit din hastighetsgräns.
Name
500 - Internal Server Error
Description
Ett oväntat serverfel inträffade — till exempel att innehållsmodereringstjänsten var otillgänglig, att förberedelsen (staging) av den angivna bilden misslyckades, eller att uppgiften inte kunde skapas. I detta fall skapas ingen uppgift, så det är säkert att försöka igen.
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>" }'
Generera den slutliga texturerade 3D-keycap-modellen från en lyckad prototyp-uppgift och en av dess kandidater. En enda build-uppgift kör hela pipelinen från start till slut — generering av vitmodell från den valda designen, automatisk placering och skärning på keycap-basen med hjälp av en kalibrerad standardpose (ingen interaktiv justering behövs), färgläggning av hela modellen samt slutlig sammansättning och export. En build tar vanligtvis 3–7 minuter, mot den övre gränsen när flera builds körs samtidigt.
Se The Keycap Build Task Object
för svarets format.
Parametrar
Name
input_task_id
Type
string
Obligatorisk
Description
Uppgifts-ID för en prototyp-uppgift som skapats via samma OpenAPI-endpoint. Prototypen måste ha skapats av samma Meshy-konto, måste ha nått SUCCEEDED och måste ha genererat minst en kandidat.
Prototyp-uppgifter skapade via webbappen accepteras inte — build-endpointen accepterar endast prototyp-uppgifter som skapats via POST /openapi/creative-lab/keycap/v1/prototype och avvisar alla andra källor med 404.
Name
candidate_id
Type
string
Obligatorisk
Description
Kandidaten som ska byggas, hämtad från candidate_ids-arrayen i den lyckade prototyp-uppgiften. Måste tillhöra den uppgiften; alla andra värden avvisas med 400.
Name
name
Type
string
Description
Valfritt uppgiftsnamn för visningsändamål. Maximalt 100 tecken.
options
Valfri geometrijustering. Varje fält har ett kalibrerat standardvärde — skicka endast de du vill åsidosätta.
Name
base_model
Type
string
standard cherry-mx-1x1-r1
Description
Keycap-basen att bygga på. För närvarande är det enda tillgängliga värdet
cherry-mx-1x1-r1 — en standard Cherry MX-profil 1u keycap. 3–5
ytterligare vanliga standardstorlekar är planerade; anpassade storlekar
stöds inte.
Name
head_size_mm
Type
number
standard 23
Description
Målstorlek för det skulpterade huvudet, i millimeter: dess längsta dimension skalas till detta värde. Intervall: [10, 40]. Värden över ungefär 32.9 kan reduceras så att huvudet fortfarande får plats inom basens skyddande fotavtrycksgräns, så den levererade längsta dimensionen kan bli mindre än begärt. Det tillämpade värdet återges inte i uppgiftsobjektet idag — om du behöver bekräfta storleken du faktiskt fick, mät begränsningsrutan för keycap-head-nätet i den nedladdade modellen.
Name
vertical_offset_mm
Type
number
standard 0
Description
Vertikal förskjutning som appliceras på huvudet innan det placeras på basen, i millimeter. Intervall: [-5, 5].
Returer
Egenskapen result i svaret innehåller uppgifts-id för den nyskapade keycap build-uppgiften. Fråga Get a Task-endpointen eller prenumerera på stream tills uppgiften når SUCCEEDED, ladda sedan ner artefakterna från model_urls.glb och model_urls.obj_zip.
Både GLB och OBJ-paketet exporteras i verklig
millimeterskala, med ett Y-upp-koordinatsystem och framsidan av
keycappen riktad mot +Z.
Felläge
Name
400 - Bad Request
Description
Förfrågan var oacceptabel. Vanliga orsaker:
Saknad parameter: input_task_id och candidate_id krävs.
Ogiltig UUID: input_task_id är inte en giltig UUID.
Överordnad ej lyckad: Den refererade prototyp-uppgiften har ännu inte nått SUCCEEDED.
Inga kandidater: Prototyp-uppgiften lyckades men genererade inga kandidater.
Okänd kandidat: candidate_id är inte en av inmatningsuppgiftens kandidater.
Alternativ utanför intervall: Ett av fälten i options låg utanför sitt tillåtna intervall eller enum-uppsättning.
Name
401 - Unauthorized
Description
Autentiseringen misslyckades. Kontrollera din API-nyckel.
Name
402 - Payment Required
Description
Kontot har den kostnadsfria planen (en betald plan krävs för att skapa uppgifter) eller har otillräckliga credits.
Name
404 - Not Found
Description
Den refererade prototyp-uppgiften finns inte, tillhör en annan användare, eller skapades via webbappen (endast prototyp-uppgifter i API-mode kan kedjas till build).
Name
429 - Too Many Requests
Description
Du har överskridit din hastighetsgräns.
Name
500 - Internal Server Error
Description
Ett oväntat serverfel inträffade — till exempel var innehållsmoderationstjänsten otillgänglig, det gick inte att förbereda indatabilden, eller uppgiften kunde inte skapas. Ingen uppgift skapas i detta fall, så det är säkert att försöka igen.
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 } }'
Hämta en prototyp- eller byggnadsuppgift givet ett giltigt uppgifts-id. URL-sökvägen
måste matcha uppgiftens skede — en byggnadsuppgift som hämtas via
/prototype/:id returnerar 404, och vice versa.
Avbryt en keycap-uppgift. Om uppgiften fortfarande är PENDING
återbetalas de credits som förbrukades vid skapandet. Uppgifter som redan är
IN_PROGRESS avbryts utan återbetalning (arbetaren kan redan hålla på att
förbruka resurser). Uppgifter som redan har nått ett slutgiltigt tillstånd
(SUCCEEDED, FAILED, CANCELED) kan inte avbrytas.
URL-sökvägen måste matcha uppgiftens steg — DELETE på
/prototype/:buildId returnerar 404.
Sökvägsparametrar
Name
id
Type
path
Description
Unik identifierare för keycap-uppgiften som ska avbrytas.
Returnerar
Returnerar 204 No Content vid lyckat anrop med en tom body.
Felläge
Name
400 - Bad Request
Description
Uppgiften är redan i ett slutgiltigt tillstånd och kan inte avbrytas.
Name
404 - Not Found
Description
Uppgiften finns inte, tillhör en annan användare, eller dess steg matchar inte URL-sökvägen.
Name
500 - Internal Server Error
Description
Ett oväntat serverfel uppstod vid avbrytning. Uppgiften kan eller kan inte ha blivit avbruten — läs den igen för att bekräfta innan du försöker igen.
Streama realtidsuppdateringar för en keycap-uppgift via Server-Sent Events (SSE).
URL-sökvägen måste matcha uppgiftens fas — att öppna en ström vid
/prototype/:buildId/stream skickar en enda event: error-payload med
status_code: 404 och stänger sedan strömmen.
Parametrar
Name
id
Type
path
Description
Unik identifierare för den keycap-uppgift som ska strömmas.
Returnerar
Returnerar en ström av Keycap Prototype-
eller Keycap Build-uppgiftsobjekt som
Server-Sent Events. Varje bildruta innehåller hela uppgiftsobjektet för fasen — samma form som
Get-endpointen returnerar — så medan uppgiften är PENDING eller IN_PROGRESS är
utdatafälten helt enkelt inte ifyllda ännu (null, [] eller {}) och
finished_at är 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=***" }}
Hämta en sidindelad lista över dina keycap-uppgifter för ett enskilt steg. URL-sökvägen
väljer steget — /prototype returnerar prototypuppgifter; /build
returnerar byggnadsuppgifter. Uppgifter från det andra steget ingår inte i något av
svaren.
Sökvägsparametrar
Name
stage
Type
path
Obligatorisk
Description
Antingen prototype eller build. Samlingen returnerar endast uppgifter
vars steg matchar URL:en — att hämta /prototype returnerar aldrig
byggnadsuppgifter och vice versa.
Frågeparametrar
Name
page_num
Type
integer
standard 1
Description
Sidnummer för paginering.
Name
page_size
Type
integer
standard 10
Description
Gräns för sidstorlek. Maximalt tillåtet är 100 objekt.
Name
sort_by
Type
string
standard -created_at
Description
Fält att sortera efter. Tillgängliga värden:
+created_at: Sortera efter skapandetid i stigande ordning.
-created_at: Sortera efter skapandetid i fallande ordning.
Objektet Keycap Prototype Task är en arbetsenhet som Meshy håller reda på för att
generera en bild av den färdiga keycap-designen från ett källfoto. Resultatet
av detta steg kopplas vidare till byggsteget
via input_task_id plus candidate_id.
Egenskaper
Name
id
Type
string
Description
Unik identifierare för uppgiften. Även om vi använder en k-sorterbar UUID för uppgiftsid:n som implementationsdetalj, bör du inte göra några antaganden om formatet på id:t.
Name
type
Type
string
Description
Uppgiftens typ. Värdet är creative-lab-keycap-prototype.
Name
name
Type
string
Description
Uppgiftens namn som angavs när uppgiften skapades. Tom sträng om inget namn angavs.
Name
status
Type
string
Description
Uppgiftens status. Möjliga värden är ett av PENDING, IN_PROGRESS, SUCCEEDED, FAILED, CANCELED.
Name
progress
Type
integer
Description
Uppgiftens progress. Om uppgiften ännu inte har startat är denna egenskap 0. När uppgiften har lyckats blir den 100.
Name
created_at
Type
timestamp
Description
Tidsstämpel för när uppgiften skapades, i millisekunder.
En tidsstämpel representerar antalet millisekunder som förflutit sedan 1 januari 1970 UTC, enligt
standarden RFC 3339.
Till exempel representeras fredagen den 1 september 2023 12:00:00 PM GMT som 1693569600000. Detta gäller
för alla tidsstämplar i Meshy API.
Name
started_at
Type
timestamp
Description
Tidsstämpel för när uppgiften startades, i millisekunder. Om uppgiften ännu inte har startat är denna egenskap 0.
Name
finished_at
Type
timestamp
Description
Tidsstämpel för när uppgiften avslutades, i millisekunder. Om uppgiften ännu inte är avslutad är denna egenskap 0.
Name
expires_at
Type
timestamp
Description
Tidsstämpel för när uppgiftens resultat upphör att gälla, i millisekunder.
Name
preceding_tasks
Type
integer
Description
Antalet föregående uppgifter.
Värdet i detta fält är endast meningsfullt om uppgiftens status är PENDING.
Name
task_error
Type
object
Description
Felinformation för misslyckade uppgifter. Se Fel för den fullständiga referensen för objektet task_error.
Name
consumed_credits
Type
integer
Description
Antalet credits som förbrukats av denna uppgift. En uppgift som når SUCCEEDED debiteras hela beloppet för sitt steg. En uppgift som aldrig skapas (ett 4xx-fel vid förfrågningstillfället, inklusive ett moderation-avslag) debiteras inte alls. En uppgift som når FAILED returnerar 0 — avgiften återbetalas, inklusive en asynkron moderation-blockering. Att avbryta via DELETE återbetalar endast medan uppgiften fortfarande är PENDING; en uppgift som redan är IN_PROGRESS förblir debiterad, eftersom arbetet redan har utförts.
Name
image_urls
Type
array of strings
Description
Nedladdningsbar URL för renderingen av den färdiga keycap-designen — hur kandidaten ser ut som färdig keycap. Innehåller en enda post; image_urls[i] motsvarar candidate_ids[i]. Tom tills uppgiften når SUCCEEDED. URL:en är endast för visning; build-endpointen använder candidate_ids, inte dessa URL:er. Samma URL-livscykel som model_urls: signerad, ingen Authorization-header, giltig till expires_at, och stabil när uppgiften läses om.
Name
candidate_ids
Type
array of strings
Description
Opaka kandidatidentifierare, parallella med image_urls. Skicka posten som motsvarar din valda design som candidate_id i build-förfrågan. Gör inte några antaganden om formatet på dessa id:n.
Keycap Build Task-objektet är en arbetsenhet som Meshy håller reda på för att
generera den slutliga texturerade 3D-keycappen från en lyckad prototyptask och
en vald kandidat. En enskild build körs genom hela pipelinen — vitmodellsgenerering,
automatisk placering och kapning, färgsättning, sammansättning och export.
Egenskaper
Name
id
Type
string
Description
Unik identifierare för tasken.
Name
type
Type
string
Description
Taskens typ. Värdet är creative-lab-keycap-build.
Name
name
Type
string
Description
Tasknamnet som angavs när tasken skapades. Tom sträng om inget namn angavs.
Name
status
Type
string
Description
Taskens status. Möjliga värden är ett av PENDING, IN_PROGRESS, SUCCEEDED, FAILED, CANCELED.
Name
progress
Type
integer
Description
Taskens progress. Om tasken ännu inte har startats är denna egenskap 0. När tasken har lyckats blir den 100.
Name
created_at
Type
timestamp
Description
Tidsstämpel för när tasken skapades, i millisekunder.
Name
started_at
Type
timestamp
Description
Tidsstämpel för när tasken startades, i millisekunder.
Name
finished_at
Type
timestamp
Description
Tidsstämpel för när tasken avslutades, i millisekunder.
Name
expires_at
Type
timestamp
Description
Tidsstämpel för när taskens resultat upphör att gälla, i millisekunder.
Name
preceding_tasks
Type
integer
Description
Antalet föregående tasks. Är endast meningsfullt när status är PENDING.
Name
task_error
Type
object
Description
Felinformation för misslyckade tasks. Se Fel för den fullständiga referensen för task_error-objektet.
Name
consumed_credits
Type
integer
Description
Antalet credits som förbrukats av denna task. En task som når SUCCEEDED debiteras hela beloppet för sitt steg. En task som aldrig skapas (ett 4xx-fel vid begäranstillfället, inklusive en moderation-avvisning) debiteras inte alls. En task som når FAILED returnerar 0 — avgiften återbetalas, inklusive vid en asynkron moderation-blockering. Att avbryta via DELETE återbetalar endast medan tasken fortfarande är PENDING; en task som redan är IN_PROGRESS förblir debiterad, eftersom arbetet redan har utförts.
Name
model_urls
Type
object
Description
Nedladdningsbara URL:er för de genererade modellfilerna. Både GLB- och OBJ-paketet exporteras i verklig skala i millimeter, Y-upp, med framsidan av keycappen riktad mot +Z. Näten heter keycap-head och keycap-base; när basen faller tillbaka på en mönsterfyllning finns även ett tredje nät, keycap-base-interior, för stamhålan. Utgå inte från att det alltid finns exakt två nät.
Dessa är signerade URL:er: hämta dem utan en Authorization-header. De förblir giltiga tills expires_at, som är 3 dagar efter finished_at, och om tasken läses igen inom det fönstret returneras samma URL istället för en nysignerad. Ladda ner och spara filerna själv innan det inträffar — det finns inget sätt att uppdatera en utgången länk.
Name
glb
Type
string
Description
Nedladdningsbar URL till den slutliga texturerade model.glb.
Name
obj_zip
Type
string
Description
Nedladdningsbar URL till ett zip-paket som innehåller model.obj, model.mtl och de texturer i PNG-format som dess MTL faktiskt refererar till. En bas med enfärgad yta levereras endast med keycap-head.png; en bas med mönster levereras även med keycap-base.png.
Name
process_image_urls
Type
object
Description
Nedladdningsbara URL:er för mellanliggande processbilder, nyckelbaserade efter typ. Samma URL-livscykel som model_urls: signerad, ingen Authorization-header, giltig tills expires_at, och stabil när tasken läses igen. Typer som för närvarande genereras:
head_design — designbilden för den valda kandidaten som build-processen använde (finns alltid).
composite — den färdiga keycap-visningsrendreringen av den valda kandidaten (finns när tillgänglig).
base_canvas — den målade keycap-bas-canvasen (finns när tillgänglig).
Betrakta nyckelsetet som öppet för utökning; nya typer kan läggas till utan att det innebär en brytande ändring.
Det fullständiga flödet: skapa en prototyp från ett foto, pollra den till
SUCCEEDED, välj en kandidat från candidate_ids, skapa en build med
den kandidaten, pollra builden till SUCCEEDED, och ladda sedan ner GLB-
och OBJ-paketet från model_urls.
Exemplet väljer den första kandidaten programmatiskt. I en verklig
integration skulle du visa posten image_urls för slutanvändaren och
låta dem välja; det valda indexet mappar 1:1 mot 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"