SafiTrack Documentation
v1.4.0 · Mar 2026Everything you need to integrate SafiTrack into your workflow — from quick-start guides to full API references and SDK examples. If you get stuck, the Help Center and API Reference have you covered.
Quick Start
Get your team set up in under 10 minutes. You'll need an active SafiTrack account — sign up here if you don't have one yet.
- Create your workspace — sign in at
/crm/and complete the onboarding flow. It takes about 2 minutes. - Import contacts — upload a CSV (max 50,000 rows) or connect a Google Sheet from Settings → Integrations.
- Add companies and assign reps — companies can be created manually or synced via the REST API.
- Log your first visit — open the mobile app, tap New Visit, pick a company, and tap Save. GPS coordinates are captured automatically.
- Create a route plan — from the web dashboard go to Route Plans → New, select up to 25 stops, and tap Optimise.
Use the Chrome extension to import contacts directly from LinkedIn or your CRM while browsing. Install it from Settings → Extensions.
Authentication
SafiTrack uses Supabase Auth under the hood. All API calls require a short-lived JWT Bearer token obtained after sign-in. Tokens expire after 1 hour — use the refresh token to get a new one without re-authenticating.
Obtain a token
curl -X POST "https://ndrkncirkekpqjjkasiy.supabase.co/auth/v1/token?grant_type=password" \
-H "apikey: YOUR_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"you@company.com","password":"yourpassword"}'
The response includes access_token, refresh_token, and expires_in. Pass
the access token in every subsequent request:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Never commit your ANON_KEY or access_token to version control. Store secrets in
environment variables or a secrets manager.
Contacts & Companies
Every person and business in your pipeline lives in SafiTrack's contact graph. The contacts table
stores individuals; companies stores organisations. A contact can belong to multiple companies via
the contact_companies join table.
Fetch companies
const { data, error } = await supabase
.from('companies')
.select('id, name, industry, owner_id, created_at')
.order('created_at', { ascending: false })
.limit(50);
if (error) throw error;
console.log(data); // [{ id: 'co_...', name: 'Acme Ltd', ... }]
Create a contact
const { data, error } = await supabase
.from('contacts')
.insert({
first_name: 'Jane',
last_name: 'Mwangi',
email: 'jane@acme.co',
phone: '+254712345678',
company_id: 'co_abc123',
owner_id: 'rep_xyz789'
})
.select()
.single();
Bulk import via CSV
Navigate to Contacts → Import in the dashboard and upload a .csv file. Supported columns:
| Column | Type | Notes |
|---|---|---|
first_name required |
string | Max 100 chars |
last_name |
string | Max 100 chars |
email required |
string | Must be valid email format |
phone |
string | E.164 format recommended |
company_name |
string | Auto-matched or created if new |
tags |
string | Comma-separated, e.g. vip,renewal |
Sales Visits
Visit records are the core data unit in SafiTrack. Each visit captures GPS coordinates, duration, structured outcome, free-text notes, and optional photo attachments. Visits power AI summaries in the Intelligence Hub and the weekly performance digest emails.
Visit object
| Field | Type | Description |
|---|---|---|
id |
string | UUID, auto-generated |
rep_id |
string | ID of the rep who made the visit |
company_id |
string | Company visited |
contact_id |
string | Primary contact met (optional) |
lat / lng |
float | GPS coordinates at check-in |
duration_mins |
integer | Duration in minutes |
outcome |
enum | demo_booked · follow_up · no_contact · closed_won
|
notes |
string | Free-text, max 5,000 chars |
visited_at |
timestamp | ISO 8601, defaults to now |
GPS coordinates are captured automatically when using the mobile app. When creating visits via the API, you
may pass lat/lng manually or omit them.
Route Plans
A route plan is an ordered list of stops (companies or GPS waypoints) that SafiTrack's optimisation engine sequences for minimum travel time. Route plans can be created in the dashboard or via the API and are pushed to the mobile app automatically.
// Create and optimise a route plan
const { data } = await supabase
.from('route_plans')
.insert({
name: 'Nairobi East — Tue 4 Mar',
rep_id: 'rep_xyz789',
date: '2026-03-04',
stops: [
{ company_id: 'co_001', priority: 1 },
{ company_id: 'co_002', priority: 2 },
{ company_id: 'co_003', priority: 1 }
],
optimise: true // triggers server-side TSP optimisation
})
.select()
.single();
Opportunities
Opportunities track deals through a configurable Kanban pipeline. Each opportunity belongs to one company and one owner, and moves through stages you define in Settings → Pipeline.
Opportunity fields include title, value (currency amount), stage,
close_date, probability (0–100), and notes. The Intelligence Hub surfaces
stalled opportunities automatically when a deal hasn't moved stages in 14+ days.
Reminders & Tasks
Reminders are time-based notifications attached to contacts, companies, or opportunities. Tasks are checklist items within a visit or deal. Both are synced to the mobile app and can optionally fire email or push notifications.
// Create a follow-up reminder
const { data } = await supabase
.from('reminders')
.insert({
title: 'Follow up on demo',
contact_id: 'ct_111',
company_id: 'co_001',
due_at: '2026-03-06T09:00:00Z',
notify_push: true,
notify_email: false
})
.select()
.single();
Webhooks
Subscribe to real-time events using webhook endpoints. SafiTrack sends a signed POST request to
your URL whenever a trigger fires. Each request includes an X-SafiTrack-Signature header — a
HMAC-SHA256 of the raw body using your webhook secret.
Available events
| Event | Fires when |
|---|---|
visit.completed |
A rep saves a completed visit |
opportunity.stage_changed |
An opportunity moves to a new pipeline stage |
contact.created |
A new contact is added (any source) |
route_plan.started |
A rep begins navigating a route plan |
reminder.due |
A reminder's due_at timestamp is reached |
Example payload
{
"event": "visit.completed",
"timestamp": "2026-03-01T09:41:22Z",
"version": "1",
"data": {
"visit_id": "vis_abc123",
"rep_id": "rep_456",
"company_id": "co_789",
"outcome": "demo_booked",
"lat": -1.2921,
"lng": 36.8219,
"duration_mins": 28
}
}
Verify the signature
import crypto from 'crypto';
function verifyWebhook(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
SDKs
SafiTrack exposes its data via the Supabase JS and Python clients. Install them with your package manager:
# JavaScript / TypeScript
npm install @supabase/supabase-js
# Python
pip install supabase
from supabase import create_client
url = "https://ndrkncirkekpqjjkasiy.supabase.co"
key = "YOUR_ANON_KEY"
client = create_client(url, key)
# Fetch visits for a rep
response = (
client.table("visits")
.select("*")
.eq("rep_id", "rep_456")
.order("visited_at", desc=True)
.limit(20)
.execute()
)
print(response.data)
Changelog
- Intelligence Hub: AI visit summaries and rep coaching suggestions
- Bulk contact import — up to 50,000 rows, with duplicate detection
- Webhook delivery retries with exponential back-off (up to 5 attempts)
- Python SDK now officially supported
- Route plan optimisation engine (TSP-based, up to 25 stops)
- Territory management UI with polygon drawing on maps
- Google Calendar two-way sync for reminders and tasks
- Custom pipeline stages in Settings → Pipeline
- Opportunities Kanban board with drag-and-drop stage changes
- Custom fields on contacts and companies (text, number, date, select)
- CSV export for all entities including visits and call logs
- Role-based access control — Manager, Rep, Read-only
- Initial public launch
- Core CRM: contacts, companies, visits, reminders, call logs
- iOS and Android mobile apps
- REST API and Supabase JS SDK support