reMarkable Paper Pro — Field Guide All sections ↗

← Back to overview · Section 9 of 10

Building Your Own Custom Remote Connector

reMarkable's built-in Integrations cover Dropbox, Google Drive, and OneDrive, but they stop where your own infrastructure begins. If you keep documents in an S3 bucket, a Nextcloud instance, or any WebDAV endpoint, you have to build the bridge yourself. A "custom remote connector" is any glue that moves files between your storage and the reMarkable Cloud (or the tablet directly) on a schedule. This section covers the cloud API authentication flow you must implement, the two architectures available to you, and a worked, copy-pasteable example that pushes a folder to the tablet via rmapi and rclone. The load-bearing decision, made early, is *where the connector runs* — because that choice determines whether an over-the-air firmware update silently destroys your setup.

The Cloud API authentication flow

The reMarkable Cloud uses a three-stage token exchange: a one-time code becomes a long-lived device token, which mints short-lived user tokens. This is the same flow the official apps and every community tool (rmapi, rmapy) implement.

Stage 1 — one-time code. Log into your account and visit https://my.remarkable.com/connect/desktop (or /connect/mobile). The page displays an 8-character alphanumeric code, e.g. apwngead. It is single-use — consumed on the first request whether that request succeeds or fails — but you can generate as many as you need.

Stage 2 — device token. Register a "device" to receive a durable token (a JWT). You run this exactly once per client.

POST https://my.remarkable.com/token/json/2/device/new
Content-Type: application/json

{
  "code": "apwngead",
  "deviceDesc": "desktop-linux",
  "deviceID": "d4605307-a145-48d2-b60a-3be2c46035ef"
}

deviceDesc must be one of desktop-windows, desktop-macos, mobile-android, mobile-ios, browser-chrome, or remarkable; deviceID is any client-generated UUID. The response body is the plain-text device token. Persist it — this is what you keep.

Stage 3 — user token. Exchange the device token for a short-lived user token that actually authorizes API calls.

POST https://my.remarkable.com/token/json/2/user/new
Authorization: Bearer {device_token}

The body is empty. The response is a plain-text user token (also a JWT). Every subsequent storage, sync, or notification request carries Authorization: Bearer {user_token}. Because it expires, you re-run Stage 3 whenever needed using the stored device token — you never repeat Stages 1 and 2. User-token JWTs carry scopes that gate features: sync, intgr, screenshare, docedit, mail, and hwc (handwriting conversion).

Figure 9.1: Three-stage token exchange with the reMarkable Cloud API

sequenceDiagram participant User as Browser (one-time code) participant Client as Connector (rmapi) participant API as reMarkable Cloud API User->>Client: 8-char one-time code Client->>API: "POST /token/json/2/device/new (code)" API-->>Client: device token (JWT, stored once) Client->>API: "POST /token/json/2/user/new (Bearer device_token)" API-->>Client: user token (short-lived JWT) Client->>API: "storage request (Bearer user_token)" API-->>Client: document data Note over Client,API: On expiry, repeat only Stage 3

In practice you never hand-roll this. rmapi performs the full flow on first run, prompts for the one-time code, and persists the device token to ~/.rmapi (override the path with the RMAPI_CONFIG environment variable). Everything below leans on that.

Off-device vs on-device: pick off-device

There are two fundamentally different places to run a connector.

Off-device (recommended). The connector runs on a server, NAS, or Raspberry Pi. It talks to the reMarkable *Cloud* over the API using rmapi, and to your storage (S3, WebDAV, Nextcloud, Dropbox) using rclone or a native client. Nothing is installed on the tablet. This is how reMarkable-autosync, remarkdav, and sync_zotero_remarkable all work — and, critically, an OTA firmware update on the tablet cannot touch a cron job on your server.

On-device. You install a daemon or proxy on the tablet itself via developer mode and SSH. It is more powerful — you can redirect the tablet's own native sync — but it is fragile across updates (covered below) and, on the Paper Pro, enabling developer mode performs a factory reset.

The recommendation is unambiguous: unless you specifically need to intercept the tablet's built-in sync (the rmfakecloud use case), build off-device. It is simpler, safer, update-proof, and needs no root.

Figure 9.2: Off-device connector architecture (server bridges your storage to the tablet)

