SYNCTHING-EVENT-API(7)

hiii bestie ๐Ÿ’– welcome to SYNCTHING-EVENT-API(7) from the Syncthing manual โ€” yes the whole thing is literally called the Event API and we're gonna get through it together, no stress ๐Ÿซถ

๐ŸŽ€ NAME ๐ŸŽ€

syncthing-event-api โ€” Event API โœจ


๐ŸŒธ DESCRIPTION ๐ŸŒธ

okay so here's the vibe: Syncthing ships a simple long polling interface that exposes events from the core utility out to a GUI ๐Ÿ’… that's it, that's the whole job.

and like โ€” to actually receive these events? you go look at GET /rest/events. that's your entry point, bestie ๐Ÿšชโœจ


๐Ÿฆ‹ EVENT STRUCTURE ๐Ÿฆ‹

every event is an object, and it looks a little something like this ๐Ÿ‘‡

{
    "id": 2,
    "globalID": 3,
    "type": "DeviceConnected",
    "time": "2014-07-13T21:04:33.687836696+02:00",
    "data": {
        "addr": "172.16.32.25:22000",
        "id": "NFGKEKE-7Z6RTH7-I3PRZXS-DEJF3UJ-FRWJBFO-VBBTDND-4SGNGVZ-QUQHJAG"
    }
}

