# SparkVault — JavaScript SDK (@sparkvault/sdk-js) > The complete SDK guide for browsers and Node.js. For anything the SDK does not wrap, the REST reference is https://sparkvault.com/llms-rest.txt --- # JavaScript SDK: SparkVault API Reference > Add passwordless authentication or secure file uploads to your web app. Canonical: https://sparkvault.com/api/docs/sdk-js/ · OpenAPI: https://sparkvault.com/openapi.yaml ## What are you trying to do? [ ### Replace my login system Passwordless authentication with passkeys, email codes, social login, and more. ](#use-case-login)[ ### Accept secure file uploads Let users upload files that get encrypted and stored in your vault. ](#use-case-upload)[ ### Check API availability Detect whether SparkVault is reachable before starting network-dependent flows. ](#use-case-health) ## Use Case: Replace My Login System Add passwordless verification in 2 steps. Users verify via passkey, email code, SMS, SparkLink, or social login. Your backend verifies the signed JWT and maps the person by SVID. For a full login/session replacement, use the [OIDC/BFF flow](/api/docs/products/identity/) described in the Identity API docs. ### Step 1: Add this to your HTML Copy this snippet and edit the highlighted values: ```html ``` ### Step 2: Verify the token on your backend > **Security Critical** > > **Always verify tokens server-side.** Never trust the frontend result alone. > **Identity Product Endpoint** > > Verification code must use `https://auth.sparkvault.com` for both the JWKS URL and the expected issuer. ```node.js import * as jose from 'jose'; const ACCOUNT_ID = 'acc_YOUR_ACCOUNT_ID'; // ← your account ID const IDENTITY_URL = 'https://auth.sparkvault.com'; app.post('/api/auth/login', async (req, res) => { const { token } = req.body; const jwksUrl = `${IDENTITY_URL}/${ACCOUNT_ID}/.well-known/jwks.json`; const JWKS = jose.createRemoteJWKSet(new URL(jwksUrl)); const { payload } = await jose.jwtVerify(token, JWKS, { issuer: `${IDENTITY_URL}/${ACCOUNT_ID}`, audience: ACCOUNT_ID, algorithms: ['EdDSA'] }); // Token is valid. SVID is the canonical identity key for your app. const svid = payload.svid || payload.sub; const user = await findOrCreateUserBySvid(svid, { identity: payload.identity, identityType: payload.identity_type }); req.session.userId = user.id; res.json({ success: true }); }); ``` ```python import jwt from jwt import PyJWKClient ACCOUNT_ID = 'acc_YOUR_ACCOUNT_ID' # ← your account ID IDENTITY_URL = 'https://auth.sparkvault.com' @app.route('/api/auth/login', methods=['POST']) def login(): token = request.json.get('token') jwks_url = f"{IDENTITY_URL}/{ACCOUNT_ID}/.well-known/jwks.json" jwks_client = PyJWKClient(jwks_url) signing_key = jwks_client.get_signing_key_from_jwt(token) claims = jwt.decode(token, signing_key.key, algorithms=["EdDSA"], issuer=f"{IDENTITY_URL}/{ACCOUNT_ID}", audience=ACCOUNT_ID) # Token is valid. SVID is the canonical identity key for your app. svid = claims.get('svid') or claims['sub'] user = find_or_create_user_by_svid( svid, identity=claims['identity'], identity_type=claims['identity_type'] ) session['user_id'] = user.id return jsonify({'success': True}) ``` Configure which auth methods to offer in [Identity Product Settings](https://app.sparkvault.com/products/identity). > **Managed Sessions** > > The JavaScript SDK simple flow returns a signed verification proof. To have SparkVault own refresh-token rotation, revocation, and logout, use OIDC with a BFF: exchange codes server-side, keep refresh tokens in `HttpOnly`/`Secure` cookies, validate access JWTs locally with JWKS for normal requests, introspect only for hard checks, and call `/revoke` on logout. ## Use Case: Accept Secure File Uploads Let users upload files that get encrypted and stored in your vault. > **Important: File Access** > > Uploaded files are **encrypted at rest**. To access them later, you need a [vault member](https://app.sparkvault.com/vaults) with download access or server-side access via the [Ingots API](/api/docs/ingots/). **Public uploaders cannot retrieve files.** ### Step 1: Enable public upload [Vaults](https://app.sparkvault.com/vaults) → Edit your vault → Enable **Public Ingot Upload**. ### Step 2: Add this to your HTML ```html ``` > **No code option** > > Share the public upload URL from [Vault Settings](https://app.sparkvault.com/vaults) directly, no code required. ### Native integration (advanced) Drive the upload yourself instead of using the SDK drop-zone. Pass a `File` from your own button or drop target; the SDK shows only the secure-upload progress dialog, then closes silently so your app renders its own result UI. Ideal for chat attachments and similar flows. ```javascript // e.g. a chat attachment paperclip fileInput.addEventListener('change', () => { SparkVault.vaults.upload({ vaultId: 'vlt_YOUR_VAULT_ID', file: fileInput.files[0], // skip the SDK drop-zone folder: 'chat/thread-42', // optional vault sub-path, created if missing token: vaultAccessToken, // optional VAT: authorize without public upload hideSuccessScreen: true, // close silently when the upload finishes onSuccess: (result) => { // your app renders the "attached" UI addAttachmentToChat(result.ingotId, result.filename); }, }); }); ``` > **upload() options** > > `file` skips the drop-zone, `hideSuccessScreen` closes the dialog silently on completion, `folder` targets a vault sub-path (find-or-created), and `token` (a Vault Access Token) authorizes the upload without public upload being enabled. ## Use Case: Check API Availability Use the SDK health module for lightweight online checks. It calls SparkVault's root health endpoint and returns a structured result instead of throwing on connectivity failures. ```javascript const health = await SparkVault.health.check(); if (!health.online) { console.log('SparkVault is unreachable:', health.error); } ``` ## Installation ### CDN (Recommended) ```html ``` ### npm / yarn ```bash npm install @sparkvault/sdk-js ``` ```javascript import SparkVault from '@sparkvault/sdk-js'; const sparkvault = SparkVault.init({ accountId: 'acc_YOUR_ACCOUNT_ID' }); ``` > **Browser-Only SDK** > > This SDK is for browser applications (it renders UI). For server-side operations, use the [REST API](/api/docs/). ## React Native Mobile SDK Use `@sparkvault/sdk-mobile` for React Native and Expo apps. It includes the same config-driven Identity Product experience as the web popup: email/phone UI derived from enabled delivery methods, configured branding, light/dark mode, SVID-scoped passkeys, and OTP email/SMS/voice. ```bash npm install @sparkvault/sdk-mobile ``` ```tsx import { createSparkVaultMobileClient } from '@sparkvault/sdk-mobile'; import { SparkVaultIdentityDialog } from '@sparkvault/sdk-mobile/identity-dialog'; const client = createSparkVaultMobileClient({ tokenStorage, identityAccountId: 'acc_YOUR_ACCOUNT_ID', identityBaseUrl: 'https://auth.sparkvault.com', apiBaseUrl: 'https://api.sparkvault.com/v1' }); setVisible(false)} onSuccess={async (result) => { await client.products.identity.verifyIdentityToken(result.token, { jwks: result.jwks }); const session = await client.account.exchangeIdentityToken(result.token); // Continue only after local JWT verification and Core exchange succeed. }} />; ``` > **Token Verification** > > Mobile client-side verification protects the app flow before token exchange. Third-party backends must still verify Identity JWTs server-side with the JWKS endpoint and map product records by SVID. * * * ## Reference ## Identity API Reference > **Namespaced under products** > > Identity is a first-party product and lives at `sparkvault.products.identity`. All methods below are called as `sparkvault.products.identity.signIn(...)`, etc. ### signIn(options?) Open the sign-in popup. Returns a promise that resolves with the verification result. This is the single programmatic entry point. `attach()` binds it to element clicks. Sign-in always runs in a first-party popup on the Identity origin (so cross-domain SSO works); there is no inline/in-page mode. ```javascript const result = await sparkvault.products.identity.signIn({ email: 'user@example.com', // Pre-fill email (optional) onCancel: () => { ... } // Called if user closes the popup }); // result.token - Signed JWT (verify on backend!) // result.identity - Verified email or phone // result.identityType - 'email', 'phone', or 'social' // result.svid - Canonical SparkVault ID // result.sessionId - Managed Identity session ID ``` #### Options | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | Optional | Pre-fill email address | | `phone` | string | Optional | Pre-fill phone (E.164 format: "+14155551234") | | `backdropBlur` | boolean | Optional | Override backdrop blur for this popup (uses global config if omitted) | | `flow` | "auto" | "popup" | "redirect" | Optional | "auto" (default) and "popup" open the popup; "redirect" navigates the tab to the sign-in page instead (its return leg is handleRedirectResult()) | | `onSuccess` | function | Optional | Callback on success | | `onError` | function | Optional | Callback on error | | `onCancel` | function | Optional | Callback when user closes the popup | #### Result | Field | Type | Description | | --- | --- | --- | | `token` | string | Signed JWT token (Ed25519). Verify on your backend. | | `identity` | string | Verified email or phone number | | `identityType` | "email" | "phone" | "social" | Type of identity verified | | `svid` | string | Canonical SparkVault ID for the person | | `sessionId` | string | Managed Identity session ID | | `refreshToken` | string | Present only for flows that intentionally return managed-session refresh material | ### handleRedirectResult(options?) Call once on every page load. It redeems a returned one-time, PKCE-bound code (from a `flow: 'redirect'` sign-in, or the silent SSO self-heal), never the token itself, and, when a popup's result was lost (e.g. a mobile browser discarded the backgrounded tab mid-login), it silently re-mints from the SSO session so the sign-in still completes. It no-ops (resolves `null`) otherwise. Wiring it is strongly recommended: it is what makes the popup reliable on mobile. The result arrives here as the same shape `signIn()` delivers; verify `result.token` on your backend. ```javascript // On EVERY page load: redeems a returned code or self-heals a lost popup result. sparkvault.products.identity.handleRedirectResult({ onSuccess: (result) => createSession(result.token), // same handler as signIn() }).catch((error) => showError(error)); ``` ### SparkVault.isRecoveryPending() Static, side-effect-free, and instance-free: returns `true` when this page load has a sign-in to complete: a returned handoff code/error, or a popup whose result was lost. On a high-traffic page where you defer `init()` for performance, call this first so you only boot the SDK when `handleRedirectResult()` actually has work to do. A visitor who never signed in always gets `false`. ```javascript // Boot the SDK for the return leg ONLY when a sign-in needs completing. if (SparkVault.isRecoveryPending()) { SparkVault.init({ accountId: 'acc_...' }) .products.identity.handleRedirectResult({ onSuccess: (result) => createSession(result.token), }).catch(showError); } ``` ### attach(selector, options) Bind verification to element clicks. Returns a cleanup function. ```javascript const cleanup = sparkvault.products.identity.attach('.login-btn', { onSuccess: (result) => { ... }, onCancel: () => { ... } }); // Later: remove click handlers cleanup(); ``` ### close() Programmatically close the sign-in popup. ```javascript sparkvault.products.identity.close(); ``` ### signInWithFedcm(options?) One-click sign-in through the browser's native FedCM account chooser. When the person is already signed into auth.sv (carrying the SSO session), the browser surfaces a "Continue as …" prompt with no popup, redirect, or typing. Requires a registered `clientId` in the SDK config. Resolves with the verified result, or `null` when the browser has no FedCM support (so you can fall back to `signIn()`); throws `UserCancelledError` when the person dismisses the chooser. Feature-detect with the static `IdentityModule.isFedcmAvailable()`. ```javascript import { IdentityModule } from '@sparkvault/sdk-js'; const result = IdentityModule.isFedcmAvailable() ? await sparkvault.products.identity.signInWithFedcm() .catch(() => sparkvault.products.identity.signIn()) : await sparkvault.products.identity.signIn(); ``` | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `mediation` | 'optional' | 'required' | 'silent' | Optional | FedCM mediation mode passed to the browser's credential request | | `nonce` | string | Optional | Replay-binding value carried into the token's `nonce` claim (generated when omitted) | ### portalUrl(options?) Build a deep link into the auth.sv self-service Identity portal, where a signed-in person manages their SparkVault Identity across every SparkVault-integrated site. Returns an absolute URL on the configured `portalBaseUrl` (default `https://auth.sv`). ```javascript // Manage this site (block, notifications, etc.) window.location.href = sparkvault.products.identity.portalUrl({ section: 'sites' }); // Open the security screen const url = sparkvault.products.identity.portalUrl({ section: 'security' }); ``` | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `section` | string | Optional | Portal screen to open: `home` | `identity` | `security` | `sessions` | `sites`Default: `home` | | `site` | string | Optional | Focus a specific connected site on the Sites screen (an `acc_…` account ID). When `section` is `sites` and `site` is omitted, defaults to this SDK's own `accountId`. Ignored for other sections. | ### decodeToken(token) Decode an identity token's claims **without verifying its signature**. This is an unverified, client-side decode: it parses the JWT payload and sanity-checks structure, `exp`, `iss`, `aud`, and the header `alg` (rejecting `none` and any non-EdDSA algorithm). It does not verify the Ed25519 signature, so its result must never be trusted for an authorization decision. Verify tokens server-side against the Identity JWKS endpoint before trusting any claim. ```javascript const claims = sparkvault.products.identity.decodeToken(result.token); console.log(claims.svid, claims.exp); ``` ### Authentication Methods Configure which methods to offer in [Identity Product Settings](https://app.sparkvault.com/products/identity). > **Social redirects** > > Social providers use a full-page OAuth redirect. When the provider returns to your page with `#token=...`, `products.identity.signIn()` and `products.identity.attach()` consume that fragment once, clear it from the URL, and call `onSuccess` with the same result shape as passkey, OTP, and SparkLink. | Method | Description | | --- | --- | | `passkey` | WebAuthn biometric or security key | | `otp_email` | 6-digit code via email | | `otp_sms` | 6-digit code via SMS | | `otp_voice` | 6-digit code via voice call | | `sparklink` | One-click sign-in link via email | | `social_google` | Sign in with Google | | `social_apple` | Sign in with Apple | | `social_microsoft` | Sign in with Microsoft | | `social_github` | Sign in with GitHub | | `social_facebook` | Sign in with Facebook | | `social_linkedin` | Sign in with LinkedIn | ### Token Claims ```json { "iss": "https://auth.sparkvault.com/acc_xxx", "sub": "ing_019e66a4e27875f2822ede0e4f5d8792", "aud": "acc_xxx", "jti": "0192f3a1-7c4e-7b21-b3d4-9a8e6f5c2d10", "identity": "user@example.com", "identity_type": "email", "method": "otp_email", "verified_at": 1703977200, "svid": "ing_019e66a4e27875f2822ede0e4f5d8792", "session_id": "sess_abc123", "iat": 1703977200, "exp": 1703977500 } ``` Simple tokens expire after 5 minutes by default (`exp` = `iat` + 300). Optional claims (`nonce`, `amr`, `acr`, and `action_hash`) appear only when the flow binds them. ## Upload API Reference ### upload(options) Open the upload widget. Returns a promise that resolves when upload completes. ```javascript const result = await sparkvault.vaults.upload({ vaultId: 'vlt_abc123', target: '#container', // Render inline instead of modal (optional) onProgress: (p) => console.log(p.percentage + '%') }); // result.ingotId - Uploaded file ID // result.filename - Original filename // result.sizeBytes - File size ``` #### Options | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `vaultId` | string | Required | Vault ID to upload to (public upload must be enabled unless `token` is provided) | | `target` | HTMLElement | string | Optional | CSS selector or element for inline rendering | | `file` | File | Optional | Upload this File directly, skipping the SDK drop-zone. Your app owns file selection and the SDK shows only the secure-upload progress dialog | | `folder` | string | Optional | Slash-delimited folder path within the vault (e.g. `Accounts/001xx`); missing path segments are find-or-created. Omitted means the vault root. | | `token` | string | Optional | Vault Access Token (VAT). When provided, the VAT authorizes the upload; no public-upload flag required. When omitted, the public upload flow is used. | | `backdropBlur` | boolean | Optional | Blur background in dialog mode (default: true) | | `hideUploadAnother` | boolean | Optional | Hide "Upload Another File" button on success (for single-file flows) | | `hideSuccessScreen` | boolean | Optional | Skip the success screen entirely: on completion the SDK fires `onSuccess` and closes the dialog, so your app renders its own post-upload UI | | `onSuccess` | function | Optional | Callback on success | | `onError` | function | Optional | Callback on error | | `onCancel` | function | Optional | Callback when user cancels | | `onProgress` | function | Optional | Callback for progress updates | #### Result | Field | Type | Description | | --- | --- | --- | | `ingotId` | string | Unique identifier for the uploaded file | | `vaultId` | string | Vault the file was uploaded to | | `filename` | string | Original filename | | `sizeBytes` | number | File size in bytes | | `uploadTime` | string | Upload timestamp (ISO 8601) | #### Progress callback ```javascript { bytesUploaded: 52428800, bytesTotal: 104857600, percentage: 50, phase: 'uploading' // 'uploading' | 'ceremony' | 'complete' } ``` ### upload.attach(selector, options) Bind upload to element clicks. Returns a cleanup function. ```javascript const cleanup = sparkvault.vaults.upload.attach('.upload-btn', { vaultId: 'vlt_abc123', onSuccess: (result) => { ... } }); cleanup(); // Remove handlers ``` ### upload.close() Programmatically close the upload widget. ### upload.pop(options) / upload.render(options) Aliases on the callable: `pop()` opens the upload widget as a dialog, equivalent to calling `upload(options)`, and `render()` renders it inline, equivalent to `upload({ ...options, target })`. ## Vaults API Reference The `vaults` module is the control plane: create vaults, unseal them for data-plane access, and manage their sharing and upload configuration. The browser upload widget (`vaults.upload`, documented above) also lives here. > **Store the VMK** > > `create()` returns the Vault Master Key (`vmk`) exactly once. It cannot be recovered. Store it securely. Unsealing, deleting, and enabling sharing all require it. ```javascript // Create a vault. Save the VMK! const vault = await sparkvault.vaults.create({ name: 'My Vault' }); // Unseal to get a short-lived Vault Access Token (VAT). // The returned object is passed to ingots/folders calls. const unsealed = await sparkvault.vaults.unseal(vault.id, vault.vmk); // Inspect, rename, seal const detail = await sparkvault.vaults.get(vault.id); await sparkvault.vaults.update(vault.id, { name: 'Renamed' }); await sparkvault.vaults.seal(vault.id); // List vaults — paginated: returns a page { vaults, nextCursor }. // Follow nextCursor to fetch subsequent pages. const page = await sparkvault.vaults.list(); page.vaults.forEach((v) => console.log(v.name)); // Delete requires the vault's exact name as confirmation (sent as the // X-Confirm-Name header, case-sensitive) — not the VMK. await sparkvault.vaults.delete(vault.id, vault.name); ``` ### Sharing ```javascript const config = await sparkvault.vaults.getSharingConfig(vault.id); await sparkvault.vaults.enableSharing(vault.id, vault.vmk, { allIngotsPublic: false, sessionLengthSeconds: 3600, }); await sparkvault.vaults.updateSharingConfig(vault.id, { sessionLengthSeconds: 7200 }); await sparkvault.vaults.disableSharing(vault.id); ``` ### Upload configuration & access-log retention ```javascript const upload = await sparkvault.vaults.getUploadConfig(vault.id); await sparkvault.vaults.enableUploadPortal(vault.id, vault.vmk); await sparkvault.vaults.disableUploadPortal(vault.id); await sparkvault.vaults.enableUploadWidget(vault.id, vault.vmk); await sparkvault.vaults.disableUploadWidget(vault.id); await sparkvault.vaults.updateUploadConfig(vault.id, { maxSizeBytes: 1048576, notificationEmail: 'ops@example.com', }); const retention = await sparkvault.vaults.getAccessLogRetention(vault.id); await sparkvault.vaults.updateAccessLogRetention(vault.id, 7776000); ``` ## Ingots API Reference The `ingots` module operates on the contents of an _unsealed_ vault. Pass the object returned by `vaults.unseal(...)` as the first argument. It carries the short-lived Vault Access Token. > **Primitives vs. high-level flows** > > `createUpload()` and `createDownloadLink()` are discrete primitives that return a Forge URL. `upload()` and `download()` are the high-level flows: they drive the resumable transfer end-to-end and verify server-side finalization. ```javascript const unsealed = await sparkvault.vaults.unseal(vault.id, vault.vmk); // High-level upload: creates the session, streams over TUS, verifies finalized. const ingot = await sparkvault.ingots.upload(unsealed, { file: myFile, name: 'document.pdf', onProgress: (uploaded, total) => console.log(uploaded / total), }); // High-level download: fetches a fresh signed link and returns a Blob. const blob = await sparkvault.ingots.download(unsealed, ingot.id); // Discrete primitives: drive the transfer yourself. const session = await sparkvault.ingots.createUpload({ vault: unsealed, name: 'big.zip', contentType: 'application/zip', sizeBytes: file.size, }); const link = await sparkvault.ingots.createDownloadLink(unsealed, ingot.id); ``` ### Manage & organize ```javascript await sparkvault.ingots.rename(unsealed, ingot.id, 'renamed.pdf'); await sparkvault.ingots.move(unsealed, ingot.id, 'fld_xyz'); // null = vault root await sparkvault.ingots.replace(unsealed, ingot.id, { file: newFile }); await sparkvault.ingots.delete(unsealed, ingot.id); const meta = await sparkvault.ingots.get(unsealed, ingot.id); const { ingots } = await sparkvault.ingots.list(unsealed); const found = await sparkvault.ingots.search(unsealed, { q: 'invoice' }); const contents = await sparkvault.ingots.listContents(unsealed, { folderId: 'fld_xyz' }); ``` `list()`, `search()`, `listContents()`, and `getAccessLogs()` accept `{ limit, cursor }` pagination and return a `nextCursor`. `list()` and `listContents()` also accept `sortBy` (`name` | `size` | `content_type` | `created` | `access_count`) and `sortOrder` (`asc` | `desc`); `search()` also accepts `fileType`, `minSize`, `maxSize`, and `sortOrder`. ### Sharing & access logs ```javascript await sparkvault.ingots.updateSharingConfig(unsealed, ingot.id, { shared: true, visibility: 'invite_only' }); const sharing = await sparkvault.ingots.getSharingConfig(unsealed, ingot.id); // Optional 4th arg: invite lifetime (omit to inherit the link's existing expiry) const invite = await sparkvault.ingots.addInvite(unsealed, ingot.id, 'user@example.com', { expiresInSeconds: 86400 }); await sparkvault.ingots.revokeInvite(unsealed, ingot.id, invite.id); const { logs } = await sparkvault.ingots.getAccessLogs(unsealed, ingot.id, { limit: 100 }); ``` ## Folders API Reference The `folders` module organizes ingots within an _unsealed_ vault. The flat list returned by `list()` is enough to build the folder tree client-side. ```javascript const unsealed = await sparkvault.vaults.unseal(vault.id, vault.vmk); const folders = await sparkvault.folders.list(unsealed); const folder = await sparkvault.folders.create(unsealed, { name: 'Invoices' }); const detail = await sparkvault.folders.get(unsealed, folder.id); const trail = await sparkvault.folders.getBreadcrumb(unsealed, folder.id); await sparkvault.folders.update(unsealed, folder.id, { name: 'Renamed' }); await sparkvault.folders.move(unsealed, folder.id, 'fld_parent'); // null = vault root await sparkvault.folders.togglePin(unsealed, folder.id, true); await sparkvault.folders.moveIngot(unsealed, 'ing_abc', folder.id); // null = vault root await sparkvault.folders.delete(unsealed, folder.id); ``` ## Sparks API Reference Sparks are end-to-end encrypted, read-once ephemeral secrets scoped to your account (no vault or VAT required). Reading a spark with `get()` burns it. ```javascript const spark = await sparkvault.sparks.create({ payload: 'top secret', ttlMinutes: 60, // defaults to 1440 (24h) contentType: 'text/plain', // optional MIME type (base64 payload for binary types) filename: 'note.txt', // optional, for file payloads }); // Kindling groups related sparks into a family const first = await sparkvault.sparks.create({ payload: 'one', withKindling: true }); const sibling = await sparkvault.sparks.create({ payload: 'two', kindling: first.kindling }); const { sparks } = await sparkvault.sparks.list(); // filters: { kindling, status, limit, cursor } const revealed = await sparkvault.sparks.get(spark.id); // burns the spark await sparkvault.sparks.delete(spark.id); ``` ### Public sharing (SparkLink) ```javascript // Share via a public x.sv link const share = await sparkvault.sparks.share(spark.id, { visibility: 'public' }); console.log(share.shareUrl); // https://x.sv/... const status = await sparkvault.sparks.getShare(spark.id); await sparkvault.sparks.unshare(spark.id); ``` ## Entropy API Reference Generate cryptographically strong random values from the SparkVault hardware-backed entropy service. ```javascript // Formatted output: hex | base64 | base64url | alphanumeric | alphanumeric-mixed | password | numeric | uuid | bytes const { value } = await sparkvault.entropy.generate({ bytes: 32, format: 'hex' }); // Raw bytes as a Uint8Array const key = await sparkvault.entropy.generateBytes(32); ``` `bytes` must be an integer from 1 to 1024 (a `ValidationError` is thrown otherwise). For `format: 'bytes'` the `value` is a number array. ## Notifications API Reference The signed-in user's metadata-only notification inbox. The recipient is always derived from the session token, so every call reads or mutates only the caller's own inbox. Rows are locked pointers: the content lives behind each row's SparkLink (`sparklink_code`), never in the row itself. Requires an access token (`accessToken` / `getAccessToken`). ```javascript // Page the inbox (newest first). Views: all | unseen | unread | archived (default all). const { notifications, cursor } = await sparkvault.notify.getInbox({ state: 'unread', limit: 50 }); // A page can come back SHORT of limit yet still carry a non-null cursor — always follow the cursor. // Bounded unseen badge count; `capped` true means render "N+". const { unread_count, capped } = await sparkvault.notify.getUnreadCount(); // Mark one of your rows seen | read | archived (idempotent). `createdAt` MUST be the row's own // created_at, round-tripped from the list row — the id alone cannot address the row. await sparkvault.notify.markState(row.notification_id, { createdAt: row.created_at, state: 'read' }); ``` ### Browser Web Push Opt the current browser into push notifications for the pointer (never content). Call from a user gesture. Your app hosts and registers the service worker; the SDK handles the VAPID key, permission, subscription, and server registration, returning a typed status instead of throwing on the expected declines. `subscribeWebPush` returns `not_configured` until the platform VAPID key is provisioned, so gate your UI on `isWebPushSupported()` and that status. ```javascript if (sparkvault.notify.isWebPushSupported()) { const registration = await navigator.serviceWorker.register('/sw.js'); const { status } = await sparkvault.notify.subscribeWebPush({ serviceWorkerRegistration: registration }); // status: 'subscribed' | 'already_subscribed' | 'not_supported' | 'not_configured' | 'permission_denied' // Later, to turn it off on this device: await sparkvault.notify.unsubscribeWebPush({ serviceWorkerRegistration: registration }); } ``` ## Health API Reference ### health.check(options?) Check SparkVault API availability. Returns a structured result and does not throw for offline/network failures. ```javascript const result = await sparkvault.health.check({ timeout: 5000 }); // result.online - true when the API returns 2xx // result.status - 'healthy' | 'unhealthy' | 'unreachable' // result.httpStatus - HTTP status when a response was received // result.checkedAt - Unix timestamp // result.error - network/timeout message when unreachable ``` ### health.isOnline(options?) Convenience method that resolves to a boolean. ```javascript const online = await sparkvault.health.isOnline({ timeout: 5000 }); ``` ## Error Handling ```javascript try { const result = await sparkvault.products.identity.signIn(); } catch (error) { switch (error.code) { case 'user_cancelled': // User closed the popup (not an error) break; case 'validation_error': console.error('Invalid input:', error.message); break; case 'network_error': console.error('Network error'); break; case 'timeout_error': console.error('Request timed out'); break; default: console.error('Error:', error.message); } } ``` | Code | Description | | --- | --- | | `user_cancelled` | User closed the popup | | `validation_error` | Invalid input or configuration (HTTP 400) | | `authentication_error` | Missing or invalid credentials (HTTP 401) | | `authorization_error` | Not permitted to access this resource (HTTP 403) | | `PLAN_REQUIRED` | The account has no active subscription and attempted a billable action (HTTP 402); surface a subscribe CTA to admins | | `QUOTA_EXCEEDED` | A pooled-capacity or seat limit is exhausted (HTTP 402); `error.resource` identifies the pool: `storage` | `bandwidth` | `seats` | `identity` | | `network_error` | Network connectivity failure | | `timeout_error` | Request timed out | | `popup_blocked` | Browser blocked popup | The 401/403 and 402 billing gates apply to the authenticated `vaults`, `ingots`, and `sparks` calls on this page. Each code maps to an exported error class (e.g. `PlanRequiredError`, `QuotaExceededError`) for `instanceof` checks. ## Configuration Options ```javascript const sparkvault = SparkVault.init({ accountId: 'acc_YOUR_ACCOUNT_ID', // Required backdropBlur: true, // Blur background on modals (default: true) timeout: 30000, // HTTP timeout in ms (default: 30000) preloadConfig: true // Preload config for instant modals (default: true) }); ``` ### Advanced options | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `apiBaseUrl` | string | Optional | Core API origin. A trailing `/v1` is accepted and normalized.Default: `https://api.sparkvault.com` | | `identityBaseUrl` | string | Optional | Identity Product API base URLDefault: `https://auth.sparkvault.com` | | `portalBaseUrl` | string | Optional | auth.sv portal origin for `portalUrl()` deep linksDefault: `https://auth.sv` | | `accessToken` | string | Optional | Static SparkVault Core access token for authenticated API calls | | `getAccessToken` | function | Optional | Dynamic Core access-token provider (sync or async) for authenticated API calls | | `apiKey` | string | Optional | API key for server-side SDK use | | `clientId` | string | Optional | OIDC client ID registered for this account. Required only for FedCM one-click sign-in (`signInWithFedcm()`). | | `allowedDownloadHostPatterns` | RegExp\[\] | Optional | Host allowlist for backend-issued ingot download URLs. Defaults to the canonical SparkVault Forge / S3 / CloudFront hosts; override only when pointing at a custom deployment. | ## TypeScript ```typescript import SparkVault, { SparkVaultConfig, VerifyOptions, VerifyResult, UploadOptions, UploadResult, UserCancelledError, ValidationError, PlanRequiredError, QuotaExceededError, NetworkError } from '@sparkvault/sdk-js'; const config: SparkVaultConfig = { accountId: 'acc_YOUR_ACCOUNT_ID' }; const sparkvault = SparkVault.init(config); const result: VerifyResult = await sparkvault.products.identity.signIn(); ``` ## Debug Mode Enable verbose console logging: ```html ``` ## Browser Support - Chrome 80+ - Firefox 75+ - Safari 13.1+ - Edge 80+ Passkey authentication requires WebAuthn support. On unsupported browsers, passkey won't appear as an option.