External API — Developer Guide

Convert any image into an AR model and embed it on your website with one line of HTML.

Authentication

Every API request must include your API key in the X-API-Key header. Keep this key secret — never expose it in client-side JavaScript on a public page.

HeaderValue
X-API-KeyYour VISIONARI_API_KEY

Option A — Fully Automatic (no backend code)

Add one script tag and one div to your HTML. The widget handles everything: submits the image, waits for conversion, and renders the AR viewer automatically.

The API key is embedded in the HTML attribute below. Only use this on private/internal pages or if you have a read-only API key. For public pages use Option B.

Interactive variant animated — launches AR on mobile

HTML
<!-- 1. Load the widget script once, anywhere in the page -->
<script src="https://visionarispace.com/api/v1/widget.js"></script>

<!-- 2. Place a div where you want the AR widget to appear -->
<div
  data-visionari-image="https://your-cdn.com/product.jpg"
  data-visionari-key="YOUR_API_KEY"
  data-visionari-variant="interactive"
  style="width:400px; height:480px;"
></div>

Static variant display only — image + badge, no button

HTML
<script src="https://visionarispace.com/api/v1/widget.js"></script>

<div
  data-visionari-image="https://your-cdn.com/product.jpg"
  data-visionari-key="YOUR_API_KEY"
  data-visionari-variant="static"
  style="width:400px; height:480px;"
></div>

Widget attributes

AttributeRequiredDescription
data-visionari-imageYes*Public URL of the image to convert. Use this OR data-visionari-job.
data-visionari-jobYes*An existing job_id from a previous /convert call. Use this OR data-visionari-image.
data-visionari-keyYesYour API key.
data-visionari-variantNointeractive (default) or static.

Aspect Ratio & Dimensions

By default, the API reads the uploaded image's pixel dimensions and automatically sets the AR plane to the correct proportions — a 900×600 landscape image produces a 36″×24″ plane with no distortion. You can override this in two ways.

Option 1 — Default: auto-detect from the image

Omit all dimension parameters. The API detects the pixel size and computes proportional inches (24″ base for the shorter side). Square images stay at 24″×24″.

{
  "image_url": "https://cdn.example.com/mural-900x600.jpg"
}

Result: AR plane is 36″×24″ (3:2 ratio)

Option 2 — Shorthand: aspect_ratio

Pass a ratio string when you know the proportions but not the exact inches. Format: "W:H". The API sets 24″ for the shorter side and scales the other accordingly.

{
  "image_url": "https://cdn.example.com/mural.jpg",
  "aspect_ratio": "3:2"
}

Other examples: "16:9" → 42.67″×24″  |  "2:3" (portrait) → 24″×36″  |  "1:1" → 24″×24″

Option 3 — Explicit: width_inches + height_inches

Provide both values for precise physical sizing. Both must be supplied together (0.1–120 inches each). Overrides aspect_ratio if also present.

{
  "image_url": "https://cdn.example.com/mural.jpg",
  "width_inches": 48,
  "height_inches": 32
}

Option B — Backend-first (your server controls the conversion)

Your backend calls /api/v1/convert, gets a job_id, stores it, then passes it to the widget div in your HTML. The API key never touches the browser.

1

Submit the image from your backend

Python
Node.js
PHP
import requests

response = requests.post(
    "https://visionarispace.com/api/v1/convert",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json"
    },
    json={
        "image_url": "https://your-cdn.com/product.jpg"
        # Dimensions are auto-detected from the image pixels.
        # Use aspect_ratio="3:2" for landscape/mural images, or
        # supply width_inches + height_inches for precise control.
    }
)

data = response.json()
job_id = data["job_id"]   # e.g. "api_a1b2c3d4..."
# Store job_id in your database or session
const response = await fetch('https://visionarispace.com/api/v1/convert', {
  method: 'POST',
  headers: {
    'X-API-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    image_url: 'https://your-cdn.com/product.jpg'
    // Dimensions are auto-detected from the image pixels.
    // Use aspect_ratio: '3:2' for landscape/mural images, or
    // supply width_inches + height_inches for precise control.
  })
});

const data = await response.json();
const jobId = data.job_id;   // e.g. "api_a1b2c3d4..."
// Store jobId in your database or session
$ch = curl_init('https://visionarispace.com/api/v1/convert');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'X-API-Key: YOUR_API_KEY',
        'Content-Type: application/json'
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'image_url' => 'https://your-cdn.com/product.jpg'
        // Dimensions are auto-detected from the image pixels.
        // Use 'aspect_ratio' => '3:2' for landscape/mural images, or
        // supply 'width_inches' + 'height_inches' for precise control.
    ])
]);
$data  = json_decode(curl_exec($ch), true);
$jobId = $data['job_id'];  // e.g. "api_a1b2c3d4..."
// Store $jobId in your database or session
2

Render the widget in your HTML (pass the job_id)

HTML
<script src="https://visionarispace.com/api/v1/widget.js"></script>

