# 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.