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
Walk every Syncro API endpoint, page by page, capturing IDs and updated-at timestamps.
Fetch the full record for each ID. The raw JSON response is saved untouched.
Extract known fields into typed columns + a SQLite FTS5 full-text index for sub-50ms search.
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.
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.
Nightly delta sync by default. Configurable per entity β hourly for tickets, weekly for products, off entirely for anything you don't care about.
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.
Page and item-level checkpoints. Network drop, container restart, server reboot β sync picks up where it left off. No duplicate API calls.
Global throttle watches Syncro's X-RateLimit-Remaining header and backs off before you ever see a 429. Your other API consumers keep working.
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.
Stop the container, copy data/syncro.db, restart. The DB is a single portable file β works anywhere SQLite works.
Built-in .backup endpoint uses SQLite's online backup β no container stop, no read lock, consistent snapshot while sync continues to run.
Schedule rclone/restic/borg to push the nightly snapshot to S3, B2, Backblaze, or your own NAS. Restore is a single file copy.
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.
Upload the export bundles Syncro's Teams Plan produces β tickets, customers, invoices, assets, products, the lot. SyncNo detects the schema automatically.
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.
Re-subscribe later? Re-enable the API sync and SyncNo merges incoming updates with your imported history β no data lost, no duplicates.
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.