Simplio3D

Simplio3D SDK v3.4

The official TypeScript SDK for the Simplio3D 3D CPQ platform. Full headless support for option blocks, conditional logic, pricing formulas, contact forms, 3D viewer, animations, and more.

Why Use the SDK?

The SDK provides a clean, type-safe wrapper around the Simplio3D REST API with built-in authentication, error handling, timeouts, and retries.

Type Safety
Full TypeScript definitions
Auto Retry
Built-in retry logic
Error Handling
Typed error classes
Headless Ready
Complete configurator API

SDK Modules

Core

Projects, assets, materials, categories

Configurator

Option blocks, variants, conditions

CPQ & Forms

Pricing, formulas, quotes, forms

3D Viewer

Embed config, state, screenshots

Team & Sharing

Members, share links, email logs

Animations

Motion blocks, settings, branding

Installation

Get started with the Simplio3D SDK in your JavaScript or TypeScript project.

Using npm

npm install @simplio3d/sdk

Using yarn

yarn add @simplio3d/sdk

Using pnpm

pnpm add @simplio3d/sdk

Requirements

  • • Node.js 18.x or higher
  • • Modern browser with ES2020+ support
  • • TypeScript 5.0+ (optional, for type checking)

Quick Start

Get up and running with the Simplio3D SDK in minutes.

1. Import the SDK

import { createSimplio3DClient } from '@simplio3d/sdk';

2. Initialize the Client

const client = createSimplio3DClient({
  apiUrl: 'https://your-project.supabase.co/functions/v1/make-server-0532dd87',
  accessToken: 'YOUR_ACCESS_TOKEN',
  timeout: 30000,
  retryAttempts: 3
});

3. Build a Headless Configurator

// Load project and all configurator data
const project = await client.getProject('proj_123');
// SDK normalizes large-project response variants:
// - project.sceneData (when available)
// - project.sceneDataCompressed + project.sceneDataEncoding (fallback)
const blocks = await client.getOptionBlocks('proj_123');
const pricingBlocks = await client.getPricingBlocks('proj_123');
const formFields = await client.getFormFields('proj_123');

// Evaluate conditional logic based on user selections
const visibility = await client.evaluateConditions('proj_123', {
  selections: {
    dropdownSelections: { 'blk_frame': 'black' },
    selectMaterialSelections: {},
    checkboxSelections: {},
    toggleSwitchSelections: { 'blk_armrests': 'with' },
    carouselSelections: {}
  }
});

// Calculate price (includes tax, formatting from project settings)
const price = await client.calculatePrice('proj_123', {
  selections: { /* same as above */ },
  variables: { width: 160 }
});

console.log('Total:', price.formatted);        // "$1,223.99"
console.log('Subtotal:', price.formattedSubtotal); // "$1,019.99"
console.log('Tax:', price.formattedTax, price.taxLabel); // "$204.00 VAT"

// Submit quote
const quote = await client.submitQuote('proj_123', {
  formData: { name: 'Jane', email: '[email protected]' },
  configuration: { selections: { /* ... */ }, variables: { width: 160 } }
});

Client Setup

Configure the Simplio3D client with various options.

Client Configuration

import { createSimplio3DClient } from '@simplio3d/sdk';

const client = createSimplio3DClient({
  apiUrl: 'https://your-project.supabase.co/functions/v1/make-server-0532dd87',
  accessToken: 'YOUR_ACCESS_TOKEN',   // JWT from Supabase Auth
  apiToken: 'YOUR_API_TOKEN',         // Alternative: API token from Profile > API/SDK
  timeout: 30000,                     // Request timeout (ms)
  retryAttempts: 3,                   // Retry on network errors
  onError: (error) => {
    console.error('API Error:', error);
  }
});

Error Handling

import { AuthenticationError, NotFoundError, ValidationError } from '@simplio3d/sdk';

try {
  const project = await client.getProject('project_123');
} catch (error) {
  if (error instanceof NotFoundError) {
    console.error('Project not found');
  } else if (error instanceof AuthenticationError) {
    console.error('Session expired - redirect to login');
  } else if (error instanceof ValidationError) {
    console.error('Invalid data:', error.details);
  } else {
    console.error('Error:', error.message, 'Code:', error.code);
  }
}

Projects

Full CRUD for 3D projects. Types: configurator, viewer, cpq, ar-preview, embedded.

List Projects

const projects = await client.getProjects();
console.log(projects); // Project[]

