SYNCTHING-REST-API(7)

๐Ÿ’… syncthing-rest-api โ€” NAME ๐Ÿ’…

Okay bestie, welcome to the Syncthing REST API page โ€” this is the official SYNCTHING-REST-API(7) manual, Syncthing flavour, and it's lowkey a whole vibe โœจ

So here's the tea: Syncthing exposes a REST interface over HTTP on the GUI port. That's what the GUI itself uses (from Javascript, periodt), and it's also what any other process can use if they wanna control Syncthing from the outside ๐Ÿ‘‘. In most cases both the input and output data is in JSON format. And the interface is subject to change โ€” no cap, that's literally in the docs, so don't get too attached ๐Ÿ™ˆ


๐Ÿ”‘ API KEY ๐Ÿ”‘

Okay so you cannot just waltz in here. To use the REST API an API key must be set and used. Where do you get one?

  • You can generate it in the GUI, or
  • set it in the configuration/gui/apikey element in the configuration file ๐ŸŽ€

And then how do you actually present it? Two ways, both iconic:

  • set the request header X-API-Key to the API key value, or
  • set it as a Bearer token in the Authorization header ๐Ÿ’…

For example, these work with curl:

curl -X POST -H "X-API-Key: abc123" http://localhost:8384/rest/...
curl -X POST -H "Authorization: Bearer abc123" http://localhost:8384/rest/...
๐Ÿซถ psst โ€” add the -k flag when using HTTPS with a Syncthing generated or self signed certificate. Your girl is not letting a cert warning ruin the moment.

One exception to this requirement is /rest/noauth โ€” you do not need an API key to use those endpoints. Why? Because this way third-party devices and services can do simple calls that don't expose sensitive information without having to expose your API key. It's giving respectful boundaries, honestly ๐Ÿ’–


๐Ÿ“„ RESULT PAGINATION ๐Ÿ“„

Some GET endpoints take optional page and perpage arguments for pagination. Here's how it goes:

  • No more than perpage result entries are returned in an array โ€” and perpage defaults to 65536 if not given ๐Ÿ˜ญ (yes, big number, we love her).
  • To access further entries, passing the page parameter will advance in the results by that many pages.
  • The actually used parameters are always returned as attributes in the response object โ€” so you can check what the server actually did, which is honestly so transparent of it ๐ŸŒธ

๐Ÿ› ๏ธ SYSTEM ENDPOINTS ๐Ÿ› ๏ธ

GET /rest/system/browse

Returns a list of directories matching the path given by the optional parameter current.

The path can use patterns as described in Go's filepath package: https://pkg.go.dev/path/filepath#Match. And heads up โ€” a * will always be appended to the given path, so like /tmp/ matches all its subdirectories. Sneaky but efficient ๐Ÿ’…

If the option current is not given, filesystem root paths are returned.

Watch her work โœจ

