Home / API

Photo and video to AR, by API

Send an image, get back a USDZ for iPhone and a GLB for Android at real size, plus a hosted AR viewer link. Send a video, get a photogrammetry 3D model.

ConversionPriceSpeed
Photo to AR (USDZ + GLB)$0.15Synchronous, about 1 to 3 s
Video to 3D model (USDZ + GLB)$1.50Queued, usually minutes

You only pay for results

  • The price is held from your prepaid balance when a conversion starts and charged only when it finishes with files. Anything that fails releases the hold automatically.
  • Requests rejected before work starts (bad input, missing balance, rate limits) are never charged.
  • Send an Idempotency-Key header: a retry with the same key returns the same conversion and is never charged twice.
  • Every hold, charge and release is listed in GET /v1/usage. Each conversion response has charged and an x-charged-usd header.
  • A daily spending cap (default $50, adjustable in your account) protects you if a key leaks.

Create a key and add balance in your account.

Authentication

Send your key as Authorization: Bearer jtu_live_…. Keep it on your server; never ship it in a browser or app. Revoke it instantly from your account.

Convert a photo

POST /v1/conversions with multipart/form-data: file (JPG, PNG, HEIC, WebP, AVIF, TIFF, GIF; up to 40 MB and 50 megapixels) and optional width_cm or height_cm.

curl -X POST https://jpgtousdz.com/v1/conversions \
  -H "Authorization: Bearer $JTU_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F file=@poster.jpg \
  -F width_cm=50

Response (200):

{
  "id": "Qm3k9Zr1a8Hc2LxD",
  "type": "photo",
  "status": "done",
  "charged": true,
  "width_cm": 50,
  "height_cm": 70,
  "files": {
    "usdz": "https://jpgtousdz.com/f/Qm3k9Zr1a8Hc2LxD/model.usdz",
    "glb": "https://jpgtousdz.com/f/Qm3k9Zr1a8Hc2LxD/model.glb",
    "poster": "https://jpgtousdz.com/f/Qm3k9Zr1a8Hc2LxD/poster.webp"
  },
  "viewer_url": "https://jpgtousdz.com/v/Qm3k9Zr1a8Hc2LxD"
}

From a URL

curl -X POST https://jpgtousdz.com/v1/conversions \
  -H "Authorization: Bearer $JTU_KEY" \
  -H "Content-Type: application/json" \
  -d '{"source_url":"https://example.com/art.png","height_cm":80}'

JavaScript

const form = new FormData();
form.append("file", fileInput.files[0]);
form.append("width_cm", "60");
const res = await fetch("https://jpgtousdz.com/v1/conversions", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.JTU_KEY}`, "Idempotency-Key": crypto.randomUUID() },
  body: form,
});
const { files } = await res.json(); // files.usdz, files.glb

Convert a video to 3D

Videos go straight to storage with a signed URL, then join the 3D queue. 5 seconds to 5 minutes, up to 1 GB, up to 4K.

# 1. Get an upload URL
curl -X POST https://jpgtousdz.com/v1/uploads -H "Authorization: Bearer $JTU_KEY" \
  -H "Content-Type: application/json" -d '{"content_type":"video/mp4","size":48211000}'
# → {"upload_id":"…","upload_url":"…","method":"PUT","headers":{"Content-Type":"video/mp4","x-goog-content-length-range":"1,1073741824"}}

# 2. Upload the file, sending exactly the returned headers
curl -X PUT "$UPLOAD_URL" -H "Content-Type: video/mp4" -H "x-goog-content-length-range: 1,1073741824" --data-binary @chair.mp4

# 3. Start the 3D conversion (height of the real object, in cm)
curl -X POST https://jpgtousdz.com/v1/conversions -H "Authorization: Bearer $JTU_KEY" \
  -H "Content-Type: application/json" -d '{"upload_id":"…","type":"video","height_cm":82}'
# → 202 {"status":"queued","charged":false,"queue":{"ahead":2,"eta_minutes":24}}

# 4. Poll until status is "done" (charged) or "failed" (not charged)
curl https://jpgtousdz.com/v1/conversions/ID -H "Authorization: Bearer $JTU_KEY"

Endpoints

MethodPathWhat it does
POST/v1/conversionsMultipart file, or JSON with source_url or upload_id. Fields: type (photo|video), width_cm, height_cm, name. Header: Idempotency-Key (recommended).
GET/v1/conversions/{id}Status: processing, queued, done, failed. Includes queue and progress for videos.
POST/v1/uploadsReturns a signed upload_url, the headers to send with the PUT, and an upload_id. Body: content_type, size.
GET/v1/accountBalance, daily cap and prices.
GET/v1/usageYour last 100 holds, charges, releases and top-ups.

Errors

StatusMeaning
401Missing or invalid API key.
402Not enough balance (insufficient_balance).
413File too large.
415Unsupported or corrupt file.
422Video can't be used, or an Idempotency-Key was reused with different input.
429Rate limit or daily spending cap reached.

Error bodies look like {"error":{"message":"…","code":"…"}}.

Showing the model on your site

<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<model-viewer src="FILES.glb" ios-src="FILES.usdz" ar ar-placement="wall" camera-controls></model-viewer>