Get Project

const project = await client.getProject('proj_123');
console.log(project.name, project.type);

// Normalized scene access:
if (project.sceneData) {
  console.log('Scene models:', project.sceneData.models?.length || 0);
} else if (project.sceneDataCompressed) {
  // Runtime does not support decompression; consume compressed payload
  console.log('Compressed scene payload:', project.sceneDataEncoding);
}

Create Project

const newProject = await client.createProject({
  name: 'Product Config',
  type: 'configurator'
});
console.log(newProject.id);

Update Project

const updated = await client.updateProject('proj_123', {
  name: 'Updated Name',
  data: { settings: { autoRotate: true } }
});

Delete Project

await client.deleteProject('proj_123');

Large-Project Handling in SDK

client.getProject() normalizes optimized API variants so consumers use a stable shape.

  • • Lifts scene payloads from project.data when needed.
  • • Tries to decompress gzip-base64 scene data when runtime supports DecompressionStream.
  • • Ensures camera vectors and scene settings have safe defaults.

Option Blocks

Manage configurator option blocks: dropdowns, material selectors, checkboxes, toggles, carousels, text inputs, hotspots, pattern designers, design canvases, and more.

List Option Blocks

const blocks = await client.getOptionBlocks('proj_123');
blocks.forEach(b => {
  console.log(b.name, b.type, b.dropdownVariants?.length);
});

Create Dropdown Block

const block = await client.createOptionBlock('proj_123', {
  type: 'dropdown',
  name: 'Frame Color',
  dropdownVariants: [
    {
      id: crypto.randomUUID(),
      label: 'Matte Black',
      value: 'matte-black',
      targetPartNames: ['frame_body', 'frame_legs'],
      materialData: {
        baseColor: '#1a1a1a',
        metallic: 0.8,
        roughness: 0.4
      }
    },
    {
      id: crypto.randomUUID(),
      label: 'Chrome Silver',
      value: 'chrome-silver',
      targetPartNames: ['frame_body', 'frame_legs'],
      materialData: {
        baseColor: '#C0C0C0',
        metallic: 0.95,
        roughness: 0.05
      }
    }
  ],
  defaultDropdownValue: 'matte-black'
});

Create Thumbnail Selector

const block = await client.createOptionBlock('proj_123', {
  type: 'thumbnail-selector',
  name: 'Fabric',
  thumbnailStyle: 'grid',
  thumbnailSize: '60x60',
  thumbnailShape: 'rounded',
  labelPosition: 'below',
  dropdownVariants: [
    { id: crypto.randomUUID(), label: 'Linen', value: 'linen',
      thumbnailUrl: 'https://cdn.example.com/linen.jpg',
      materialData: { baseColor: '#f0e6d3', roughness: 0.9 } },
    { id: crypto.randomUUID(), label: 'Velvet', value: 'velvet',
      thumbnailUrl: 'https://cdn.example.com/velvet.jpg',
      materialData: { baseColor: '#2d1f4e', roughness: 0.95 } }
  ]
});

Create Toggle with Visibility

const block = await client.createOptionBlock('proj_123', {
  type: 'toggle-switch',
  name: 'Headrest',
  dropdownVariants: [
    { id: crypto.randomUUID(), label: 'Without', value: 'without',
      visibilityConfig: { targetObjectIds: ['model_headrest'], action: 'hide' } },
    { id: crypto.randomUUID(), label: 'With Headrest', value: 'with',
      visibilityConfig: { targetObjectIds: ['model_headrest'], action: 'show' } }
  ]
});

Create Text Input Block

const block = await client.createOptionBlock('proj_123', {
  type: 'text-input',
  name: 'Custom Engraving',
  textInputTargets: [{
    id: crypto.randomUUID(),
    label: 'Engraving Text',
    targetPartName: 'nameplate',
    placeholder: 'Enter your text',
    maxLength: 30,
    fontFamily: 'Montserrat',
    fontSize: 36,
    fontColor: '#gold',
    textAlign: 'center',
    canvasWidth: 1024,
    canvasHeight: 256
  }]
});

Create Select Material — From Category (bulk)

