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.
| Conversion | Price | Speed |
|---|---|---|
| Photo to AR (USDZ + GLB) | $0.15 | Synchronous, about 1 to 3 s |
| Video to 3D model (USDZ + GLB) | $1.50 | Queued, 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-Keyheader: 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 haschargedand anx-charged-usdheader. - 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=50Response (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
| Method | Path | What it does |
|---|---|---|
| POST | /v1/conversions | Multipart 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/uploads | Returns a signed upload_url, the headers to send with the PUT, and an upload_id. Body: content_type, size. |
| GET | /v1/account | Balance, daily cap and prices. |
| GET | /v1/usage | Your last 100 holds, charges, releases and top-ups. |
Errors
| Status | Meaning |
|---|---|
| 401 | Missing or invalid API key. |
| 402 | Not enough balance (insufficient_balance). |
| 413 | File too large. |
| 415 | Unsupported or corrupt file. |
| 422 | Video can't be used, or an Idempotency-Key was reused with different input. |
| 429 | Rate 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>