Simplio3D API v3.4
Build powerful headless 3D CPQ applications with the Simplio3D RESTful API. Full control over option blocks, conditional logic, pricing, contact forms, 3D viewer, animations, and more.
Getting Started
The Simplio3D API is organized around REST. It accepts JSON-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes and JWT-based authentication.
Quick Example
// Get all projects
const response = await fetch(BASE_URL + '/projects', {
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data.projects);Available Endpoints
Authentication
Sign up, sign in, sessions
Projects
Full CRUD + model uploads
Option Blocks
Configurator blocks CRUD + reorder
Conditional Logic
Rules management + evaluation
Pricing & CPQ
Price blocks, formulas, calculate
Contact Forms
Form fields CRUD + submissions
3D Viewer
Embed config, state, screenshots
Animations
Animation blocks CRUD
Project Settings
Branding, lighting, camera
Assets
Upload, manage, download
Materials
PBR materials CRUD
Categories
Asset organization
Team
Invite, update, remove members
Sharing
Public links & settings
SMTP test, logs, resend
Quote Submissions
Submit, list, manage quotes
Headless Architecture
Build your own UI with full API access to option blocks, conditional logic, pricing formulas, and 3D viewer state. Perfect for custom storefronts.
Secure by Default
JWT-based authentication with Supabase Auth. All mutating endpoints require a valid access token.
Authentication
The Simplio3D API uses JWT tokens for authentication. All API requests must include a valid access token.
Sign Up
const response = await fetch(BASE_URL + '/auth/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: '[email protected]',
password: 'securePassword123',
name: 'John Doe'
})
});
const data = await response.json();
// { "success": true, "userId": "...", "message": "Account created" }Sign In
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const { data: { session }, error } = await supabase.auth.signInWithPassword({
email: '[email protected]',
password: 'securePassword123'
});
// Use session.access_token for all API calls
const accessToken = session.access_token;Get Session
const response = await fetch(BASE_URL + '/auth/session', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// { "success": true, "userId": "...", "email": "[email protected]" }Using Access Tokens
Include the access token in the Authorization header for all authenticated requests:
Authorization: Bearer YOUR_ACCESS_TOKENProjects
Manage 3D projects and configurations. Project types include configurator, viewer, cpq, ar-preview, and embedded.
List All Projects
const response = await fetch(BASE_URL + '/projects', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// {
// "success": true,
// "projects": [
// {
// "id": "proj_123",
// "name": "Modern Office Design",
// "type": "configurator",
// "thumbnail": "https://...",
// "createdAt": "2026-02-25T10:00:00Z",
// "updatedAt": "2026-03-01T14:30:00Z"
// }
// ]
// }Create Project
const response = await fetch(BASE_URL + '/projects', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'New Product Configuration',
type: 'configurator' // 'configurator' | 'viewer' | 'cpq' | 'ar-preview' | 'embedded'
})
});
const data = await response.json();
// { "success": true, "project": { ... } }Get Project
Large projects may return an optimized payload variant to stay within edge gateway limits. Always handle both full and compressed response shapes.
const response = await fetch(BASE_URL + '/projects/proj_123', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// Variant A (full / normalized scene)
// data.project.sceneData
// Variant B (optimized)
// data.project.data.sceneDataCompressed + data.project.data.sceneDataEncoding
// OR data.project.data.sceneData + data.project.data.models + data.project.data.cameraLarge-Project Response Variants
The API may return one of these variants for GET /projects/:id:
- • project.sceneData (fully expanded scene)
- • project.data.sceneDataCompressed + sceneDataEncoding=gzip-base64
- • project.data.sceneData with project.data.models and project.data.camera
Update Project
const response = await fetch(BASE_URL + '/projects/proj_123', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Updated Project Name',
data: { settings: { autoRotate: true } }
})
});Duplicate Project
Creates an independent copy by deep-cloning the project's entire sceneData — all 3D models (with nested groups and parts), every option block and variant (hotspots, scenery, materials, …), the full pricing structure (price groups, 1D/2D tables, unique-price blocks, variables, and all SKU maps), the complete form, animations, conditional logic, and project settings. Asset-library models, materials, and textures are reused by reference (no extra storage cost). Per-project uploaded models, the AR USDZ file, and the thumbnail are copied to the new project's storage path and their stored paths rewritten so the copy is independent of the source. Share settings, WooCommerce / Shopify product links, and saved configurations are not carried over.
Plan gate: requires Pro or Enterprise. Starter callers receive 403.
const response = await fetch(BASE_URL + '/projects/proj_123/clone', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'My Configurator Copy' // optional — defaults to "{source name} Copy"
})
});
const data = await response.json();
// {
// "success": true,
// "project": { "id": "proj_new", "name": "My Configurator Copy", "type": "configurator", ... },
// "warnings": { "failedModelCopies": [...], "message": "..." } // present only on partial failure
// }Delete Project
const response = await fetch(BASE_URL + '/projects/proj_123', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Option Blocks
Option blocks are the core configurator building blocks. Each block type represents a different UI control that drives 3D model changes, material swaps, visibility toggles, and more. Supports 15 block types for headless integration.
Supported Block Types
Select Material — authoring modes
The select-material block exposes two authoring modes via a top-level materialSource field:
- Manual (
materialSource: 'manual', default) — variants are authored individually indropdownVariants, each with its own target, material, and pricing. Supports Price Group / Variable / Price Table linking. - From Category (
materialSource: 'category') — runtime variants are auto-populated from every material inmaterialCategoryId. The block uses a shared target that can span multiple objects —categoryTargetObjectIds(string[]) and per-modelcategoryTargetParts(Record<modelId, string[]>) — applied to every synthesized variant (legacy single-objectcategoryTargetObjectId/categoryTargetPartNamesare still read as a fallback). Pricing flows through the standard Pricing system (Price Group / Variable / Price Table / Unique Price) — link a pricing block to the category-mode Select Material block and author one price per material. Variant IDs at runtime areauto:{materialId}.
Public share viewers fetch referenced category materials at first paint via GET /share/:projectId/:token/category-materials?ids=cat1,cat2, with GET /share/:projectId/:token/category-materials/:categoryId still available for a single category.
List Option Blocks
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// {
// "success": true,
// "blocks": [
// {
// "id": "blk_001",
// "type": "dropdown",
// "name": "Frame Color",
// "visible": true,
// "enabled": true,
// "dropdownVariants": [
// {
// "id": "var_a",
// "label": "Black",
// "value": "black",
// "materialData": { "baseColor": "#1a1a1a", "metallic": 0.8, "roughness": 0.2 },
// "targetPartNames": ["frame_body", "frame_legs"]
// },
// {
// "id": "var_b",
// "label": "Silver",
// "value": "silver",
// "materialData": { "baseColor": "#C0C0C0", "metallic": 0.95, "roughness": 0.1 },
// "targetPartNames": ["frame_body", "frame_legs"]
// }
// ],
// "defaultDropdownValue": "black"
// },
// {
// "id": "blk_002",
// "type": "thumbnail-selector",
// "name": "Fabric Type",
// "visible": true,
// "enabled": true,
// "thumbnailStyle": "grid",
// "thumbnailSize": "60x60",
// "thumbnailShape": "rounded",
// "dropdownVariants": [
// {
// "id": "var_c",
// "label": "Linen",
// "value": "linen",
// "thumbnailUrl": "https://...",
// "materialData": { "baseColor": "#f0e6d3", "roughness": 0.9 }
// }
// ]
// },
// {
// "id": "blk_003",
// "type": "toggle-switch",
// "name": "Armrests",
// "visible": true,
// "enabled": true,
// "dropdownVariants": [
// { "id": "var_d", "label": "Without", "value": "without",
// "visibilityConfig": { "targetObjectIds": ["model_armrest"], "action": "hide" } },
// { "id": "var_e", "label": "With Armrests", "value": "with",
// "visibilityConfig": { "targetObjectIds": ["model_armrest"], "action": "show" } }
// ]
// }
// ]
// }Create Option Block
// Create a Dropdown block with material-swapping variants
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'dropdown',
name: 'Wood Finish',
dropdownVariants: [
{
id: crypto.randomUUID(),
label: 'Oak',
value: 'oak',
targetPartNames: ['desk_top', 'desk_drawer'],
materialData: {
baseColor: '#8B6F47',
metallic: 0.0,
roughness: 0.8,
textureMap: 'https://cdn.example.com/textures/oak.jpg',
normalMap: 'https://cdn.example.com/textures/oak_normal.jpg'
}
},
{
id: crypto.randomUUID(),
label: 'Walnut',
value: 'walnut',
targetPartNames: ['desk_top', 'desk_drawer'],
materialData: {
baseColor: '#5C3D2E',
metallic: 0.0,
roughness: 0.75,
textureMap: 'https://cdn.example.com/textures/walnut.jpg'
}
}
],
defaultDropdownValue: 'oak'
})
});
// { "success": true, "block": { "id": "blk_new", ... } }Create Checkbox Block
// Checkbox — multiple selections, each toggling 3D object visibility
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'checkbox',
name: 'Optional Accessories',
checkboxAlignment: 'vertical',
showSelectAll: true,
dropdownVariants: [
{
id: crypto.randomUUID(),
label: 'Cup Holder',
value: 'cup-holder',
visibilityConfig: { targetObjectIds: ['model_cup_holder'], action: 'show' }
},
{
id: crypto.randomUUID(),
label: 'Monitor Stand',
value: 'monitor-stand',
visibilityConfig: { targetObjectIds: ['model_monitor_arm'], action: 'show' }
},
{
id: crypto.randomUUID(),
label: 'Cable Tray',
value: 'cable-tray',
visibilityConfig: { targetObjectIds: ['model_cable_tray'], action: 'show' }
}
],
defaultCheckboxValues: ['cup-holder']
})
});Create Carousel Block
// Carousel — swipe/arrow navigation through variants
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'carousel',
name: 'Upholstery',
carouselArrowStyle: 'filled',
carouselAnimation: 'slide',
carouselLoop: true,
carouselShowCounter: true,
carouselShowLabel: true,
dropdownVariants: [
{ id: crypto.randomUUID(), label: 'Velvet Red', value: 'velvet-red',
thumbnailUrl: 'https://...', materialData: { baseColor: '#8B0000', roughness: 0.9 } },
{ id: crypto.randomUUID(), label: 'Leather Brown', value: 'leather-brown',
thumbnailUrl: 'https://...', materialData: { baseColor: '#654321', roughness: 0.6, metallic: 0.1 } }
]
})
});Create Text Input Block
// Text Input — user-typed text rendered as a texture on a 3D part
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'text-input',
name: 'Engraving',
textInputTargets: [{
id: crypto.randomUUID(),
label: 'Custom Text',
targetPartName: 'engraving_plate',
placeholder: 'Enter up to 20 characters',
maxLength: 20,
fontFamily: 'Roboto',
fontSize: 48,
fontColor: '#1a1a1a',
textAlign: 'center',
canvasWidth: 1024,
canvasHeight: 256,
backgroundColor: '',
// Restrict consumer input. 'numbers' triggers the mobile numeric keypad
// and strips any non-digit characters on every change. Use 'any' (or
// omit the field) for unrestricted text. For decimal math / pricing,
// use the dedicated number-input option block instead.
contentType: 'numbers',
// Let consumers swap the font, but only between these three.
// Omit (or set to undefined) to expose every built-in font.
allowConsumerFontFamily: true,
consumerAllowedFonts: ['Roboto', 'Montserrat', 'Playfair Display']
}],
textInputShowLivePreview: true
})
});Create Hotspot Block
// Hotspot — interactive 3D marker with popup content
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'hotspot',
name: 'Adjustable Mechanism',
hotspotPosition: { x: 0.5, y: 1.2, z: 0.3 },
hotspotTitle: 'Tilt Mechanism',
hotspotDescription: 'Multi-angle tilt lock supports 5 positions from 90-135 degrees.',
hotspotIcon: 'info',
hotspotTrigger: 'click',
hotspotAnimationStyle: 'pulse',
hotspotImageUrl: 'https://cdn.example.com/mechanism.jpg',
hotspotLinkUrl: 'https://example.com/specs',
hotspotLinkLabel: 'View Specifications'
})
});Update Option Block
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks/blk_001', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Updated Block Name',
visible: false,
dropdownVariants: [/* updated variants */]
})
});Delete Option Block
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks/blk_001', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Reorder Option Blocks
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks/reorder', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
blockIds: ['blk_003', 'blk_001', 'blk_002'] // New order
})
});
// { "success": true, "blocks": [/* blocks with updated order */] }Conditional Logic
Define rules that dynamically show/hide option blocks or specific variants based on user selections. Rules evaluate at runtime using "all" (AND) or "any" (OR) logic operators.
How Conditional Logic Works
Hide rules: Block/variants start visible and become hidden when conditions match.
Show rules: Block/variants start hidden and become visible when conditions match.
Evaluation order: Hide rules first, then show rules can override.
Match types: is-selected and is-not-selected
Auto-apply on show: When a block transitions from hidden → visible (e.g. via a show / block rule), the default or previously-selected material from that block is automatically applied to the 3D viewport. This ensures the model always reflects the newly-visible block's material without requiring an explicit user click.
Add Conditional Rules to a Block
// Hide the "Armrest Color" block when "Armrests" toggle is set to "without"
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks/blk_armrest_color', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
conditionalRules: [
{
id: crypto.randomUUID(),
action: 'hide',
targetScope: 'block',
targetVariantIds: [],
operator: 'all',
conditions: [
{
id: crypto.randomUUID(),
sourceBlockId: 'blk_armrests',
matchType: 'is-selected',
sourceVariantIds: ['var_without_armrests']
}
]
}
]
})
});Variant-Level Conditional Logic
// Hide specific fabric options when "Outdoor" is selected in another block
const rules = [{
id: crypto.randomUUID(),
action: 'hide',
targetScope: 'variants',
targetVariantIds: ['var_silk', 'var_velvet'], // These fabrics are indoor-only
operator: 'all',
conditions: [{
id: crypto.randomUUID(),
sourceBlockId: 'blk_environment',
matchType: 'is-selected',
sourceVariantIds: ['var_outdoor']
}]
}];
// Show premium materials only when "Enterprise" plan is detected
const showPremiumRule = {
id: crypto.randomUUID(),
action: 'show',
targetScope: 'variants',
targetVariantIds: ['var_carbon_fiber', 'var_titanium'],
operator: 'any',
conditions: [{
id: crypto.randomUUID(),
sourceBlockId: 'blk_tier',
matchType: 'is-selected',
sourceVariantIds: ['var_enterprise', 'var_premium']
}]
};Multi-Condition Rules (AND / OR)
// Show the "Heated Seats" option ONLY when BOTH conditions are met:
// 1. Vehicle type is "SUV" or "Sedan"
// 2. Climate package is selected
const rule = {
id: crypto.randomUUID(),
action: 'show',
targetScope: 'block',
targetVariantIds: [],
operator: 'all', // ALL conditions must match (AND logic)
conditions: [
{
id: crypto.randomUUID(),
sourceBlockId: 'blk_vehicle_type',
matchType: 'is-selected',
sourceVariantIds: ['var_suv', 'var_sedan'] // ANY of these
},
{
id: crypto.randomUUID(),
sourceBlockId: 'blk_climate_pkg',
matchType: 'is-selected',
sourceVariantIds: ['var_climate_yes']
}
]
};3D Object Visibility Rules
Use targetScope: '3d' to show/hide 3D objects in the viewport without affecting the UI block. This lets you keep the option panel visible while controlling 3D representation independently.
// Hide the armrest 3D objects when "Minimalist" style is selected
const rule = {
id: crypto.randomUUID(),
action: 'hide',
targetScope: '3d',
target3dObjectNames: ['Armrest_Left', 'Armrest_Right'],
targetVariantIds: [],
operator: 'all',
conditions: [{
id: crypto.randomUUID(),
sourceBlockId: 'blk_style',
matchType: 'is-selected',
sourceVariantIds: ['var_minimalist']
}]
};
// A block can have BOTH a block-hide rule and a 3D-hide rule:
// Rule 1: Hide the block UI when condition X
// Rule 2: Hide 3D objects when condition Y3D Visibility Rules
Use targetScope: '3d' with targetObjectIds to show/hide whole 3D objects, or targetScope: '3d-parts' with targetPartNames to show/hide individual mesh parts (sub-meshes) in the viewport.
// Hide decorative stitching and logo when "Plain" finish is selected
const rule = {
id: crypto.randomUUID(),
action: 'hide',
targetScope: '3d-parts',
target3dPartNames: ['stitching_detail', 'logo_emboss', 'button_caps'],
targetVariantIds: [],
operator: 'all',
conditions: [{
id: crypto.randomUUID(),
sourceBlockId: 'blk_finish',
matchType: 'is-selected',
sourceVariantIds: ['var_plain']
}]
};
// Use '3d' scope for whole objects, '3d-parts' for individual meshes.
// Both keep the UI block visible while controlling viewport visibility.Evaluate Conditions (Runtime)
Evaluate all conditional logic rules against a set of consumer selections. Returns which blocks and variants are visible.
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks/evaluate', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
selections: {
dropdownSelections: { 'blk_frame': 'black', 'blk_size': 'large' },
selectMaterialSelections: { 'blk_fabric': 'linen' },
checkboxSelections: { 'blk_accessories': ['cup-holder', 'monitor-stand'] },
toggleSwitchSelections: { 'blk_armrests': 'with' },
carouselSelections: {}
}
})
});
const data = await response.json();
// {
// "success": true,
// "results": [
// { "blockId": "blk_001", "blockVisible": true, "hiddenVariantIds": [], "hidden3dObjectNames": [], "hidden3dPartNames": [] },
// { "blockId": "blk_002", "blockVisible": true, "hiddenVariantIds": ["var_silk"], "hidden3dObjectNames": [], "hidden3dPartNames": [] },
// { "blockId": "blk_armrest_color", "blockVisible": true, "hiddenVariantIds": [], "hidden3dObjectNames": ["armrest_left", "armrest_right"], "hidden3dPartNames": ["logo_emboss"] },
// { "blockId": "blk_heated", "blockVisible": false, "hiddenVariantIds": [], "hidden3dObjectNames": [], "hidden3dPartNames": [] }
// ]
// }Pricing & CPQ
Configure pricing with base prices, price groups, lookup tables, unique modular prices, and variables. Build formulas that combine pricing blocks with mathematical operators. Calculate real-time prices based on user selections.
Pricing Block Types
List Pricing Blocks
const response = await fetch(BASE_URL + '/projects/proj_123/pricing-blocks', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// {
// "success": true,
// "blocks": [
// { "id": "pb_1", "type": "base-price", "name": "Base Price",
// "basePrice": 499.99, "baseCurrency": "USD", "enabled": true },
// { "id": "pb_2", "type": "variable", "name": "Width",
// "variableKey": "width", "variableDefaultValue": 120,
// "variableMin": 80, "variableMax": 200, "variableStep": 10, "variableUnit": "cm" },
// { "id": "pb_3", "type": "price-table", "name": "Material Upcharge",
// "tableColumns": [
// { "id": "col_1", "label": "Material", "type": "option-ref" },
// { "id": "col_2", "label": "Price", "type": "number" }
// ],
// "tableRows": [
// { "id": "row_1", "cells": { "col_1": "oak", "col_2": 0 }, "priceAdjustment": 0 },
// { "id": "row_2", "cells": { "col_1": "walnut", "col_2": 89 }, "priceAdjustment": 89 },
// { "id": "row_3", "cells": { "col_1": "marble", "col_2": 299 }, "priceAdjustment": 299 }
// ]
// },
// { "id": "pb_4", "type": "unique-price", "name": "Connect Full Wall Unique Price",
// "uniquePriceModularBlockId": "blk_roof_parts",
// "uniquePriceVariantId": "mod_connect_full_wall",
// "uniquePriceColumnOptionBlockId": "blk_treatment",
// "uniquePriceQuantities": [1, 2],
// "uniquePriceCellPrices": { "1::natural": 249, "2::natural": 699 },
// "uniquePriceDefaultCellPrices": { "natural": 399, "painted": 549 }
// }
// ]
// }Create Pricing Block
// Create a variable for "Width" dimension
const response = await fetch(BASE_URL + '/projects/proj_123/pricing-blocks', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'variable',
name: 'Width',
variableKey: 'width',
variableDefaultValue: 120,
variableMin: 80,
variableMax: 200,
variableStep: 10,
variableUnit: 'cm'
})
});Update Pricing Block
// Update a price table with new rows
const response = await fetch(BASE_URL + '/projects/proj_123/pricing-blocks/pb_3', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
tableRows: [
{ id: 'row_1', cells: { col_1: 'oak', col_2: 0 }, priceAdjustment: 0 },
{ id: 'row_2', cells: { col_1: 'walnut', col_2: 120 }, priceAdjustment: 120 },
{ id: 'row_new', cells: { col_1: 'carbon-fiber', col_2: 450 }, priceAdjustment: 450 }
]
})
});Set Pricing Formula
Define the formula that combines pricing blocks. Uses token-based format with block references, numbers, and operators.
const response = await fetch(BASE_URL + '/projects/proj_123/pricing-formula', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
formulaTokens: [
{ type: 'block', blockId: 'pb_1' }, // @Base_Price
{ type: 'operator', value: '+' },
{ type: 'block', blockId: 'pb_3' }, // @Material_Upcharge
{ type: 'operator', value: '+' },
{ type: 'paren', value: '(' },
{ type: 'block', blockId: 'pb_2' }, // @Width
{ type: 'operator', value: '*' },
{ type: 'number', value: '2.5' }, // per-cm cost
{ type: 'paren', value: ')' }
]
// Human readable: @Base_Price + @Material_Upcharge + (@Width * 2.5)
})
});Calculate Price
Calculate the final price given a set of option block selections and variable values.
const response = await fetch(BASE_URL + '/projects/proj_123/pricing-blocks/calculate', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
selections: {
dropdownSelections: { 'blk_wood': 'walnut' },
selectMaterialSelections: {},
checkboxSelections: { 'blk_accessories': ['cup-holder'] },
toggleSwitchSelections: { 'blk_armrests': 'with' },
carouselSelections: {}
},
variables: {
width: 160,
quantity: 2
}
})
});
const data = await response.json();
// {
// "success": true,
// "basePrice": 499.99,
// "adjustments": [
// { "blockId": "pb_3", "blockName": "Material Upcharge", "amount": 120 },
// { "blockId": "pb_2", "blockName": "Width", "amount": 400 }
// ],
// "totalPrice": 1019.99,
// "currency": "USD",
// "subtotal": 1019.99,
// "tax": 203.998,
// "totalWithTax": 1223.988,
// "taxRate": 20,
// "taxMode": "exclusive",
// "taxLabel": "VAT",
// "taxEnabled": true,
// "formatted": "$1,223.99",
// "formattedSubtotal": "$1,019.99",
// "formattedTax": "$204.00"
// }Contact Forms
Build contact/quote request forms with configurable fields. Supports name, email, phone, text, dropdowns, country pickers, legal acceptance, custom headers, and confirmation screens.
List Form Fields
const response = await fetch(BASE_URL + '/projects/proj_123/form-fields', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// {
// "success": true,
// "fields": [
// { "id": "ff_1", "type": "header-title", "label": "Header",
// "headerText": "Request a Quote", "headerSubtext": "Fill in the details below.",
// "headerAlignment": "center", "enabled": true, "required": false, "width": "full" },
// { "id": "ff_2", "type": "name", "label": "Full Name", "placeholder": "Enter your name",
// "required": true, "enabled": true, "width": "full" },
// { "id": "ff_3", "type": "email", "label": "Email", "placeholder": "[email protected]",
// "required": true, "enabled": true, "width": "half",
// "validationPattern": "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+quot; },
// { "id": "ff_4", "type": "telephone", "label": "Phone", "width": "half",
// "required": false, "enabled": true },
// { "id": "ff_5", "type": "dropdown", "label": "Industry", "width": "full",
// "options": [
// { "id": "1", "label": "Architecture", "value": "architecture" },
// { "id": "2", "label": "Retail", "value": "retail" },
// { "id": "3", "label": "Manufacturing", "value": "manufacturing" }
// ] },
// { "id": "ff_6", "type": "country-state-city", "label": "Location",
// "includeState": true, "includeCity": true },
// { "id": "ff_7", "type": "text", "label": "Additional Notes",
// "multiline": true, "rows": 4, "maxLength": 500 },
// { "id": "ff_8", "type": "acceptance", "label": "I agree to the Terms",
// "acceptanceText": "By submitting this form...", "required": true },
// { "id": "ff_9", "type": "submit", "label": "Submit",
// "submitLabel": "Submit Your Request", "submitStyle": "gradient" },
// { "id": "ff_10", "type": "confirmation", "label": "Confirmation",
// "confirmationTitle": "Thank You!",
// "confirmationMessage": "We will get back to you within 24-48 hours.",
// "confirmationShowSummary": true, "confirmationShowBackButton": true }
// ]
// }Create Form Field
// Add a dropdown field for "Budget Range"
const response = await fetch(BASE_URL + '/projects/proj_123/form-fields', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'dropdown',
label: 'Budget Range',
required: true,
width: 'half',
options: [
{ id: '1', label: 'Under $1,000', value: 'under-1000' },
{ id: '2', label: '$1,000 - $5,000', value: '1000-5000' },
{ id: '3', label: '$5,000 - $10,000', value: '5000-10000' },
{ id: '4', label: 'Over $10,000', value: 'over-10000' }
],
allowOther: true
})
});Update Form Field
const response = await fetch(BASE_URL + '/projects/proj_123/form-fields/ff_2', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
label: 'Your Full Name',
placeholder: 'First and Last name',
width: 'half'
})
});Delete Form Field
const response = await fetch(BASE_URL + '/projects/proj_123/form-fields/ff_5', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Reorder Form Fields
const response = await fetch(BASE_URL + '/projects/proj_123/form-fields/reorder', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
fieldIds: ['ff_1', 'ff_2', 'ff_3', 'ff_4', 'ff_7', 'ff_5', 'ff_8', 'ff_9', 'ff_10']
})
});3D Viewer
Embed and control the 3D viewer programmatically. Configure camera, lighting, environment, manage viewer state, take screenshots, and respond to user interactions via callbacks.
Get Embed Configuration
const response = await fetch(BASE_URL + '/projects/proj_123/viewer/embed-config', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// {
// "success": true,
// "config": {
// "projectId": "proj_123",
// "cameraPosition": [0, 2, 5],
// "cameraTarget": [0, 0, 0],
// "cameraFOV": 50,
// "enableZoom": true,
// "enablePan": true,
// "enableAutoRotate": false,
// "environmentPreset": "studio",
// "lightIntensity": 1.0,
// "ambientLightIntensity": 0.5,
// "enableShadows": true,
// "backgroundColor": "#f5f5f5",
// "enableAR": false,
// "loadingAnimation": "spinner",
// "loadingMessage": "Loading 3D model..."
// }
// }Embed via iframe
The simplest way to embed a 3D viewer is via the share URL in an iframe.
<!-- Simple iframe embed -->
<iframe
src="https://your-project.supabase.co/share/proj_123/SHARE_TOKEN"
width="100%"
height="600"
frameborder="0"
allow="xr-spatial-tracking"
allowfullscreen
></iframe>
<!-- Embed with custom parameters -->
<iframe
src="https://your-project.supabase.co/share/proj_123/SHARE_TOKEN?autoRotate=true&bg=%23ffffff"
width="800"
height="600"
style="border: none; border-radius: 12px;"
></iframe>Headless Viewer Integration
For fully custom UIs, use the headless API to build your own viewer with any 3D library (Three.js, Babylon.js, etc.).
// 1. Load project data with all option blocks and settings
const projectRes = await fetch(BASE_URL + '/projects/proj_123', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const { project } = await projectRes.json();
// 2. Load option blocks for the configurator UI
const blocksRes = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const { blocks } = await blocksRes.json();
// 3. Load pricing blocks for CPQ
const pricingRes = await fetch(BASE_URL + '/projects/proj_123/pricing-blocks', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const { blocks: pricingBlocks } = await pricingRes.json();
// 4. Build your custom UI with any framework
// - Render 3D scene using project.data.scene
// - Render option blocks as custom components
// - Apply material/visibility changes based on selections
// - Calculate prices in real-timeGet Viewer State
const response = await fetch(BASE_URL + '/projects/proj_123/viewer/state', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// {
// "success": true,
// "state": {
// "loaded": true,
// "selections": {
// "dropdownSelections": { "blk_frame": "black" },
// "selectMaterialSelections": { "blk_fabric": "linen" },
// "checkboxSelections": { "blk_accessories": ["cup-holder"] },
// "toggleSwitchSelections": { "blk_armrests": "with" },
// "carouselSelections": {}
// },
// "visibleBlocks": ["blk_001", "blk_002", "blk_003"],
// "currentPrice": { "totalPrice": 1019.99, "currency": "USD" },
// "cameraPosition": [0, 2, 5],
// "cameraTarget": [0, 0, 0]
// }
// }Take Screenshot
const response = await fetch(BASE_URL + '/projects/proj_123/viewer/screenshot', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
width: 1920,
height: 1080,
format: 'png',
cameraPosition: [2, 3, 5],
cameraTarget: [0, 0.5, 0]
})
});
const data = await response.json();
// { "success": true, "url": "https://...signed-screenshot-url..." }PostMessage API (iframe communication)
When embedding via iframe, use postMessage for two-way communication.
// Send commands to the embedded viewer
const iframe = document.getElementById('simplio3d-viewer');
// Set a selection
iframe.contentWindow.postMessage({
type: 'simplio3d:setSelection',
blockId: 'blk_frame',
value: 'silver'
}, '*');
// Set camera position
iframe.contentWindow.postMessage({
type: 'simplio3d:setCamera',
position: [3, 2, 4],
target: [0, 0, 0]
}, '*');
// Request a screenshot
iframe.contentWindow.postMessage({
type: 'simplio3d:screenshot',
format: 'png'
}, '*');
// Listen for events from the viewer
window.addEventListener('message', (event) => {
if (event.data.type === 'simplio3d:selectionChanged') {
console.log('Selection changed:', event.data.selections);
}
if (event.data.type === 'simplio3d:priceChanged') {
console.log('Price:', event.data.totalPrice, event.data.currency);
}
if (event.data.type === 'simplio3d:screenshotReady') {
console.log('Screenshot URL:', event.data.url);
}
if (event.data.type === 'simplio3d:formSubmitted') {
console.log('Form data:', event.data.formData);
}
});Animations
Define looping animations applied to 3D objects for presentations. Supports 6 animation types with configurable speed, amplitude, easing, and loop modes.
Animation Types
List Animation Blocks
const response = await fetch(BASE_URL + '/projects/proj_123/animation-blocks', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// {
// "success": true,
// "blocks": [
// {
// "id": "anim_1",
// "type": "rotation",
// "name": "Turntable",
// "enabled": true,
// "targetObjectId": "all",
// "targetPartNames": [],
// "axis": "y",
// "speed": 0.2,
// "amplitude": 360,
// "easing": "linear",
// "loopMode": "loop",
// "delay": 0
// },
// {
// "id": "anim_2",
// "type": "float",
// "name": "Hover Effect",
// "enabled": true,
// "targetPartNames": ["product_body"],
// "axis": "y",
// "speed": 0.4,
// "amplitude": 0.3,
// "easing": "ease-in-out",
// "loopMode": "ping-pong",
// "delay": 0.5
// }
// ]
// }Create Animation Block
// Create an orbit animation for a satellite object
const response = await fetch(BASE_URL + '/projects/proj_123/animation-blocks', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'orbit',
name: 'Orbiting Camera Target',
targetPartNames: ['highlight_ring'],
axis: 'y',
speed: 0.15,
amplitude: 1,
easing: 'linear',
loopMode: 'loop',
orbitRadius: 2.5,
orbitCenter: [0, 0, 0],
orbitPlane: 'xz'
})
});Update Animation Block
const response = await fetch(BASE_URL + '/projects/proj_123/animation-blocks/anim_1', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
speed: 0.1,
enabled: false
})
});Delete Animation Block
const response = await fetch(BASE_URL + '/projects/proj_123/animation-blocks/anim_2', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Project Settings
Configure every aspect of a project: branding, lighting, camera, loading screens, layout, watermarks, email notifications, e-commerce integration, webhooks, and analytics.
Get Project Settings
const response = await fetch(BASE_URL + '/projects/proj_123/settings', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// {
// "success": true,
// "settings": {
// "enableAutoRotate": false,
// "autoRotateSpeed": 1,
// "cameraFOV": 50,
// "enableZoom": true,
// "enablePan": true,
// "environmentPreset": "studio",
// "lightIntensity": 1.0,
// "enableShadows": true,
// "viewportBackgroundColor": "#f5f5f5",
// "sidebarBackgroundColor": "#ffffff",
// "primaryAccentColor": "#6b7280",
// "actionButtonBgColor": "#6b7280",
// "actionButtonHoverBgColor": "#4b5563",
// "actionButtonTextColor": "#ffffff",
// "fontFamily": "system",
// "sidebarWidth": 360,
// "shareViewLayout": "default",
// "enableWatermark": false,
// "showPrice": true,
// "enableForm": true,
// "enableAR": false,
// "emailEnabled": false,
// "ecommerceEnabled": false,
// "webhookUrl": "",
// "googleAnalyticsId": ""
// }
// }Update Project Settings
const response = await fetch(BASE_URL + '/projects/proj_123/settings', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
// Branding
viewportBackgroundColor: '#1a1a2e',
sidebarBackgroundColor: '#16213e',
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,
// Loading screen
customLoadingAnimation: 'pulse',
loadingMessage: 'Preparing your experience...',
backgroundColor: '#1a1a2e',
// Features
enableAR: true,
showPrice: true,
enableForm: true,
// Webhooks
webhookUrl: 'https://your-server.com/webhook',
webhookEvents: ['configuration_changed', 'quote_requested'],
// Analytics
googleAnalyticsId: 'G-XXXXXXXXXX'
})
});Configure Email Notifications
const response = await fetch(BASE_URL + '/projects/proj_123/settings', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
emailEnabled: true,
smtpHost: 'smtp.example.com',
smtpPort: 587,
smtpEncryption: 'tls',
smtpUsername: '[email protected]',
emailFromAddress: '[email protected]',
emailFromName: 'Simplio3D Configurator'
})
});Assets
Manage 3D models, textures, and graphics. Upload, organize, and retrieve assets with signed download URLs.
List Assets
// Get all assets (optionally filter by type)
const response = await fetch(BASE_URL + '/assets?type=3d', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});Upload Asset
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('name', 'Chair Model');
formData.append('type', '3d');
formData.append('category', 'furniture');
const response = await fetch(BASE_URL + '/assets', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + accessToken },
body: formData
});Get Asset
const response = await fetch(BASE_URL + '/assets/asset_123', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});Update Asset
const response = await fetch(BASE_URL + '/assets/asset_123', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Renamed Asset', category: 'new-category' })
});Delete Asset
const response = await fetch(BASE_URL + '/assets/asset_123', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Download Asset (Signed URL)
const response = await fetch(BASE_URL + '/assets/asset_123/download', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// { "success": true, "url": "https://...signed-url..." }Materials
Manage PBR materials with texture maps. Create realistic surface properties for your 3D models.
List Materials
const response = await fetch(BASE_URL + '/materials', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// { "success": true, "materials": [{ "id": "mat_123", "name": "Brushed Metal",
// "pbr": { "baseColor": "#808080", "metallic": 0.9, "roughness": 0.3 } }] }Create Material
const response = await fetch(BASE_URL + '/materials', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Oak Wood',
category: 'wood',
pbr: { baseColor: '#8B6F47', metallic: 0.0, roughness: 0.8 }
})
});Update Material
const response = await fetch(BASE_URL + '/materials/mat_123', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ pbr: { roughness: 0.5, metallic: 0.8 } })
});Delete Material
const response = await fetch(BASE_URL + '/materials/mat_123', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Categories
Organize assets and materials into categories for better management and filtering.
List Categories
const response = await fetch(BASE_URL + '/categories/3d', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});Create Category
const response = await fetch(BASE_URL + '/categories', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Furniture', assetType: '3d', color: '#2E8BCB' })
});Update Category
const response = await fetch(BASE_URL + '/categories/cat_123', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Updated Name', color: '#FF6600' })
});Delete Category
const response = await fetch(BASE_URL + '/categories/cat_123', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Team Management
Invite and manage team members with role-based access. Supported roles: admin, editor, viewer.
List Team Members
const response = await fetch(BASE_URL + '/team/members', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// { "success": true, "members": [{ "id": "mem_123", "email": "[email protected]",
// "name": "Jane Smith", "role": "editor", "status": "active" }] }Invite Team Member
const response = await fetch(BASE_URL + '/team/members', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: '[email protected]',
role: 'editor',
name: 'John Doe'
})
});Update Team Member
const response = await fetch(BASE_URL + '/team/members/mem_123', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ role: 'admin', status: 'active' })
});Remove Team Member
const response = await fetch(BASE_URL + '/team/members/mem_123', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Project Sharing
Generate public share links for projects. Supports password protection, expiry, and download permissions.
Get Share Configuration
const response = await fetch(BASE_URL + '/projects/proj_123/share', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// { "success": true, "shareConfig": { "enabled": true, "shareUrl": "https://...",
// "viewCount": 42, "passwordProtected": false } }Enable Sharing
const response = await fetch(BASE_URL + '/projects/proj_123/share/enable', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Update Share Settings
const response = await fetch(BASE_URL + '/projects/proj_123/share', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
allowDownload: true,
passwordProtected: true,
password: 'secret123',
expiresAt: '2026-06-01T00:00:00Z'
})
});Regenerate Share Token
const response = await fetch(BASE_URL + '/projects/proj_123/share/regenerate', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Access Shared Project (Public)
const response = await fetch(BASE_URL + '/share/proj_123/abc123', {
headers: {
// Required when calling Supabase Edge Functions directly from browser clients
'apikey': SUPABASE_ANON_KEY,
'Authorization': 'Bearer ' + SUPABASE_ANON_KEY
}
});
const data = await response.json();
// Handles large-project variants internally in the share viewerSubmit Shared Form (Async Processing)
const response = await fetch(BASE_URL + '/share/proj_123/abc123/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': SUPABASE_ANON_KEY,
'Authorization': 'Bearer ' + SUPABASE_ANON_KEY
},
body: JSON.stringify({
formData: { name: 'Jane Smith', email: '[email protected]' },
selectedOptions: {},
configurationSummary: '...',
screenshots: []
})
});
const data = await response.json();
// Immediate acknowledgment:
// { "success": true, "requestId": "..." }
// Screenshots/PDF/emails/webhooks are processed asynchronously after response.Async Submit Semantics
Shared-form submit endpoints persist the request first and respond immediately. Heavy side effects (screenshot upload, PDF generation, SMTP notifications, webhook dispatch) run in background tasks, so they are eventually consistent and may complete after the initial 200 response.
Email & Logs
Send test emails, view delivery logs, and resend failed emails. Emails use your own SMTP configuration.
Send Test Email
const response = await fetch(BASE_URL + '/email/test', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
emailSettings: {
smtpHost: 'smtp.example.com',
smtpPort: 587,
smtpEncryption: 'tls',
smtpUsername: '[email protected]',
smtpPassword: 'password',
emailFromAddress: '[email protected]'
},
testRecipient: '[email protected]'
})
});Get Email Logs
const response = await fetch(BASE_URL + '/email/logs', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
// Returns up to 200 logs. Auto-purged after 30 days.Delete Email Log
const response = await fetch(BASE_URL + '/email/logs/log_123', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Resend Failed Email
// Max 3 resend attempts per log entry
const response = await fetch(BASE_URL + '/email/logs/log_123/resend', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Bulk Delete Logs
const response = await fetch(BASE_URL + '/email/logs/bulk-delete', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ logIds: ['log_1', 'log_2', 'log_3'] })
});API Tokens
Generate and manage API tokens for programmatic access. Tokens can be used as an alternative to JWT session tokens.
Generate API Token
const response = await fetch(BASE_URL + '/api-token', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// { "success": true, "token": "550e8400-e29b-41d4-a716-446655440000" }Get Current Token
const response = await fetch(BASE_URL + '/api-token', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});Regenerate Token
const response = await fetch(BASE_URL + '/api-token/regenerate', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Token Security
API tokens provide full access to your account. Keep them secure, never commit them to version control, and regenerate immediately if compromised.
Quote Submissions
Submit and manage quote requests. Each submission captures the customer's form data, configuration selections, calculated pricing, and optional screenshot.
Submit a Quote (Public)
This endpoint can be used without authentication for public-facing configurators.
const response = await fetch(BASE_URL + '/projects/proj_123/quote-submissions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
formData: {
name: 'Jane Smith',
email: '[email protected]',
phone: '+1 555 0123',
message: 'Interested in bulk pricing for 50 units.'
},
configuration: {
selections: {
dropdownSelections: { 'blk_frame': 'black', 'blk_wood': 'walnut' },
selectMaterialSelections: { 'blk_fabric': 'linen' },
checkboxSelections: { 'blk_accessories': ['cup-holder', 'monitor-stand'] },
toggleSwitchSelections: { 'blk_armrests': 'with' },
carouselSelections: {}
},
variables: { width: 160, quantity: 50 },
screenshotUrl: 'https://cdn.example.com/screenshots/config_abc.png'
}
})
});
const data = await response.json();
// {
// "success": true,
// "submission": {
// "id": "qs_123",
// "projectId": "proj_123",
// "status": "new",
// "customerName": "Jane Smith",
// "customerEmail": "[email protected]",
// "submittedAt": "2026-03-10T15:30:00Z"
// }
// }List Quote Submissions
const response = await fetch(BASE_URL + '/projects/proj_123/quote-submissions', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// {
// "success": true,
// "submissions": [
// {
// "id": "qs_123",
// "status": "new",
// "customerName": "Jane Smith",
// "customerEmail": "[email protected]",
// "formData": { ... },
// "configuration": { "selections": { ... }, "pricing": { "totalPrice": 1019.99 } },
// "submittedAt": "2026-03-10T15:30:00Z"
// }
// ]
// }Update Submission Status
const response = await fetch(BASE_URL + '/projects/proj_123/quote-submissions/qs_123', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
status: 'replied' // 'new' | 'viewed' | 'replied' | 'archived'
})
});Delete Submission
const response = await fetch(BASE_URL + '/projects/proj_123/quote-submissions/qs_123', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Webhooks
Receive real-time HTTP callbacks when events occur in your Simplio3D projects. Configure multiple webhooks per project with event filtering and HMAC-SHA256 signature verification.
Supported Event Types
List Webhooks
/projects/:projectId/webhooksRetrieve all webhook configurations for a project.
curl -X GET \
https://api.simplio3d.com/v1/projects/proj_123/webhooks \
-H "Authorization: Bearer YOUR_TOKEN"Create / Update Webhook
/projects/:projectId/webhooks/:webhookIdCreate a new webhook or update an existing one. The webhook ID is idempotent — use the same ID to update.
curl -X PUT \
https://api.simplio3d.com/v1/projects/proj_123/webhooks/wh_abc \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Order Notifications",
"url": "https://your-server.com/webhook",
"events": ["quote.submitted", "form.submitted"],
"secret": "whsec_your_signing_secret",
"enabled": true
}'Request Body
url (required) — HTTPS endpoint to receive events
events (required) — Array of event types to subscribe to
secret — Signing secret for HMAC-SHA256 verification
name — Display name for the webhook
enabled — Whether the webhook is active (default: true)
Delete Webhook
/projects/:projectId/webhooks/:webhookIdcurl -X DELETE \
https://api.simplio3d.com/v1/projects/proj_123/webhooks/wh_abc \
-H "Authorization: Bearer YOUR_TOKEN"Test Webhook
/projects/:projectId/webhooks/:webhookId/testSend a test webhook.test event to verify connectivity. The response includes the HTTP status from your server.
curl -X POST \
https://api.simplio3d.com/v1/projects/proj_123/webhooks/wh_abc/test \
-H "Authorization: Bearer YOUR_TOKEN"
# Response:
# { "success": true, "test": { "statusCode": 200, "statusText": "OK", "delivered": true } }Delivery Logs
/projects/:projectId/webhooks/:webhookId/deliveriesReturns up to the last 50 delivery attempts for a specific webhook.
curl -X GET \
https://api.simplio3d.com/v1/projects/proj_123/webhooks/wh_abc/deliveries \
-H "Authorization: Bearer YOUR_TOKEN"
# Response:
# { "success": true, "deliveries": [
# { "webhookId": "wh_abc", "event": "quote.submitted",
# "statusCode": 200, "delivered": true, "timestamp": "..." },
# ...
# ] }Trigger Webhooks
/projects/:projectId/webhooks/triggerManually fire an event to all matching webhooks. Useful for testing integrations or replaying events.
curl -X POST \
https://api.simplio3d.com/v1/projects/proj_123/webhooks/trigger \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "event": "quote.submitted", "data": { "quoteId": "q_456" } }'Signature Verification
Each webhook delivery includes these headers for security:
X-Simplio3D-Event — Event type (e.g. quote.submitted)
X-Simplio3D-Delivery — Unique delivery UUID
X-Simplio3D-Signature — HMAC-SHA256 hex digest: sha256=...
// Node.js verification example
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature), Buffer.from(expected)
);
}Augmented Reality (AR)
Enable AR experiences for your 3D configurator projects. Desktop users see a QR code, mobile users launch native AR viewers (Scene Viewer on Android, AR Quick Look on iOS).
Upload USDZ File (iOS)
Upload a .usdz file for native iOS AR Quick Look support. Without a USDZ file, iOS devices may fall back to a less reliable GLB-based AR experience.
/projects/:id/usdzcurl -X POST \
".../projects/PROJECT_ID/usdz" \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "[email protected]"
# Response:
{
"success": true,
"usdzId": "uuid",
"usdzPath": "userId/project-usdz/...",
"fileName": "model.usdz"
}Tip: Convert GLB to USDZ using Apple's Reality Converter (macOS), Blender's USDZ export, or online tools like glb-to-usdz.com.
Delete USDZ File
/projects/:id/usdz/:usdzIdTrack AR Analytics Event
Record AR interaction events. This endpoint is public (no auth) and used via sendBeacon from the viewer. Fire-and-forget.
/ar-analytics{
"event": "ar_launch_ios",
"projectId": "PROJECT_ID",
"shareToken": "optional",
"meta": { "source": "qr_code" }
}
// Valid event types:
// ar_dialog_open, ar_qr_copy, ar_qr_open_tab,
// ar_launch_ios, ar_launch_android,
// ar_auto_launch, ar_page_viewGet AR Analytics Summary
Fetch aggregated AR analytics for a project (requires auth).
/projects/:id/ar-analytics// Response:
{
"projectId": "...",
"totalEvents": 142,
"byEvent": {
"ar_dialog_open": 58,
"ar_launch_ios": 41,
"ar_launch_android": 29,
"ar_qr_copy": 14
},
"lastUpdated": "2026-03-17T...",
"recentEvents": [...]
}Public USDZ URL (Share Viewer)
Get a signed URL for a shared project's USDZ file. Used by the share viewer to launch iOS AR Quick Look with the USDZ file.
/share/:projectId/usdzAR Project Settings
AR behavior is controlled via Project Settings fields. Set these when saving a project:
{
"enableAR": true, // Master toggle
"arUsdzFileName": "...", // iOS USDZ file name
"arUsdzModelPath": "...", // Storage path (set by upload)
"arUsdzModelId": "...", // Storage ID (set by upload)
"arAutoLaunch": true, // Auto-launch AR on ?ar=1
"arAnalyticsEnabled": true // Track AR events
}API Changelog
Track all API changes, new endpoints, deprecations, and improvements across versions.
API Changelog
Track all changes, updates, and improvements
v3.4
patchLatest✨ New Features
- •Large Project Transport + Async Submit Semantics: documented response variants for GET /projects/:id (full scene, gzip-base64 compressed scene payload, and split scene+models+camera payloads).
- •Sharing docs now include public form submit endpoint semantics: POST /share/:projectId/:token/submit returns immediate acknowledgment while screenshot/PDF/email/webhook side effects complete asynchronously.
⚡ Improvements
- •Project Sharing examples now show browser-safe Supabase Edge Function headers (apikey + bearer anon token) for direct public viewer/submit calls.