// Auto-populates one swatch per material in the chosen category.
// Use this to expose large material libraries without authoring each variant by hand.
// Variants are synthesized at runtime as 'auto:{materialId}' — `dropdownVariants` stays empty.
// Pricing flows through the standard Pricing system (Price Group / Variable / Price Table /
// Unique Price) — link a pricing block to this option block to author per-material pricing.
const block = await client.createOptionBlock('proj_123', {
  type: 'select-material',
  name: 'Fabric',
  materialSource: 'category',          // ← opt into category mode
  materialCategoryId: 'cat_woods',     // ← owner-scoped category id
  materialCategoryName: 'Woods',
  // Target one or more 3D objects. Per-model parts: [] / omitted = every mesh in that model.
  categoryTargetObjectIds: ['model_main_roof', 'model_extra_roofs'],
  categoryTargetParts: {
    model_main_roof: ['part_01', 'part_02'],
    model_extra_roofs: []              // empty = all meshes in this object
  },
  categorySortOrder: 'name',           // 'name' | 'newest' | 'oldest'
  // Visual config (same as manual mode):
  thumbnailStyle: 'grid',
  thumbnailSize: '60x60',
  thumbnailShape: 'rounded',
  labelPosition: 'below'
});

// To switch back to manual authoring:
await client.updateOptionBlock('proj_123', block.id, {
  materialSource: 'manual'
});

Update Option Block

const updated = await client.updateOptionBlock('proj_123', 'blk_001', {
  name: 'Updated Name',
  visible: false,
  dropdownVariants: [/* updated variants */]
});

Reorder Blocks

const blocks = await client.reorderOptionBlocks('proj_123', {
  blockIds: ['blk_003', 'blk_001', 'blk_002']
});
console.log('New order:', blocks.map(b => b.name));

Conditional Logic

Dynamically show/hide blocks, variants, 3D objects, and individual mesh parts based on user selections. Attach rules to blocks, then evaluate at runtime.

Add Rules to a Block

// Hide "Armrest Color" when armrests toggle is off
await client.updateOptionBlock('proj_123', 'blk_armrest_color', {
  conditionalRules: [{
    id: crypto.randomUUID(),
    action: 'hide',
    targetScope: 'block',
    targetVariantIds: [],
    operator: 'all',
    conditions: [{
      id: crypto.randomUUID(),
      sourceBlockId: 'blk_armrests',
      matchType: 'is-selected',
      sourceVariantIds: ['var_without']
    }]
  }]
});

Variant-Level Rules

// Hide indoor-only fabrics when "Outdoor" is selected
await client.updateOptionBlock('proj_123', 'blk_fabric', {
  conditionalRules: [{
    id: crypto.randomUUID(),
    action: 'hide',
    targetScope: 'variants',
    targetVariantIds: ['var_silk', 'var_velvet'],
    operator: 'all',
    conditions: [{
      id: crypto.randomUUID(),
      sourceBlockId: 'blk_environment',
      matchType: 'is-selected',
      sourceVariantIds: ['var_outdoor']
    }]
  }]
});

3D Object Visibility Rules

// Hide 3D objects in the viewport without hiding the UI block
await client.updateOptionBlock('proj_123', 'blk_chair_parts', {
  conditionalRules: [{
    id: crypto.randomUUID(),
    action: 'hide',
    targetScope: '3d',
    targetVariantIds: [],
    target3dObjectNames: ['headrest_mesh', 'armrest_left', 'armrest_right'],
    operator: 'all',
    conditions: [{
      id: crypto.randomUUID(),
      sourceBlockId: 'blk_style',
      matchType: 'is-selected',
      sourceVariantIds: ['var_minimal']
    }]
  }]
});

3D Part Visibility Rules

// Hide individual mesh parts (sub-meshes) in the viewport
// Targets specific part names from targetPartName / targetPartNames
// rather than whole objects (targetObjectName)
await client.updateOptionBlock('proj_123', 'blk_details', {
  conditionalRules: [{
    id: crypto.randomUUID(),
    action: 'hide',
    targetScope: '3d-parts',
    targetVariantIds: [],
    target3dPartNames: ['logo_emboss', 'stitching_detail', 'button_caps'],
    operator: 'all',
    conditions: [{
      id: crypto.randomUUID(),
      sourceBlockId: 'blk_finish',
      matchType: 'is-selected',
      sourceVariantIds: ['var_plain']
    }]
  }]
});

Evaluate Conditions

const results = await client.evaluateConditions('proj_123', {
  selections: {
    dropdownSelections: { 'blk_frame': 'black' },
    selectMaterialSelections: { 'blk_fabric': 'linen' },
    checkboxSelections: { 'blk_acc': ['cup-holder'] },
    toggleSwitchSelections: { 'blk_armrests': 'with' },
    carouselSelections: {}
  }
});

