npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cymmetrik-dev/3d-transfer-sdk

v1.6.1

Published

TypeScript SDK for Mobius 3D Transfer API - Convert images to 3D models

Downloads

1,199

Readme

@cymmetrik-dev/3d-transfer-sdk

TypeScript SDK for Mobius 3D Transfer API - Convert images to 3D models using AI-powered style transfer.

Features

  • Full TypeScript support with auto-generated types
  • Support for multiple frameworks (Vite, Next.js, Expo, Node.js)
  • Environment variable based configuration
  • Axios-based HTTP client
  • React ModelViewer component for displaying 3D GLB models
  • Default style filtering for single-style deployments
  • Additional instruction for custom style guidance (max 2000 chars)
  • Add-to-cart redirect for seamless shopping cart integration (supports guest users)
  • Split 2D/3D workflow - Control when 3D generation starts with auto_3d parameter

Installation

npm install @cymmetrik-dev/3d-transfer-sdk
# or
yarn add @cymmetrik-dev/3d-transfer-sdk
# or
pnpm add @cymmetrik-dev/3d-transfer-sdk

Environment Variables

Configure the SDK using environment variables based on your framework:

| Variable | Framework | Description | |----------|-----------|-------------| | VITE_TRANSFER_3D_API_URL | Vite | API base URL | | VITE_TRANSFER_3D_API_TOKEN | Vite | API authentication token | | VITE_TRANSFER_3D_DEFAULT_STYLE | Vite | Default style (optional) | | VITE_DEFAULT_PRODUCT_SLUG | Vite | Default product slug (optional) | | NEXT_PUBLIC_TRANSFER_3D_API_URL | Next.js | API base URL | | NEXT_PUBLIC_TRANSFER_3D_API_TOKEN | Next.js | API authentication token | | NEXT_PUBLIC_TRANSFER_3D_DEFAULT_STYLE | Next.js | Default style (optional) | | NEXT_PUBLIC_DEFAULT_PRODUCT_SLUG | Next.js | Default product slug (optional) | | EXPO_PUBLIC_TRANSFER_3D_API_URL | Expo | API base URL | | EXPO_PUBLIC_TRANSFER_3D_API_TOKEN | Expo | API authentication token | | EXPO_PUBLIC_TRANSFER_3D_DEFAULT_STYLE | Expo | Default style (optional) | | EXPO_PUBLIC_DEFAULT_PRODUCT_SLUG | Expo | Default product slug (optional) | | TRANSFER_3D_API_URL | Node.js | API base URL | | TRANSFER_3D_API_TOKEN | Node.js | API authentication token | | TRANSFER_3D_DEFAULT_STYLE | Node.js | Default style (optional) | | DEFAULT_PRODUCT_SLUG | Node.js | Default product slug (optional) |

Example .env file

# For Vite projects
VITE_TRANSFER_3D_API_URL=https://api.example.com/api
VITE_TRANSFER_3D_API_TOKEN=your-api-token-here
VITE_TRANSFER_3D_DEFAULT_STYLE=        # Optional: filter to single style
VITE_DEFAULT_PRODUCT_SLUG=             # Optional: default product for accessory info

# For Next.js projects
NEXT_PUBLIC_TRANSFER_3D_API_URL=https://api.example.com/api
NEXT_PUBLIC_TRANSFER_3D_API_TOKEN=your-api-token-here
NEXT_PUBLIC_TRANSFER_3D_DEFAULT_STYLE= # Optional: filter to single style
NEXT_PUBLIC_DEFAULT_PRODUCT_SLUG=      # Optional: default product for accessory info

# For Node.js/backend projects
TRANSFER_3D_API_URL=https://api.example.com/api
TRANSFER_3D_API_TOKEN=your-api-token-here
TRANSFER_3D_DEFAULT_STYLE=             # Optional: filter to single style
DEFAULT_PRODUCT_SLUG=                  # Optional: default product for accessory info

Quick Start

Initialize SDK (Recommended)

Use initTransfer3D() to configure the SDK once at app startup. This sets the base URL and token for all subsequent API calls.

import { initTransfer3D } from '@cymmetrik-dev/3d-transfer-sdk';

// Option 1: Auto-detect from environment variables
initTransfer3D();

// Option 2: Explicit configuration
initTransfer3D({
  baseURL: 'https://api.example.com/api',
  token: 'your-api-token',
});