flowchart LR Storage["Your storage: S3 / WebDAV / Nextcloud"] subgraph Server["Server / Raspberry Pi (connector)"] Inbox["Watched inbox folder"] Tools["cron + rmapi + rclone"] end Cloud["reMarkable Cloud"] Tablet["Paper Pro tablet"] Storage -->|"rclone mount"| Inbox Inbox --> Tools Tools -->|"rmapi put"| Cloud Cloud -->|"native sync"| Tablet

Worked example: cron + rmapi + rclone → the Cloud

rmapi speaks the Cloud API but has no native S3 or WebDAV support. rclone, which mounts 40+ backends, bridges that gap: mount your remote into a local directory, then let rmapi upload from it. Authenticate rmapi once (the flow above), configure an rclone remote once, then wire the two together with a script.

#!/usr/bin/env bash
# /srv/remarkable/sync.sh — push new PDFs from an rclone-mounted remote to the tablet
set -euo pipefail

WATCH=/srv/remarkable/inbox          # rclone-mounted S3/WebDAV dir
DONE=/srv/remarkable/done
export RMAPI_CONFIG=/srv/remarkable/rmapi.conf

# For S3/WebDAV, ensure the remote is mounted (idempotent):
mountpoint -q "$WATCH" || \
  rclone mount mystore:remarkable-inbox "$WATCH" --daemon --read-only

for f in "$WATCH"/*.pdf; do
  [ -e "$f" ] || continue          # no matches -> skip the literal glob
  if rmapi put "$f" "/Inbox"; then # rmapi exits 0 on success, 1 on failure
    mv "$f" "$DONE/"
  else
    echo "FAILED: $f" >&2
  fi
done

rmapi is built for exactly this: put/mput upload (the latter recursively), get/mget/geta download, and ls/cd/find/mkdir/mv/rm/stat for navigation. It runs non-interactively and returns exit code 0 on success or 1 on failure, so it slots cleanly into a script's control flow. Useful env vars include RMAPI_TRACE=1 (debug) and RMAPI_CONCURRENT (upload parallelism, default 20).

Schedule it with plain cron. The stock reMarkable image is systemd-based and does not ship a full crond, but your *server* does:

# /etc/cron.d/remarkable — every 10 minutes, logging to a file
*/10 * * * *  user  /srv/remarkable/sync.sh >> /var/log/remarkable-sync.log 2>&1

That is a complete, durable connector. Real projects follow the same shape: reMarkable-autosync runs rclone+rmapi every minute; remarkdav schedules 0 */6 * * *. Swap the rclone remote definition to point at S3 (s3fs also works) or any WebDAV server and nothing else changes.

On-device: systemd service and surviving OTA updates

If you do run on-device, standard systemd applies. Custom units live in /etc/systemd/system/:

[Unit]
Description=My reMarkable custom connector
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/home/root/myconnector/run.sh
Restart=on-failure
RestartSec=30
StandardOutput=append:/home/root/myconnector/connector.log
StandardError=append:/home/root/myconnector/connector.log

[Install]
WantedBy=multi-user.target

Enable it with systemctl daemon-reload && systemctl enable --now myconnector.service. Note that ExecStart is executed directly with no shell — use absolute paths, never ~, $HOME, or globs. Point it at a script under /home/root/... for a reason that is the whole game on this device.

The Paper Pro uses an A/B dual-partition scheme. An update is written to the inactive root partition and the bootloader swaps to it. The consequence: the entire root partition (/) is replaced on every update. Anything you added under /etc, /usr, or /opt, your custom unit in /etc/systemd/system/, any /etc/hosts edits, and any installed certificates are wiped back to stock. The root filesystem is also read-only by default and reverts to read-only on reboot.

Only /home survives — it is a separate data partition. So every durable payload (binaries, scripts, the .rmapi token, config) must live under /home/root/.... This is exactly Toltec's survival trick: it keeps everything in /opt, which is bind-mounted to /home/root/.entware. The payload persists, but the *mount and root-partition registration* do not, so after each update Toltec must be re-enabled to recreate its root-FS modifications (it tracks what to re-apply via marker files in /opt/share/toltec/reenable.d). rmfakecloud is blunt about the same reality: "Whenever the tablet receives a system update, the cloud connection will break, and will have to be reenabled."

So if you go on-device, budget for two things: keep the payload under /home/root/, and keep a re-apply script (also under /home/root/) that recreates your systemd unit, /etc/hosts edits, and certs after every OTA update. You *will* run it each update. This maintenance tax is precisely why the off-device architecture wins for most connectors — a firmware update simply cannot reach it.

References