Offline first
The app writes to its own database, whether SwiftData, SQLite or Room. The SDK tracks every change and syncs as soon as the network is back.

tightrelay is the sync service for native apps. Your app keeps its local database. tightrelay delivers the changes to every device and resolves conflicts on the server.
Take Windows offline and edit there, then edit on the Mac and reconnect Windows. The later change wins, everywhere.
The service has no app-specific tables. It stores JSON documents, versions them and distributes changes. Your app’s schema is configuration, not server code.
The app writes to its own database, whether SwiftData, SQLite or Room. The SDK tracks every change and syncs as soon as the network is back.
The key lives in the Secure Enclave, TPM or Android Keystore. New devices find their account via iCloud Keychain or Block Store; others pair by QR.
Shared spaces for teams or businesses, with invites by QR code and roles to manage, edit or read.
Every change carries a hybrid logical clock timestamp. Per document, the later change wins, and every device ends up in the same state.
If you want, tightrelay only syncs with an active subscription. The server verifies purchases, renewals, expiry and refunds itself. When a subscription lapses, sync pauses and the data stays.
You manage collections and JSON Schemas in the console. Every new version is checked, and only changes that don’t break older app versions pass. Unknown fields are preserved.
Swift, C# and Kotlin, with no third-party dependencies. All three run the same test vectors and behave the same.
let client = TightRelayClient( configuration: .init(baseURL: api, appId: "stundennachweis", clientSchema: 1, deviceName: "Mac"), store: MySwiftDataStore(), identities: KeychainIdentityStore(appId: "stundennachweis"), recovery: ICloudKeychainRecoveryStore(appId: "stundennachweis")) try await client.create(collection: "clients", data: ["name": "Miller Ltd"]) try await client.update(id, set: ["archived": true]) // merge try await client.delete(id) // tombstone let report = try await client.sync() // push, then pull
var client = new TightRelayClient( new TightRelayConfiguration(api, "stundennachweis", ClientSchema: 1, DeviceName: Environment.MachineName), store: new MySqliteStore(db), identities: new FileIdentityStore(identityPath), // key in the TPM recovery: new DpapiRecoveryStore(recoveryPath)); await client.CreateAsync("clients", new JsonObject { ["name"] = "Miller Ltd" }); await client.UpdateAsync(id, new JsonObject { ["archived"] = true }); await client.DeleteAsync(id); var report = await client.SyncAsync();
val client = TightRelayAndroid.client( context, api, appId = "stundennachweis", clientSchema = 1, store = MyRoomStore(db)) // key in the Keystore client.create("clients", jsonOf("name" to "Miller Ltd")) client.update(id, jsonOf("archived" to true)) client.delete(id) val report = client.sync() // also from WorkManager
Each round is a push and a pull over HTTPS. Every request is signed with the device key.
The app saves as usual. The SDK stamps the change with the device clock and queues it for upload.
The server validates the schema, compares timestamps and only accepts newer data. If the change is older, the device gets the current version back.
Every accepted change gets the next number in its space, without gaps, even with concurrent uploads.
The device fetches everything after its last number, page by page, one transaction each. If the connection drops, nothing is lost.
A small scope keeps the service easy to understand and predictable. If your app needs one of these, tightrelay isn’t the right fit today.
No real-time pushDevices sync after changes, at launch and every few minutes. Too slow for chat, plenty for work data.
No field-level mergingWhen two devices change the same document, the later change wins as a whole.
No filesPhotos and PDFs don’t belong in the sync. A document is at most 64 KB.
No web clientBuilt for native apps with their own database, not for browsers.
The time tracker for freelancers syncs clients, projects and entries through tightrelay. A running timer shows up on the other devices right away.
Start a timer on two devices at once and a rule on the server stops the older one. Rules like this run in the same transaction as the upload.
Sign up in the console. Once your account is approved, create your app, define its collections and add the SDK.