In a Vite/React project, create a setup file:

// src/lib/transfer3d.ts
import { initTransfer3D } from '@cymmetrik-dev/3d-transfer-sdk';

initTransfer3D({
  baseURL: import.meta.env.VITE_TRANSFER_3D_API_URL,
  token: import.meta.env.VITE_TRANSFER_3D_API_TOKEN,
});

Then import it early in your app:

// src/main.tsx
import './lib/transfer3d';  // Initialize SDK
import { createRoot } from 'react-dom/client';
import App from './App';

createRoot(document.getElementById('root')!).render(<App />);

Alternative: Create Custom Client

If you need multiple clients (e.g., different API endpoints), use createTransfer3DClient():

import {
  createTransfer3DClient,
  getTransfer3dStyles,
  initiateTransfer3dConversion,
  getTransfer3dStatus,
} from '@cymmetrik-dev/3d-transfer-sdk';

// Create a custom client instance
const client = createTransfer3DClient({
  baseURL: 'https://api.example.com/api',
  token: 'your-api-token',
});

// Pass client to each API call
const styles = await getTransfer3dStyles({ client });

Complete Workflow Example

import {
  createTransfer3DClient,
  getTransfer3dStyles,
  initiateTransfer3dConversion,
  getTransfer3dStatus,
} from '@cymmetrik-dev/3d-transfer-sdk';

async function convertImageTo3D(imageFile: File) {
  // 1. Initialize client
  const client = createTransfer3DClient({
    token: 'your-api-token',
  });

  // 2. Get available styles
  const stylesResponse = await getTransfer3dStyles({ client });
  console.log('Available styles:', stylesResponse.data);
  // Output: [{ mode_key: 'smart', mode_name: 'Q版公仔', ... }]

  // 3. Upload image and start conversion
  const conversionResponse = await initiateTransfer3dConversion({
    client,
    body: {
      image: imageFile,
      style: 'smart', // Use a style from step 2
      additional_instruction: '增加金色高光效果', // Optional: custom guidance
    },
  });

  const requestId = conversionResponse.data.data.request_id;
  console.log('Conversion started:', requestId);

  // 4. Poll for status until completed
  let status = 'PENDING';
  let result = null;

  while (status !== 'COMPLETED' && status !== 'FAILED') {
    await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2 seconds

    const statusResponse = await getTransfer3dStatus({
      client,
      path: { requestId },
    });

    const data = statusResponse.data.data;
    status = data.status;
    console.log('Status:', status, 'Phase:', data.phase, 'Progress:', data.progress);

    // 2D preview is available when phase changes to 3d_generation
    if (data.result?.preview_2d_url) {
      console.log('2D Preview ready:', data.result.preview_2d_url);
    }

    if (status === 'COMPLETED') {
      result = data.result;
    }
  }

  if (result) {
    console.log('3D Model URL:', result.model_3d_url);
    console.log('GLB File URL:', result.glb_url);
    console.log('2D Preview URL:', result.preview_2d_url);
  }

  return result;
}

Split 2D/3D Workflow (Manual 3D Trigger)

When you want to let users preview the 2D styled image before proceeding to 3D generation:

import {
  createTransfer3DClient,
  initiateTransfer3dConversion,
  triggerTransfer3dConversion,
  getTransfer3dStatus,
} from '@cymmetrik-dev/3d-transfer-sdk';

async function convertWithManual3DTrigger(imageFile: File) {
  const client = createTransfer3DClient();

  // 1. Start 2D-only conversion
  const { data } = await initiateTransfer3dConversion({
    client,
    body: {
      image: imageFile,
      style: 'smart',
      auto_3d: false,  // Don't auto-proceed to 3D
    },
  });

  const requestId = data.data.request_id;

  // 2. Poll until 2D completes
  let status = 'PENDING';
  let preview2dUrl = null;

  while (status !== '2D_COMPLETED' && status !== 'FAILED') {
    await new Promise((r) => setTimeout(r, 2000));

    const { data: statusData } = await getTransfer3dStatus({
      client,
      path: { requestId },
    });

    status = statusData.data.status;

    if (statusData.data.result?.preview_2d_url) {
      preview2dUrl = statusData.data.result.preview_2d_url;
    }
  }

  console.log('2D Preview ready:', preview2dUrl);
  // At this point, show preview_2d_url to user and wait for confirmation

  // 3. When user confirms, trigger 3D generation
  await triggerTransfer3dConversion({
    client,
    body: { request_id: requestId },
  });

  // 4. Poll until 3D completes
  status = 'IN_PROGRESS';
  let glbUrl = null;

  while (status !== 'COMPLETED' && status !== 'FAILED') {
    await new Promise((r) => setTimeout(r, 2000));

    const { data: statusData } = await getTransfer3dStatus({
      client,
      path: { requestId },
    });

    status = statusData.data.status;

    if (status === 'COMPLETED') {
      glbUrl = statusData.data.result?.glb_url;
    }
  }

  return { preview2dUrl, glbUrl };
}

