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',

  // Recommended. A Supabase Auth JWT — unlocks the FULL API surface.
  accessToken: 'YOUR_ACCESS_TOKEN',

  // Optional. An API token from Profile > API/SDK. Sent as X-API-Key, and
  // reaches only the option-block, pricing-block, quote-submission and
  // webhook endpoints — see "Which token can do what" below.
  apiToken: 'YOUR_API_TOKEN',

  timeout: 30000,                     // Request timeout (ms)
  retryAttempts: 3,                   // Retry on network errors
  onError: (error) => {
    console.error('API Error:', error);
  }
});

// If both are set, accessToken wins.

Which token can do what

The two credentials are not interchangeable. An access token reaches every endpoint; an API token reaches a deliberately narrow subset.

CredentialHeaderReaches
accessTokenAuthorization: BearerEvery endpoint the signed-in account can use.
apiTokenX-API-KeyOption blocks, pricing blocks, quote submissions and webhooks only — but within those four families the grant includes create, update and delete writes, not just reads. Everything else returns 401 — including GET /projects/:id, so client.getProject() does not work with an API token.
const client = createSimplio3DClient({ apiUrl, apiToken: 'YOUR_API_TOKEN' });

// The four families it DOES reach — reads and writes alike.
await client.getOptionBlocks(projectId);                        // works
await client.createOptionBlock(projectId, { /* ... */ });       // works (write)
await client.getPricingBlocks(projectId);                       // works
await client.calculatePrice(projectId, {
  selections: { blk_wood: 'walnut' },   // FLAT map: blockId -> variant value
});                                                             // works
await client.getQuoteSubmissions(projectId);                    // works
await client.createWebhook(projectId, { /* ... */ });           // works (write)

await client.getProject(projectId);         // 401 — use accessToken
await client.getMaterials();                // 401 — use accessToken
await client.getAssets();                   // 401 — use accessToken

Never ship an API token in a client application

An API token authenticates as the account that issued it, across all of that account’s projects. Anything distributed to end users — a mobile app, a desktop app, or browser JavaScript — can be decompiled or proxied, so an embedded token is a published one. It is also disabled while a subscription is inactive, and is rotated automatically 90 days after a lapse.

Use it from your own server only. For customer-facing apps, use the public share endpoints (see Mobile & native apps); for a signed-in user, exchange their session for an accessToken.

Getting and rotating an API token

The raw API token is shown once, at the moment it is created, and is unrecoverable afterwards — the server persists only a SHA-256 hash plus an 8-character prefix. GET /api-token therefore returns metadata only. Store the plaintext in your secret manager the moment you receive it.

// Non-secret metadata about the current token — contains NO token material.
const info = await client.getApiTokenInfo();   // ApiTokenInfo | null
if (info) {
  console.log(info.prefix);         // "a1b2c3d4" — for display / identification only
  console.log(info.createdAt, info.regeneratedAt, info.exists);
} else {
  console.log('This account has no API token yet.');
}

// The ONLY way to obtain a usable token. Returns the plaintext once and
// invalidates any previous token, so every integration using the old one breaks.
const token = await client.regenerateApiToken();   // string
console.log(token);  // store this now — it can never be read back

// DEPRECATED: always resolves to null. It read a field the server has never
// sent, so it was a silent no-op rather than an error. Use getApiTokenInfo().
await client.getApiToken();   // null

regenerateApiToken() is destructive: it invalidates the previous token immediately. Roll it only when you are ready to update every integration that uses it.

Error Handling

import {
  AuthenticationError, NotFoundError, ValidationError, ConflictError,
} 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 if (error instanceof ConflictError) {
    // HTTP 409 — someone else wrote the row after you read it. Re-read and
    // re-apply; error.serverUpdatedAt is the version to read at.
    console.error('Changed by another writer at', error.serverUpdatedAt);
  } else {
    console.error('Error:', error.message, 'Code:', error.code);
  }
}

// Every SDK error extends Simplio3DError, so ConflictError is additive:
// existing `instanceof Simplio3DError` handlers keep matching it.

Continue reading