Search docs.molo17.com

    Backup REST API

    The /backup endpoints control the backups of Core Hub to Gluesync Connect. Use them to turn backups on, start one, change the settings or turn them off from a script.

    The endpoints follow the conventions of the Core Hub REST API: JSON over HTTP, the root of the server as base path, and a Bearer token from /authentication/login. The Control Plane uses the same endpoints.

    Access

    Every /backup endpoint needs a user with the Super Admin role. A backup holds every credential of the installation, and the recovery key that opens it is chosen here. Other roles get 403 Forbidden.

    curl -X GET https://your-gluesync.com/backup \
      -H "Authorization: Bearer <your-token>"

    Endpoints

    Method Path Description

    GET

    /backup

    The backup status, the plan limits and the backups Gluesync Connect holds for this site, and the warnings about the installation.

    POST

    /backup/enable

    Turn backups on. The body carries the public recovery key. See Enable backups.

    POST

    /backup/rotate-key

    Seal the next backups to a new recovery key. The body carries the new public key. See Rotate the recovery key.

    POST

    /backup/disable

    Turn backups off and forget the public key. Backups already in Gluesync Connect are kept until their retention ends.

    PUT

    /backup/settings

    Change the settings. Only includeMetrics can be changed.

    POST

    /backup/run

    Start a backup now. It obeys the frequency of the plan.

    There is no setting for the frequency. It is the interval of the plan: one backup per day on the free plan, one per hour on a PRO plan.

    Generate a recovery key and its public key

    The recovery key is a private key. The best key is one that never reaches Core Hub. You generate the key pair outside Core Hub, keep the private half and send only the public half. The Control Plane does this in the browser. For a script, use the --generate-recovery-key command of the Core Hub program. It starts no server, makes no network call and reads no Core Hub data.

    java -jar <path to the Core Hub jar> --generate-recovery-key

    With Docker, from any host that has the Core Hub image:

    docker run --rm <Core Hub image> ./entrypoint.sh --generate-recovery-key

    The command prints, in this order:

    • the recovery key, GSRK1-XXXXX-XXXXX-…​. It is secret. Store it as described in Keep the recovery key;

    • the public key, the base64 text of the 32 raw bytes of the X25519 public key. It is not secret. This is the value you send to the API as publicKey;

    • the key ID, 16 hexadecimal characters. Core Hub and Gluesync Connect use it to name the key. It reveals nothing about the key.

    The exit status is 0. An argument that the command does not know gives exit status 1 and a usage message.

    If you lose the recovery key, the backups sealed to it cannot be opened. Nobody can recover it, including MOLO17.

    Enable backups

    Send the public key:

    curl -X POST https://your-gluesync.com/backup/enable \
      -H "Authorization: Bearer <your-token>" \
      -H "Content-Type: application/json" \
      -d '{"publicKey": "<base64 public key>"}'
    Field Type Description

    publicKey

    string, optional

    The public key from Generate a recovery key and its public key: base64 of the 32 raw bytes of an X25519 public key. The body is limited to 4096 bytes.

    The answer is 200 OK with the ID of the key. Core Hub starts the first backup shortly after.

    { "keyId": "3fa9c01d5b7e2a64" }

    Core Hub refuses a key that is not base64, that is not 32 bytes long, or that is a point of small order, with 400 Bad Request, the code invalid_request and "field": "publicKey". A field other than publicKey, a body that is not a JSON object, and a publicKey that is not a string give 400 too.

    409 Conflict with the code backup_already_enabled means backups are already on. Use /backup/rotate-key to change the key.

    Let Core Hub generate the key

    Without a publicKey (an empty body or {}), Core Hub generates the key itself and answers with it:

    {
      "recoveryKey": "GSRK1-XXXXX-XXXXX-...",
      "keyId": "3fa9c01d5b7e2a64"
    }

    The answer carries Cache-Control: no-store. The key is shown once and Core Hub does not keep it.

    A recovery key in a response body is a secret, so Core Hub hands one out only to an interactive Super Admin session that reached Core Hub directly. The request is refused with 403 Forbidden and the code recovery_key_generation_not_allowed when it comes:

    • with a Personal API Token;

    • from an MCP tool call;

    • through Gluesync Connect, whose proxy records response bodies.

    In these cases, generate the key with --generate-recovery-key and send its public key.

    Rotate the recovery key

    POST /backup/rotate-key takes the same optional body as /backup/enable, with the public key of the new recovery key, and has the same answers and refusals. Core Hub seals the next backups to the new key. The first backup under the new key is made when the interval of your plan allows it: up to a day on the free plan. The backups that already exist stay sealed to the old key. Keep every old key until the last backup sealed to it has aged out.

    409 Conflict with the code backup_not_enabled means backups are off.

    Read the status

    curl -X GET https://your-gluesync.com/backup \
      -H "Authorization: Bearer <your-token>"
    {
      "status": {
        "state": "idle",
        "enabled": true,
        "reason": null,
        "message": null,
        "includeMetrics": false,
        "keyId": "3fa9c01d5b7e2a64",
        "lastAttemptAt": "2026-10-22T03:00:00Z",
        "lastSuccessAt": "2026-10-22T03:00:41Z",
        "lastBackupId": "bk_8d1f2c",
        "lastError": null,
        "consecutiveFailures": 0,
        "nextRunAt": "2026-10-23T03:00:00Z",
        "intervalSec": 86400
      },
      "policy": {
        "enabled": true,
        "reason": null,
        "plan": "community",
        "retentionDays": 5,
        "maxSites": 1,
        "siteSlotHeld": true,
        "minIntervalSec": 86400,
        "maxObjectBytes": 2147483648,
        "maxSiteBytes": 21474836480,
        "usedBytes": 183500800,
        "backupCount": 5,
        "latestBackupAt": "2026-10-22T03:00:41Z"
      },
      "backups": [
        {
          "id": "bk_8d1f2c",
          "status": "complete",
          "sizeBytes": 36700160,
          "createdAt": "2026-10-22T03:00:02Z",
          "completedAt": "2026-10-22T03:00:41Z",
          "expiresAt": "2026-10-27T03:00:02Z",
          "meta": {
            "keyId": "3fa9c01d5b7e2a64",
            "coreHubVersion": "2.3.1.0",
            "schemaVersions": { "gluesync.db": 36 },
            "contents": [ "gluesync.db", "bootstrap-core-hub.json", "gs-license.dat", "connections" ]
          }
        }
      ],
      "connectError": null,
      "warnings": []
    }
    Field Description

    status

    The local state. See The status object.

    policy

    What Gluesync Connect says this site may do: the plan (community for the free plan, or professional, enterprise, custom), retentionDays, maxSites, minIntervalSec (the plan’s spacing between backups), maxObjectBytes, maxSiteBytes, and the usage of this site: usedBytes, backupCount, latestBackupAt. enabled is false when Gluesync Connect cannot take backups from this site, and reason says why. siteSlotHeld tells whether this site holds the single place of the free plan. null when Gluesync Connect could not be asked.

    backups

    The latest backups that Gluesync Connect holds for this site, newest first. Each has an id, a status, its sizeBytes, createdAt, completedAt and expiresAt, and meta: the keyId that sealed it, the coreHubVersion, the database schemaVersions and the contents. null when Gluesync Connect could not be asked.

    connectError

    Why Gluesync Connect could not be asked, or null.

    warnings

    Facts about the installation that are not errors, for example that the storage key is the default one or that Core Hub runs in DEBUG mode.

    The request makes a call to Gluesync Connect. It stores nothing and starts no backup.

    The status object

    Field Type Description

    state

    string

    disabled, waiting_for_connect, idle, running, rate_limited, error or unavailable. See Check the status.

    enabled

    boolean

    Whether backups are on.

    reason

    string or null

    Machine-readable cause behind waiting_for_connect (not_connected, session_expired) and unavailable (backup_not_configured, site_limit, connect_without_https, connect_unsupported).

    message

    string or null

    The same, in words.

    includeMetrics

    boolean

    Whether the metrics database is part of the backup.

    keyId

    string or null

    The ID of the recovery key that backups are sealed to. null when backups are off.

    lastAttemptAt, lastSuccessAt

    string or null

    ISO-8601 times of the last attempt and of the last backup that reached Gluesync Connect.

    lastBackupId

    string or null

    The ID of the last backup that reached Gluesync Connect.

    lastError

    string or null

    The reason the last attempt failed.

    consecutiveFailures

    integer

    How many attempts in a row failed. After 3, Core Hub raises one notification.

    nextRunAt

    string or null

    When the next backup is due.

    intervalSec

    integer or null

    The spacing between backups that the plan allows, once Gluesync Connect has said so.

    Change the settings

    curl -X PUT https://your-gluesync.com/backup/settings \
      -H "Authorization: Bearer <your-token>" \
      -H "Content-Type: application/json" \
      -d '{"includeMetrics": true}'
    Field Type Description

    includeMetrics

    boolean, required

    Also back up the metrics database. It is large and rebuilt as the platform runs. Off by default.

    The body is limited to 4096 bytes. A field other than includeMetrics, a missing field or a value that is not true or false gives 400 Bad Request, and the field of the error names it. The answer is 200 OK with the backup status.

    Run a backup now

    curl -X POST https://your-gluesync.com/backup/run \
      -H "Authorization: Bearer <your-token>"

    The answer is 202 Accepted:

    { "started": true }

    202 means that the backup was started, not that it finished. Poll GET /backup and read status.state and status.lastSuccessAt.

    A manual backup obeys the same limit as a scheduled one. When the plan allows no backup yet, the answer is 429 Too Many Requests, with the Retry-After header in seconds and the time in the body:

    {
      "notificationId": -1,
      "exceptionMessage": "The plan allows no backup before 2026-10-23T02:50:41Z.",
      "message": "The plan allows no backup before 2026-10-23T02:50:41Z.",
      "code": "backup_rate_limited",
      "field": null,
      "nextAllowedAt": "2026-10-23T02:50:41Z",
      "retryAfterSec": 64241
    }

    Disable backups

    POST /backup/disable turns backups off, cancels a backup in progress and removes the public key from Core Hub. The answer is 200 OK with the backup status. The backups in Gluesync Connect are kept until their retention ends.

    /backup/disable and /backup/run take no body. An empty body or {} is accepted. Any other body gives 400 Bad Request.

    Responses and errors

    A refused request answers with the error body of the other Core Hub endpoints, plus a machine-readable code:

    {
      "notificationId": -1,
      "exceptionMessage": "Backups are already enabled. Rotate the key to use a new recovery key.",
      "message": "Backups are already enabled. Rotate the key to use a new recovery key.",
      "code": "backup_already_enabled",
      "field": null,
      "nextAllowedAt": null,
      "retryAfterSec": null
    }

    notificationId is always -1: a refused request is the answer to the caller, not an event of the platform, and raises no notification. field names the field of a 400. nextAllowedAt and retryAfterSec are set on a 429.

    Status Code Meaning

    200 OK

    The request completed and the body holds the result.

    202 Accepted

    POST /backup/run: a backup was started.

    400 Bad Request

    invalid_request

    The body is not valid. field names the field (publicKey, includeMetrics or body).

    401 Unauthorized

    unauthorized

    The Bearer token is missing, expired or not valid.

    403 Forbidden

    permission_denied

    The user is not a Super Admin.

    403 Forbidden

    recovery_key_generation_not_allowed

    POST /backup/enable and /backup/rotate-key without a publicKey, from an API token, an MCP tool or through Gluesync Connect. Send a public key.

    408 Request Timeout

    Core Hub did not answer in time. The body is plain text, not JSON.

    409 Conflict

    backup_already_enabled

    POST /backup/enable: backups are already on.

    409 Conflict

    backup_not_enabled

    POST /backup/rotate-key and POST /backup/run: backups are off.

    409 Conflict

    backup_running

    POST /backup/run: a backup is already running.

    409 Conflict

    backup_waiting_for_connect

    POST /backup/run: Core Hub is not connected to Gluesync Connect right now.

    409 Conflict

    backup_unavailable

    POST /backup/run: Gluesync Connect cannot take backups from this site. message says why.

    429 Too Many Requests

    backup_rate_limited

    POST /backup/run: the plan allows no backup before nextAllowedAt.

    500 Internal Server Error

    backup_failed

    The operation failed. The Core Hub log has the details.

    MCP tools

    Two MCP tools cover the read and the run of a backup: get_backup_status and run_backup. Both need a Super Admin, and a read-only deployment does not expose run_backup. Turning backups on, rotating the key and turning them off are not available through MCP, Chronos or Bootstrapper, because a person must hold the recovery key. See Core Hub MCP server.