# TimeRetain > Browser-based time tracker with JSON import/export. Canonical URL: https://timeretain.com ## Key pages - [Home / App](https://timeretain.com/) - [About](https://timeretain.com/about) - [Privacy](https://timeretain.com/privacy) - [What's new](https://timeretain.com/whats-new) ## JSON import format Produce a single JSON file matching the shape below and import it via **Data & Sync -> Import**. Rows are upserted by `id`, so re-importing is idempotent. ### Types - `Id` -- 22-character URL-safe Base64 string (alphabet `A-Z`, `a-z`, `0-9`, `-`, `_`; no padding) encoding 16 bytes. Generate randomly, or deterministically as the first 16 bytes of `SHA-256(utf8(externalId))` then URL-safe Base64 without padding. The deterministic form keeps foreign-key references stable across re-runs. - `Bool` -- `0` or `1` (never `false` / `true`). - Row timestamps (`interval.start`, `interval.end`) are Unix time in **milliseconds**. The envelope `timestamp` is an ISO-8601 string. All numbers must be finite. ### Envelope ```json { "version": "0.5", "timestamp": "", "checksum": "", "data": { "interval": [], "preferences": [], "stopwatch": [], "tag": [], "tagStopwatch": [] } } ``` All five keys inside `data` must be present (arrays may be empty) and appear in exactly this order: `interval, preferences, stopwatch, tag, tagStopwatch`. ### Schema ```ts interval: { id: Id; stopwatchId: Id; // -> stopwatch.id start: number; // Unix ms end: number | null; // Unix ms, or null if in-progress } preferences: { // exactly one row id: Id; useMilitaryTime: Bool; mergeOverlappingStopwatches: Bool; useAdjust: Bool; adjustDefault: number; // minutes; may be 0 or negative backupReminderIntervalInDays: number; currencyCode: string; // ISO 4217, e.g. "USD" earningsTrackingEnabled: Bool; defaultHourlyRate: number | null; weekStartsOn: number; // 0 = Sunday ... 6 = Saturday } stopwatch: { id: Id; description: string; adjustMinutes: number; // integer; may be negative isActive: Bool; isExpanded: Bool; hourlyRate: number | null; isHourlyRateManual: Bool | null; targetMinutes: number | null; } tag: { id: Id; name: string; hourlyRate: number | null; } tagStopwatch: { id: Id; tagId: Id; // -> tag.id stopwatchId: Id; // -> stopwatch.id } ``` ### Checksum `envelope.checksum` is the lowercase hex SHA-256 digest of `JSON.stringify(data)` (the inner `data` object, no pretty-printing). ### Example ```json { "version":"0.5", "timestamp":"2026-04-17T12:00:00.000Z", "checksum":"a2eaa40fdf3e6e67725bd7e040aa7999e4714302f65258223440e6c6ab176a34", "data":{ "interval":[ { "id":"YW9roj4Umr06HK2c_oGbRQ", "stopwatchId":"TJ3LRvQ2CG4JNG6IeMSKCQ", "start":1763884800000, "end":1763890200000 } ], "preferences":[ { "id":"mraKnXn4mDdyepbJHuq5Gg", "useMilitaryTime":0, "mergeOverlappingStopwatches":1, "useAdjust":0, "adjustDefault":0, "backupReminderIntervalInDays":7, "currencyCode":"USD", "earningsTrackingEnabled":1, "defaultHourlyRate":null, "weekStartsOn":1 } ], "stopwatch":[ { "id":"TJ3LRvQ2CG4JNG6IeMSKCQ", "description":"Write the report", "adjustMinutes":0, "isActive":0, "isExpanded":0, "hourlyRate":null, "isHourlyRateManual":0, "targetMinutes":null } ], "tag":[ { "id":"l-YnOENRtutJUAqR9DhS7Q", "name":"work", "hourlyRate":null } ], "tagStopwatch":[ { "id":"SP2mdxvegNECgyj6wf1SjA", "tagId":"l-YnOENRtutJUAqR9DhS7Q", "stopwatchId":"TJ3LRvQ2CG4JNG6IeMSKCQ" } ] } } ```