Everything you need to integrate TinyCRM into your apps. Lightweight NPM package, two methods, under 2KB.
Three steps to start syncing customers:
tcrm_proj_...).identify().import { TinyCRM } from 'tinycrm-sdk';
const tinycrm = new TinyCRM('tcrm_proj_xxxxxxxxxxxxxxxx');
// On user login, signup, or any key event
await tinycrm.identify({
email: 'john@example.com',
name: 'John Doe',
status: 'paid',
params: { plan: 'pro', source: 'producthunt' },
});That's it. The customer now appears in your TinyCRM dashboard.
npm install tinycrm-sdk
# or
pnpm add tinycrm-sdk
# or
yarn add tinycrm-sdkWorks everywhere. Browser, Node.js, Deno, Bun, Cloudflare Workers — anywhere JavaScript runs.
Zero dependencies. Under 2KB minified. ~50 lines of code.
Upserts a customer record in TinyCRM. Call this when a user logs in, signs up, changes plan, or at any key event.
await tinycrm.identify({
email: 'jane@acme.com', // required — merge key
name: 'Jane Doe', // optional
status: 'paid', // optional — 'free' | 'paid'
params: { // optional — custom key:value
plan: 'pro',
source: 'producthunt',
role: 'admin',
},
});| Field | Type | Required | Description |
|---|---|---|---|
| string | Yes | Primary merge key. Must be a valid email. | |
| name | string | No | Customer display name |
| status | 'free' | 'paid' | No | Payment status for this project |
| params | Record<string, string | number | boolean> | No | Custom key:value pairs. Shallow-merged with existing params. |
Customers are identified by email. If a customer with that email already exists under your account, they're updated. If they exist in another project, they're merged — one row in the table, multiple project badges.
Lightweight activity heartbeat. Only updates last_activity_at — does not create new customers or update any other fields.
await tinycrm.ping('jane@acme.com');Returns silently if the customer doesn't exist for this project. Use this for periodic "this user is still active" signals.
// Simple — just pass the API key
const tinycrm = new TinyCRM('tcrm_proj_xxxxxxxxxxxxxxxx');
// Full config object
const tinycrm = new TinyCRM({
apiKey: 'tcrm_proj_xxxxxxxxxxxxxxxx',
baseUrl: 'https://api.tinycrm.dev', // default
silent: true, // default
});| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | (required) | Project API key from your dashboard |
| baseUrl | string | https://api.tinycrm.dev | Override for local dev or self-hosted |
| silent | boolean | true | When true, all errors are swallowed silently |
The NPM package wraps these two HTTP endpoints. You can also call them directly if you prefer.
Base URL
https://api.tinycrm.dev
Authenticate with your project API key in the Authorization header:
curl -X POST https://api.tinycrm.dev/v1/track \
-H "Authorization: Bearer tcrm_proj_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"email": "john@example.com", "name": "John Doe", "status": "paid"}'Upsert a customer. Creates or updates the customer record and their association with the project identified by the API key.
{
"email": "john@example.com",
"name": "John Doe",
"status": "paid",
"params": {
"plan": "pro",
"source": "producthunt"
}
}{
"ok": true,
"customer_id": "uuid",
"merged": false
}| Field | Type | Required | Description |
|---|---|---|---|
| string | Yes | Merge key. Must be a valid email. | |
| name | string | No | Customer display name |
| status | string | No | 'free' or 'paid' |
| params | object | No | Custom key:value pairs (shallow-merged) |
Rate limit: 100 requests/minute per API key.
Update last activity timestamp for an existing customer. Does not create new customers.
{
"email": "john@example.com"
}{
"ok": true
}Returns 404 if the customer doesn't exist for this project.
import { TinyCRM } from 'tinycrm-sdk';
export const tinycrm = new TinyCRM(
process.env.TINYCRM_API_KEY!
);import { tinycrm } from '@/lib/tinycrm';
export async function POST(request: Request) {
const { email, name } = await request.json();
// Your auth logic...
const user = await createUser({ email, name });
// Sync to TinyCRM
await tinycrm.identify({
email: user.email,
name: user.name,
status: 'free',
params: { source: 'signup' },
});
return Response.json({ ok: true });
}import { TinyCRM } from 'tinycrm-sdk';
const tinycrm = new TinyCRM('tcrm_proj_xxxxxxxxxxxxxxxx');
app.post('/api/login', async (req, res) => {
const user = await authenticateUser(req.body);
await tinycrm.identify({
email: user.email,
name: user.name,
status: user.isPaid ? 'paid' : 'free',
params: { plan: user.plan },
});
res.json({ ok: true });
});import { TinyCRM } from 'tinycrm-sdk';
const tinycrm = new TinyCRM('tcrm_proj_xxxxxxxxxxxxxxxx');
app.post('/webhooks/stripe', async (req, res) => {
const event = req.body;
if (event.type === 'checkout.session.completed') {
await tinycrm.identify({
email: event.data.object.customer_email,
status: 'paid',
params: { plan: 'pro' },
});
}
res.sendStatus(200);
});By default, silent: true means the package never throws. All errors are silently swallowed so TinyCRM never breaks your app.
Set silent: false during development to see errors in the console:
const tinycrm = new TinyCRM({
apiKey: 'tcrm_proj_xxxxxxxxxxxxxxxx',
silent: process.env.NODE_ENV === 'production',
});| Scenario | silent=true | silent=false |
|---|---|---|
| Invalid email | Swallowed | [tinycrm] 400: ... |
| API key revoked | Swallowed | [tinycrm] 401: ... |
| Subscription expired | Swallowed | [tinycrm] 403: ... |
| Rate limited | Swallowed | [tinycrm] 429: ... |
| Network failure | Swallowed | [tinycrm] Error: ... |
Need help? Reach out at support@tinycrm.dev