React Components

ModelViewer

The SDK includes a React component for displaying 3D GLB/GLTF models using Google's model-viewer web component.

import { ModelViewer } from '@cymmetrik-dev/3d-transfer-sdk/react';

function My3DViewer() {
  return (
    <ModelViewer
      src="https://example.com/model.glb"
      alt="3D Model"
      cameraControls
      autoRotate
      style={{ width: '100%', height: '400px' }}
    />
  );
}

ModelViewer Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | src | string | required | URL to the GLB/GLTF model file | | alt | string | '3D Model' | Alt text for accessibility | | cameraControls | boolean | true | Enable camera controls (pan, zoom, rotate) | | autoRotate | boolean | true | Enable auto rotation | | shadowIntensity | number | 1 | Shadow intensity (0-1) | | transparentBackground | boolean | false | Enable transparent background (removes default gradient) | | poster | string | - | Poster image URL to show while loading | | className | string | - | CSS class name | | style | CSSProperties | - | Inline styles | | loading | 'auto' \| 'lazy' \| 'eager' | 'auto' | Loading strategy | | reveal | 'auto' \| 'manual' | 'auto' | Reveal strategy | | onLoad | () => void | - | Callback when model is loaded | | onError | (error: Error) => void | - | Callback when error occurs |

Transparent Background Example

To display the 3D model with a transparent background (useful for overlays or custom backgrounds):

<ModelViewer
  src="https://example.com/model.glb"
  transparentBackground
  cameraControls
  autoRotate
  style={{ width: '200px', height: '200px' }}
/>

Complete React Example with ModelViewer

import { useState, useEffect } from 'react';
import {
  createTransfer3DClient,
  getTransfer3dStyles,
  initiateTransfer3dConversion,
  getTransfer3dStatus,
} from '@cymmetrik-dev/3d-transfer-sdk';
import { ModelViewer } from '@cymmetrik-dev/3d-transfer-sdk/react';

export function ImageTo3DConverter() {
  const [status, setStatus] = useState<string>('idle');
  const [progress, setProgress] = useState(0);
  const [preview2dUrl, setPreview2dUrl] = useState<string | null>(null);
  const [glbUrl, setGlbUrl] = useState<string | null>(null);
  const [webViewUrl, setWebViewUrl] = useState<string | null>(null);

  const handleConvert = async (file: File, style: string) => {
    const client = createTransfer3DClient();

    setStatus('uploading');
    const { data } = await initiateTransfer3dConversion({
      client,
      body: { image: file, style },
    });

    const requestId = data.data.request_id;
    setStatus('processing');

    // Poll for completion
    const pollStatus = async () => {
      const { data: statusData } = await getTransfer3dStatus({
        client,
        path: { requestId },
      });

      const result = statusData.data;
      setProgress(result.progress || 0);

      // Web view URL for viewing on main site
      if (result.web_view_url) {
        setWebViewUrl(result.web_view_url);
      }

      // 2D preview becomes available during 3D generation phase
      if (result.result?.preview_2d_url) {
        setPreview2dUrl(result.result.preview_2d_url);
      }

      if (result.status === 'COMPLETED') {
        setGlbUrl(result.result.glb_url);
        setStatus('completed');
      } else if (result.status === 'FAILED') {
        setStatus('failed');
      } else {
        setTimeout(pollStatus, 2000);
      }
    };

    pollStatus();
  };

  return (
    <div>
      <p>Status: {status} ({progress}%)</p>

      {/* Link to view progress on main site */}
      {webViewUrl && (
        <a href={webViewUrl} target="_blank" rel="noopener noreferrer">
          View on main site
        </a>
      )}

      {/* 2D styled preview */}
      {preview2dUrl && (
        <div>
          <h3>2D Style Preview</h3>
          <img src={preview2dUrl} alt="2D Preview" />
        </div>
      )}

      {/* 3D Model Viewer */}
      {glbUrl && (
        <div>
          <h3>3D Model</h3>
          <ModelViewer
            src={glbUrl}
            alt="Generated 3D Model"
            cameraControls
            autoRotate
            style={{ width: '100%', height: '400px' }}
          />
        </div>
      )}
    </div>
  );
}

