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:
- Local — workspaces on the free tier (no Plugipay module) keep customers in Storlaunch's own
Customertable. - Plugipay-proxied — once the Plugipay module is enabled, every endpoint here transparently proxies through to the Plugipay
customersAPI.
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. |
Response — 201 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.createdis 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
- Checkout sessions — how a customer pays the first time.
- Subscriptions — recurring billing tied to a customer.
- Invoices — one-off or scheduled invoices for a customer.
- Public storefront — guest checkouts that auto-create customers.