Billing
The billing resource is Fulkruma's own subscription surface — it tells you what plan the merchant is on, how much they've used in the current period, recent invoices, and how to launch the Plugipay-hosted checkout for a plan change. It's not about charging the merchant's buyers; it's about charging the merchant for Fulkruma itself.
Plans are billed through Plugipay (Pattern 2 partner billing). Fulkruma never holds card data; Plugipay does.
The /billing/plans endpoint is public; everything else requires a signed request. See Authentication.
Endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET |
/api/v1/billing/plans |
public | List available plans |
GET |
/api/v1/billing/plan |
HMAC | Read the merchant's current plan |
GET |
/api/v1/billing/subscription |
HMAC | Read the merchant's Plugipay subscription view |
GET |
/api/v1/billing/usage |
HMAC | Current-period usage counters |
GET |
/api/v1/billing/invoices |
HMAC | List past invoices |
POST |
/api/v1/billing/checkout |
HMAC | Start a Plugipay checkout to subscribe / upgrade |
POST |
/api/v1/billing/cancel |
HMAC | Cancel the current subscription |
List plans
GET /api/v1/billing/plans
Public — no auth. Returns the plan catalog as an array: free, starter, growth, scale. Used by the marketing site's pricing section and the dashboard's plan picker.
{
"data": [
{
"id": "starter",
"name": "Starter",
"price": 299000,
"currency": "IDR",
"features": ["500 orders/month", "3 warehouses", "Reservations + low-stock alerts", "100 license keys"]
}
],
"error": null,
"meta": { ... }
}
price is per month, in IDR; features are display strings. The limits themselves are on GET /billing/plan.
Read current plan
GET /api/v1/billing/plan
Returns the merchant's plan and its limits: plan, planName, isForjioInternal, ordersLimit, warehousesLimit, licenseKeysLimit, apiKeysLimit, webhookEndpointsLimit, rateLimit, biteshipShipmentsLimit (each -1 for unlimited), and billingCycleEnd.
Read subscription
GET /api/v1/billing/subscription
Returns plan, planName, isForjioInternal, status (lowercase: active, canceling, …), currentPeriodStart, currentPeriodEnd and cancelAt.
Current-period usage
GET /api/v1/billing/usage
Returns this calendar month's counters: plan, ordersFulfilled, ordersLimit, shipmentsCreated, licensesIssued, and resetAt (the first day of next month). Powers the dashboard's "X of Y used" widgets.
List invoices
GET /api/v1/billing/invoices
Cursor-paginated, newest first; up to limit=50 per page (default 20). Lists the merchant's past Fulkruma invoices, mirrored from Plugipay's invoice resource.
Query parameters
| Param | Type | Description |
|---|---|---|
limit |
integer | Page size. Capped at 50. |
cursor |
string | The previous page's data.cursor. |
Response
{
"data": {
"data": [
{ "id": "inv_...", "plan": "growth", "amount": 799000, "currency": "IDR", "status": "paid", "paidAt": "...", "receiptUrl": "...", "createdAt": "..." }
],
"cursor": "inv_...",
"hasMore": true
},
"error": null,
"meta": { ... }
}
cursor is null on the last page.
Start a checkout
POST /api/v1/billing/checkout
Starts a Plugipay subscription for the chosen plan and returns the hosted checkout session where the first invoice is paid.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
plan |
STARTER | GROWTH | SCALE |
yes | The plan to subscribe to. |
email |
string | conditional | Required for an API-key (HMAC) caller, which has no email of its own; a signed-in session's email is used otherwise. The Plugipay-side customer is keyed off this. |
name |
string | no | Display name on the Plugipay receipt. |
currency |
IDR | USD |
no | Defaults by the caller's country: IDR in Indonesia, USD elsewhere. |
Response — 200 OK
{
"data": {
"subscriptionId": "sub_01HX...",
"invoiceId": "inv_01HX...",
"checkoutSessionId": "cs_01HX...",
"checkoutUrl": "https://plugipay.com/c/cs_01HX..."
},
"error": null,
"meta": { ... }
}
Errors
| Status | error.code |
When |
|---|---|---|
400 |
VALIDATION |
Bad plan key, or email missing and not in JWT. |
503 |
PLAN_NOT_CONFIGURED |
Fulkruma's environment hasn't been wired with the Plugipay plan IDs yet (operator config error). |
500 |
CHECKOUT_FAILED |
Plugipay-side error. The message carries the upstream detail. |
Cancel subscription
POST /api/v1/billing/cancel
Cancels the current Plugipay subscription at the end of the current period. No body. Returns the subscription as GET /billing/subscription does, now with status: "canceling"; a workspace with no paid subscription gets its state back unchanged. A Plugipay-side failure is 500 CANCEL_FAILED.
Events
Billing-resource webhook events fire from Plugipay, not from Fulkruma — they arrive at Fulkruma's /api/v1/webhooks/plugipay inbound handler, which updates the local subscription state. If you need to mirror billing state into your own systems, subscribe to Plugipay's invoice.paid, subscription.updated, etc. directly. See Plugipay's webhook docs for the catalog.
Next
- Authentication.
- Integrations — check the Plugipay connection's health.