How SyncNo works

A self-hosted sync engine that pulls every record from your Syncro MSP API β€” and keeps the full raw payload, so you never lose access to data you've already paid to collect.

πŸ’Ύ

We save the entire raw JSON β€” not just the fields we render

Every response from the Syncro API is stored verbatim in a raw_json column. The list views, detail pages, and search indexes read from a subset of that payload today β€” but nothing is discarded. If you decide tomorrow that you want to surface a field we don't currently render, you can add it without re-syncing, without calling the API again, and even after your Syncro subscription has ended.

The sync pipeline

1️⃣
Catalog

Walk every Syncro API endpoint, page by page, capturing IDs and updated-at timestamps.

2️⃣
Detail

Fetch the full record for each ID. The raw JSON response is saved untouched.

3️⃣
Index

Extract known fields into typed columns + a SQLite FTS5 full-text index for sub-50ms search.

4️⃣
Repeat

Next run picks up only what changed since since_updated_at. Resume from the last checkpoint if interrupted.

Every Syncro endpoint. Every record.

SyncNo walks the full Syncro API surface β€” no cherry-picking. New endpoints Syncro adds appear on subsequent syncs automatically.

πŸ‘₯ Customers
🎫 Tickets
🧾 Invoices
πŸ’³ Payments
πŸ“ Estimates
πŸ–₯️ Assets
πŸ“¦ Products
πŸ“‹ Purchase Orders
🏭 Vendors
πŸ“‘ Contracts
🎯 Leads
πŸ“… Appointments
πŸ“š Wiki
πŸ—‚οΈ Policy Folders

Plus comments, time entries, line items, contacts, serials, SKUs, appointment types, and every other linked sub-resource.

Future-proof by design

Syncro ships new fields on every release. Most exporters pick a handful and discard the rest. SyncNo keeps the whole envelope β€” so when Syncro adds a field you suddenly care about, you already have historical values for every record you've ever synced.

  • βœ“ Add a column tomorrow and backfill the missing values from raw_json β€” no re-sync needed.
  • βœ“ No re-sync required β€” no API calls, no rate-limit budget burned.
  • βœ“ Works after your Syncro sub ends. The data is yours.
  • βœ“ Schema migrations are additive β€” old rows stay valid forever.
-- Add a column, backfill from raw_json
ALTER TABLE tickets ADD COLUMN slack_channel TEXT;

UPDATE tickets
SET slack_channel = json_extract(raw_json, '$.slack_channel')
WHERE json_extract(raw_json, '$.slack_channel') IS NOT NULL;

β†’ 18,247 rows updated in 2.1s
-- no API calls, no Syncro sub required

Sync options

Run SyncNo however suits you β€” manual one-off pulls, scheduled cron, or continuous delta sync.

⏱️
Scheduled (cron)

Nightly delta sync by default. Configurable per entity β€” hourly for tickets, weekly for products, off entirely for anything you don't care about.

▢️
Manual trigger

One-click full re-sync from the admin UI. Useful after schema migrations or when you've added a new entity type to the rotation.

πŸ”
Resume-capable

Page and item-level checkpoints. Network drop, container restart, server reboot β€” sync picks up where it left off. No duplicate API calls.

Rate-limit aware

Global throttle watches Syncro's X-RateLimit-Remaining header and backs off before you ever see a 429. Your other API consumers keep working.

Soft-delete detection

Records that vanish from the catalog between syncs are flagged deleted_at rather than dropped β€” preserves referential integrity for historical tickets and invoices.

Backup options

SyncNo is a SQLite database on disk. Back it up however you back up files.

πŸ“‚
File-level backup

Stop the container, copy data/syncro.db, restart. The DB is a single portable file β€” works anywhere SQLite works.

πŸ”„
Online backup API

Built-in .backup endpoint uses SQLite's online backup β€” no container stop, no read lock, consistent snapshot while sync continues to run.

☁️
Off-site replication

Schedule rclone/restic/borg to push the nightly snapshot to S3, B2, Backblaze, or your own NAS. Restore is a single file copy.

πŸ§ͺ
Test-restore dashboard

Built-in verify job opens the latest backup, confirms row counts match, and flags any corruption before you need it.

Login with Microsoft 365

SyncNo ships with Azure AD / Microsoft Entra ID OIDC out of the box. No new passwords to manage β€” your staff sign in with the same M365 identity they already use for email.

  • βœ“ Auth Code + PKCE flow β€” no client secrets in the browser.
  • βœ“ Signed JWT cookie session, rotated on each login.
  • βœ“ Admin allowlist seeded from your Azure object IDs.
  • βœ“ Conditional Access, MFA, and tenant restrictions inherit from your existing Entra policy.
# .env
AZURE_TENANT_ID=00000000-0000-0000-0000-000000000000
AZURE_CLIENT_ID=00000000-0000-0000-0000-000000000000
AZURE_CLIENT_SECRET=...
JWT_SECRET=...

# bootstrap admin allowlist
npm run bootstrap-admin \
  <azure-object-id>

β†’ admin user seeded
β†’ visit /admin β€” M365 redirect

Already left Syncro? Bring your data with you.

If you've cancelled your Syncro subscription but kept the Teams Plan CSV exports, SyncNo's importer ingests them directly. You get the same searchable UI, the same linked entity graph, and the same future-proof schema β€” no API connection required.

πŸ“€
Drop the CSVs

Upload the export bundles Syncro's Teams Plan produces β€” tickets, customers, invoices, assets, products, the lot. SyncNo detects the schema automatically.

πŸ”—
Relationships preserved

Foreign keys in the CSVs (ticket→customer, invoice→ticket, asset→serial) are rebuilt during import. Search across linked entities works exactly the same as live-synced data.

♻️
Mix with live sync

Re-subscribe later? Re-enable the API sync and SyncNo merges incoming updates with your imported history β€” no data lost, no duplicates.

CSV import is read-only too

Imported rows are flagged exactly like synced rows β€” SyncNo never writes back to Syncro, whether the data came from the API or a CSV. Your historical record is immutable.

Your network. Your database. Your call.

No Syncro credentials ever leave your infrastructure. No third party sees your customer data. SyncNo is a single Docker container you run inside your own network.