API Reference

initTransfer3D(config?)

Initialize the default SDK client. Call this once at app startup to configure the base URL and authentication token for all API calls.

Parameters:

  • config.baseURL (optional): API base URL. Falls back to environment variables.
  • config.token (optional): Bearer authentication token. Falls back to environment variables.

Example:

import { initTransfer3D, getTransfer3dStyles } from '@cymmetrik-dev/3d-transfer-sdk';

// Initialize with explicit config
initTransfer3D({
  baseURL: 'https://api.example.com/api',
  token: 'your-api-token',
});

// Or auto-detect from environment variables
initTransfer3D();

// Now all API calls use this configuration (no need to pass client)
const styles = await getTransfer3dStyles();

createTransfer3DClient(config?)

Create a separate API client instance. Use this when you need multiple clients or want to override the default configuration for specific calls.

Parameters:

  • config.baseURL (optional): API base URL
  • config.token (optional): Bearer authentication token
  • config.timeout (optional): Request timeout in ms (default: 30000)

Returns: Configured axios client


getTransfer3dStyles(options)

Get available 3D transfer styles.

Parameters:

  • query.default_style (optional): string - Filter to return only this style

Example:

// Get all styles
const allStyles = await getTransfer3dStyles({ client });

// Get single style (useful for single-style deployments)
const singleStyle = await getTransfer3dStyles({
  client,
  query: { default_style: 'smart' },
});

Returns:

{
  success: true,
  data: Array<{
    mode_key: string;      // Style identifier (e.g., 'smart')
    mode_name: string;     // Display name (e.g., 'Q版公仔')
    mode_description: string;
    preview_image_url: string | null;
  }>
}

Note: If default_style is specified but not found, API returns all styles as fallback.


initiateTransfer3dConversion(options)

Upload an image and start the 3D conversion process.

Parameters:

  • body.image: File - Image to convert (max 10MB, supported: jpg, png, webp)
  • body.style: string - Style key from getTransfer3dStyles()
  • body.additional_instruction (optional): string - Custom style guidance (max 2000 chars)
  • body.auto_3d (optional): boolean - Auto-proceed to 3D after 2D completes (default: true)

Example:

// Basic conversion (auto 2D → 3D)
const result = await initiateTransfer3dConversion({
  client,
  body: {
    image: imageFile,
    style: 'smart',
  },
});

// With additional instruction for fine-tuning
const result = await initiateTransfer3dConversion({
  client,
  body: {
    image: imageFile,
    style: 'smart',
    additional_instruction: '增加金色高光效果,使用暖色調',
  },
});

// 2D-only mode: Stop after 2D style transfer
const result = await initiateTransfer3dConversion({
  client,
  body: {
    image: imageFile,
    style: 'smart',
    auto_3d: false,  // Will stop at 2D_COMPLETED status
  },
});

Additional Instruction Use Cases:

  • Fine-tune style output (e.g., "更卡通化", "增加質感")
  • Specify colors or materials (e.g., "使用暖色調", "金屬質感")
  • Adjust lighting effects (e.g., "增加高光", "柔和陰影")

Note: additional_instruction is appended to the style's base instructions, not replaced.

Returns:

{
  success: true,
  data: {
    request_id: string;       // UUID for status polling
    chat_thread_id: number;   // Internal ChatThread ID (for add-to-cart)
    status: 'PENDING';
    message: string;
  }
}

triggerTransfer3dConversion(options)

Trigger 3D generation for a request that completed 2D styling. Use this when you initiated a conversion with auto_3d: false.

Parameters:

  • body.request_id: string - The request ID from the original /convert call

Example:

// After status shows '2D_COMPLETED', trigger 3D generation
const result = await triggerTransfer3dConversion({
  client,
  body: {
    request_id: 'uuid-xxx',
  },
});

Returns:

{
  success: true,
  data: {
    request_id: string;
    chat_thread_id: number;   // Internal ChatThread ID (for add-to-cart)
    status: 'IN_PROGRESS';
    message: '3D conversion initiated';
  }
}

Error Responses:

  • 400 - 2D style transfer not yet completed
  • 400 - 3D conversion already in progress or completed
  • 404 - Request not found

getTransfer3dStatus(options)

Get the status of a conversion request.

Parameters:

  • path.requestId: string - Request ID from conversion response

Returns:

{
  success: true,
  data: {
    request_id: string;
    status: 'PENDING' | 'IN_PROGRESS' | '2D_COMPLETED' | 'COMPLETED' | 'FAILED';
    phase: '2d_style_transfer' | '3d_generation_pending' | '3d_generation';
    progress: number;  // 0-100 (2D_COMPLETED = 50%)
    web_view_url: string;  // URL to view progress on main site
    auto_3d?: boolean;  // Present for API requests, indicates if 3D will auto-start
    result?: {
      preview_2d_url: string;  // Available when 2D transfer completes
      model_3d_url: string;    // Available when fully completed
      glb_url: string;         // GLB file URL (same as model_3d_url)
    };
    error?: string;  // Present if status is FAILED
  }
}

Note: When auto_3d: false, the status will be 2D_COMPLETED (not COMPLETED) when 2D styling finishes. Call triggerTransfer3dConversion() to start 3D generation.


getTransfer3DProduct(options)

Get product information by slug, including name, description, price, and images.

Parameters:

  • path.slug: string - The product slug

Example:

import {
  createTransfer3DClient,
  getTransfer3DProduct,
  getDefaultProductSlug,
} from '@cymmetrik-dev/3d-transfer-sdk';

const client = createTransfer3DClient();

// Get product by slug
const { data } = await getTransfer3DProduct({
  client,
  path: { slug: 'my-product-slug' },
});

console.log('Product:', data.data.name);
console.log('Price:', data.data.price?.formatted);
console.log('Main Image:', data.data.main_image_url);

// Or use default product slug from environment
const defaultSlug = getDefaultProductSlug();
if (defaultSlug) {
  const { data } = await getTransfer3DProduct({
    client,
    path: { slug: defaultSlug },
  });
}

Returns:

{
  success: true,
  data: {
    slug: string;
    name: string;
    description: string;
    price: {
      value: number;      // Price in smallest currency unit
      formatted: string;  // e.g., "NT$1,990"
      currency: string;   // e.g., "TWD"
    } | null;
    main_image_url: string | null;
    images: Array<{
      url: string;
      alt: string;
    }>;
    variants: Array<{
      id: number;           // Variant ID (use for add-to-cart)
      sku: string;          // SKU code
      options: Array<{      // Variant options (e.g., color, size)
        name: string;       // Option name (e.g., "顏色")
        value: string;      // Option value (e.g., "紅色")
      }>;
      price: {
        value: number;
        formatted: string;
        currency: string;
      };
    }>;
  }
}

Example with Variants:

const { data } = await getTransfer3DProduct({
  client,
  path: { slug: 'my-product' },
});

// Display product with variant selector
console.log('Product:', data.data.name);

data.data.variants.forEach((variant) => {
  const optionText = variant.options
    .map(o => `${o.name}: ${o.value}`)
    .join(', ');
  console.log(`- ${variant.sku} (${optionText}): ${variant.price.formatted}`);
});

// Add specific variant to cart
const selectedVariant = data.data.variants[0];
const cartUrl = buildAddToCartUrl('https://shop.example.com', {
  products: [{ model_type: 'product_variant', model_id: selectedVariant.id }],
});

getDefaultProductSlug()

Get the default product slug from environment variables.

Returns: string | undefined

Example:

import { getDefaultProductSlug } from '@cymmetrik-dev/3d-transfer-sdk';

const slug = getDefaultProductSlug();
// Returns value from VITE_DEFAULT_PRODUCT_SLUG, NEXT_PUBLIC_DEFAULT_PRODUCT_SLUG, etc.

getDefaultStyle()

Get the default style from environment variables.

Returns: string | undefined

Example:

import { getDefaultStyle } from '@cymmetrik-dev/3d-transfer-sdk';

const style = getDefaultStyle();
// Returns value from VITE_TRANSFER_3D_DEFAULT_STYLE, etc.

