Workspaces
A workspace is a Plugipay tenant — one workspace, one set of customers, one ledger, one set of payment adapters. Most accounts use exactly one workspace; multi-brand operators (running, say, two retail brands out of one company) split them. This page covers the merchant-facing plugipay.workspaces namespace, which lets you list, create, update, and delete the workspaces the calling key can access. For the underlying HTTP surface see API: Workspaces; for partner-provisioned workspaces (managed by Storlaunch / Fulkruma / Ripllo on your behalf) see the admin.provisionWorkspace method in Reference.
Namespace
plugipay.workspaces — every method on this namespace:
plugipay.workspaces.list()
plugipay.workspaces.create(input)
plugipay.workspaces.update(id, patch)
plugipay.workspaces.delete(id)
There's no get — the list response is small (workspaces are bounded by your account, not by traffic) and contains every field, so a separate retrieve isn't needed. To pick one, list and filter client-side.
Methods
workspaces.list
Signature. plugipay.workspaces.list(): Promise<Workspace[]>
Returns every workspace the current key can access. For a regular merchant key, that's usually the one workspace the key belongs to; for a platform-admin key, it's every workspace under that admin's purview.
import { PlugipayClient } from '@forjio/plugipay-node';
const plugipay = new PlugipayClient({
keyId: process.env.PLUGIPAY_KEY_ID!,
secret: process.env.PLUGIPAY_SECRET!,
});
const workspaces = await plugipay.workspaces.list();
for (const w of workspaces) {
console.log(`${w.id}: ${w.brandName ?? '(no brand)'} → ${w.businessEmail ?? '(no email)'}`);
}
workspaces.create
Signature. plugipay.workspaces.create(input): Promise<Workspace>
Creates a new workspace under your account. Both brandName and businessEmail are optional — you can leave them empty at create and fill them in via update later (or via the dashboard's onboarding wizard). The SDK auto-attaches an Idempotency-Key.
const ws = await plugipay.workspaces.create({
brandName: 'Acme Retail',
businessEmail: 'billing@acme-retail.id',
});
console.log(ws.id, ws.accountId);
The new workspace starts empty — no adapters configured, no API keys minted, no customers. Each of those is set up through its own namespace.
workspaces.update
Signature. plugipay.workspaces.update(id, patch): Promise<Workspace>
PATCH semantics — only the fields you pass are touched.
const ws = await plugipay.workspaces.update('ws_01HX...', {
brandName: 'Acme Retail Indonesia',
businessEmail: 'finance@acme.id',
});
workspaces.delete
Signature. plugipay.workspaces.delete(id): Promise<void>
Deletes a workspace. Plugipay refuses deletion if the workspace has any outstanding obligations — non-zero balance, in-flight payouts, unfinalized invoices — in which case the call returns state_conflict. Resolve those first (e.g. sweep the balance, void open invoices), then retry.
await plugipay.workspaces.delete('ws_01HX...');
Deletion is final. All workspace data — customers, payment history, ledger entries, receipts — is retained for a regulatory window and then purged. There's no un-delete. For "I just want to stop using this workspace", consider revoking the API keys instead.
Types
interface Workspace {
id: string; // 'ws_...'
accountId: string; // 'acc_...' — your account
brandName: string | null;
businessEmail: string | null;
createdAt: string;
updatedAt: string;
}
For the full reference (including the relationship to accountId, partner workspace overlays, and the deletion holdback rules), see API: Workspaces.
Common patterns
Pick the workspace for a multi-brand operator
If you operate multiple brands out of one Plugipay account, your code needs to select the right workspace at startup:
async function pickWorkspace(brandName: string): Promise<Workspace> {
const all = await plugipay.workspaces.list();
const found = all.find((w) => w.brandName === brandName);
if (!found) throw new Error(`No workspace for brand ${brandName}`);
return found;
}
In practice you'd cache the workspace ID at deploy time rather than looking it up on every request — the ID is stable.
Provision a new brand
The standard "we're launching a new brand" flow:
async function launchBrand(brand: { name: string; email: string }) {
// 1. Create the workspace.
const ws = await plugipay.workspaces.create({
brandName: brand.name,
businessEmail: brand.email,
});
// 2. Mint an API key for the new workspace.
// NOTE: you need to act under the new workspace for this — see "Act on
// behalf of a workspace" below.
const scoped = plugipay.forMerchant(ws.accountId);
const key = await scoped.apiKeys.create({
description: `${brand.name} production key`,
});
// 3. Configure adapters (if you have direct provider credentials).
await scoped.adapters.updateXendit({ /* … */ });
// 4. Register a webhook endpoint.
await scoped.webhookEndpoints.create({
url: `https://yourapp.com/webhooks/plugipay/${ws.id}`,
});
return { workspaceId: ws.id, keyId: key.keyId, secret: key.secret! };
}
Audit workspaces
For an internal "what brands do we run?" report:
async function brandAudit() {
const all = await plugipay.workspaces.list();
return all.map((w) => ({
id: w.id,
brand: w.brandName ?? '(unnamed)',
email: w.businessEmail ?? '(no contact)',
ageDays: Math.floor((Date.now() - Date.parse(w.createdAt)) / 86_400_000),
}));
}
Act on behalf of a workspace (platform admin)
If you're a platform admin with plugipay:platform:admin scope (Storlaunch, Fulkruma, Ripllo), call forMerchant to scope subsequent calls to a specific workspace's accountId:
const platform = new PlugipayClient({
keyId: process.env.PLUGIPAY_PLATFORM_KEY_ID!,
secret: process.env.PLUGIPAY_PLATFORM_SECRET!,
});
const merchant = platform.forMerchant('acc_01HX...');
await merchant.checkoutSessions.create({ /* runs as the merchant */ });
See Authentication for the underlying X-Plugipay-On-Behalf-Of mechanism.
Safely delete a workspace
Wrap the deletion in a small dry-run guard so you don't surprise yourself:
async function deleteWorkspaceSafely(workspaceId: string) {
// Switch to the workspace context to inspect it.
const scoped = plugipay.forMerchant(workspaceId.replace(/^ws_/, 'acc_'));
// (For your own workspaces, the IDs are linked via Workspace.accountId.)
const balance = await scoped.payouts.balance();
if (balance.available > 0) {
throw new Error(`Workspace has non-zero balance: ${balance.available} ${balance.currency}`);
}
const openInvoices = await scoped.invoices.list({ status: 'open', limit: 1 });
if (openInvoices.data.length > 0) {
throw new Error('Workspace has open invoices; void or pay them first');
}
await plugipay.workspaces.delete(workspaceId);
}
Errors
| Code | Status | Cause |
|---|---|---|
validation_error |
400 | Bad businessEmail format, brandName too long. |
not_found |
404 | Workspace ID doesn't exist or doesn't belong to your account. |
state_conflict |
409 | Deletion blocked by non-zero balance, in-flight payouts, or open invoices. |
forbidden |
403 | Key lacks plugipay:workspace:write scope. |
Next
- API keys — minting keys for a new workspace.
- Adapters — configuring providers in a new workspace.
- Authentication — the
forMerchant/onBehalfOfpattern. - API: Workspaces — HTTP reference.