<!-- Interactive -->
<div
  data-visionari-job="api_a1b2c3d4..."
  data-visionari-key="YOUR_API_KEY"
  data-visionari-variant="interactive"
  style="width:400px; height:480px;"
></div>

<!-- Static -->
<div
  data-visionari-job="api_a1b2c3d4..."
  data-visionari-key="YOUR_API_KEY"
  data-visionari-variant="static"
  style="width:400px; height:480px;"
></div>
3

(Optional) Poll for status from your backend

If you need to wait for completion before rendering (e.g. for server-side HTML rendering), poll the status endpoint:

Python
Node.js
import time

while True:
    r = requests.get(
        f"https://visionarispace.com/api/v1/status/{job_id}",
        headers={"X-API-Key": "YOUR_API_KEY"}
    )
    status = r.json()["status"]
    if status == "completed":
        break
    elif status == "failed":
        raise Exception("Conversion failed")
    time.sleep(3)

result = requests.get(
    f"https://visionarispace.com/api/v1/result/{job_id}",
    headers={"X-API-Key": "YOUR_API_KEY"}
).json()

embed_url          = result["embed_url"]          # animated, clickable
embed_url_static   = result["embed_url_static"]   # display only
async function waitForResult(jobId) {
  while (true) {
    const r = await fetch(`https://visionarispace.com/api/v1/status/${jobId}`, {
      headers: { 'X-API-Key': 'YOUR_API_KEY' }
    });
    const data = await r.json();
    if (data.status === 'completed') break;
    if (data.status === 'failed') throw new Error('Conversion failed');
    await new Promise(res => setTimeout(res, 3000));
  }
  const result = await fetch(`https://visionarispace.com/api/v1/result/${jobId}`, {
    headers: { 'X-API-Key': 'YOUR_API_KEY' }
  }).then(r => r.json());

  return {
    embedUrl:       result.embed_url,         // animated, clickable
    embedUrlStatic: result.embed_url_static   // display only
  };
}

Widget variants

interactive default

Animated "Powered by VisionARI" badge with shimmer. Shows a "View in Your Space" button. On iOS launches USDZ Quick Look; on Android opens Scene Viewer. Best for product pages and showrooms.

static display only

Plain text badge, no animation, no button. Shows the Space Visual image only. Ideal for email-embedded previews, PDFs, or anywhere interaction is not possible.

Programmatic JavaScript API

After the script loads, window.VisionARI is available with two methods:

JavaScript
// Mount using a pre-existing job ID
VisionARI.mount('#my-container', {
  jobId:   'api_a1b2c3d4...',
  apiKey:  'YOUR_API_KEY',
  variant: 'interactive'  // or 'static'
});

// Submit image and auto-render (image_url mode)
VisionARI.embed('#my-container', {
  imageUrl: 'https://your-cdn.com/product.jpg',
  apiKey:   'YOUR_API_KEY',
  variant:  'static'
});

Webhook / callback_url

Pass an optional callback_url in the convert request. VisionARI will POST to it when conversion completes (or fails), so you don't need to poll.

Python
requests.post(
    "https://visionarispace.com/api/v1/convert",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "image_url": "https://your-cdn.com/product.jpg",
        "callback_url": "https://yourapp.com/visionari/webhook"
    }
)

# Your webhook endpoint receives:
# POST /visionari/webhook
# {
#   "job_id": "api_a1b2c3d4...",
#   "status": "completed",          # or "failed"
#   "embed_url": "...",
#   "embed_url_static": "...",
#   "usdz_url": "...",
#   "glb_url": "..."
# }

API Reference

POST /api/v1/convert

ParameterTypeRequiredDescription
image_urlstringYesPublic HTTP/HTTPS URL of the image to convert.
aspect_ratiostringNoConvenience shorthand, e.g. "3:2", "16:9", "1:1". Computes width/height from ratio (24" base for shorter side). Ignored when width_inches + height_inches are both supplied.
width_inchesfloatNoPhysical width in inches. Auto-detected from image when omitted. Range: 0.1–120. Must be paired with height_inches.
height_inchesfloatNoPhysical height in inches. Auto-detected from image when omitted. Range: 0.1–120. Must be paired with width_inches.
callback_urlstringNoWebhook URL to notify on completion.

Response 202: { "job_id": "api_...", "status": "queued" }

GET /api/v1/status/<job_id>

FieldTypeDescription
statusstringqueued | processing | completed | failed
progressfloat0–100 percentage complete.

GET /api/v1/result/<job_id>

FieldDescription
embed_urlInteractive widget URL (animated, clickable AR launch).
embed_url_staticStatic widget URL (display only).
widget_script_urlURL of widget.js to include in your page.
models.usdz.urlDirect Supabase URL to the USDZ model file (iOS AR).
models.glb.urlDirect Supabase URL to the GLB model file (Android AR).
dimensionsPhysical dimensions used for the model (width_inches, height_inches).

GET /api/v1/widget.js — no auth required

Public JavaScript loader. Include once per page with a <script> tag.