Workspaces

A workspace is the billable, branded merchant container in Plugipay — one workspace per business identity (Forjio Studio Indonesia, BigCorp Indonesia, an indie merchant). Every customer, plan, invoice, adapter config, and ledger entry lives under exactly one workspace. The Go SDK exposes the four merchant-facing workspace methods behind client.Workspaces; a platform admin can additionally provision workspaces on behalf of merchants via client.Admin.ProvisionWorkspace. For wire shapes, billing-tier coupling, and the partner-provisioning flow, see API → Workspaces.

Field on the Client

client.Workspaces — type *plugipay.WorkspacesResource. Installed by NewClient; shares the parent *http.Client, base URL, key, and OnBehalfOf default. Safe for concurrent use.

Methods

List

Signature. func (r *WorkspacesResource) List(ctx context.Context) ([]Workspace, error)

Returns every workspace the key is allowed to see. For a regular merchant key this is one workspace; for a platform-admin key it's the full set the admin has access to.

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

wss, err := client.Workspaces.List(ctx)
if err != nil {
    return err
}
for _, ws := range wss {
    log.Printf("%s brand=%v email=%v",
        ws.ID, deref(ws.BrandName), deref(ws.BusinessEmail))
}

Create

Signature. func (r *WorkspacesResource) Create(ctx context.Context, in WorkspaceCreateInput) (*Workspace, error)

Creates a new workspace. Both fields are optional pointers — you can mint a bare workspace and fill in BrandName / BusinessEmail later via Update. Auto-keyed for idempotency. The merchant identity behind the API key becomes the workspace owner.

brand := "Forjio Studio Indonesia"
email := "billing@forjio.com"

ws, err := client.Workspaces.Create(ctx, plugipay.WorkspaceCreateInput{
    BrandName:     &brand,
    BusinessEmail: &email,
})
if err != nil {
    return err
}
log.Printf("created workspace %s (account=%s)", ws.ID, ws.AccountID)

Update

Signature. func (r *WorkspacesResource) Update(ctx context.Context, id string, patch WorkspaceUpdateInput) (*Workspace, error)

PATCH semantics — only the fields you pass are touched. Use it to rename a workspace, or change the billing email that receives Plugipay invoices.

newEmail := "ap@forjio.com"
ws, err := client.Workspaces.Update(ctx, "acc_01HX...", plugipay.WorkspaceUpdateInput{
    BusinessEmail: &newEmail,
})

Delete

Signature. func (r *WorkspacesResource) Delete(ctx context.Context, id string) error

Hard-deletes a workspace and everything under it (customers, plans, ledger, ...) once Plugipay's deletion guard rails pass — you can't delete a workspace with active subscriptions or a non-zero ledger balance. Returns nil on success; *plugipay.Error{Code: "conflict"} when the guard rails refuse.

if err := client.Workspaces.Delete(ctx, "acc_01HX..."); err != nil {
    var pe *plugipay.Error
    if errors.As(err, &pe) && pe.Code == "conflict" {
        log.Printf("can't delete: %s", pe.Message)
        return nil
    }
    return err
}

For partner-provisioned merchant workspaces (Storlaunch, Fulkruma, Ripllo), see client.Admin.ProvisionWorkspace — that path bypasses the merchant signup and creates a workspace under your platform's billing account.

Types

type Workspace struct {
    ID            string  `json:"id"`         // "acc_..."
    AccountID     string  `json:"accountId"`
    BrandName     *string `json:"brandName"`
    BusinessEmail *string `json:"businessEmail"`
    CreatedAt     string  `json:"createdAt"`
    UpdatedAt     string  `json:"updatedAt"`
}

For the partner-billing variant (PartnerWorkspace) returned by admin methods, see API → Workspaces and the Go SDK reference → Admin.

Common patterns

"Switch workspace" for multi-workspace users

If an identity owns several workspaces, render a switcher in your UI and re-bind the SDK to the chosen one with ForMerchant:

wss, err := c.Workspaces.List(ctx)
if err != nil { return err }

chosen := wss[0].AccountID // e.g. from your UI

scoped := c.ForMerchant(chosen)
// scoped.Customers, scoped.Invoices, ... all now operate as that workspace.

ForMerchant is the canonical Go idiom for "make all subsequent calls happen in this workspace". The returned *Client is a shallow clone — it shares the underlying *http.Client, so the cost is a struct copy.

Onboarding a brand-new merchant

// 1) Create the workspace bare
ws, err := c.Workspaces.Create(ctx, plugipay.WorkspaceCreateInput{})
if err != nil { return err }

// 2) Add branding once they fill the form
brand := "BigCorp Indonesia"
ws, err = c.Workspaces.Update(ctx, ws.ID, plugipay.WorkspaceUpdateInput{
    BrandName: &brand,
})

// 3) Kick off adapter onboarding
scoped := c.ForMerchant(ws.AccountID)
_, _ = scoped.Adapters.StartManagedOnboarding(ctx, plugipay.ManagedOnboardingStartInput{
    Kind: plugipay.AdapterKindXendit,
})

Delete-guard branching

The Delete guard rails translate to two error codes you should handle explicitly:

err := c.Workspaces.Delete(ctx, id)
var pe *plugipay.Error
if errors.As(err, &pe) {
    switch pe.Code {
    case "conflict":
        // active subs or non-zero ledger — show the merchant which entity is blocking
    case "not_found":
        // already gone — treat as success
        err = nil
    case "insufficient_scope":
        // caller key isn't a workspace owner
    }
}

Context cancellation

ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
defer cancel()
ws, err := c.Workspaces.Update(ctx, id, patch)

Update is not auto-keyed (PATCH is naturally idempotent), so retry behavior is determined by the underlying field semantics — safe to retry an Update(name = X) because the second call posts the same state.

Errors

Code Status Cause
validation_error 400 Bad email format, name too long.
not_found 404 Workspace id doesn't exist.
conflict 409 Delete blocked by active subscriptions or non-zero ledger.
insufficient_scope 403 Caller key isn't a workspace admin.

Full mechanics: Errors.

Next

Plugipay — Payments that don't tax your success