HomeDocumentation
Get Started

Documentation

Everything you need to integrate TinyCRM into your apps. Lightweight NPM package, two methods, under 2KB.

Quick Start

Three steps to start syncing customers:

  1. Create an account at app.tinycrm.dev and create a project.
  2. Copy your project API key (format: tcrm_proj_...).
  3. Install the package and call identify().
your-app/server.tstypescript
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.

Installation

terminalbash
npm install tinycrm-sdk
# or
pnpm add tinycrm-sdk
# or
yarn add tinycrm-sdk

Works everywhere. Browser, Node.js, Deno, Bun, Cloudflare Workers — anywhere JavaScript runs.

Zero dependencies. Under 2KB minified. ~50 lines of code.

identify()

Upserts a customer record in TinyCRM. Call this when a user logs in, signs up, changes plan, or at any key event.

example.tstypescript
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',
  },
});
FieldTypeRequiredDescription
emailstringYesPrimary merge key. Must be a valid email.
namestringNoCustomer display name
status'free' | 'paid'NoPayment status for this project
paramsRecord<string, string | number | boolean>NoCustom 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.

ping()

Lightweight activity heartbeat. Only updates last_activity_at — does not create new customers or update any other fields.

example.tstypescript
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.

Constructor Options

setup.tstypescript
// 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
});
OptionTypeDefaultDescription
apiKeystring(required)Project API key from your dashboard
baseUrlstringhttps://api.tinycrm.devOverride for local dev or self-hosted
silentbooleantrueWhen true, all errors are swallowed silently

API Reference

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:

terminalbash
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"}'

POST /v1/track

Upsert a customer. Creates or updates the customer record and their association with the project identified by the API key.

request bodyjson
{
  "email": "john@example.com",
  "name": "John Doe",
  "status": "paid",
  "params": {
    "plan": "pro",
    "source": "producthunt"
  }
}
response 200json
{
  "ok": true,
  "customer_id": "uuid",
  "merged": false
}
FieldTypeRequiredDescription
emailstringYesMerge key. Must be a valid email.
namestringNoCustomer display name
statusstringNo'free' or 'paid'
paramsobjectNoCustom key:value pairs (shallow-merged)

Rate limit: 100 requests/minute per API key.

POST /v1/ping

Update last activity timestamp for an existing customer. Does not create new customers.

request bodyjson
{
  "email": "john@example.com"
}
response 200json
{
  "ok": true
}

Returns 404 if the customer doesn't exist for this project.

Framework Guides

Next.js (App Router)

lib/tinycrm.tstypescript
import { TinyCRM } from 'tinycrm-sdk';

export const tinycrm = new TinyCRM(
  process.env.TINYCRM_API_KEY!
);
app/api/auth/callback/route.tstypescript
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 });
}

Express.js

server.tstypescript
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 });
});

Stripe/Paddle Webhook

webhooks/stripe.tstypescript
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);
});

Error Handling

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:

dev-setup.tstypescript
const tinycrm = new TinyCRM({
  apiKey: 'tcrm_proj_xxxxxxxxxxxxxxxx',
  silent: process.env.NODE_ENV === 'production',
});
Scenariosilent=truesilent=false
Invalid emailSwallowed[tinycrm] 400: ...
API key revokedSwallowed[tinycrm] 401: ...
Subscription expiredSwallowed[tinycrm] 403: ...
Rate limitedSwallowed[tinycrm] 429: ...
Network failureSwallowed[tinycrm] Error: ...

Need help? Reach out at support@tinycrm.dev