results.forEach(r => {
  console.log(r.blockId, 'visible:', r.blockVisible);
  if (r.hiddenVariantIds.length > 0) {
    console.log('  hidden variants:', r.hiddenVariantIds);
  }
  if (r.hidden3dObjectNames?.length > 0) {
    console.log('  hidden 3D objects:', r.hidden3dObjectNames);
  }
  if (r.hidden3dPartNames?.length > 0) {
    console.log('  hidden 3D parts:', r.hidden3dPartNames);
  }
});

Target Scopes Reference

ScopeEffectTarget Field
blockHides/shows the entire option block UItargetVariantIds (ignored)
variantsHides/shows specific variants within the blocktargetVariantIds
3dHides/shows whole 3D objects in the viewport (UI stays visible)target3dObjectNames
3d-partsHides/shows individual mesh parts in the viewport (UI stays visible)target3dPartNames

Pricing & CPQ

Configure and calculate prices with base prices, lookup tables, variables, and formulas.

List Pricing Blocks

const blocks = await client.getPricingBlocks('proj_123');
blocks.forEach(b => {
  console.log(b.name, b.type, b.basePrice || b.variableKey);
});

Create Base Price

const block = await client.createPricingBlock('proj_123', {
  type: 'base-price',
  name: 'Base Price',
  basePrice: 499.99,
  baseCurrency: 'USD'
});

Create Variable

const widthVar = await client.createPricingBlock('proj_123', {
  type: 'variable',
  name: 'Width',
  variableKey: 'width',
  variableDefaultValue: 120,
  variableMin: 80,
  variableMax: 200,
  variableStep: 10,
  variableUnit: 'cm'
});

Update Price Table

await client.updatePricingBlock('proj_123', 'pb_3', {
  tableRows: [
    { id: 'r1', cells: { col_1: 'oak' }, priceAdjustment: 0 },
    { id: 'r2', cells: { col_1: 'walnut' }, priceAdjustment: 120 },
    { id: 'r3', cells: { col_1: 'marble' }, priceAdjustment: 399 }
  ]
});

Calculate Price

const price = await client.calculatePrice('proj_123', {
  selections: {
    dropdownSelections: { 'blk_wood': 'walnut' },
    selectMaterialSelections: {},
    checkboxSelections: { 'blk_accessories': ['cup-holder'] },
    toggleSwitchSelections: { 'blk_armrests': 'with' },
    carouselSelections: {}
  },
  variables: { width: 160, quantity: 2 }
});

console.log('Base:', price.basePrice);
console.log('Adjustments:', price.adjustments);
console.log('Total:', price.totalPrice, price.currency);
console.log('Formatted:', price.formatted);  // "$1,223.99"
console.log('Tax:', price.formattedTax);      // "$204.00"
console.log('Subtotal:', price.formattedSubtotal); // "$1,019.99"
// Tax, formatting, and currency are applied based on Project Settings > Price

Contact Forms

Build and manage contact/quote request forms with configurable fields.

List Form Fields

const fields = await client.getFormFields('proj_123');
fields.forEach(f => {
  console.log(f.type, f.label, f.required ? '(required)' : '');
});

Create Form Field

const field = await client.createFormField('proj_123', {
  type: 'dropdown',
  label: 'Budget Range',
  required: true,
  width: 'half',
  options: [
    { id: '1', label: 'Under $1k', value: 'under-1k' },
    { id: '2', label: '$1k - $5k', value: '1k-5k' },
    { id: '3', label: '$5k - $10k', value: '5k-10k' },
    { id: '4', label: 'Over $10k', value: 'over-10k' }
  ],
  allowOther: true
});

Quote Submissions

Submit and manage quote requests with form data, configuration selections, and pricing.

Submit a Quote

const submission = await client.submitQuote('proj_123', {
  formData: {
    name: 'Jane Smith',
    email: '[email protected]',
    phone: '+1 555 0123',
    message: 'Need a quote for 50 units.'
  },
  configuration: {
    selections: {
      dropdownSelections: { 'blk_frame': 'black', 'blk_wood': 'walnut' },
      selectMaterialSelections: { 'blk_fabric': 'linen' },
      checkboxSelections: { 'blk_accessories': ['cup-holder'] },
      toggleSwitchSelections: { 'blk_armrests': 'with' },
      carouselSelections: {}
    },
    variables: { width: 160, quantity: 50 },
    screenshotUrl: 'https://cdn.example.com/screenshot.png'
  }
});

