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.
| Credential | Header | Reaches |
|---|---|---|
accessToken | Authorization: Bearer | Every endpoint the signed-in account can use. |
apiToken | X-API-Key | Option 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 accessTokenNever 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(); // nullregenerateApiToken() 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.