@favcrm/sdk
JavaScript/TypeScript SDK for FavCRM — the AI-native business OS for merchants. Manage bookings, CMS content, shop products, members, loyalty, and more — from your app or AI agent.
Install
npm install @favcrm/sdk
Quick start
Initialize the SDK
import FavCRM from '@favcrm/sdk';
const sdk = new FavCRM({
baseUrl: 'https://api.favcrm.io',
companyId: 'your-company-id',
});
Authentication
The SDK uses OTP (one-time password) authentication. Users log in with their email or phone:
const sendResponse = await sdk.auth.sendOtp({ email: 'user@example.com' });
const authResponse = await sdk.auth.verifyOtp(
{ email: 'user@example.com' },
'123456',
);
sdk.setToken(authResponse.accessToken);
All subsequent SDK calls include the token automatically.
🤖 AI Agent Skills (Cursor, Windsurf, Claude Code)
Building your frontend with an AI Agent? Teach your AI our SDK best practices, backend data shapes, and API patterns by installing our official agent skills:
npx skills add favcrm/mcp
This installs our platform-wide skills, giving your local agent deep context on how to implement bookings, shop checkout, and member operations using this SDK.
MCP Integration (for AI agents)
Connect to FavCRM's MCP (Model Context Protocol) endpoint for AI agent access:
Endpoint: https://api.favcrm.io/mcp
Auth: API Key (fav_mcp_*)
Flow:
1. Create API key via POST /v6/mcp/keys (requires JWT)
2. Request OTP via POST /v6/mcp/auth/request
3. Verify OTP and get session token via POST /v6/mcp/auth/verify
4. Use session token for AI tool access
See https://favcrm.io/developers for MCP setup and available tools.
Quickstart 1 — Booking Storefront
List available services and create a booking:
const services = await sdk.bookings.listServices();
console.log(services[0].name);
const slotsResponse = await sdk.bookings.getTimeSlots('service-id', {
date: '2026-05-15',
});
console.log(slotsResponse.slots[0]);
const booking = await sdk.bookings.create({
serviceId: 'service-id',
slotId: 'slot-id',
guestEmail: 'customer@example.com',
guestName: 'John Doe',
guestPhone: '+1234567890',
});
console.log(booking.id);
Quickstart 2 — CMS Blog Post
List and retrieve blog posts with block-based content:
const postsResult = await sdk.blog.list({ limit: 10 });
console.log(postsResult.items[0].title);
const post = await sdk.blog.getBySlug('new-features');
console.log(post.blocks);
for (const block of post.blocks) {
switch (block.type) {
case 'heading':
console.log(`<h${block.data.level}>${block.data.text}</h${block.data.level}>`);
break;
case 'paragraph':
console.log(`<div>${block.data.html}</div>`);
break;
case 'image':
console.log(`<img src="${block.data.src}" alt="${block.data.alt}" />`);
break;
}
}
For detailed content block structure, see docs/CONTENT_BLOCKS.md.
Quickstart 3 — Shop Checkout
Build a product catalog and create orders:
const products = await sdk.shop.listProducts({
category_slug: 'electronics',
sort: 'price_asc',
limit: 20,
});
console.log(products[0]);
const product = await sdk.shop.getProduct('laptop-pro');
console.log(product.description);
const paymentMethods = await sdk.shop.listPaymentMethods();
const shippingMethods = await sdk.shop.listShippingMethods(15000);
const order = await sdk.shop.createOrder({
items: [
{ productSlug: 'laptop-pro', quantity: 1 },
{ productSlug: 'usb-cable', quantity: 2 },
],
email: 'customer@example.com',
shippingMethodId: 'standard-shipping',
paymentMethodId: 'card-stripe',
couponCode: 'SUMMER2026',
});
console.log(order.id);
Quickstart 4 — Membership & Loyalty
Manage member profiles and loyalty programs:
const member = await sdk.members.getProfile();
console.log(member.email, member.loyaltyBalance);
await sdk.members.updateProfile({
firstName: 'Jane',
lastName: 'Doe',
});
const tiers = await sdk.tiers.list();
console.log(tiers[0].name);
const enrollment = await sdk.members.enroll('tier-id');
console.log(enrollment.membershipId);
const cardSettings = await sdk.members.getCardSettings();
console.log(cardSettings.cardNumber);
Quickstart 5 — Promotions & Checkout
Validate coupon codes and apply promotions:
const validation = await sdk.promotions.validate({
code: 'SUMMER2026',
itemTotal: 10000,
applicableItems: ['laptop-pro', 'usb-cable'],
});
console.log(validation.valid);
console.log(validation.discountAmount);
console.log(validation.discountPercent);
if (validation.valid) {
const order = await sdk.shop.createOrder({
items: [...],
couponCode: 'SUMMER2026',
});
}
Namespaces
auth | OTP login, token management | sendOtp, verifyOtp, getLoginChannel, register |
shop | Products, categories, orders | listProducts, getProduct, createOrder, listOrders |
bookings | Services, time slots, bookings | listServices, getTimeSlots, create, list, get |
events | Event listing and registration | list, get, register, listRegistrations |
members | Member profiles, loyalty, card | getProfile, updateProfile, getCardSettings, listPaymentMethods |
payments | Checkout, payment intents | getGateway, createIntent, getCreditBalance |
promotions | Coupon/promo validation | validate |
invoices | Invoice listing | list, get |
cms | CMS pages | listPages, getPage |
blog | Blog posts with block content | list, getBySlug |
packages | Service packages | listMyOrders, getApplicable |
tiers | Membership tiers | list |
contact | Contact/enquiry forms | submit |
walletPasses | Apple/Google wallet passes | getStatus, generate, downloadAppleBlob |
gifts | Gift offers and redemption | listMyRedemptions, getOffer, redeemOffer, claimByCode |
Event command retries
Authenticated event registration and hosted-payment mutations require a
cryptographically random command key at the API boundary. Create the options
once when the form operation begins, persist that object with the pending form
state, and reuse it for every retry of that operation. Guest flows may omit the
options because their short-lived access-token response is intentionally not
stored as a durable receipt:
import {
clearEventCommandOptions,
getOrCreateEventCommandOptions,
} from '@favcrm/sdk';
const operation = `registration:${event.id}`;
const command = getOrCreateEventCommandOptions(
sessionStorage,
operation,
);
await sdk.events.register(registration, command);
clearEventCommandOptions(sessionStorage, operation);
Create a new command only when the user starts a genuinely new operation.
Fire Club Agent portal
Agent sessions use a dedicated client and token audience. Keep this client
separate from the Customer FavCRM instance:
import {
FireClubAgentClient,
clearFireClubAgentCommandOptions,
getOrCreateFireClubAgentCommandOptions,
} from '@favcrm/sdk';
const agent = new FireClubAgentClient({
baseUrl: 'https://api.favcrm.io',
companyId: 'wolo-company-id',
});
const login = await agent.auth.login('agent@example.com', password);
if (!('requiresTwoFactor' in login)) {
agent.setToken(login.token);
}
const venues = await agent.venues.list();
const assignedCustomers = await agent.customers.list({ search: 'Ada' });
const operation = `agent-link:${event.slug}`;
const command = getOrCreateFireClubAgentCommandOptions(
sessionStorage,
operation,
);
const link = await agent.links.issue({ eventSlug: event.slug }, command);
clearFireClubAgentCommandOptions(sessionStorage, operation);
Agent Link issue and revoke commands require one persisted idempotency key per
logical operation. Reuse that key for retries and clear it only after success.
CMS Pages
Use sdk.cms.listPages() for navigation and listing screens. It returns CmsPageSummary[], which does not include page blocks.
Use sdk.cms.getPage(slug) when rendering page content. It returns the full CmsPage, including blocks.
Error Handling
All SDK methods throw FavCRMError on failure:
import { FavCRM, FavCRMError } from '@favcrm/sdk';
try {
const booking = await sdk.bookings.create({...});
} catch (error) {
if (error instanceof FavCRMError) {
console.error(`Error ${error.status}: ${error.message}`);
if (error.code === 'SLOT_NOT_AVAILABLE') {
}
}
}
Configuration
With custom fetch implementation
Useful for Node.js runtimes or custom network handlers:
const sdk = new FavCRM({
baseUrl: 'https://api.favcrm.io',
companyId: 'your-company-id',
fetch: customFetch,
});
Logout
sdk.clearToken();
Resources
License
MIT