Add to Cart Redirect

The SDK provides helper functions to build URLs that add products to the shopping cart and redirect users. This feature:

  • Works for both logged-in users and guest users
  • Supports adding multiple products at once
  • Allows custom redirect URLs after adding to cart
  • No authentication token required (public endpoint)

buildAddToCartUrl(baseURL, options)

Build a URL that adds products to cart and redirects.

Parameters:

  • baseURL: string - The shop's base URL (without /api)
  • options.products: CartProduct[] - Products to add
  • options.redirectUrl: string (optional) - Redirect URL after adding (default: /cart)

CartProduct interface:

interface CartProduct {
  model_type:
    | 'Lunar\\Models\\ProductVariant'
    | 'Projects\\Frontier\\Models\\FrontierOrder'
    | 'Projects\\Frontier\\Models\\ChatThread';  // Auto-creates FrontierOrder
  model_id: number;
  quantity?: number;  // default: 1
}

Example:

import { buildAddToCartUrl } from '@cymmetrik-dev/3d-transfer-sdk';

// Add single product
const url = buildAddToCartUrl('https://shop.example.com', {
  products: [{ model_type: 'product_variant', model_id: 123 }],
});
// Result: https://shop.example.com/api/3d-transfer/add-to-cart?products=[{"model_type":"product_variant","model_id":123}]

// Add multiple products with quantities
const url = buildAddToCartUrl('https://shop.example.com', {
  products: [
    { model_type: 'product_variant', model_id: 123, quantity: 2 },
    { model_type: 'product_variant', model_id: 456, quantity: 1 },
  ],
});

// With custom redirect URL
const url = buildAddToCartUrl('https://shop.example.com', {
  products: [{ model_type: 'product_variant', model_id: 123 }],
  redirectUrl: '/checkout',
});

// Add 3D model by ChatThread ID (auto-creates FrontierOrder if needed)
const url = buildAddToCartUrl('https://shop.example.com', {
  products: [{ model_type: 'Projects\\Frontier\\Models\\ChatThread', model_id: chatThreadId }],
});

// Or add existing FrontierOrder directly
const url = buildAddToCartUrl('https://shop.example.com', {
  products: [{ model_type: 'Projects\\Frontier\\Models\\FrontierOrder', model_id: 789 }],
});

buildAddToCartUrlFromEnv(options)

Build add-to-cart URL using the API URL from environment variables.

Parameters:

  • options.products: CartProduct[] - Products to add
  • options.redirectUrl: string (optional) - Redirect URL after adding

Returns: string | null - URL or null if API URL not configured

Example:

import { buildAddToCartUrlFromEnv } from '@cymmetrik-dev/3d-transfer-sdk';

const url = buildAddToCartUrlFromEnv({
  products: [{ model_type: 'product_variant', model_id: 123 }],
});

if (url) {
  window.location.href = url;
}

Add to Cart - React Example

import { buildAddToCartUrl, getTransfer3DProduct } from '@cymmetrik-dev/3d-transfer-sdk';

function BuyButton({ productId }: { productId: number }) {
  const handleBuy = () => {
    const url = buildAddToCartUrl('https://shop.example.com', {
      products: [{ model_type: 'product_variant', model_id: productId }],
      redirectUrl: '/checkout',
    });
    window.location.href = url;
  };

  return (
    <button onClick={handleBuy}>
      加入購物車
    </button>
  );
}

// Or use as a link
function BuyLink({ productId }: { productId: number }) {
  const url = buildAddToCartUrl('https://shop.example.com', {
    products: [{ model_type: 'product_variant', model_id: productId }],
  });

  return (
    <a href={url}>
      加入購物車
    </a>
  );
}

Add to Cart - HTML Example

<!-- Direct link to add product to cart -->
<a href="https://shop.example.com/api/3d-transfer/add-to-cart?products=[{&quot;model_type&quot;:&quot;product_variant&quot;,&quot;model_id&quot;:123}]">
  加入購物車
</a>

<!-- With redirect to checkout -->
<a href="https://shop.example.com/api/3d-transfer/add-to-cart?products=[{&quot;model_type&quot;:&quot;product_variant&quot;,&quot;model_id&quot;:123}]&redirect_url=/checkout">
  立即購買
</a>

Complete Workflow: 3D Model to Cart