$ curl -H "X-API-Key: yourkey" localhost:8384/rest/system/browse | json_pp
[
    "/"
]
$ curl -H "X-API-Key: yourkey" localhost:8384/rest/system/browse?current=/var/ | json_pp
[
    "/var/backups/",
    "/var/cache/",
    "/var/lib/",
    "/var/local/",
    "/var/lock/",
    "/var/log/",
    "/var/mail/",
    "/var/opt/",
    "/var/run/",
    "/var/spool/",
    "/var/tmp/"
]
$ curl -H "X-API-Key: yourkey" localhost:8384/rest/system/browse?current=/var/*o | json_pp
[
    "/var/local/",
    "/var/lock/",
    "/var/log/",
    "/var/opt/",
    "/var/spool/"
]

GET /rest/system/config (DEPRECATED)

๐Ÿ’€ DEPRECATED ALERT ๐Ÿ’€ Deprecated since version v1.12.0: This endpoint still works as before but is deprecated. Use /rest/config instead. She's retired but still showing up to the party, we love a legacy queen ๐Ÿ‘‘

Returns the current configuration.

{
  "version": 35,
  "folders": [
    {
      "id": "default",
      "label": "Default Folder",
      "filesystemType": "basic",
      "path": "...",
      "type": "sendreceive",
      "devices": [
        {
          "deviceID": "...",
          "introducedBy": "",
          "encryptionPassword": ""
        }
      ],
      "rescanIntervalS": 3600,
      "fsWatcherEnabled": true,
      "fsWatcherDelayS": 10,
      "ignorePerms": false,
      "autoNormalize": true,
      "minDiskFree": {
        "value": 1,
        "unit": "%"
      },
      "versioning": {
        "type": "",
        "params": {},
        "cleanupIntervalS": 3600,
        "fsPath": "",
        "fsType": "basic"
      },
      "copiers": 0,
      "pullerMaxPendingKiB": 0,
      "hashers": 0,
      "order": "random",
      "ignoreDelete": false,
      "scanProgressIntervalS": 0,
      "pullerPauseS": 0,
      "maxConflicts": -1,
      "disableSparseFiles": false,
      "paused": false,
      "markerName": ".stfolder",
      "copyOwnershipFromParent": false,
      "modTimeWindowS": 0,
      "maxConcurrentWrites": 16,
      "disableFsync": false,
      "blockPullOrder": "standard",
      "copyRangeMethod": "standard",
      "caseSensitiveFS": false,
      "junctionsAsDirs": true
    }
  ],
  "devices": [
    {
      "deviceID": "...",
      "name": "Laptop",
      "addresses": [
        "dynamic",
        "tcp://192.168.1.2:22000"
      ],
      "compression": "metadata",
      "certName": "",
      "introducer": false,
      "skipIntroductionRemovals": false,
      "introducedBy": "",
      "paused": false,
      "allowedNetworks": [],
      "autoAcceptFolders": false,
      "maxSendKbps": 0,
      "maxRecvKbps": 0,
      "ignoredFolders": [
        {
          "time": "2022-01-09T19:09:52Z",
          "id": "br63e-wyhb7",
          "label": "Foo"
        }
      ],
      "maxRequestKiB": 0,
      "untrusted": false,
      "remoteGUIPort": 0
    }
  ],
  "gui": {
    "enabled": true,
    "address": "127.0.0.1:8384",
    "unixSocketPermissions": "",
    "user": "Username",
    "password": "$2a$10$ZFws69T4FlvWwsqeIwL.TOo5zOYqsa/.TxlUnsGYS.j3JvjFTmxo6",
    "authMode": "static",
    "useTLS": false,
    "apiKey": "k1dnz1Dd0rzTBjjFFh7CXPnrF12C49B1",
    "insecureAdminAccess": false,
    "theme": "default",
    "insecureSkipHostcheck": false,
    "insecureAllowFrameLoading": false
  },
  "ldap": {
    "address": "",
    "bindDN": "",
    "transport": "plain",
    "insecureSkipVerify": false,
    "searchBaseDN": "",
    "searchFilter": ""
  },
  "options": {
    "listenAddresses": [
      "default"
    ],
    "globalAnnounceServers": [
      "default"
    ],
    "globalAnnounceEnabled": true,
    "localAnnounceEnabled": true,
    "localAnnouncePort": 21027,
    "localAnnounceMCAddr": "[ff12::8384]:21027",
    "maxSendKbps": 0,
    "maxRecvKbps": 0,
    "reconnectionIntervalS": 60,
    "relaysEnabled": true,
    "relayReconnectIntervalM": 10,
    "startBrowser": true,
    "natEnabled": true,
    "natLeaseMinutes": 60,
    "natRenewalMinutes": 30,
    "natTimeoutSeconds": 10,
    "urAccepted": 0,
    "urSeen": 0,
    "urUniqueId": "...",
    "urURL": "https://data.syncthing.net/newdata",
    "urPostInsecurely": false,
    "urInitialDelayS": 1800,
    "autoUpgradeIntervalH": 12,
    "upgradeToPreReleases": false,
    "keepTemporariesH": 24,
    "progressUpdateIntervalS": 5,
    "limitBandwidthInLan": false,
    "minHomeDiskFree": {
      "value": 1,
      "unit": "%"
    },
    "releasesURL": "https://upgrades.syncthing.net/meta.json",
    "alwaysLocalNets": [],
    "overwriteRemoteDeviceNamesOnConnect": false,
    "tempIndexMinBlocks": 10,
    "unackedNotificationIDs": [
      "authenticationUserAndPassword"
    ],
    "trafficClass": 0,
    "setLowPriority": true,
    "maxFolderConcurrency": 0,
    "crURL": "https://crash.syncthing.net/newcrash",
    "crashReportingEnabled": true,
    "stunKeepaliveStartS": 180,
    "stunKeepaliveMinS": 20,
    "stunServers": [
      "default"
    ],
    "maxConcurrentIncomingRequestKiB": 0,
    "announceLANAddresses": true,
    "sendFullIndexOnUpgrade": false,
    "featureFlags": [],
    "connectionLimitEnough": 0,
    "connectionLimitMax": 0
  },
  "remoteIgnoredDevices": [
    {
      "time": "2022-01-09T20:02:01Z",
      "deviceID": "...",
      "name": "...",
      "address": "192.168.0.20:22000"
    }
  ],
  "defaults": {
    "folder": {
      "id": "",
      "label": "",
      "filesystemType": "basic",
      "path": "~",
      "type": "sendreceive",
      "devices": [
        {
          "deviceID": "...",
          "introducedBy": "",
          "encryptionPassword": ""
        }
      ],
      "rescanIntervalS": 3600,
      "fsWatcherEnabled": true,
      "fsWatcherDelayS": 10,
      "ignorePerms": false,
      "autoNormalize": true,
      "minDiskFree": {
        "value": 1,
        "unit": "%"
      },
      "versioning": {
        "type": "",
        "params": {},
        "cleanupIntervalS": 3600,
        "fsPath": "",
        "fsType": "basic"
      },
      "copiers": 0,
      "pullerMaxPendingKiB": 0,
      "hashers": 0,
      "order": "random",
      "ignoreDelete": false,
      "scanProgressIntervalS": 0,
      "pullerPauseS": 0,
      "maxConflicts": 10,
      "disableSparseFiles": false,
      "paused": false,
      "markerName": ".stfolder",
      "copyOwnershipFromParent": false,
      "modTimeWindowS": 0,
      "maxConcurrentWrites": 16,
      "disableFsync": false,
      "blockPullOrder": "standard",
      "copyRangeMethod": "standard",
      "caseSensitiveFS": false,
      "junctionsAsDirs": false
    },
    "device": {
      "deviceID": "",
      "name": "",
      "addresses": [
        "dynamic"
      ],
      "compression": "metadata",
      "certName": "",
      "introducer": false,
      "skipIntroductionRemovals": false,
      "introducedBy": "",
      "paused": false,
      "allowedNetworks": [],
      "autoAcceptFolders": false,
      "maxSendKbps": 0,
      "maxRecvKbps": 0,
      "ignoredFolders": [],
      "maxRequestKiB": 0,
      "untrusted": false,
      "remoteGUIPort": 0
    }
  }
}

And that's where part one wraps โ€” but bestie, we are so not done, more endpoints are coming ๐Ÿฆ‹

GET /rest/system/config/insync โ€” DEPRECATED ๐Ÿ’€

Deprecated since version v1.12.0: this endpoint still works exactly as before, but it's deprecated, bestie. Use /rest/config/restart-required instead. โœจ

Tells you whether the config is in sync โ€” like, is the config that's actually running the same one that's sitting on disk? ๐Ÿ‘€

{
  "configInSync": true
}

POST /rest/system/config โ€” DEPRECATED ๐Ÿ™ˆ

Deprecated since version v1.12.0: still works as before, but deprecated. Use Config Endpoints instead, periodt.

You post the full contents of the configuration here, in the same format that the matching GET request gives you back. When the post goes through, the config you posted gets applied immediately โ€” except for the changes that need a restart. To check if a restart is needed, hit /rest/config/restart-required. ๐Ÿ”

This endpoint is literally the main character when it comes to controlling Syncthing โ€” even if the change is only about a tiny lil part of the config. The usual workflow is: get the config, change the parts you need, post it right back. That's the whole vibe. ๐Ÿ’…


GET /rest/system/connections ๐Ÿ”Œ

NOTE: the return format changed in versions 0.13.0, 0.14.14, 1.2.0, 1.19.0, 1.23.0 and 1.25.0. So if you're on an old version, don't come at me if it looks different. ๐Ÿ˜ญ

Returns the list of configured devices plus some metadata that goes with them. ๐Ÿซถ

The connection types are tcp-client, tcp-server, relay-client, relay-server, quic-client and quic-server. Slay.

{
  "connections": {
    "DOVII4U-SQEEESM-VZ2CVTC-CJM4YN5-QNV7DCU-5U3ASRL-YVFG6TH-W5DV5AA": {
      "address": "",
      "at": "0001-01-01T00:00:00Z",
      "clientVersion": "",
      "connected": false,
      "inBytesTotal": 0,
      "isLocal": false,
      "outBytesTotal": 0,
      "paused": false,
      "startedAt": "0001-01-01T00:00:00Z",
      "type": ""
    },
    "UYGDMA4-TPHOFO5-2VQYDCC-7CWX7XW-INZINQT-LE4B42N-4JUZTSM-IWCSXA4": {
      "address": "",
      "at": "0001-01-01T00:00:00Z",
      "clientVersion": "",
      "connected": false,
      "inBytesTotal": 0,
      "isLocal": false,
      "outBytesTotal": 0,
      "paused": false,
      "startedAt": "0001-01-01T00:00:00Z",
      "type": ""
    },
    "YZJBJFX-RDBL7WY-6ZGKJ2D-4MJB4E7-ZATSDUY-LD6Y3L3-MLFUYWE-AEMXJAC": {
      "address": "127.0.0.1:22002",
      "at": "2015-11-07T17:29:47.691548971+01:00",
      "clientVersion": "v0.12.1",
      "connected": true,
      "inBytesTotal": 556,
      "isLocal": true,
      "outBytesTotal": 550,
      "paused": false,
      "startedAt": "2015-11-07T00:09:47Z",
      "type": "tcp-client"
    }
  },
  "total": {
    "at": "2015-11-07T17:29:47.691637262+01:00",
    "inBytesTotal": 1479,
    "outBytesTotal": 1318
  }
}

GET /rest/system/discovery ๐Ÿงญ

Hands you back the contents of the local discovery cache. Cute, right? ๐ŸŒธ

{
  "LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q": [
    "192.162.129.11:22000"
  ]
}

POST /rest/system/discovery โ€” REMOVED ๐Ÿ’€

NOTE: removed in v0.12.0. Gone gone, bestie. ๐Ÿ˜ญ

You'd post with the query parameters device and addr to add entries into the discovery cache. Here's what that used to look like for the historians:

curl -X POST http://127.0.0.1:8384/rest/system/discovery?device=LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q\&addr=192.162.129.11:22000
# Or with the X-API-Key header:
curl -X POST --header "X-API-Key: TcE28kVPdtJ8COws1JdM0b2nodj77WeQ" http://127.0.0.1:8384/rest/system/discovery?device=LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q\&addr=192.162.129.11:22000

POST /rest/system/error/clear ๐Ÿงน

Post with an empty body to yeet all the recent errors. Clean slate energy. โœจ


GET /rest/system/error ๐Ÿ˜ญ

NOTE: the return format changed in 0.12.0.

Returns the list of recent errors. Look at them. Look at what you did. ๐Ÿ™ˆ

{
  "errors": [
    {
      "when": "2014-09-18T12:59:26.549953186+02:00",
      "message": "This is an error string"
    }
  ]
}

POST /rest/system/error ๐Ÿ“ฎ

Post with an error message in the body (plain text, no JSON dress-up) to register a new error. That new error gets shown on any active GUI clients โ€” so yeah, everyone's gonna see it. ๐Ÿ‘€


GET /rest/system/log ๐Ÿ“œ

Added in version 0.12.0.

Returns the list of recent log entries. The optional since parameter limits the results to messages newer than the given timestamp in RFC 3339 format. No cap.

{
  "messages": [
    {
      "when": "2014-09-18T12:59:26.549953186+02:00",
      "message": "This is a log entry"
    }
  ]
}

GET /rest/system/log.txt ๐Ÿ“

Same info as above, but formatted as a text log instead of a JSON object. For the plain-text girlies. ๐ŸŽ€


GET /rest/system/loglevels ๐ŸŽš๏ธ

Added in version 2.0.0.

Returns the set of log facilities and their current log level. A whole vibe check for your logging. โญ

{
  "levels": {
    "api": "INFO",
    "beacon": "INFO",
    ...
    "versioner": "INFO",
    "watchaggregator": "INFO"
  },
  "packages": {
    "api": "REST API",
    "beacon": "Multicast and broadcast discovery",
    ...
    "versioner": "File versioning",
    "watchaggregator": "Filesystem event watcher"
  }
}

POST /rest/system/loglevels ๐Ÿ› ๏ธ

Added in version 2.0.0.

Changes the log level for the facilities you specify. Post an object with the log facilities as keys and the desired log level as the values โ€” the levels are DEBUG, INFO, WARN, or ERROR. Go off. ๐Ÿ”ฅ

$ curl -H X-API-Key:abc123 -d '{"beacon":"DEBUG","discovery":"WARN"}' 'http://localhost:8384/rest/system/loglevels'

GET /rest/system/paths ๐Ÿ—‚๏ธ

Added in version 1.21.0.

Returns the path locations that get used internally for storing configuration, database, and other stuff. It's giving "peek behind the curtain". ๐Ÿฆ‹

{
  "auditLog": "/home/user/.local/share/syncthing/audit-${timestamp}.log",
  "baseDir-config": "/home/user/.config/syncthing",
  "baseDir-data": "/home/user/.local/share/syncthing",
  "baseDir-userHome": "/home/user",
  "certFile": "/home/user/.config/syncthing/cert.pem",
  "config": "/home/user/.config/syncthing/config.xml",
  "csrfTokens": "/home/user/.config/syncthing/csrftokens.txt",
  "database": "/home/user/.local/share/syncthing/index-v2",
  "defFolder": "/home/user/Sync",
  "guiAssets": "/home/user/src/syncthing/gui",
  "httpsCertFile": "/home/user/.config/syncthing/https-cert.pem",
  "httpsKeyFile": "/home/user/.config/syncthing/https-key.pem",
  "keyFile": "/home/user/.config/syncthing/key.pem",
  "logFile": "-",
  "panicLog": "/home/user/.local/share/syncthing/panic-${timestamp}.log"
}

POST /rest/system/pause โธ๏ธ

Pauses the device you name, or all the devices.

It takes the optional parameter device (that's a device ID). When you omit it, it pauses all the devices. Returns status 200 and no content on success, or status 500 and a plain text error if it fails. ๐Ÿ’…


GET /rest/system/ping ๐Ÿ“

Returns a {"ping": "pong"} object. That's it. That's the whole endpoint. Iconic, honestly.

{
  "ping": "pong"
}

POST /rest/system/ping ๐Ÿ“

Returns a {"ping": "pong"} object. Yes, the POST version does the same thing. Yes, it's still iconic. ๐Ÿ‘‘


POST /rest/system/reset ๐Ÿงจ

Post with an empty body to erase the current index database and restart Syncthing. With no query parameters, the entire database gets erased from disk. Like, gone.

If you specify the folder parameter with a valid folder ID, only the information for that folder gets erased:

curl -X POST -H "X-API-Key: abc123" http://localhost:8384/rest/system/reset?folder=ab1c2-def3g

It also creates .stfolder folders inside each sync folder, if they don't already exist. ๐ŸŽ€

CAUTION: make sure all sync folders that are mountpoints are already mounted before you do this. If a mountpoint gets mounted later and it contains older versions, you can end up with inconsistent versions. Be careful, bestie โ€” this one bites. ๐Ÿ˜ญ

POST /rest/system/restart ๐Ÿ”„

Post with an empty body to immediately restart Syncthing. Right now. No thoughts, head empty.


POST /rest/system/resume โ–ถ๏ธ

Resumes the device you name, or all the devices.

Takes the optional parameter device (device ID). When omitted, it resumes all the devices. Returns status 200 and no content on success, or status 500 and a plain text error on failure. Back online, slay. ๐Ÿ’–


POST /rest/system/shutdown ๐Ÿ›‘

Post with an empty body to make Syncthing exit โ€” and it does not restart. It's really leaving. ๐ŸŒ™


GET /rest/system/status ๐Ÿ“Š

Returns information about the current system status and resource usage. Heads up though: the CPU percent value has been deprecated from the API and will always report 0. So don't trust it, it's not even trying anymore. ๐Ÿ’€

          {
            "alloc": 30618136,
            "connectionServiceStatus": {
              "dynamic+https://relays.syncthing.net/endpoint": {
                "error": null,
                "lanAddresses": [
                  "relay://23.92.71.120:443/?id=53STGR7-YBM6FCX-PAZ2RHM-YPY6OEJ-WYHVZO7-PCKQRCK-PZLTP7T-434XCAD&pingInterval=1m0s&networkTimeout=2m0s&sessionLimitBps=0&globalLimitBps=0&statusAddr=:22070&providedBy=canton7"
                ],
                "wanAddresses": [
                  "relay://23.92.71.120:443/?id=53STGR7-YBM6FCX-PAZ2RHM-YPY6OEJ-WYHVZO7-PCKQRCK-PZLTP7T-434XCAD&pingInterval=1m0s&networkTimeout=2m0s&sessionLimitBps=0&globalLimitBps=0&statusAddr=:22070&providedBy=canton7"
                ]
              },
              "tcp://0.0.0.0:22000": {
                "error": null,
                "lanAddresses": [
                  "tcp://0.0.0.0:22000"
                ],
                "wanAddresses": [
                  "tcp://0.0.0.0:22000"
                ]
              }
            },
            "cpuPercent": 0,
            "discoveryEnabled": true,
            "discoveryErrors": {
              "global@https://discovery-v4-1.syncthing.net/v2/": "500 Internal Server Error",
              "global@https://discovery-v4-2.syncthing.net/v2/": "Post https://discovery-v4-2.syncthing.net/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)",
              "global@https://discovery-v4-3.syncthing.net/v2/": "Post https://discovery-v4-3.syncthing.net/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)",
              "global@https://discovery-v6-1.syncthing.net/v2/": "Post https://discovery-v6-1.syncthing.net/v2/: dial tcp [2001:470:28:4d6::5]:443: connect: no route to host",
              "global@https://discovery-v6-2.syncthing.net/v2/": "Post https://discovery-v6-2.syncthing.net/v2/: dial tcp [2604:a880:800:10::182:a001]:443: connect: no route to host",
              "global@https://discovery-v6-3.syncthing.net/v2/": "Post https://discovery-v6-3.syncthing.net/v2/: dial tcp [2400:6180:0:d0::d9:d001]:443: connect: no route to host"
            },
            "discoveryStatus": {
              "IPv4 local": {
                "error": null
              },
              "IPv6 local": {
                "error": null
              },
              "global@https://discovery-v4-1.syncthing.net/v2/": {
                "error": "500 Internal Server Error"
              },
              "global@https://discovery-v4-2.syncthing.net/v2/": {
                "error": "Post https://discovery-v4-2.syncthing.net/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)"
              },
              "global@https://discovery-v4-3.syncthing.net/v2/": {
                "error": "Post https://discovery-v4-3.syncthing.net/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)"
              },
              "global@https://discovery-v6-1.syncthing.net/v2/": {
                "error": "Post https://discovery-v6-1.syncthing.net/v2/: dial tcp [2001:470:28:4d6::5]:443: connect: no route to host"
              },
              "global@https://discovery-v6-2.syncthing.net/v2/": {
                "error": "Post https://discovery-v6-2.syncthing.net/v2/: dial tcp [2604:a880:800:10::182:a001]:443: connect: no route to host"
              },
              "global@https://discovery-v6-3.syncthing.net/v2/": {
                "error": "Post https://discovery-v6-3.syncthing.net/v2/: dial tcp [2400:6180:0:d0::d9:d001]:443: connect: no route to host"
              }
            },
            "discoveryMethods": 8,
            "goroutines": 49,
            "lastDialStatus": {
                "tcp://10.20.30.40": {
                  "when": "2019-05-16T07:41:23Z",
                  "error": "dial tcp 10.20.30.40:22000: i/o timeout"
                },
                "tcp://172.16.33.3:22000": {
                  "when": "2019-05-16T07:40:43Z",
                  "ok": true
                },
                "tcp://83.233.120.221:22000": {
                  "when": "2019-05-16T07:41:13Z",
                  "error": "dial tcp 83.233.120.221:22000: connect: connection refused"
                }
            },
            "myID": "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2",
            "pathSeparator": "/",
            "startTime": "2016-06-06T19:41:43.039284753+02:00",
            "sys": 42092792,
            "themes": [
              "default",
              "dark"
            ],
            "tilde": "/Users/jb",
            "uptime": 2635
          }

A lil history moment for the girlies, because version notes matter and I refuse to skip them ๐Ÿ’…

Added in version 1.2.0: the lastDialStatus dictionary contains the last error (or null for success) for each peer address that Syncthing has attempted to contact. The connectionServiceStatus entries gained "error": null attributes where previously there would be no error attribute at all in the success case. So like, in the success case, there's genuinely nothing there before โ€” now there's a null. Slay.

Added in version 1.18.0: the discoveryStatus dictionary lists all configured discovery methods, not only failed ones like the now deprecated discoveryErrors. Each entry is an object itself (for consistency with other fields), where a null value for the error attribute means the method is running successfully. This effectively deprecates the discoveryMethods attribute as well, which now always matches the number of entries in discoveryStatus. Obsessed with a field that justโ€ฆ agrees with itself now. That's growth.


GET /rest/system/upgrade

Checks for a possible upgrade and returns an object describing the newest version and upgrade possibility. Vibe check on whether you're behind ๐Ÿ‘€

{
  "latest": "v0.14.47",
  "majorNewer": false,
  "newer": true,
  "running": "v0.14.46"
}

POST /rest/system/upgrade

Perform an upgrade to the newest released version and restart. Does nothing if there is no newer version than currently running. Like a refresh button that respects you enough to no-op when there's nothing new ๐Ÿซถ

GET /rest/system/version

Returns the current Syncthing version information. Receipts for what you're running ๐Ÿ’–

{
  "arch": "amd64",
  "longVersion": "syncthing v0.10.27+3-gea8c3de (go1.4 darwin-amd64 default) jb@syno 2015-03-16 11:01:29 UTC",
  "os": "darwin",
  "version": "v0.10.27+3-gea8c3de"
}

โš™๏ธ CONFIG ENDPOINTS โš™๏ธ

Config Endpoints

Added in version 1.12.0. โœจ

Okay bestie, these endpoints let you access and modify the config in a granular way. Config sent to the endpoints must be in the same format as returned by the corresponding GET request. When posting the configuration succeeds, the posted configuration is immediately applied, except for changes that require a restart. Query /rest/config/restart-required to check if a restart is required. So like โ€” most stuff just goes live, but some things need the whole app to get up and stretch. Check first, don't guess ๐Ÿ’€

For all endpoints supporting PATCH, it takes the existing config and unmarshals the given JSON object on top of it. This means all child objects will replace the existing objects, not extend them. For example for RawListenAddresses in options, which is an array of strings, sending {RawListenAddresses: ["tcp://10.0.0.2"]} will replace all existing listen addresses. Not me thinking it appends. It does not append. It REPLACES. Say it with me ๐Ÿ“ข

Real talk: PATCH replaces child objects wholesale. It is not a merge-into-arrays situation. Plan accordingly or your listen addresses are gonna have a bad time ๐Ÿ˜ญ

/rest/config

GET returns the entire config and PUT replaces it. The whole entire thing, no crumbs โœจ

/rest/config/restart-required

GET returns whether a restart of Syncthing is required for the current config to take effect. Simple yes/no energy, and honestly, we respect it.

/rest/config/folders, /rest/config/devices

GET returns all folders respectively devices as an array. PUT takes an array and POST a single object. In both cases if a given folder/device already exists, it's replaced, otherwise a new one is added. Existing? Swapped. New? Welcomed in. Very hospitable ๐Ÿ’…

/rest/config/folders/*id*, /rest/config/devices/*id*

Put the desired folder- respectively device-ID in place of id. GET returns the folder/device for the given ID, PUT replaces the entire config, PATCH replaces only the given child objects and DELETE removes the folder/device. You got a whole little menu here: read it, overwrite it, patch a bit of it, or yeet it entirely ๐ŸŒธ

/rest/config/defaults/folder, /rest/config/defaults/device

GET returns a template folder / device configuration object with all default values, which only needs a unique ID to be applied. PUT replaces the default config (omitted values are reset to the hard-coded defaults), PATCH replaces only the given child objects. Lowkey the best way to start a folder/device โ€” grab the template, slap an ID on it, done ๐Ÿ‘‘

/rest/config/defaults/ignores

Added in version 1.19.0. ๐ŸŽ€

GET returns an object with a single lines attribute listing ignore patterns to be used by default on folders, as an array of single-line strings. PUT replaces the default ignore patterns from an object of the same format. One line per pattern, bestie. Not multi-line. Single. Line. ๐Ÿฆ‹

/rest/config/options, /rest/config/ldap, /rest/config/gui

GET returns the respective object, PUT replaces the entire object and PATCH replaces only the given child objects. Three lil objects, three lil identical rules. Iconic consistency ๐ŸŒˆ

NOTE: The gui.password configuration option has special handling to accept already hashed passwords. Any valid bcrypt hash is stored verbatim, while a plaintext password is first hashed. So you can hand it a hash and it'll keep it exactly as-is โ€” but hand it plaintext and it does the hashing for you. Convenient! ๐Ÿ”ฅ

๐ŸŒˆ CLUSTER ENDPOINTS ๐ŸŒˆ

Okay bestie, this whole section is about the mesh network structure โ€” like, who's in the group chat, who's trying to join, and who's sliding into your DMs with folders you didn't ask for. ๐Ÿ‘€

๐Ÿ’ซ DELETE /rest/cluster/pending/devices

Added in version 1.18.0.

This one yeets the record of a pending remote device that tried to connect. So like, they knocked on the door and you're deleting the ring-cam footage. ๐Ÿšชโœจ

Valid values for the device parameter are those from the corresponding GET /rest/cluster/pending/devices endpoint โ€” so go look at that list first, don't just make up an ID, iykyk.

$ curl -X DELETE -H "X-API-Key: abc123" http://localhost:8384/rest/cluster/pending/devices?device=P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2

Returns status 200 and no content upon success, or status 500 and a plain text error on failure. A PendingDevicesChanged event will be generated in response. ๐Ÿ’…

Heads up: if you want a more permanent effect, also for future connections from the same device ID, the device should be ignored in the configuration instead. Deleting the record is just closing the door; ignoring it in config is changing the locks. ๐Ÿ”

๐Ÿ’ซ GET /rest/cluster/pending/devices

Added in version 1.13.0.

Lists remote devices which have tried to connect, but are not yet configured in our instance. It's literally the "requests" folder of your Syncthing. ๐Ÿซถ

{
  "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2": {
    "time": "2020-03-18T11:43:07Z",
    "name": "Friend Joe",
    "address": "192.168.1.2:22000"
  }
}

๐Ÿ’ซ DELETE /rest/cluster/pending/folders

Added in version 1.18.0.

Same vibe as the device one, but for folders instead. This removes records about a pending folder announced from a remote device. โœจ

Valid values for the folder and device parameters are those from the corresponding GET /rest/cluster/pending/folders endpoint. The device parameter is optional and affects announcements of this folder from the given device, or from any device if omitted โ€” so leave it out and you're nuking it across the board, bestie. ๐Ÿ’€

$ curl -X DELETE -H "X-API-Key: abc123" http://localhost:8384/rest/cluster/pending/folders?folder=cpkn4-57ysy&device=P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2

Returns status 200 and no content upon success, or status 500 and a plain text error on failure. A PendingFoldersChanged event will be generated in response. ๐ŸŽ€

Again with the permanence: for a more permanent effect, also for future announcements of the same folder ID, the folder should be ignored in the configuration instead. ๐Ÿงš

๐Ÿ’ซ GET /rest/cluster/pending/folders

Added in version 1.13.0.

Lists folders which remote devices have offered to us, but are not yet shared from our instance to them. So they're holding up a folder like "want some?" and we haven't said yes yet. ๐Ÿ™ˆ

Takes the optional device parameter to only return folders offered by a specific remote device. Other offering devices are also omitted from the result โ€” it's giving "filter by bestie."

{
  "cpkn4-57ysy": {
    "offeredBy": {
      "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2": {
        "time": "2020-03-18T11:43:07Z",
        "label": "Joe's folder",
        "receiveEncrypted": true,
        "remoteEncrypted": false
      },
      "DOVII4U-SQEEESM-VZ2CVTC-CJM4YN5-QNV7DCU-5U3ASRL-YVFG6TH-W5DV5AA": {
        "time": "2020-03-01T10:12:13Z",
        "label": "Jane's and Joe's folder",
        "receiveEncrypted": false,
        "remoteEncrypted": false
      }
    }
  },
  "abcde-fghij": {
    "offeredBy": {
      "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2": {
        "time": "2020-03-18T11:43:07Z",
        "label": "MyPics",
        "receiveEncrypted": false,
        "remoteEncrypted": false
      }
    }
  }
}

๐Ÿฆ‹ FOLDER ENDPOINTS ๐Ÿฆ‹

Runtime state of the individual shared folders. Like, how are the folders doing today? ๐Ÿ’–

๐ŸŒธ GET /rest/folder/errors

Added in version 0.14.53.

Takes one mandatory parameter, folder, and returns the list of errors encountered during scanning or pulling. It's the "what went wrong" receipt, and ngl sometimes it's a lot. ๐Ÿ˜ญ

The results can be paginated using the common pagination parameters โ€” yes, the whole page/perpage situation from earlier, slay.

{
  "folder": "nnhic-sxuae",
  "errors": [
    {
      "path": "noperm.txt",
      "error": "hashing: open /path/to/folder/noperm.txt: permission denied"
    }
  ],
  "page": 1,
  "perpage": 100
}

๐ŸŒธ GET /rest/folder/pullerrors (DEPRECATED)

โš ๏ธ Deprecated since version v0.14.53: This endpoint still works as before but is deprecated. Use GET /rest/folder/errors instead, which returns the same information. It's giving "we keep the old one around for the vibes but pls move on." ๐Ÿ’…

See โ€” https://github.com/syncthing/syncthing/commit/d510e3cca3d5caae42121fa206b3decc981ae59e

๐ŸŒธ GET /rest/folder/versions

Added in version 0.14.44.

Takes one mandatory parameter, folder, and returns the list of archived files that could be recovered. Like a little time capsule for your files. โณโœจ

How many versions are available depends on the File Versioning configuration โ€” so your mileage is entirely config-dependent, no cap. Each entry specifies when the file version was archived as the versionTime, the modTime when it was last modified before being archived, and the size in bytes. ๐Ÿ”ฅ

{
  "dir1/dir2/bar": [
    {
      "versionTime": "2022-02-06T20:44:12+01:00",
      "modTime": "2021-01-14T13:21:22+01:00",
      "size": 4
    }
  ],
  "baz": [
    {
      "versionTime": "2022-02-06T20:44:20+01:00",
      "modTime": "2021-01-14T13:23:49+01:00",
      "size": 4
    }
  ],
  "foo": [
    {
      "versionTime": "2022-02-06T20:55:31+01:00",
      "modTime": "2022-02-06T20:44:13+01:00",
      "size": 4
    },
    {
      "versionTime": "2022-02-06T20:44:20+01:00",
      "modTime": "2021-01-14T13:21:16+01:00",
      "size": 4
    }
  ]
}

๐ŸŒธ POST /rest/folder/versions

Restore archived versions of a given set of files. This is the "undo" button, bestie, and it ate. ๐Ÿ’…

Expects an object with attributes named after the relative file paths, with timestamps as values matching valid versionTime entries in the corresponding GET /rest/folder/versions response object. So like, you literally copy the timestamp from the GET and hand it back. Iconic.

Takes the mandatory parameter folder (folder ID). Returns an object containing any error messages that occurred during restoration of the file, with the file path as attribute name. ๐Ÿซถ

curl -X POST -H X-API-key:... http://127.0.0.1:8384/rest/folder/versions?folder=default -d '{
  "dir1/dir2/bar": "2022-02-06T20:44:12+01:00",
  "baz": "2022-02-06T20:44:20+01:00"
}'

๐Ÿ‘‘ DATABASE ENDPOINTS ๐Ÿ‘‘

โญ GET /rest/db/browse

Returns the directory tree of the global model. Directories are always JSON objects (map/dictionary), and files are always arrays of modification time and size. The first integer is the files modification time, and the second integer is the file size. ๐Ÿ—‚๏ธ

The call takes one mandatory folder parameter and two optional parameters. Optional parameter levels defines how deep within the tree we want to dwell down (0 based, defaults to unlimited depth). Optional parameter prefix defines a prefix within the tree where to start building the structure. So you can be like "just the top two levels of DCIM pls." โœจ

$ curl -s http://localhost:8384/rest/db/browse?folder=j663y-3ct3e&prefix=DCIM&levels=2
[
    {
        "modTime" : "2020-10-02T23:48:52.076996974+02:00",
        "name" : "100ANDRO",
        "size" : 128,
        "type" : "FILE_INFO_TYPE_DIRECTORY"
    },
    {
        "children" : [
            {
                "modTime" : "2020-12-16T23:31:34.5009668+01:00",
                "name" : "IMG_20201114_124821.jpg",
                "size" : 10682189,
                "type" : "FILE_INFO_TYPE_FILE"
            },
            {
                "modTime" : "2020-12-16T23:31:35.0106367+01:00",
                "name" : "IMG_20201213_122451.jpg",
                "size" : 7936351,
                "type" : "FILE_INFO_TYPE_FILE"
            },
            {
                "modTime" : "2020-12-13T12:25:05.017097469+01:00",
                "name" : "IMG_20201213_122504.jpg",
                "size" : 8406507,
                "type" : "FILE_INFO_TYPE_FILE"
            },
            {
                "modTime" : "2020-12-13T12:25:06.127097469+01:00",
                "name" : "IMG_20201213_122505.jpg",
                "size" : 8381931,
                "type" : "FILE_INFO_TYPE_FILE"
            },
            {
                "modTime" : "2020-12-13T12:53:29.707298401+01:00",
                "name" : "IMG_20201213_125329.jpg",
                "size" : 4388331,
                "type" : "FILE_INFO_TYPE_FILE"
            },
        ],
        "modTime" : "2020-10-09T13:04:42.4410738+02:00",
        "name" : "Camera",
        "size" : 128,
        "type" : "FILE_INFO_TYPE_DIRECTORY"
    },
]
โš ๏ธ NOTE: This is an expensive call, increasing CPU and RAM usage on the device. Use sparingly. Fr fr, don't be spamming this one โ€” you will absolutely cook your little box. ๐Ÿ”ฅ๐Ÿ’€

โญ GET /rest/db/completion

Returns the completion percentage (0 to 100) and byte / item counts. It's the progress bar of your dreams. ๐Ÿ“Š๐Ÿ’–

Takes optional device and folder parameters:

  • folder specifies the folder ID to calculate completion for. An empty or absent folder parameter means all folders as an aggregate.
  • device specifies the device ID to calculate completion for. An empty or absent device parameter means the local device.

If a device is specified but no folder, completion is calculated for all folders shared with that device. Mix and match, it's cute like that. ๐ŸŽ€

๐ŸŒŸ Example Queries

Completion status for folder abcd-1234 on device I6KAH76-...-3PSROAU:

/rest/db/completion?folder=abcd-1234&device=I6KAH76-...-3PSROAU

Aggregated completion status for device I6KAH76-...-3PSROAU (all folders shared with them):

/rest/db/completion?device=I6KAH76-...-3PSROAU

Completion status for folder abcd-1234 on the local device:

/rest/db/completion?folder=abcd-1234

Aggregated completion status for all folders on the local device:

/rest/db/completion

๐ŸŒŸ Example Response

{
  "completion": 99.9937565835,
  "globalBytes": 156793013575,
  "needBytes": 9789241,
  "globalItems": 7823,
  "needItems": 412,
  "needDeletes": 0,
  "remoteState": "valid",
  "sequence": 12
}

Added in version 1.8.0: The ability to aggregate multiple folders by leaving out the folder ID. Querying data for the local device by leaving out the device ID. Returning the globalItems counter in the response. โœจ

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 meaningless for aggregated responses, unknown when the remote device is not connected. Otherwise it can be either paused, notSharing, or valid if the remote is sharing back. ๐Ÿซถ

โญ GET /rest/db/file

Returns most data about a given file, including version and availability. It's giving "tell me everything about this one file." ๐Ÿ‘‘

๐Ÿ’œ Request

The call requires parameters for folder and file in the query string:

  • folder is the folder ID which you can find in the Syncthing Web GUI, e.g. 5camp-slpa8.
  • file โ€” the relative path of the file, starting from the folder root, pointing at the file you actually care about ๐ŸŽฏ. Bestie, the path and the filename must be correctly URL-encoded, no cap.

โœจ Example Request โœจ

$ curl -s -H X-API-KEY:... "http://localhost:8384/rest/db/file?folder=YOUR_FOLDER_ID" --url-query "file=path/to/file.pdf"

๐Ÿ’– Response ๐Ÿ’–

{
  "availability": [
    {
      "id": "ITZRNXE-YNROGBZ-HXTH5P7-VK5NYE5-QHRQGE2-7JQ6VNJ-KZUEDIU-5PPR5AM",
      "fromTemporary": false
    }
  ],
  "global": { /* a file entry */ },
  "local": { /* a file entry */ },
  "mtime": {
    "err": null,
    "value": {
      "real": "0001-01-01T00:00:00Z",
      "virtual": "0001-01-01T00:00:00Z"
  }
}

