Express
Express integrations usually start with a small request helper. The goal is to
resolve customerId once and keep route code focused on product behavior.
Install
Section titled “Install”npm install @guapocado/sdk expressnpm install --save-dev @types/expressMinimal Raw SDK Route
Section titled “Minimal Raw SDK Route”import { createGuapocadoClient } from "@guapocado/sdk";import express from "express";
const app = express();app.use(express.json());
function createBilling(customerId: string) { return createGuapocadoClient({ apiKey: process.env.GUAPOCADO_API_KEY!, customerId, });}
app.get("/features/:feature", async (req, res, next) => { try { const customerId = String(req.query.customerId ?? ""); if (!customerId) return res.status(400).json({ error: "customerId required" });
const guap = createBilling(customerId); const hasAccess = await guap.has(req.params.feature);
return res.json({ feature: req.params.feature, hasAccess }); } catch (error) { next(error); }});Request Middleware
Section titled “Request Middleware”In real apps, do not read the customer from a query string. Resolve it from auth or your active organization middleware.
import type { GuapocadoClient } from "@guapocado/sdk";import { createGuapocadoClient } from "@guapocado/sdk";import type { RequestHandler } from "express";
declare global { namespace Express { interface Request { customerId?: string; guap?: GuapocadoClient; } }}
export const attachBilling: RequestHandler = (req, res, next) => { const orgId = req.header("x-active-org-id"); if (!orgId) return res.status(401).json({ error: "organization required" });
req.customerId = `org_${orgId}`; req.guap = createGuapocadoClient({ apiKey: process.env.GUAPOCADO_API_KEY!, customerId: req.customerId, });
next();};Then use it on protected routes:
app.post("/reports", attachBilling, async (req, res, next) => { try { if (!(await req.guap!.has("advanced-analytics"))) { return res.status(403).json({ error: "Upgrade required" }); }
const report = await createReport(req.customerId!); return res.json({ report }); } catch (error) { next(error); }});Usage Example
Section titled “Usage Example”app.post("/ai/summarize", attachBilling, async (req, res, next) => { try { const input = String(req.body.text ?? ""); const credits = Math.ceil(input.length / 4);
await req.guap!.usage.consume("ai-credits", credits);
try { const summary = await summarize(input); return res.json({ summary, credits }); } catch (error) { await req.guap!.usage.refund("ai-credits", credits); throw error; } } catch (error) { next(error); }});Checkout Example
Section titled “Checkout Example”app.post("/billing/checkout/:productKey", attachBilling, async (req, res, next) => { try { await req.guap!.customers.create({ id: req.customerId, email: req.header("x-user-email") ?? undefined, });
const checkout = await req.guap!.checkout.create({ productKey: req.params.productKey, successUrl: `${process.env.APP_URL}/billing/success`, cancelUrl: `${process.env.APP_URL}/billing`, });
return res.redirect(checkout.url); } catch (error) { next(error); }});Error Handling
Section titled “Error Handling”The SDK throws typed errors for failed Guapocado API responses:
import { GuapocadoAuthError, GuapocadoError, GuapocadoRateLimitError,} from "@guapocado/sdk";
app.use((error, req, res, next) => { if (error instanceof GuapocadoRateLimitError) { return res.status(429).json({ error: error.message }); }
if (error instanceof GuapocadoAuthError) { return res.status(401).json({ error: error.message }); }
if (error instanceof GuapocadoError) { return res.status(error.status).json({ error: error.message }); }
return res.status(500).json({ error: "Internal server error" });});