console.log('Submitted:', submission.id, submission.status);

List Submissions

const submissions = await client.getQuoteSubmissions('proj_123');
submissions.forEach(s => {
  console.log(s.customerName, s.customerEmail, s.status);
  console.log('Config:', s.configuration.pricing?.totalPrice);
});

3D Viewer

Embed and control the 3D viewer. Manage camera, lighting, state, and take screenshots.

Get Embed Config

const config = await client.getViewerEmbedConfig('proj_123');
console.log('Camera:', config.cameraPosition);
console.log('Environment:', config.environmentPreset);
console.log('AR enabled:', config.enableAR);

Get Viewer State

const state = await client.getViewerState('proj_123');
console.log('Loaded:', state.loaded);
console.log('Selections:', state.selections);
console.log('Visible blocks:', state.visibleBlocks);
console.log('Price:', state.currentPrice?.totalPrice);

Set Viewer State

const newState = await client.setViewerState('proj_123', {
  loaded: true,
  selections: {
    dropdownSelections: { 'blk_frame': 'silver' },
    selectMaterialSelections: {},
    checkboxSelections: {},
    toggleSwitchSelections: {},
    carouselSelections: {}
  },
  visibleBlocks: ['blk_001', 'blk_002'],
  cameraPosition: [3, 2, 5],
  cameraTarget: [0, 0.5, 0]
});

Animations

Define looping animations: move, rotation, float, scale-pulse, swing, orbit.

List Animations

const anims = await client.getAnimationBlocks('proj_123');
anims.forEach(a => console.log(a.name, a.type, a.speed));

Create Animation

const anim = await client.createAnimationBlock('proj_123', {
  type: 'float',
  name: 'Hover Effect',
  targetPartNames: ['product_body'],
  axis: 'y',
  speed: 0.4,
  amplitude: 0.3,
  easing: 'ease-in-out',
  loopMode: 'ping-pong'
});
console.log(anim.id);

Project Settings

Configure branding, lighting, camera, loading screens, email, e-commerce, and webhooks.

Update Settings

const project = await client.updateProjectSettings('proj_123', {
  // Branding
  viewportBackgroundColor: '#1a1a2e',
  primaryAccentColor: '#e94560',
  actionButtonBgColor: '#e94560',
  actionButtonHoverBgColor: '#c73450',
  actionButtonTextColor: '#ffffff',
  fontFamily: 'poppins',
  sidebarWidth: 400,
  compactMode: true,

  // Lighting
  environmentPreset: 'sunset',
  lightIntensity: 1.2,
  enableShadows: true,

  // Camera
  cameraFOV: 45,
  enableAutoRotate: true,
  autoRotateSpeed: 0.5,

  // Features
  enableAR: true,
  showPrice: true,
  enableForm: true,

  // Webhooks
  webhookUrl: 'https://your-server.com/webhook',
  webhookEvents: ['configuration_changed', 'quote_requested'],

  // Analytics
  googleAnalyticsId: 'G-XXXXXXXXXX'
});

Assets

Upload and manage 3D models, textures, and graphics.

List Assets

const assets = await client.getAssets('3d');
assets.forEach(a => console.log(a.name, a.format, a.fileSize));

Create Asset

const asset = await client.createAsset({
  name: 'Chair Model',
  type: '3d',
  url: 'https://storage.example.com/chair.glb',
  fileSize: 1024000,
  category: 'furniture'
});

Get Download URL

const url = await client.getAssetDownloadUrl('asset_123');
// Returns a time-limited signed URL

Delete Asset

await client.deleteAsset('asset_123');

Materials

PBR materials with base color, metallic, roughness, normal maps.

List Materials

const materials = await client.getMaterials();
materials.forEach(m => console.log(m.name, m.pbr.baseColor));

Create Material

const material = await client.createMaterial({
  name: 'Brushed Metal',
  category: 'metals',
  pbr: { baseColor: '#808080', metallic: 0.9, roughness: 0.3 }
});

Update Material

await client.updateMaterial('mat_123', {
  pbr: { roughness: 0.5, metallic: 0.8 }
});

Delete Material

await client.deleteMaterial('mat_123');

Categories

Organize assets and materials into categories.

List Categories

const categories = await client.getCategories('3d');

Create Category

