Skip to content

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.

Quick Starttypescript
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.

Silent create usertypescript
// 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.

Generate embed tokentypescript
// 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.

Per-User Tokentypescript
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 keyPer-user token
AttributionThe API keyThe individual user
RolesNoneEnforced by the API
SetupOne key, no backendCreate users once, issue a token per page load
Credential lives inThe iframe URLPage memory, delivered by postMessage
ExpiryUntil you revoke the keyShort-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

Full Configurationtypescript
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

OptionTypeDescription
apiKeystringEntity API key (ek_live_* or ek_sandbox_*). Use this or userToken, not both.
userTokenstringShort-lived user session token (session_*). Use this or apiKey, not both. See Per-user tokens.
entityIdstringEntity ID to operate on

Provide exactly one of apiKey or userToken. Passing both, or neither, throws an error.

Optional Options

OptionTypeDescription
baseUrlstringCustom base URL for self-hosted deployments
localeEmbedLocaleUI language (en, sl, de, it, fr, es, pt, nl, pl, hr, hu). Defaults to browser language detection.
themeThemeConfigTheme customization options
rememberListViewbooleanRemember 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

CallbackParametersDescription
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 Optionstypescript
// 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 }],
  },
});
OptionTypeDescription
containerstring | HTMLElementContainer element or selector
idstringDocument/customer ID for single-item views
prefillRecord<string, unknown>Pre-fill data for creation forms
searchParamsRecord<string, string | number | boolean | Array<...>>Initial route search params for lists, like order_by, search, and filters

Methods

MethodDescription
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

Next Steps

  • Views — All available views
  • Theming — Customize appearance
  • Events — Handle user actions