
Research
/Security News
PolinRider Spreads Through Compromised GitHub Accounts and Packagist
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.
@favcrm/sdk
Advanced tools
FavCRM SDK — AI-native business OS for merchants. Manage bookings, CMS, shop, members, and loyalty.
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.
npm install @favcrm/sdk
import FavCRM from '@favcrm/sdk';
const sdk = new FavCRM({
baseUrl: 'https://api.favcrm.io',
companyId: 'your-company-id',
});
The SDK uses OTP (one-time password) authentication. Users log in with their email or phone:
// Step 1: Send OTP
const sendResponse = await sdk.auth.sendOtp({ email: 'user@example.com' });
// → OTP delivered to their inbox
// Step 2: User receives OTP and verifies
const authResponse = await sdk.auth.verifyOtp(
{ email: 'user@example.com' },
'123456',
);
// Step 3: Store token and use for authenticated requests
sdk.setToken(authResponse.accessToken);
All subsequent SDK calls include the token automatically.
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.
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.
List available services and create a booking:
// List all booking services
const services = await sdk.bookings.listServices();
console.log(services[0].name); // e.g. "Haircut", "Massage"
// Get available time slots for a specific date
const slotsResponse = await sdk.bookings.getTimeSlots('service-id', {
date: '2026-05-15',
});
console.log(slotsResponse.slots[0]);
// { startTime: '2026-05-15T09:00:00Z', available: true, ... }
// Create a booking
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); // Booking confirmed
List and retrieve blog posts with block-based content:
// List blog posts (paginated)
const postsResult = await sdk.blog.list({ limit: 10 });
console.log(postsResult.items[0].title); // "New Features"
// Get a single post by slug
const post = await sdk.blog.getBySlug('new-features');
// Posts contain structured blocks (paragraphs, images, headings, etc.)
console.log(post.blocks);
// [
// { id: '1', type: 'heading', version: 1, data: { level: 2, text: 'Introduction' } },
// { id: '2', type: 'paragraph', version: 1, data: { html: '<p>Welcome...</p>' } },
// { id: '3', type: 'image', version: 1, data: { src: 'https://...', alt: 'Demo' } },
// ]
// Render blocks in your frontend using a block renderer
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.
Build a product catalog and create orders:
// List products with filtering
const products = await sdk.shop.listProducts({
category_slug: 'electronics',
sort: 'price_asc',
limit: 20,
});
console.log(products[0]); // { id, name, price, image, ... }
// Get single product details
const product = await sdk.shop.getProduct('laptop-pro');
console.log(product.description);
// List available payment methods
const paymentMethods = await sdk.shop.listPaymentMethods();
// Get shipping methods for order amount
const shippingMethods = await sdk.shop.listShippingMethods(15000); // $150.00
// Create an order
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', // optional
});
console.log(order.id); // Order created and payment processed
Manage member profiles and loyalty programs:
// Get current member profile (requires authentication)
const member = await sdk.members.getProfile();
console.log(member.email, member.loyaltyBalance);
// Update profile
await sdk.members.updateProfile({
firstName: 'Jane',
lastName: 'Doe',
});
// List available membership tiers
const tiers = await sdk.tiers.list();
console.log(tiers[0].name); // e.g. "Gold", "Platinum"
// Enroll in a membership tier
const enrollment = await sdk.members.enroll('tier-id');
console.log(enrollment.membershipId);
// Get loyalty card settings
const cardSettings = await sdk.members.getCardSettings();
console.log(cardSettings.cardNumber);
Validate coupon codes and apply promotions:
// Validate a coupon or promotion code
const validation = await sdk.promotions.validate({
code: 'SUMMER2026',
itemTotal: 10000, // $100.00
applicableItems: ['laptop-pro', 'usb-cable'],
});
console.log(validation.valid); // true
console.log(validation.discountAmount); // 2000 (20% off)
console.log(validation.discountPercent); // 20
// Use validation result when creating orders
if (validation.valid) {
const order = await sdk.shop.createOrder({
items: [...],
couponCode: 'SUMMER2026',
});
}
| Namespace | Purpose | Key Methods |
|---|---|---|
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 |
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.
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.
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.
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') {
// Handle specific error
}
}
}
Useful for Node.js runtimes or custom network handlers:
const sdk = new FavCRM({
baseUrl: 'https://api.favcrm.io',
companyId: 'your-company-id',
fetch: customFetch, // optional; defaults to globalThis.fetch
});
sdk.clearToken();
MIT
FAQs
FavCRM SDK — AI-native business OS for merchants. Manage bookings, CMS, shop, members, and loyalty.
The npm package @favcrm/sdk receives a total of 114 weekly downloads. As such, @favcrm/sdk popularity was classified as not popular.
We found that @favcrm/sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.

Company News
Allow myself to introduce... myself.