import {
  createTransfer3DClient,
  initiateTransfer3dConversion,
  getTransfer3dStatus,
  buildAddToCartUrl,
} from '@cymmetrik-dev/3d-transfer-sdk';

// Full workflow: Upload image → Convert → Add to cart
async function convertAndAddToCart(imageFile: File) {
  const client = createTransfer3DClient();

  // 1. Start conversion and get chat_thread_id
  const { data: conversionData } = await initiateTransfer3dConversion({
    client,
    body: { image: imageFile, style: 'smart' },
  });

  const { request_id: requestId, chat_thread_id: chatThreadId } = conversionData.data;
  console.log('ChatThread ID:', chatThreadId);  // Use this for add-to-cart

  // 2. Poll until completed
  let status = 'PENDING';
  while (status !== 'COMPLETED' && status !== 'FAILED') {
    await new Promise((r) => setTimeout(r, 2000));
    const { data } = await getTransfer3dStatus({ client, path: { requestId } });
    status = data.data.status;
  }

  // 3. Add to cart using ChatThread ID (auto-creates FrontierOrder)
  if (status === 'COMPLETED') {
    const cartUrl = buildAddToCartUrl('https://shop.example.com', {
      products: [{
        model_type: 'Projects\\Frontier\\Models\\ChatThread',
        model_id: chatThreadId,
      }],
    });
    window.location.href = cartUrl;
  }
}

Conversion Phases

| Phase | Progress | Description | |-------|----------|-------------| | 2d_style_transfer | 0-50% | Image is being styled with AI | | 3d_generation_pending | 50% | 2D complete, waiting for 3D to start | | 3d_generation | 50-100% | 3D model is being generated |

Note: The preview_2d_url becomes available when the phase changes to 3d_generation_pending or 3d_generation, allowing you to show the styled image while the 3D model is still being generated.

Status Values

| Status | Description | |--------|-------------| | PENDING | Conversion request queued | | IN_PROGRESS | Currently processing (2D or 3D) | | 2D_COMPLETED | 2D styling finished, waiting for 3D trigger (only when auto_3d: false) | | COMPLETED | Full conversion finished (2D + 3D) | | FAILED | An error occurred |

Tip: For auto_3d: false requests, poll until status === '2D_COMPLETED', show the preview_2d_url, then call triggerTransfer3dConversion() when ready.

TypeScript Types

import type {
  // Request/Response types (auto-generated)
  GetTransfer3dStylesResponse,
  InitiateTransfer3dConversionData,
  InitiateTransfer3dConversionResponse,
  TriggerTransfer3dConversionData,
  TriggerTransfer3dConversionResponse,
  GetTransfer3dStatusData,
  GetTransfer3dStatusResponse,
  GetTransfer3DProductData,
  GetTransfer3DProductResponse,
  // Add to cart types
  CartProduct,
  AddToCartOptions,
} from '@cymmetrik-dev/3d-transfer-sdk';

// Helper functions
import {
  getDefaultProductSlug,
  getDefaultStyle,
  buildAddToCartUrl,
  buildAddToCartUrlFromEnv,
} from '@cymmetrik-dev/3d-transfer-sdk';

// API functions
import {
  getTransfer3dStyles,
  initiateTransfer3dConversion,
  triggerTransfer3dConversion,
  getTransfer3dStatus,
  getTransfer3DProduct,
} from '@cymmetrik-dev/3d-transfer-sdk';

// React component types
import type { ModelViewerProps } from '@cymmetrik-dev/3d-transfer-sdk/react';

Error Handling

import { AxiosError } from 'axios';

try {
  const response = await initiateTransfer3dConversion({
    client,
    body: { image: file, style: 'smart' },
  });
} catch (error) {
  if (error instanceof AxiosError) {
    if (error.response?.status === 401) {
      console.error('Authentication failed. Check your API token.');
    } else if (error.response?.status === 422) {
      console.error('Validation error:', error.response.data.errors);
    } else if (error.response?.status === 429) {
      console.error('Rate limit exceeded. Please try again later.');
    }
  }
  throw error;
}

Demo Application

A complete React demo application is available in the examples/react-demo directory.

To run the demo:

cd examples/react-demo
pnpm install
cp .env.example .env  # Configure your API URL and token
pnpm dev

Support


This SDK is auto-generated from the Mobius Frontier OpenAPI specification using @hey-api/openapi-ts