Configuration
Choose how the embed signs in
Provide exactly one of apiKey or userToken for each embed instance. Existing embeds that use apiKey need no change.
Shared entity key
Everyone who opens the embed acts as the entity. Documents and activity are attributed to the API key, and no roles apply. It is the simplest setup: one key, no backend step.
import { SpaceInvoices } from "@spaceinvoices/embed-sdk";
const si = new SpaceInvoices({
apiKey: "ek_live_your_entity_api_key",
entityId: "ent_123",
onAction: (_action) => {},
});
// Open invoice list in the container
si.open("invoices", { container: "#space-invoices-embed" });Per-user token
Each of your users has their own Space Invoices user. Documents and activity are attributed to that person, and their role (viewer, editor, admin or staff) is enforced by the API, so a viewer’s attempt to write is refused. It needs a small backend step.
1. Backend, once per user: create the user silently. No email is sent. Keep your own user ID in metadata so you can find the user again.
// title: Provision a user silently
// No email is sent and the user has no password
const user = await sdk.entityUsers.addEntityUser({
email: "jane@company.com",
role: "editor",
silent: true,
metadata: { external_id: "u_123" },
}, { entity_id: entity.id });See Provision Users Silently for the details.
2. Backend, on every page load: request a short-lived token for that user. Never call this from the browser, because it needs your account API key.
// title: Issue a short-lived embed token
// Call this from your backend on every page load; ttl is in seconds (300-86400)
const embedToken = await sdk.entityUsers.generateEntityUserToken(user.user_id, { ttl: 900 }, { entity_id: entity.id });3. Browser: pass the token as userToken and return a fresh one from onTokenExpired.
import { SpaceInvoices } from "@spaceinvoices/embed-sdk";
// Your own backend endpoint. It calls POST /entities/users/{user_id}/token with a __PROTECTED_0__
// using your account API key and returns the short-lived token. Never call that
// endpoint from the browser.
async function fetchUserToken(): Promise<string> {
const response = await fetch("/api/space-invoices-token");
const { token } = await response.json();
return token;
}
const si = new SpaceInvoices({
userToken: await fetchUserToken(),
entityId: "ent_123",
// Called when the token has expired. Return a fresh one from your backend.
onTokenExpired: fetchUserToken,
onError: (error) => {
if (error.code === "auth_failed") {
// The token could not be renewed, for example your session ended
window.location.reload();
}
},
});
si.open("invoices", { container: "#space-invoices-embed" });The token is delivered to the embed by postMessage and is never placed in the iframe URL. It is short-lived (ttl of 300 to 86400 seconds) and cannot be used to create other credentials. When the token expires, the embed reloads itself and shows its session-expired screen until the page reloads. The reloaded embed asks your page for a token, so onTokenExpired is called (it has 30 seconds to return a new token), and onReady fires again once the reloaded embed is ready. Unsaved input in an open form is lost. If renewal fails, the embed shows its session-expired screen and onError receives the auth_failed code once. Choose a token ttl long enough for a working session.
Comparison
| Shared entity key | Per-user token | |
|---|---|---|
| Attribution | The API key | The individual user |
| Roles | None | Enforced by the API |
| Setup | One key, no backend | Create users once, issue a token per page load |
| Credential lives in | The iframe URL | Page memory, delivered by postMessage |
| Expiry | Until you revoke the key | Short-lived, renewed through onTokenExpired |
Pick the shared key for the quickest start or when your users do not need individual identities. Pick per-user tokens when you need to know who did what, or when different people should have different permissions.
Configuration Options
import { SpaceInvoices } from "@spaceinvoices/embed-sdk";
const _si = new SpaceInvoices({
// Required
apiKey: "ek_live_your_entity_api_key",
entityId: "ent_123",
// Optional: Custom base URL (for self-hosted)
baseUrl: "https://eu.spaceinvoices.com",
// Optional: UI language (defaults to browser detection)
locale: "en",
// Optional: Theme customization
theme: {
primary: "#6366f1",
radius: 8,
},
// Callbacks
onAction: (_action) => {},
onReady: () => {},
onError: (error) => console.error("Error:", error),
onClose: () => {},
});Required Options
| Option | Type | Description |
|---|---|---|
apiKey | string | Entity API key (ek_live_* or ek_sandbox_*). Use this or userToken, not both. |
userToken | string | Short-lived user session token (session_*). Use this or apiKey, not both. See Per-user tokens. |
entityId | string | Entity ID to operate on |
Provide exactly one of apiKey or userToken. Passing both, or neither, throws an error.
Optional Options
| Option | Type | Description |
|---|---|---|
baseUrl | string | Custom base URL for self-hosted deployments |
locale | EmbedLocale | UI language (en, sl, de, it, fr, es, pt, nl, pl, hr, hu). Defaults to browser language detection. |
theme | ThemeConfig | Theme customization options |
rememberListView | boolean | Remember the sort order and rows per page of document lists in this browser. Defaults to false. |
onTokenExpired | () => string | Promise<string> | userToken mode only. Called when the embed’s token has expired. Return a fresh token from your backend within 30 seconds. The embed reloads itself when its token expires, so unsaved input is lost. |
Per-user tokens
Each of your users acts as their own Space Invoices user. See Choose how the embed signs in for the full flow, and Provision Users Silently for creating the users.
Remember list sort and page size
By default, document lists (invoices, estimates, credit notes, advance invoices and delivery notes) open with your searchParams or the defaults. Set rememberListView: true and they remember the sort order and rows per page the user picked in this browser. They are restored when the user returns to the list, for example with the Back button inside the embed. Values you pass through searchParams still win.
White-label capability controls
The embed reads capability controls from the white-label configuration resolved by the API. They are account-managed policy, not SDK options, and the embed waits for that configuration before it exposes routes or actions.
Document action IDs use actions.documents.<type>.<action>. The supported outgoing types are invoice, estimate, credit_note, advance_invoice, and delivery_note; actions include edit, finalize, delete_draft, share, and send_email. Invoices also support send_reminder. Resource controls include actions.items.delete, actions.taxes.edit, and actions.taxes.delete.
Hiding a document family also hides its child actions. Country availability still takes precedence, while historical draft deletion and unsharing remain available when a country no longer permits new issuance. The document and resource action controls listed above gate actions that existed before they became configurable, so they stay visible unless they are listed in hidden_features, even when hide_new_features_by_default is enabled. Only genuinely new features follow that default: when it is enabled, their IDs stay hidden while they are missing from reviewed_feature_catalog_ids, until the account explicitly reviews and saves the catalog. When it is disabled, future registered controls remain visible unless they are listed in hidden_features.
Callbacks
| Callback | Parameters | Description |
|---|---|---|
onAction | (action: EmbedAction) | Called when user performs an action |
onReady | () | Called when embed is fully loaded, and again after the embed reloaded itself with a renewed token |
onError | (error: EmbedError) | Called on errors |
onClose | () | Called when embed is closed |
Open Options
When calling open() or navigate(), you can pass additional options:
// Open in a specific container
si.open("invoices", {
container: "#my-container",
});
// Open invoices with a custom initial sort
si.open("invoices", {
container: "#my-container",
searchParams: {
order_by: "-date",
},
});
// Open a specific invoice
si.open("invoice", {
container: "#my-container",
id: "inv_123",
});
// Pre-fill a new invoice
si.open("invoice/new", {
container: "#my-container",
prefill: {
client: { name: "Acme Corp" },
items: [{ name: "Service", price: 100 }],
},
});| Option | Type | Description |
|---|---|---|
container | string | HTMLElement | Container element or selector |
id | string | Document/customer ID for single-item views |
prefill | Record<string, unknown> | Pre-fill data for creation forms |
searchParams | Record<string, string | number | boolean | Array<...>> | Initial route search params for lists, like order_by, search, and filters |
Methods
| Method | Description |
|---|---|
open(view, options?) | Open a view in a container |
navigate(view, options?) | Navigate to a different view |
updateTheme(theme) | Update theme dynamically |
setLocale(locale) | Change UI language dynamically |
close() | Close and unmount the embed |
ready() | Returns promise that resolves when ready |
getInstance() | Get current embed instance |
destroy() | Destroy SDK and clean up |