Skip to content

Server SDK API

The server SDK is created with createGuapocadoClient().

import { createGuapocadoClient } from "@guapocado/sdk";
const guap = createGuapocadoClient({
apiKey: process.env.GUAPOCADO_API_KEY!,
customerId: "org_123",
});
type GuapocadoClientOptions = {
apiKey: string;
customerId?: string;
apiUrl?: string;
adapter?: GuapAdapter;
};

Most apps should not set apiUrl. The default hosted API is:

https://api.guapocado.dev
await guap.has("advanced-analytics");

Returns boolean.

await guap.limit("seats");

Returns:

type LimitBalance = {
limit: number;
included: number;
purchased: number;
expansionAllowed: boolean;
autoExpansionEnabled: boolean;
};
await guap.usage.balance("api-calls");
await guap.usage.consume("api-calls", 1);
await guap.usage.refund("api-calls", 1, { idempotencyKey: refundRequestId });
await guap.usage.configure("api-calls", {
overageEnabled: true,
});

consume() accepts an optional idempotency key so a retried call (timeout, queue redelivery) is applied at most once:

await guap.usage.consume("api-calls", 1, { idempotencyKey: requestId });

refund() also accepts an optional idempotency key. It reverses the exact allocation originally consumed, including the specific purchase-credit lots.

balance(), consume(), and refund() return:

type UsageBalance = {
balance: number;
included: number;
consumed: number;
overage: number;
overageAllowed: boolean;
overageEnabled: boolean;
resets: string | null;
};
await guap.limits.configure("seats", {
purchased: 3,
autoExpansionEnabled: false,
});

Returns LimitBalance.

await guap.customers.create({
id: "org_123",
name: "Acme Inc.",
email: "owner@example.com",
});

Returns the customer record.

await guap.context({
features: ["advanced-analytics"],
usage: ["api-calls"],
limits: ["seats"],
includePlans: true,
includeSubscription: true,
});

Use context when a route needs several billing answers at once.

await guap.plans.list();

Returns products pushed from config.

await guap.purchases.list();
await guap.purchases.refund("pur_123", {
mode: "prorated",
idempotencyKey: "refund-pur-123",
});

Prorated purchase refunds are supported for a single metered-credit grant. Full refunds revoke all remaining grants from the purchase.

await guap.subscription.current();
await guap.subscription.change("pro");
await guap.subscription.refund("sub_123", {
mode: "prorated",
idempotencyKey: "refund-sub-123",
});

Use checkout for new subscriptions. Use change() when a customer already has a subscription. A subscription refund cancels immediately and uses the unused fraction of the actual paid invoice period.

await guap.checkout.create({
productKey: "pro",
successUrl: "https://app.example.com/billing/success",
cancelUrl: "https://app.example.com/billing",
});

Returns:

type CheckoutResponse = {
url: string;
};
await guap.webhooks.register({
url: "https://app.example.com/api/guap-webhook",
events: "*",
integration: "custom",
registrationKey: "custom:primary",
});

Returns the registered receiver and signing secret.

import { createGuapocadoClientWithLocal } from "@guapocado/sdk";
const guap = createGuapocadoClientWithLocal({
apiKey: process.env.GUAPOCADO_API_KEY!,
store: myGuapStore, // defaults to createMemoryGuapStore()
maxAgeMs: 60_000, // optional; unset means no expiry
webhook: { publicUrl: "https://app.example.com/webhooks/guap" },
hooks: { onCancel, onPurchase },
});
export default { fetch: (request: Request) => guap.handler()(request) };

createGuapocadoClientWithLocal combines createGuapLocal with createGuapocadoClient, attaching .handler to the returned client. createGuapLocal alone returns the pieces separately:

type GuapLocal = {
adapter: GuapAdapter; // pass to createGuapocadoClient({ adapter })
handler: (hooks?: GuapWebhookHooks) => (request: Request) => Promise<Response>;
project: (event: GuapDomainEventEnvelope) => Promise<void>; // test/queue seam
register: () => Promise<{ id: string; status: string; url: string }>;
};

webhook.publicUrl is required for auto-registration (see Local Read Model). hooks accepts onEvent, the raw per-type hooks (onCustomerUpdated, onSubscriptionUpdated, onPurchaseCompleted, onPurchaseUpdated, onEntitlementsUpdated, onInvoiceUpdated), the semantic transition hooks (onSubscribe, onCancel, onPlanChange), and onPurchase. See Webhook hooks for the two-tier delivery contract and idempotency requirement.

export function verifyGuapocadoSignature(input: {
payload: string;
secret: string;
signature: string | null | undefined;
toleranceSeconds?: number; // default 300
}): Promise<boolean>;

Verifies a guapocado-signature: t=...,v1=... header against the raw request body. createGuapLocal’s handler uses this internally; call it directly if you write a receiver by hand instead.

testGuapStoreContract (from @guapocado/sdk/testing) validates a custom GuapStore implementation against the same contract suite the built-in in-memory store passes.

Per-customer custom pricing and limits. Uses the same entitlement keys as your catalog, with negotiated values, plus an optional custom price.

await guap.contracts.set(
{
priceAmount: 200000, // cents; e.g. $2,000
priceInterval: "month",
entitlements: {
seats: { included: 500 },
"api-calls": { included: 50_000_000 },
"advanced-analytics": true,
},
committedVolume: 50_000_000,
notes: "Annual enterprise agreement",
},
{ customerId: "org_123" },
);
const deal = await guap.contracts.get({ customerId: "org_123" });
await guap.contracts.delete({ customerId: "org_123" }); // reverts to catalog plan

Setting a deal applies the negotiated entitlement values immediately; checkout then bills that customer at their custom price. See Enterprise deals for the full model.

Every mutating action is recorded with the token that performed it.

const { logs, nextCursor } = await guap.audit.list({
action: "usage.consume",
resourceType: "meter",
limit: 50,
});

Every customer-scoped method accepts a customer override:

await guap.has("advanced-analytics", {
customerId: "org_456",
});

Use this for admin tools and background jobs.