the top level keys id, globalID, time, type and data are always present โ€” like, no cap, every single time ๐Ÿ™Œ โ€” though data may legitimately be null (it's giving "empty but valid", we respect it ๐Ÿ’€).

  • id โ€” a unique ID for this event on the events API. it always increases by 1: the first event generated has id 1, the next has id 2, etc. and important: if this increases by more than 1, then one or more events have been skipped by the events API ๐Ÿซฅ so yeah, watch for gaps.
  • globalID โ€” a global ID for this event, spanning across the events API, the audit log, and any other sources ๐ŸŒ it may increase by more than 1, but it will always be greater than or equal to the id (fr fr, that's guaranteed ๐Ÿ‘‘).
  • time โ€” the time the event was generated ๐Ÿ•ฐ๏ธ
  • type โ€” indicates the type of (i.e. the reason for) the event, and it's one of the event types below ๐Ÿท๏ธ
  • data โ€” an object containing optional extra information; the exact structure is determined by the event type ๐ŸŽ

โœจ EVENT TYPES โœจ

๐ŸŒˆ ClusterConfigReceived

Added in version 1.20.0. โญ

the ClusterConfigReceived event gets emitted after processing such a protocol message received from a remote device ๐Ÿ“ก it's mainly used for internal purposes (iykyk, not really your main character moment).

{
    "id": 84,
    "globalID": 84,
    "type": "ClusterConfigReceived",
    "time": "2022-04-27T14:14:27.043576583+09:00",
    "data": {
        "device": "I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"
    }
}

๐ŸŽ€ ConfigSaved

emitted after the config has been saved โ€” either by the user or by Syncthing itself ๐Ÿ’พโœจ

{
    "id": 50,
    "globalID": 50,
    "type": "ConfigSaved",
    "time": "2014-12-13T00:09:13.5166486Z",
    "data": {
        "version": 7,
        "folders": [{"..."}],
        "devices": [{"..."}],
        "gui": {"..."},
        "ldap": {"..."},
        "options": {"..."},
        "remoteIgnoredDevices": [{"..."}],
        "defaults": {"..."}
    }
}

๐Ÿ’œ DeviceConnected

generated each time a connection to a device has been established ๐Ÿคโœจ

{
    "id": 2,
    "globalID": 2,
    "type": "DeviceConnected",
    "time": "2014-07-13T21:04:33.687836696+02:00",
    "data": {
        "addr": "172.16.32.25:22000",
        "id": "NFGKEKE-7Z6RTH7-I3PRZXS-DEJF3UJ-FRWJBFO-VBBTDND-4SGNGVZ-QUQHJAG",
        "deviceName": "Laptop",
        "clientName": "syncthing",
        "clientVersion": "v0.13.4",
        "type": "TCP (Client)"
    }
}

๐Ÿ’” DeviceDisconnected

generated each time a connection to a device has been terminated ๐Ÿ˜ญ

{
    "id": 48,
    "globalID": 48,
    "type": "DeviceDisconnected",
    "time": "2014-07-13T21:18:52.859929215+02:00",
    "data": {
        "error": "unexpected EOF",
        "id": "NFGKEKE-7Z6RTH7-I3PRZXS-DEJF3UJ-FRWJBFO-VBBTDND-4SGNGVZ-QUQHJAG"
    }
}
NOTE: ๐Ÿšจ the error key contains the cause for disconnection โ€” which might not necessarily be an error as such. specifically, โ€œEOFโ€ and โ€œunexpected EOFโ€ both signify TCP connection termination, either because the other device restarted or went offline, or because of a network change ๐Ÿ”Œ so like, don't panic-fix things that aren't broken bestie ๐Ÿ’…

๐Ÿฆ‹ DeviceDiscovered

emitted when a new device is discovered using local discovery ๐Ÿ”โœจ

{
    "id": 13,
    "globalID": 13,
    "type": "DeviceDiscovered",
    "time": "2014-07-17T13:28:05.043465207+02:00",
    "data": {
        "addrs": [
            "172.16.32.25:22000"
        ],
        "device": "NFGKEKE-7Z6RTH7-I3PRZXS-DEJF3UJ-FRWJBFO-VBBTDND-4SGNGVZ-QUQHJAG"
    }
}

โธ๏ธ DevicePaused

emitted when a device was paused ๐Ÿ›‘

{
    "id": 13,
    "globalID": 13,
    "type": "DevicePaused",
    "time": "2014-07-17T13:28:05.043465207+02:00",
    "data": {
        "device": "NFGKEKE-7Z6RTH7-I3PRZXS-DEJF3UJ-FRWJBFO-VBBTDND-4SGNGVZ-QUQHJAG"
    }
}

๐Ÿ’€ DeviceRejected (DEPRECATED)

Deprecated since version v1.13.0: this event is still emitted for compatibility, but it's deprecated โœ‹ use the replacement PendingDevicesChanged event instead ๐Ÿ‘‘

emitted when there's a connection from a device we are not configured to talk to ๐Ÿ™ˆ (stranger danger, but make it networking)

{
    "id": 24,
    "globalID": 24,
    "type": "DeviceRejected",
    "time": "2014-08-19T10:43:00.562821045+02:00",
    "data": {
        "address": "127.0.0.1:51807",
        "name": "My dusty computer",
        "device": "EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK"
    }
}

โ–ถ๏ธ DeviceResumed

generated each time a device was resumed ๐ŸŒฑโœจ

{
    "id": 2,
    "globalID": 2,
    "type": "DeviceResumed",
    "time": "2014-07-13T21:04:33.687836696+02:00",
    "data": {
        "device": "NFGKEKE-7Z6RTH7-I3PRZXS-DEJF3UJ-FRWJBFO-VBBTDND-4SGNGVZ-QUQHJAG"
    }
}

๐Ÿ“ DownloadProgress

emitted during file downloads, for each folder and for each file ๐Ÿ“ฅ by default only a single file in a folder is handled at the same time, but custom configuration can cause multiple files to be shown (so if you see a whole crowd in there, that's your config talking ๐Ÿ‘€).

{
    "id": 221,
    "globalID": 221,
    "type": "DownloadProgress",
    "time": "2014-12-13T00:26:12.9876937Z",
    "data": {
        "folder1": {
            "file1": {
                "total": 800,
                "pulling": 2,
                "copiedFromOrigin": 0,
                "reused": 633,
                "copiedFromElsewhere": 0,
                "pulled": 38,
                "bytesTotal": 104792064,
                "bytesDone": 87883776
            },
            "dir\\file2": {
                "total": 80,
                "pulling": 2,
                "copiedFromOrigin": 0,
                "reused": 0,
                "copiedFromElsewhere": 0,
                "pulled": 32,
                "bytesTotal": 10420224,
                "bytesDone": 4128768
            }
        },
        "folder2": {
            "file3": {
                "total": 800,
                "pulling": 2,
                "copiedFromOrigin": 0,
                "reused": 633,
                "copiedFromElsewhere": 0,
                "pulled": 38,
                "bytesTotal": 104792064,
                "bytesDone": 87883776
            },
            "dir\\file4": {
                "total": 80,
                "pulling": 2,
                "copiedFromOrigin": 0,
                "reused": 0,
                "copiedFromElsewhere": 0,
                "pulled": 32,
                "bytesTotal": 10420224,
                "bytesDone": 4128768
            }
        }
    }
}

okay so here's what all those numbers actually mean ๐Ÿ‘‡

  • total โ€” total number of blocks in the file ๐Ÿงฑ
  • pulling โ€” number of blocks currently being downloaded ๐Ÿ“ถ
  • copiedFromOrigin โ€” number of blocks copied from the file we are about to replace โ™ป๏ธ
  • reused โ€” number of blocks reused from a previous temporary file ๐Ÿ”
  • copiedFromElsewhere โ€” number of blocks copied from other files or potentially other folders ๐Ÿ—‚๏ธ
  • pulled โ€” number of blocks actually downloaded so far โœ…
  • bytesTotal โ€” approximate total file size ๐Ÿ“
  • bytesDone โ€” approximate number of bytes already handled (already reused, copied or pulled) ๐Ÿ’ซ

where block size is 128KB ๐Ÿงฎ

files/folders appearing in the event data imply that the download has been started for that file/folder โ€” and them disappearing implies that the downloads have been finished or failed for that file/folder ๐Ÿ‘‹ and there is always a last event emitted with no data, which implies all downloads have finished/failed ๐Ÿโœจ

๐Ÿ”ฅ Failure

generated for specific errors that will also be sent to the usage reporting server, if enabled in the configuration ๐Ÿ“ฎ these are usually of special interest to the developers to troubleshoot complex errors ๐Ÿง‘โ€๐Ÿ’ป the data field contains a textual error message โœ๏ธ

{
  "id": 93,
  "globalID": 93,
  "type": "Failure",
  "time": "2021-06-07T21:22:03.414609034+02:00",
  "data": "index handler got paused while already paused"
}

๐ŸŒธ FolderCompletion

the FolderCompletion event is emitted when the local or remote contents for a folder changes ๐Ÿ”„ it contains the completion percentage for a given remote device, and it's emitted once per currently connected remote device ๐Ÿ’ป๐Ÿ’ป

Added in version 1.20.0: indication whether the remote device has accepted the folder (shares it with us) as well, and whether it is paused โธ๏ธ the remoteState field is unknown when the remote device is not connected ๐Ÿซฅ otherwise it can be either paused, notSharing, or valid if the remote is sharing back ๐Ÿซถ

{
    "id": 84,
    "globalID": 84,
    "type": "FolderCompletion",
    "time": "2022-04-27T14:14:27.043576583+09:00",
    "data": {
        "completion": 100,
        "device": "I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU",
        "folder": "default",
        "globalBytes": 17,
        "globalItems": 4,
        "needBytes": 0,
        "needDeletes": 0,
        "needItems": 0,
        "remoteState": "valid",
        "sequence": 12
    }
}

๐Ÿงš FolderErrors

the FolderErrors event is emitted when a folder cannot be successfully synchronized ๐Ÿ˜ญ the event contains the ID of the affected folder and a list of errors for files or directories therein ๐Ÿ“‹ this list of errors is obsolete once the folder changes state to syncing โ€” and if errors remain after the next synchronization attempt, a new FolderErrors event is emitted ๐Ÿ”โœจ

๐Ÿ“ FolderErrors

okay so the FolderErrors payload, she's giving nested-errors realness โ€” here's what she looks like:

{
    "id": 132,
    "type": "FolderErrors",
    "time": "2015-06-26T13:39:24.697401384+02:00",
    "data": {
        "errors": [
            {
                "error": "open /Users/jb/src/github.com/syncthing/syncthing/test/s2/h2j/.syncthing.aslkjd.tmp: permission denied",
                "path": "h2j/aslkjd"
            }
        ],
        "folder": "default"
    }
}

Added in version 0.11.12. โœจ

SEE ALSO: the StateChanged event, bestie โ€” that's your girl for the bigger picture ๐Ÿ’–


๐ŸŽ€ FolderPaused

Fires when the config changes around the "paused" state of a folder. And like, it's sent repeatedly for each changed folder โ€” so don't be like "why is this firing so much", that's literally the point ๐Ÿ’…

{
  "id": 93,
  "globalID": 93,
  "type": "FolderPaused",
  "time": "2021-06-07T23:45:03.414609034+02:00",
  "data": {
    "id": "abcde-fghij",
    "label": "My folder"
  }
}

๐Ÿ’€ FolderRejected (DEPRECATED)

Deprecated since version v1.13.0 โ€” she's still emitted for compatibility, but she's deprecated, periodt. Use the replacement PendingFoldersChanged event instead, iykyk ๐Ÿ™ˆ

She's emitted when a device sends index info for a folder we don't have, or do have but don't share with the device in question. Awkward much ๐Ÿ˜ญ

{
    "id": 27,
    "globalID": 27,
    "type": "FolderRejected",
    "time": "2014-08-19T10:41:06.761751399+02:00",
    "data": {
        "device": "EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK",
        "folder": "GXWxf-3zgnU",
        "folderLabel": "My Pictures"
    }
}

๐Ÿฆ‹ FolderResumed

Bestie this is the glow-up of FolderPaused โ€” generated when the config changes regarding the "paused" state of a folder, and yeah, sent repeatedly for each changed folder. Same energy, unpaused โœจ

{
  "id": 93,
  "globalID": 93,
  "type": "FolderResumed",
  "time": "2021-06-07T23:45:03.414609034+02:00",
  "data": {
    "id": "abcde-fghij",
    "label": "My folder"
  }
}

๐ŸŒˆ FolderScanProgress

Emitted in regular intervals (folder setting ProgressIntervalS, 2s by default โ€” yes bestie, two whole seconds) during scans. She gives you the amount of bytes already scanned and the bytes to be scanned in total, plus the current scanning rates in bytes per second. It's giving progress bar main character energy ๐Ÿ‘‘

{
   "data" : {
      "total" : 1,
      "rate" : 0,
      "current" : 0,
      "folder" : "bd7q3-zskm5"
   },
   "globalID" : 29,
   "type" : "FolderScanProgress",
   "time" : "2017-03-06T15:00:58.072004209+01:00",
   "id" : 29
}

๐Ÿ‘‘ FolderSummary

The FolderSummary event is emitted when folder contents have changed locally. Use it to calculate the current local completion state โ€” literally your "how done are we" vibe check ๐Ÿ”ฅ

{
    "id": 16,
    "type": "FolderSummary",
    "time": "2024-01-31T08:27:30.777875+01:00",
    "data": {
        "folder": "default",
        "summary": {
            "error": "",
            "errors": 0,
            "globalBytes": 4,
            "globalDeleted": 18664,
            "globalDirectories": 0,
            "globalFiles": 1,
            "globalSymlinks": 0,
            "globalTotalItems": 18665,
            "ignorePatterns": false,
            "inSyncBytes": 4,
            "inSyncFiles": 1,
            "invalid": "",
            "localBytes": 4,
            "localDeleted": 18664,
            "localDirectories": 0,
            "localFiles": 1,
            "localSymlinks": 0,
            "localTotalItems": 18665,
            "needBytes": 0,
            "needDeletes": 0,
            "needDirectories": 0,
            "needFiles": 0,
            "needSymlinks": 0,
            "needTotalItems": 0,
            "pullErrors": 0,
            "receiveOnlyChangedBytes": 0,
            "receiveOnlyChangedDeletes": 0,
            "receiveOnlyChangedDirectories": 0,
            "receiveOnlyChangedFiles": 0,
            "receiveOnlyChangedSymlinks": 0,
            "receiveOnlyTotalItems": 0,
            "remoteSequence": {
                "MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC": 37329
            },
            "sequence": 37329,
            "state": "idle",
            "stateChanged": "2024-01-31T08:27:24+01:00",
            "version": 37329,
            "watchError": ""
        }
    }
}

๐Ÿงš FolderWatchStateChanged

The FolderWatchStateChanged event pops off when a folder's watcher routine encounters a new error, or when a previous error disappeared after retrying. She contains the ID of the affected folder plus textual error messages describing the previous (from) and the updated (to) error conditions. And if there was no error in either of these? The respective field is just omitted โ€” she's discreet like that ๐Ÿ’…

{
  "id": 123,
  "type": "FolderWatchStateChanged",
  "time": "2022-03-14T12:34:56.890000000+01:00",
  "data": {
    "folder": "default",
    "from": "Something bad happened.",
    "to": "Something worse happened."
  }
}

๐ŸŒธ ItemFinished

Generated when Syncthing ends synchronizing a file to a newer version. Here's a successful operation, slay:

{
    "id": 93,
    "globalID": 93,
    "type": "ItemFinished",
    "time": "2014-07-13T21:22:03.414609034+02:00",
    "data": {
        "item": "test.txt",
        "folder": "default",
        "error": null,
        "type": "file",
        "action": "update"
    }
}

And an unsuccessful one, we've all been there ๐Ÿ˜ญ:

{
    "id": 44,
    "globalID": 44,
    "type": "ItemFinished",
    "time": "2015-05-27T11:21:05.711133004+02:00",
    "data": {
        "action": "update",
        "error": "open /Users/jb/src/github.com/syncthing/syncthing/test/s2/foo/.syncthing.hej.tmp: permission denied",
        "folder": "default",
        "item": "foo/hej",
        "type": "file"
    }
}

The action field is either update (contents changed), metadata (file metadata changed but not contents), or delete. No cap ๐Ÿซถ

Added in version 0.11.10: the metadata action.


๐ŸŽ€ ItemStarted

Generated when Syncthing begins synchronizing a file to a newer version. The prequel โœจ

{
    "id": 93,
    "globalID": 93,
    "type": "ItemStarted",
    "time": "2014-07-13T21:22:03.414609034+02:00",
    "data": {
        "item": "test.txt",
        "folder": "default",
        "type": "file",
        "action": "update"
    }
}

Same deal: the action field is either update (contents changed), metadata (file metadata changed but not contents), or delete.

Added in version 0.11.10: the metadata action.


๐Ÿ’œ ListenAddressesChanged

This event is emitted when a listen address changes. She's got a whole URL-parsing situation going on, look:

{
   "type" : "ListenAddressesChanged",
   "id" : 70,
   "time" : "2017-03-06T15:01:24.88340663+01:00",
   "globalID" : 70,
   "data" : {
      "address" : {
         "Fragment" : "",
         "RawQuery" : "",
         "Scheme" : "dynamic+https",
         "Path" : "/endpoint",
         "RawPath" : "",
         "User" : null,
         "ForceQuery" : false,
         "Host" : "relays.syncthing.net",
         "Opaque" : ""
      },
      "wan" : [
         {
            "ForceQuery" : false,
            "User" : null,
            "Host" : "31.15.66.212:443",
            "Opaque" : "",
            "Path" : "/",
            "RawPath" : "",
            "RawQuery" : "id=F4HSJVO-CP2C3IL-YLQYLSU-XTYODAG-PPU4LGV-PH3MU4N-G6K56DV-IPN47A&pingInterval=1m0s&networkTimeout=2m0s&sessionLimitBps=0&globalLimitBps=0&statusAddr=:22070&providedBy=",
            "Scheme" : "relay",
            "Fragment" : ""
         }
      ],
      "lan" : [
         {
            "RawQuery" : "id=F4HSJVO-CP2C3IL-YLQYLSU-XTYODAG-PPU4LGV-PH3MU4N-G6K56DV-IPN47A&pingInterval=1m0s&networkTimeout=2m0s&sessionLimitBps=0&globalLimitBps=0&statusAddr=:22070&providedBy=",
            "Scheme" : "relay",
            "Fragment" : "",
            "RawPath" : "",
            "Path" : "/",
            "Host" : "31.15.66.212:443",
            "Opaque" : "",
            "ForceQuery" : false,
            "User" : null
         }
      ]
   }
}

(yes the field order in the JSON is different in wan and lan, no she will not be fixing that, don't come for her ๐Ÿ’€)


๐Ÿ“ LocalChangeDetected

Generated upon scan whenever the local disk has discovered an updated file from the previous scan. And listen โ€” this does not include events that are discovered and copied from other devices (RemoteChangeDetected). Only files that were changed on the local filesystem, okay? Local only, homegrown, main character ๐Ÿ’…

NOTE: this event is not included in GET /rest/events endpoint without a mask specified โ€” you gotta select it explicitly, bestie. She doesn't just show up uninvited ๐Ÿ™ˆ
{
  "id": 7,
  "globalID": 59,
  "time": "2016-09-26T22:07:10.7189141-04:00",
  "type": "LocalChangeDetected",
  "data": {
    "action": "deleted",
    "folder": "vitwy-zjxqt",
    "folderID": "vitwy-zjxqt",
    "label": "TestSync",
    "path": "test file.rtf",
    "type": "file"
  }
}

Deprecated since version v1.1.2: the folderID field is a legacy name kept only for compatibility. Use the folder field with identical content instead, mkay? ๐Ÿซถ


โญ LocalIndexUpdated

Generated when the local index information has changed โ€” due to synchronizing one or more items from the cluster, or discovering local changes during a scan. Busy girl behaviour ๐Ÿ‘‘

{
    "id": 59,
    "globalID": 59,
    "type": "LocalIndexUpdated",
    "time": "2014-07-17T13:27:28.051369434+02:00",
    "data": {
        "folder": "default",
        "items": 1000,
        "filenames": [
            "foo",
            "bar",
            "baz"
        ],
        "sequence": 12345,
        "version": 12345
    }
}

Deprecated since version v1.10.0: the version field is a legacy name kept only for compatibility. Use the sequence field with identical content instead. Same content, different name, we love a rebrand โœจ


๐Ÿ”ฅ LoginAttempt

When authentication is enabled for the GUI, this event is emitted on every login attempt. And if either the username or password are incorrect, success is false โ€” and in any case the given username is returned. So yeah, she's watching ๐Ÿ‘€๐Ÿ’€

{
   "id" : 187,
   "time" : "2017-03-07T00:19:24.420386143+01:00",
   "data" : {
      "remoteAddress" : "127.0.0.1",
      "username" : "somename",
      "success" : false
   },
   "type" : "LoginAttempt",
   "globalID" : 195
}

But wait, bestie, there's a plot twist ๐Ÿซถ โ€” if the X-Forwared-For header is present and the connecting host is either on localhost or on the same LAN, then it gets treated as a reverse proxy. In that case the remoteAddress field gets filled with the leftmost IP address from the header, and the extra proxy field keeps the original IP of the connecting host. Two fields, two truths, no cap.

{
   "id" : 187,
   "time" : "2017-03-07T00:19:24.420386143+01:00",
   "data" : {
      "proxy" : "127.0.0.1",
      "remoteAddress" : "192.168.178.10",
      "username" : "somename",
      "success" : false
   },
   "type" : "LoginAttempt",
   "globalID" : 195
}

๐ŸŽ€ PendingDevicesChanged ๐ŸŽ€

Added in version 1.14.0.

This one drops when pending devices get added / updated (that's a connection from an unknown ID, bestie ๐Ÿ‘€) or removed (the device is ignored, dismissed or added). Literally the "who is this??" notification of the Syncthing world.

{
  "id": 87,
  "type": "PendingDevicesChanged",
  "time": "2020-12-22T22:24:37.578586718+01:00",
  "data": {
    "added": [
      {
        "address": "127.0.0.1:51807",
        "deviceID": "EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK",
        "name": "My dusty computer"
      }
    ],
    "removed": [
      {
        "deviceID": "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"
      }
    ]
  }
}

๐ŸŒธ PendingFoldersChanged ๐ŸŒธ

Added in version 1.14.0.

Emitted when pending folders were added / updated โ€” meaning some device offered them but they're not shared to them โ€” or removed (the folder got ignored, dismissed or added, or it's just no longer offered from the remote device). Big heads up ๐Ÿ’…: a removed entry without a deviceID attribute means the folder is no longer pending for any device. So no deviceID = nobody wants it, periodt.

{
  "id": 101,
  "type": "PendingFoldersChanged",
  "time": "2020-12-22T22:36:55.66744317+01:00",
  "data": {
    "added": [
      {
        "deviceID": "EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK",
        "folderID": "GXWxf-3zgnU",
        "folderLabel": "My Pictures"
        "receiveEncrypted": "false"
        "remoteEncrypted": "false"
      }
    ],
    "removed": [
      {
        "deviceID": "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2",
        "folderID": "neyfh-sa2nu"
      },
      {
        "folderID": "abcde-fghij"
      }
    ]
  }
}

๐Ÿฆ‹ RemoteChangeDetected ๐Ÿฆ‹

Generated upon scan whenever a file is locally updated because of a remote change. Files that get updated locally produce a LocalChangeDetected event instead โ€” so like, know the difference, iykyk.

NOTE: This event is not included in the GET /rest/events endpoint without a mask specified โ€” you have to select it explicitly, bestie. It's not gonna show up just because you want it to ๐Ÿ˜ญ
{
   "time" : "2017-03-06T23:58:21.844739891+01:00",
   "globalID" : 123,
   "data" : {
      "type" : "file",
      "action" : "deleted",
      "folder": "Dokumente",
      "folderID" : "Dokumente",
      "path" : "testfile",
      "label" : "Dokumente",
      "modifiedBy" : "BPDFDTU"
   },
   "type" : "RemoteChangeDetected",
   "id" : 2
}
Deprecated since version v1.1.2: the folderID field is a legacy name kept only for compatibility. Use the folder field instead โ€” identical content, so swap it and slay ๐Ÿซถ

๐Ÿ’œ RemoteDownloadProgress ๐Ÿ’œ

This event is emitted when a DownloadProgress message is received. It returns a data map of filenames with a count of downloaded blocks. The files in question are currently being downloaded on the remote device and belong to folder. It's giving "progress bar but make it JSON" โญ

{
   "time" : "2017-03-07T00:11:37.65838955+01:00",
   "globalID" : 170,
   "data" : {
      "state" : {
         "tahr64-6.0.5.iso" : 1784
      },
      "device" : "F4HSJVO-CP2C3IL-YLQYLSU-XTYODAG-PPU4LGV-PH3MU4N-G6K56DV-IPN47A",
      "folder" : "Dokumente"
   },
   "type" : "RemoteDownloadProgress",
   "id" : 163
}

๐ŸŒˆ RemoteIndexUpdated ๐ŸŒˆ

Generated each and every time new index information arrives from a device. Every time. No skipping ๐Ÿ‘‘

{
    "id": 44,
    "globalID": 44,
    "type": "RemoteIndexUpdated",
    "time": "2014-07-13T21:04:35.394184435+02:00",
    "data": {
        "device": "NFGKEKE-7Z6RTH7-I3PRZXS-DEJF3UJ-FRWJBFO-VBBTDND-4SGNGVZ-QUQHJAG",
        "folder": "lightroom",
        "items": 1000
    }
}

โœจ Starting โœจ

Emitted exactly once, when Syncthing starts โ€” and like, before parsing configuration etc. So it's the very first thing awake in the morning, before coffee โ˜•

{
    "id": 1,
    "globalID": 1,
    "type": "Starting",
    "time": "2014-07-17T13:13:32.044470055+02:00",
    "data": {
        "home": "/home/jb/.config/syncthing"
    }
}

๐Ÿงš StartupComplete ๐Ÿงš

Emitted exactly once too, when initialization is complete and Syncthing is ready to start exchanging data with other devices. That's the "okay I'm dressed, let's go" moment ๐Ÿ’…

{
    "id": 1,
    "globalID": 1,
    "type": "StartupComplete",
    "time": "2014-07-13T21:03:18.383239179+02:00",
    "data": null
}

๐Ÿ’– StateChanged ๐Ÿ’–

Emitted when a folder changes state. The possible states are idle, scanning, syncing and error. The field duration is the number of seconds the folder spent in the state named by from. In the example down below, the folder default was in state scanning for 0.198 seconds and is now chilling in state idle. Character development, honestly ๐Ÿ˜Œ

{
    "id": 8,
    "globalID": 8,
    "type": "StateChanged",
    "time": "2014-07-17T13:14:28.697493016+02:00",
    "data": {
        "folder": "default",
        "from": "scanning",
        "duration": 0.19782869900000002,
        "to": "idle"
    }
}

๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

The Syncthing Authors โ€” yes, all of them, a whole squad ๐Ÿซถ

2014-2026, The Syncthing Authors


And that's the whole page, bestie, we ate and left absolutely no crumbs ๐ŸŒธ This was SYNCTHING-EVENT-API(7), version v2.1.0, dated Oct 07, 2026 โ€” go forth and stream those events responsibly ๐Ÿ’–โœจ

๐Ÿ“ฆ where this page comes from