Customers

A customer is the persistent record of a buyer who pays you — the address book that sits under every payment, subscription, and invoice in your workspace. Guest checkouts work without one, but the moment you want a second charge, a stored card, or a clean receipt history hanging off one identity, you want a customer object.

Storlaunch supports two backends for customers:

  1. Local — workspaces on the free tier (no Plugipay module) keep customers in Storlaunch's own Customer table.
  2. Plugipay-proxied — once the Plugipay module is enabled, every endpoint here transparently proxies through to the Plugipay customers API.

The route picks one at request time based on the module state. The wire shapes are identical, so callers don't notice the swap.

All endpoints require an sk_* key.

Endpoints

Method Path Purpose
POST /v1/payment/customers Create a customer
GET /v1/payment/customers List customers
GET /v1/payment/customers/:id Retrieve one customer
PATCH /v1/payment/customers/:id Update a customer

There is no DELETE — hard-delete would cascade through payments, invoices, refunds, and ledger entries. See Delete (or archive).

Create

POST /v1/payment/customers

Creates a customer in the workspace your API key belongs to. Tier-limited — some plans cap customer count; you'll see 403 QUOTA_EXCEEDED once the cap is hit.

In local mode, email is treated as the dedupe key (a duplicate returns 409 VALIDATION_ERROR). In Plugipay mode, email uniqueness is not enforced — the wire shape matches Plugipay's API one-for-one. If you rely on local-mode behaviour today, plan to switch your dedupe logic to externalId before enabling Plugipay.

Request body

Field Type Required Description
email string (RFC 5321, ≤320) yes (local) / no (Plugipay) Buyer email. Primary contact and dedupe key in local mode.
name string (1–255) no Display name on receipts.
metadata object no Up to 50 string-to-string entries. Pass-through to webhooks.

Response201 Created

{
  "data": {
    "id": "cust_01HXAB7K3M9N2P5QRS8TVWXY3Z",
    "accountId": "acc_01HX...",
    "email": "alice@example.com",
    "name": "Alice Tan",
    "metadata": { "segment": "enterprise" },
    "createdAt": "2026-05-13T10:42:00Z",
    "updatedAt": "2026-05-13T10:42:00Z"
  },
  "error": null,
  "meta": { "requestId": "req_01HX...", "timestamp": "2026-05-13T10:42:00Z" }
}

In Plugipay mode the id is prefixed cus_ (Plugipay convention); in local mode it's cust_. Treat the prefix as opaque.

Errors specific to this endpoint

Status error.code When
400 VALIDATION_ERROR Field shape wrong, unknown field, or email not RFC-valid.
409 VALIDATION_ERROR Email already exists (local mode only).
403 QUOTA_EXCEEDED Workspace at customer cap.
curl -X POST https://storlaunch.forjio.com/api/v1/payment/customers \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","name":"Alice Tan","metadata":{"segment":"enterprise"}}'
// Node
const c = await client.customers.create({
  email: 'alice@example.com',
  name: 'Alice Tan',
});

Retrieve

GET /v1/payment/customers/:id

Returns one customer.

Errors

Status error.code When
404 RESOURCE_NOT_FOUND Customer doesn't exist or is in another workspace.

List

GET /v1/payment/customers?limit=50&cursor=…&email=…

Cursor-paginated, newest-first.

Query parameters

Param Type Default Description
limit integer 50 Page size. Clamped to [1, 100].
cursor string Opaque cursor from a previous meta.page.nextCursor.
email string Exact email match. Case-sensitive on the wire.

Update

PATCH /v1/payment/customers/:id

Partial update. Send only the fields you want to change. Pass metadata: null to clear it.

Errors

Status error.code When
404 RESOURCE_NOT_FOUND Customer missing or in another workspace.
400 VALIDATION_ERROR Shape wrong.
409 VALIDATION_ERROR New email collides with an existing customer (local mode).

Delete (or archive)

Not exposed. Soft-archive by PATCHing metadata.archived = "true" and filtering on your end. For GDPR/erasure, contact support@storlaunch.forjio.com — the redaction job scrubs identifying fields from associated payments and webhook payloads while keeping a tombstone for ledger continuity.

The customer object

Field Type Nullable Description
id string no cust_ (local) or cus_ (Plugipay) + ULID. Treat as opaque.
accountId string no Workspace owner.
email string yes Buyer email.
name string yes Display name.
metadata object no String-to-string map.
createdAt / updatedAt string (ISO 8601 UTC) no Timestamps.

Events

Event type Fires on Notes
customer.created (Plugipay mode) New customer row written. Emitted by Plugipay's outbox, routed to Storlaunch's registered endpoint. In local mode, not currently emitted — see callout below.

customer.created is reserved but not emitted in local mode. Free-tier workspaces (no Plugipay module) write customers without firing an outbox event. Subscribe defensively if you'd like consistent handling regardless of mode — the event will arrive once the workspace upgrades.

customer.updated and customer.deleted are on the roadmap but not currently emitted in either mode. If you mirror customers into another system today, poll GET /v1/payment/customers with createdAfter filtering, or piggy-back on the downstream payment events (checkout.completed, subscription.created) that carry customer references inline.

Next