const category = await client.createCategory({
  name: 'Furniture',
  type: '3d'
});

Update Category

await client.updateCategory('cat_123', { name: 'Updated' });

Delete Category

await client.deleteCategory('cat_123');

Team Management

Manage team members with role-based access (admin, editor, viewer).

List Members

const members = await client.getTeamMembers();
members.forEach(m => console.log(m.name, m.role, m.status));

Invite Member

const member = await client.inviteTeamMember({
  email: '[email protected]',
  role: 'editor',
  name: 'Jane Smith'
});

Update Member

await client.updateTeamMember('mem_123', { role: 'admin' });

Remove Member

await client.removeTeamMember('mem_123');

Project Sharing

Generate public share links with password protection and expiry.

Get Share Config

const share = await client.getShareConfig('proj_123');
console.log(share.shareUrl, share.viewCount);

Enable Sharing

const share = await client.enableSharing('proj_123');
console.log(share.shareUrl);

Update Share Settings

await client.updateShareConfig('proj_123', {
  allowDownload: true,
  passwordProtected: true,
  password: 'secret123',
  expiresAt: '2026-06-01T00:00:00Z'
});

Regenerate Token

const newShare = await client.regenerateShareToken('proj_123');
console.log(newShare.shareUrl);

Email Logs

View delivery logs, delete entries, and resend failed emails.

Get Logs

const logs = await client.getEmailLogs();
logs.forEach(log => console.log(log.type, log.recipient, log.status));

Delete Log

await client.deleteEmailLog('log_123');

Resend Failed Email

await client.resendEmail('log_123');

Webhooks

Configure HTTP callbacks to receive real-time event notifications. Supports CRUD, test pings, delivery logs, and HMAC-SHA256 signature verification.

List Webhooks

const webhooks = await client.getWebhooks('proj_123');
webhooks.forEach(wh => console.log(wh.name, wh.url, wh.enabled, wh.events));

Create Webhook

const webhook = await client.createWebhook('proj_123', {
  name: 'Order Alerts',
  url: 'https://your-server.com/webhook',
  events: ['quote.submitted', 'form.submitted', 'price.calculated'],
  secret: 'whsec_my_signing_secret',
  enabled: true
});
console.log(webhook.id, webhook.createdAt);

Update Webhook

const updated = await client.updateWebhook('proj_123', 'wh_abc', {
  events: ['quote.submitted', 'quote.updated', 'form.submitted'],
  enabled: true
});
console.log('Events:', updated.events);

Delete Webhook

await client.deleteWebhook('proj_123', 'wh_abc');

Test Webhook

const result = await client.testWebhook('proj_123', 'wh_abc');
console.log(result.statusCode, result.delivered);
// { statusCode: 200, statusText: "OK", delivered: true }

Get Delivery Logs

const deliveries = await client.getWebhookDeliveries('proj_123', 'wh_abc');
deliveries.forEach(d => {
  console.log(d.event, d.delivered, d.statusCode, d.timestamp);
});

Trigger Webhooks

const result = await client.triggerWebhooks('proj_123', {
  event: 'quote.submitted',
  data: { quoteId: 'q_456', total: 1299.99 }
});
console.log(`Triggered ${result.triggered} webhooks`);

Verify Signature (Node.js)

// Verify incoming webhook signatures
const crypto = require('crypto');

function verifyWebhook(body, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(body))
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature), Buffer.from(expected)
  );
}

// In your Express handler:
app.post('/webhook', (req, res) => {
  const sig = req.headers['x-simplio3d-signature'];
  if (!verifyWebhook(req.body, sig, 'whsec_...')) {
    return res.status(401).send('Invalid signature');
  }
  console.log('Event:', req.headers['x-simplio3d-event']);
  res.sendStatus(200);
});

Augmented Reality (AR) SDK

SDK helpers for AR file management, analytics tracking, and programmatic AR launches.

Upload USDZ File

// Upload a USDZ file for iOS AR Quick Look
const usdzFile = document.querySelector('input[type="file"]').files[0];
const result = await client.projects.uploadUsdz('PROJECT_ID', usdzFile);
console.log(result.usdzId, result.usdzPath);

// Then update project settings:
await client.projects.updateSettings('PROJECT_ID', {
  enableAR: true,
  arUsdzFileName: result.fileName,
  arUsdzModelPath: result.usdzPath,
  arUsdzModelId: result.usdzId,
  arAutoLaunch: true,
  arAnalyticsEnabled: true,
});