So local and global are the current file on the local device and the globally newest file, respectively โ€” like, respectively, okay, keep them straight ๐Ÿ˜Œ.

A file entry looks like this ๐Ÿ‘‡

{
  "deleted": false,
  "ignored": false,
  "inodeChange": "1970-01-01T01:00:00+01:00",
  "invalid": false,
  "localFlags": 0,
  "modified": "2022-09-28T08:07:19.979723+02:00",
  "modifiedBy": "523ITIE",
  "mustRescan": false,
  "name": "img",
  "noPermissions": false,
  "numBlocks": 0,
  "permissions": "0755",
  "platform": { /* platform specific data */ },
  "sequence": 914,
  "size": 128,
  "type": "FILE_INFO_TYPE_DIRECTORY",
  "version": [
    "523ITIE:1664345275"
  ]
}

Platform specific data? Could be ownership, extended attributes, etc., and it's split into entries per operating system / platform ๐ŸŒ. Here's a platform entry with ownership info for Unix systems plus an extended attribute for macOS (that's "darwin", iykyk) ๐ŸŽ:

{
  "darwin": {
    "xattrs": [
      {
        "name": "net.kastelo.xattrtest",
        "value": "aGVsbG8="
      }
    ]
  },
  "freebsd": null,
  "linux": null,
  "netbsd": null,
  "unix": {
    "gid": 20,
    "groupName": "staff",
    "ownerName": "jb",
    "uid": 501
  },
  "windows": null
}

๐ŸŒธ GET /rest/db/ignores ๐ŸŒธ

Takes one parameter, folder, and returns the content of the .stignore as the ignore field ๐Ÿ’…. There's a second field, expanded, which gives you a list of strings representing globbing patterns described by gobwas/glob (based on standard wildcards) that match the patterns in .stignore and all the includes โœจ. If appropriate, these globs get prepended by the following modifiers: ! to negate the glob, (?i) to do case insensitive matching, and (?d) to enable removing of ignored files in an otherwise empty directory ๐Ÿซถ.

{
  "ignore": [
    "(?i)/Backups"
  ],
  "expanded": [
    "(?i)Backups",
    "(?i)Backups/**"
  ]
}

๐Ÿฆ‹ POST /rest/db/ignores ๐Ÿฆ‹

Expects a format similar to the output of the GET /rest/db/ignores call, but only containing the ignore field (expanded field should be omitted, bestie ๐Ÿ™ˆ). It takes one parameter, folder, and either updates the content of the .stignore, echoing it back as a response, or returns an error ๐Ÿ˜ญ.


๐ŸŽ€ GET /rest/db/localchanged ๐ŸŽ€

Added in version 0.14.55.

Takes one mandatory parameter, folder, and returns the list of files which were changed locally in a receive-only folder ๐ŸŒˆ. So like, they differ from the global state and could be reverted by pulling from remote devices again โ€” see POST /rest/db/revert ๐Ÿ’œ.

The results can be paginated using the common pagination parameters โœจ.

{
  "files": [
    {
      "flags": "0755",
      "sequence": 6,
      "modified": "2015-04-20T23:06:12+09:00",
      "name": "ls",
      "size": 34640,
      "version": [
        "5157751870738175669:1"
      ]
    }
  ],
  "page": 1,
  "perpage": 100
}
NOTE: This is an expensive call, increasing CPU and RAM usage on the device. Use sparingly ๐Ÿ’€.

๐Ÿ‘‘ GET /rest/db/need ๐Ÿ‘‘

Takes one mandatory parameter, folder, and returns lists of files which are needed by this device in order for it to become in sync ๐Ÿซถ.

The results can be paginated using the common pagination parameters โœจ. Pagination happens across the union of all needed files โ€” that is, across all 3 sections of the response ๐Ÿ‘€. For example, given the current need state is as follows:

  1. progress has 15 items
  2. queued has 3 items
  3. rest has 12 items

If you issue a query with page=1 and perpage=10, only the progress section in the response will have 10 items. If you issue a request query with page=2 and perpage=10, the progress section will have the last 5 items, the queued section will have all 3 items, and the rest section will have the first 2 items ๐Ÿ”ฅ. If you issue a query for page=3 and perpage=10, you will only have the last 10 items of the rest section ๐Ÿ˜Œ.

NOTE: Return format changed in version 0.14.43, removing the total count attribute ๐Ÿ™ˆ.
{
  # Files currently being downloaded
  "progress": [
    {
      "flags": "0755",
      "sequence": 6,
      "modified": "2015-04-20T23:06:12+09:00",
      "name": "ls",
      "size": 34640,
      "version": [
        "5157751870738175669:1"
      ]
    }
  ],
  # Files queued to be downloaded next (as per array order)
  "queued": [
      ...
  ],
  # Files to be downloaded after all queued files will be downloaded.
  # This happens when we start downloading files, and new files get added while we are downloading.
  "rest": [
      ...
  ],
  "page": 1,
  "perpage": 100
}
NOTE: This is an expensive call, increasing CPU and RAM usage on the device. Use sparingly ๐Ÿ’€.

๐Ÿ”ฅ POST /rest/db/override ๐Ÿ”ฅ

Request an override of a send-only folder ๐Ÿซก. Override means make the local version latest, overriding changes made on other devices โ€” main character energy, honestly ๐Ÿ‘‘. This API call does nothing if the folder is not a send-only folder, fr fr.

Takes the mandatory parameter folder (folder ID).

curl -X POST -H X-API-key:... http://127.0.0.1:8384/rest/db/override?folder=default

โญ POST /rest/db/prio โญ

Moves the file to the top of the download queue ๐Ÿ’… โ€” jump the line, slay.

curl -X POST http://127.0.0.1:8384/rest/db/prio?folder=default&file=foo/bar

Response contains the same output as GET /rest/db/need โœจ.


๐Ÿ’œ GET /rest/db/remoteneed ๐Ÿ’œ

Added in version 0.14.43.

Takes the mandatory parameters folder and device, and returns the list of files which are needed by that remote device in order for it to become in sync with the shared folder ๐Ÿฆ‹.

The results can be paginated using the common pagination parameters โœจ.

{
  "files": [
    {
      "flags": "0755",
      "sequence": 6,
      "modified": "2015-04-20T23:06:12+09:00",
      "name": "ls",
      "size": 34640,
      "version": [
        "5157751870738175669:1"
      ]
    }
  ],
  "page": 1,
  "perpage": 100
}
NOTE: This is an expensive call, increasing CPU and RAM usage on the device. Use sparingly ๐Ÿ’€.

๐ŸŒˆ POST /rest/db/revert ๐ŸŒˆ

Added in version 0.14.50.

Request revert of a receive-only folder ๐Ÿ”„. Reverting a folder means undoing all local changes โ€” like, undo-undo, bestie ๐Ÿ˜ญ. This API call does nothing if the folder is not a receive-only folder.

Takes the mandatory parameter folder (folder ID).

curl -X POST -H X-API-Key:... http://127.0.0.1:8384/rest/db/revert?folder=default

๐Ÿงš POST /rest/db/scan ๐Ÿงš

Request immediate scan ๐Ÿ‘€. Takes the optional parameters folder (folder ID), sub (path relative to the folder root) and next (time in seconds). If folder is omitted or empty, all folders are scanned ๐ŸŒ. If sub is given, only this path (and children, in case it's a directory) is scanned ๐ŸŽฏ. The next argument delays Syncthing's automated rescan interval for a given amount of seconds โณ.

Requesting a scan of a path that no longer exists, but previously did, is valid and will result in Syncthing noticing the deletion of the path in question ๐Ÿ’€โœจ.

Returns status 200 and no content upon success, or status 500 and a plain text error if an error occurred during scanning ๐Ÿ™ˆ.

curl -X POST http://127.0.0.1:8384/rest/db/scan?folder=default&sub=foo/bar

๐Ÿ“ GET /rest/db/status ๐Ÿ“

Returns information about the current status of a folder ๐Ÿซถ.

Parameters: folder, the ID of a folder.

{
  "globalBytes": 0,
  "globalDeleted": 0,
  "globalDirectories": 0,
  "globalFiles": 0,
  "globalSymlinks": 0,
  "globalTotalItems": 0,
  "ignorePatterns": false,
  "inSyncBytes": 0,
  "inSyncFiles": 0,
  "invalid": "",
  "localBytes": 0,
  "localDeleted": 0,
  "localDirectories": 0,
  "localFiles": 0,
  "localSymlinks": 0,
  "localTotalItems": 0,
  "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,
  "sequence": 0,
  "state": "idle",
  "stateChanged": "2018-08-08T07:04:57.301064781+02:00",
  "version": 0
}

The various fields have the following meaning ๐Ÿ‘‡

  • global* โ€” data in the cluster latest version ๐ŸŒ
  • inSync* โ€” data that is locally the same as the cluster latest version โœ…
  • local* โ€” data that is locally present, regardless of whether it's the same or a different version as the cluster latest version ๐Ÿ“
  • need* โ€” data that is needed to become up to date with the cluster latest version (i.e., data that is out of sync) ๐Ÿ˜ญ
  • receiveOnlyChanged* โ€” data that has changed locally in a receive-only folder, and thus has not been sent to the cluster ๐Ÿ™ˆ
  • invalid โ€” deprecated, always empty ๐Ÿ’€
  • pullErrors โ€” the number of files that failed to sync during the last sync operations ๐Ÿ”ฅ
  • sequence โ€” the current folder sequence number ๐Ÿ”ข
  • state โ€” the current folder state ๐ŸŽ€
  • stateChanged โ€” when the folder state last changed โฐ
  • version โ€” deprecated, equivalent to the sequence number ๐Ÿ’…
NOTE: This is an expensive call, increasing CPU and RAM usage on the device. Use sparingly ๐Ÿ’€.

๐ŸŽ€ EVENT ENDPOINTS ๐ŸŽ€

GET /rest/events

okay so if you want in on the event stream, bestie, you literally just do a HTTP GET of /rest/events. that's it. main character endpoint behavior. ๐Ÿ’…

to filter the event list โ€” like, making a specific subscription for only the event types you actually care about โ€” you add a parameter events=EventTypeA,EventTypeB,... where the event types can be any of the Event Types. if you don't specify a filter, you get all events except LocalChangeDetected and RemoteChangeDetected. those two are gatekept by default, no cap.

the optional parameter since=<lastSeenID> sets the ID of the last event you've already seen. Syncthing hands back a JSON encoded array of event objects, starting at the event just after the one with this last seen ID. default is 0, which just gives you all the events, the whole timeline, the full lore. โœจ

โš ๏ธ there's a limit to how many events get buffered, so if the rate of events is high or the time between your polling calls is long, some events might be missed. you can spot this by noticing a discontinuity in the event IDs. keep an eye out, bestie ๐Ÿ‘€

if no new events have been produced since <lastSeenID>, the HTTP call blocks and waits for new events to happen before returning. (iconic commitment honestly.) and if <lastSeenID> is a future ID, the HTTP call blocks until such ID is reached or it timeouts. by default it times out after 60 seconds, returning an empty array. you can customize the time out duration with the optional parameter timeout=<seconds>.

want only a limited number of events? add limit=<n> with a suitable value for n and only the last n events will be returned. super handy for catching up with the latest event ID after a disconnection, for example:

/rest/events?since=0&limit=1

GET /rest/events/disk

this convenience endpoint gives you the same event stream, but pre-filtered to show only LocalChangeDetected and RemoteChangeDetected event types. the events parameter is not used here. it's giving "we already did the filtering for you, you're welcome". ๐Ÿซถ


๐ŸŒธ STATISTICS ENDPOINTS ๐ŸŒธ

GET /rest/stats/device

returns general statistics about devices. currently it only contains the time the device was last seen and the last connection duration. short and sweet.

$ curl -s http://localhost:8384/rest/stats/device | json
{
  "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2": {
    "lastSeen" : "2015-04-18T11:21:31.3256277+01:00"
    "lastConnectionDurationS": 556335.421708141
  }
}

GET /rest/stats/folder

returns general statistics about folders. currently contains the last scan time and the last synced file. ๐Ÿ—‚๏ธ

{
  "folderid": {
    "lastScan": "2016-06-02T13:28:01.288181412-04:00",
    "lastFile": {
      "filename": "file/name",
      "at": "2015-04-16T22:04:18.3066971+01:00"
    }
  }
}

๐Ÿฆ‹ MISC SERVICES ENDPOINTS ๐Ÿฆ‹

GET /rest/svc/deviceid

verifies and formats a device ID. it accepts all currently valid formats (52 or 56 characters, with or without separators, upper or lower case, with trivial substitutions โ€” so go ahead and be messy, it'll handle it ๐Ÿ’…). takes one parameter, id, and returns either a valid device ID in modern format, or an error.

$ curl -s http://localhost:8384/rest/svc/deviceid?id=1234 | json
{
  "error": "device ID invalid: incorrect length"
}
$ curl -s http://localhost:8384/rest/svc/deviceid?id=p56ioi7m--zjnu2iq-gdr-eydm-2mgtmgl3bxnpq6w5btbbz4tjxzwicq | json
{
  "id": "P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"
}

GET /rest/svc/lang

returns a list of canonicalized localization codes, as picked up from the Accept-Language header sent by the browser. multilingual queen behavior. ๐ŸŒˆ

["sv_sv","sv","en_us","en"]

GET /rest/svc/random/string

returns a strong random generated string (alphanumeric) of the specified length. takes the length parameter. ๐Ÿ”ฅ

{
  "random": "FdPaEaZQ56sXEKYNxpgF"
}

GET /rest/svc/report

returns the data sent in the anonymous usage report. the whole receipts, fr fr:

{
   "folderMaxMiB" : 0,
   "platform" : "linux-amd64",
   "totMiB" : 0,
   "longVersion" : "syncthing v0.12.2 \"Beryllium Bedbug\" (go1.4.3 linux-amd64 default) unknown-user@build2.syncthing.net 2015-11-09 13:23:26 UTC",
   "upgradeAllowedManual" : true,
   "totFiles" : 3,
   "folderUses" : {
      "ignorePerms" : 0,
      "autoNormalize" : 0,
      "sendonly" : 0,
      "ignoreDelete" : 0
   },
   "memoryUsageMiB" : 13,
   "version" : "v0.12.2",
   "sha256Perf" : 27.28,
   "numFolders" : 2,
   "memorySize" : 1992,
   "announce" : {
      "defaultServersIP" : 0,
      "otherServers" : 0,
      "globalEnabled" : false,
      "defaultServersDNS" : 1,
      "localEnabled" : false
   },
   "usesRateLimit" : false,
   "numCPU" : 2,
   "uniqueID" : "",
   "urVersion" : 2,
   "rescanIntvs" : [
      60,
      60
   ],
   "numDevices" : 2,
   "folderMaxFiles" : 3,
   "relays" : {
      "defaultServers" : 1,
      "enabled" : true,
      "otherServers" : 0
   },
   "deviceUses" : {
      "compressMetadata" : 1,
      "customCertName" : 0,
      "staticAddr" : 1,
      "compressAlways" : 0,
      "compressNever" : 1,
      "introducer" : 0,
      "dynamicAddr" : 1
   },
   "upgradeAllowedAuto" : false
}

๐Ÿ” DEBUG ENDPOINTS ๐Ÿ”

Debug Endpoints

GET /rest/debug/cpuprof

used to capture a profile of what Syncthing is doing on the CPU. see Profiling. ๐Ÿง 

GET /rest/debug/heapprof

used to capture a profile of what Syncthing is doing with the heap memory. see Profiling.

GET /rest/debug/support

collects information about the running instance for troubleshooting purposes. returns a "support bundle" as a zipped archive, which you should send to the developers after verifying it contains no sensitive personal information.

๐Ÿ’– good news though: credentials for the web GUI and the API key are automatically redacted already. so that part's handled.

GET /rest/debug/file

shows diagnostics about a certain file in a shared folder. takes the folder (folder ID) and file (folder relative path) parameters.

$ curl -H X-API-Key:... "http://localhost:8384/rest/debug/file?folder=default&file=foo/bar"

the returned object contains the same info as GET /rest/db/file, plus a summary of globalVersions. ๐Ÿ“


๐Ÿ‘‘ NOAUTH ENDPOINTS ๐Ÿ‘‘

calls that do not require authentication. yes bestie, the no-login-needed zone.

GET /rest/noauth/health

returns a {"status": "OK"} object. that's the whole vibe check. โœ…

{
  "status": "OK"
}

๐Ÿ’œ AUTHOR ๐Ÿ’œ

The Syncthing Authors. legends, honestly.

2014-2026, The Syncthing Authors


and that's the whole page, bestie โ€” you made it to the end! ๐ŸŽ‰ this was v2.1.0, dated Oct 07, 2026, and it's the SYNCTHING-REST-API(7) manual page. go forth and hit those endpoints, you absolute icon. ๐Ÿ’…โœจ

๐Ÿ“ฆ where this page comes from