Track AR Events

import { trackAREvent } from './lib/ar-analytics';

// Track an AR event (fire-and-forget, non-blocking)
trackAREvent({
  event: 'ar_launch_ios',
  projectId: 'PROJECT_ID',
  meta: { source: 'floating_tools' },
});

// Available event types:
// 'ar_dialog_open'    — Desktop QR dialog opened
// 'ar_qr_copy'        — AR link copied
// 'ar_qr_open_tab'    — AR link opened in new tab
// 'ar_launch_ios'     — iOS AR Quick Look launched
// 'ar_launch_android' — Android Scene Viewer launched
// 'ar_auto_launch'    — Auto-launched via ?ar=1
// 'ar_page_view'      — Share page loaded with AR

Fetch AR Analytics

import { fetchARAnalytics } from './lib/ar-analytics';

const analytics = await fetchARAnalytics('PROJECT_ID', accessToken);
console.log(analytics.totalEvents);     // 142
console.log(analytics.byEvent);          // { ar_launch_ios: 41, ... }
console.log(analytics.recentEvents);     // last 50 events

Programmatic AR Launch

// Generate an AR share URL
const shareUrl = `https://yourapp.com/share/${projectId}/${token}`;
const arUrl = `${shareUrl}?ar=1`;

// On iOS — use USDZ with AR Quick Look
const link = document.createElement('a');
link.setAttribute('rel', 'ar');
link.href = usdzSignedUrl; // from /share/:projectId/usdz
const img = document.createElement('img');
img.style.display = 'none';
link.appendChild(img);
document.body.appendChild(link);
link.click();

// On Android — use Scene Viewer intent
const intent = `intent://arvr.google.com/scene-viewer/1.0
  ?file=${encodeURIComponent(glbUrl)}
  &mode=ar_preferred#Intent;...;end;`;
window.location.href = intent;

TypeScript Support

Full type definitions for all models, requests, and responses.

Type Imports

import type { 
  // Client & config
  Simplio3DConfig,
  
  // Core models
  Project, Material, Asset, Category,
  ProjectType, AssetType, CategoryType,
  
  // Request types
  CreateProjectRequest, UpdateProjectRequest,
  CreateMaterialRequest, UpdateMaterialRequest,
  CreateAssetRequest, CreateCategoryRequest,
  
  // Option blocks (Headless Configurator)
  OptionBlock, OptionBlockType, DropdownVariant,
  CreateOptionBlockRequest, UpdateOptionBlockRequest,
  ReorderBlocksRequest,
  
  // Conditional logic
  ConditionalRule, ConditionalCondition,
  EvaluateConditionsRequest, BlockVisibilityResult,
  
  // Pricing & CPQ
  PricingBlock, PricingBlockType, PriceTableRow, PriceTableColumn,
  FormulaToken, CalculatePriceRequest, CalculatePriceResponse,
  
  // Contact forms
  FormField, FormFieldType, FormFieldOption,
  CreateFormFieldRequest,
  
  // Quote submissions
  QuoteSubmission, SubmitQuoteRequest,
  
  // 3D Viewer
  ViewerEmbedConfig, ViewerState, SceneManipulation,
  
  // Animations
  AnimationBlock, AnimationBlockType, AnimationAxis,
  AnimationEasing, AnimationLoopMode,
  
  // Project settings
  ProjectSettingsUpdate,
  
  // Team & sharing
  TeamMember, InviteMemberRequest, UpdateMemberRequest,
  MemberRole, MemberStatus,
  ShareConfig, UpdateShareRequest,
  
  // Email & auth
  EmailLogEntry, UserProfile, ApiTokenResponse,
  
  // Errors
  Simplio3DError, AuthenticationError,
  ValidationError, NotFoundError,
} from '@simplio3d/sdk';

Examples

Real-world examples covering common headless configurator workflows.

Complete Headless Configurator

import { createSimplio3DClient } from '@simplio3d/sdk';

async function buildConfigurator(projectId: string, accessToken: string) {
  const client = createSimplio3DClient({
    apiUrl: process.env.SIMPLIO3D_API_URL,
    accessToken
  });

  // 1. Load all configurator data
  const [project, blocks, pricing, formFields] = await Promise.all([
    client.getProject(projectId),
    client.getOptionBlocks(projectId),
    client.getPricingBlocks(projectId),
    client.getFormFields(projectId)
  ]);

  // 2. Build selection state from defaults
  const selections = {
    dropdownSelections: {} as Record<string, string>,
    selectMaterialSelections: {} as Record<string, string>,
    checkboxSelections: {} as Record<string, string[]>,
    toggleSwitchSelections: {} as Record<string, string>,
    carouselSelections: {} as Record<string, string>,
  };

  blocks.forEach(block => {
    if (block.defaultDropdownValue && block.type === 'dropdown') {
      selections.dropdownSelections[block.id] = block.defaultDropdownValue;
    }
    if (block.defaultCheckboxValues && block.type === 'checkbox') {
      selections.checkboxSelections[block.id] = block.defaultCheckboxValues;
    }
  });

  // 3. Evaluate conditional visibility
  const visibility = await client.evaluateConditions(projectId, { selections });

  // 4. Calculate initial price
  const price = await client.calculatePrice(projectId, {
    selections,
    variables: {}
  });

  return {
    project,
    blocks: blocks.filter(b => {
      const vis = visibility.find(v => v.blockId === b.id);
      return vis ? vis.blockVisible : true;
    }),
    pricing,
    formFields,
    price,
    selections,
    visibility
  };
}

React Hook: useConfigurator

import { createSimplio3DClient } from '@simplio3d/sdk';
import { useState, useEffect, useCallback, useMemo } from 'react';

function useConfigurator(projectId: string, accessToken: string) {
  const client = useMemo(() => createSimplio3DClient({
    apiUrl: import.meta.env.VITE_API_URL,
    accessToken
  }), [accessToken]);

  const [blocks, setBlocks] = useState([]);
  const [selections, setSelections] = useState({
    dropdownSelections: {},
    selectMaterialSelections: {},
    checkboxSelections: {},
    toggleSwitchSelections: {},
    carouselSelections: {},
  });
  const [visibility, setVisibility] = useState([]);
  const [price, setPrice] = useState(null);
  const [loading, setLoading] = useState(true);

  // Load initial data
  useEffect(() => {
    Promise.all([
      client.getOptionBlocks(projectId),
      client.getPricingBlocks(projectId)
    ]).then(([blocks]) => {
      setBlocks(blocks);
      setLoading(false);
    });
  }, [client, projectId]);

  // Re-evaluate when selections change
  useEffect(() => {
    client.evaluateConditions(projectId, { selections })
      .then(setVisibility);
    client.calculatePrice(projectId, { selections, variables: {} })
      .then(setPrice);
  }, [client, projectId, selections]);

  const setSelection = useCallback((blockId, value) => {
    setSelections(prev => ({
      ...prev,
      dropdownSelections: { ...prev.dropdownSelections, [blockId]: value }
    }));
  }, []);

  return { blocks, selections, setSelection, visibility, price, loading };
}

E-commerce Integration

// Add configured product to cart with pricing
async function addToCart(projectId, selections, variables) {
  const client = createSimplio3DClient({ apiUrl: API_URL, accessToken });

  // Get final price
  const price = await client.calculatePrice(projectId, {
    selections,
    variables
  });

  // Get a screenshot of the current configuration
  const screenshotUrl = 'https://cdn.example.com/screenshot.png';

  // Submit as a quote (or redirect to cart)
  const submission = await client.submitQuote(projectId, {
    formData: { name: user.name, email: user.email },
    configuration: {
      selections,
      variables,
      screenshotUrl
    }
  });

  // Or send to your e-commerce platform
  await fetch('/api/cart/add', {
    method: 'POST',
    body: JSON.stringify({
      productId: projectId,
      price: price.totalPrice,
      currency: price.currency,
      configurationId: submission.id,
      thumbnail: screenshotUrl,
      options: selections
    })
  });
}

SDK Changelog

Track all SDK changes, new methods, deprecations, and improvements across versions.

SDK Changelog

Track all changes, updates, and improvements

v3.4

patchLatest
April 8, 2026

✨ New Features

  • Large Project Transport + Async Submit Semantics: getProject() now normalizes optimized large-project API variants into a stable SDK shape.
  • SDK now attempts gzip-base64 scene decompression when runtime supports DecompressionStream and exposes fallback compressed fields otherwise.

⚡ Improvements

  • Camera vectors and scene settings from mixed transport formats are normalized with safe defaults for consistent consumer behavior.

v3.2

minor
March 11, 2026

v3.1.0

minor
February 27, 2026

v3.0.0

major
January 15, 2026