API

Backup destinations

Configure and verify storage for backups.

A backup destination is a reusable storage target — an S3-compatible bucket, an SFTP server, a local filesystem path, or another Synq server — that backup policies write their archives to. FTP/FTPS servers, Dropbox and Google Drive are destinations too; the last two connect through OAuth with your own app (see Connect Dropbox or Google Drive). This API creates and manages those targets and runs a preflight that proves a destination is actually reachable (it writes, reads back, and deletes a probe object). Credentials are encrypted at rest; serialized destinations never return secret values, only hasX flags showing which are configured.

This module is available on both self-hosted and cloud instances. The local destination kind is self-hosted only — it is rejected in cloud mode and, on self-hosted, must be explicitly enabled (see below).

Create a destination

const result = await ship.backupDestinations.create({
  name: "prod-s3",
  kind: "s3_compatible",
  bucket: "my-backups",
  region: "us-east-1",
  accessKeyId: "AKIA...",
  secretAccessKey: "...",
});
console.log(result);
curl -X POST "$SYNQ_URL/api/backup-destinations" \
  -H "Authorization: Bearer $SYNQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"prod-s3","kind":"s3_compatible","bucket":"my-backups","region":"us-east-1","accessKeyId":"AKIA...","secretAccessKey":"..."}'

Prop

Type

Required fields depend on kind:

  • s3_compatible — bucket, accessKeyId, and secretAccessKey.
  • sftp — sshHost, sshUser, and one of sftpPassword / sftpPrivateKey.
  • synq_server — serverId (a server in your org).
  • local — endpoint (an absolute path inside BACKUP_LOCAL_ROOT).
  • ftp: ftpHost, ftpUser and ftpPassword. pathPrefix is the folder (absolute, or relative to the login folder).
  • dropbox / google_drive: oauthClientId and oauthClientSecret. pathPrefix is the Dropbox folder (for example /Synq Backups) or the Google Drive folder name (default Synq Backups). The destination can't store backups until it is connected.

Update a destination

Send only the fields you want to change. For credentials, undefined (omitted) leaves the stored value unchanged, null clears it, and a string replaces it. The kind cannot be changed after creation.

const result = await ship.backupDestinations.update("bkd_123", {
  pathPrefix: "nightly/",
  isDefault: true,
});
console.log(result);
curl -X PATCH "$SYNQ_URL/api/backup-destinations/bkd_123" \
  -H "Authorization: Bearer $SYNQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pathPrefix":"nightly/","isDefault":true}'

Prop

Type

Connect Dropbox or Google Drive

Dropbox and Google Drive destinations use an OAuth app you own: a Dropbox app (scoped access, with files.metadata.read, files.content.read and files.content.write) or a Google Cloud OAuth client of type Web application with the https://www.googleapis.com/auth/drive.file scope. After creating the destination with the app's credentials, connect it in two calls:

  1. Start: send the redirect URI, which is the panel's own callback page (<panel origin>/backups/oauth/callback, HTTPS or localhost, registered in the app). The response has the provider URL to open in a browser.
  2. Complete: the provider sends the browser back to that page with code and state. Post both. Synq exchanges the code for a refresh token (stored encrypted), records the approving account, creates or reuses the Google Drive folder, runs a preflight, and returns the destination.
const { authorizeUrl } = await ship.backupDestinations.oauthStart("bkd_123", {
  redirectUri: "https://panel.example.com/backups/oauth/callback",
});
console.log(authorizeUrl); // open it, approve access

// Later, with the query parameters the callback page received:
const destination = await ship.backupDestinations.oauthComplete({ code: "...", state: "..." });
console.log(destination.oauthConnected, destination.oauthAccount);
curl -X POST "$SYNQ_URL/api/backup-destinations/bkd_123/oauth/start" \
  -H "Authorization: Bearer $SYNQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"redirectUri":"https://panel.example.com/backups/oauth/callback"}'

curl -X POST "$SYNQ_URL/api/backup-destinations/oauth/complete" \
  -H "Authorization: Bearer $SYNQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"code":"...","state":"..."}'

The state is sealed (encrypted and authenticated) by Synq and expires after 15 minutes. It only completes for the user and workspace that started the connection. The destination's read model shows the connection as oauthConnected, oauthAccount and oauthConnectedAt, and for Google Drive the folder as remoteFolderId; the refresh token and the app secret are never returned. When the provider stops honoring the refresh token (access removed, or a Google app left in Testing for 7 days), runs and preflights fail with a message that the access "was revoked or has expired"; start and complete the connection again to fix it.

Preflight (verify connectivity)

Proves the destination works by writing, reading back, and deleting a small probe object. On success it stamps lastVerifiedAt; on failure it records the error in lastVerifyError.

const result = await ship.backupDestinations.preflight("bkd_123");
console.log(result);
curl -X POST "$SYNQ_URL/api/backup-destinations/bkd_123/preflight" \
  -H "Authorization: Bearer $SYNQ_TOKEN"

A failed check is still a 200

Preflight returns 200 with {"ok": false, "reason": "..."} when the target is unreachable or misconfigured — the reason (bad credentials, DNS failure, permission denied) is in the body, not the HTTP status. A 404 here means the :id doesn't belong to your organization.

Errors

400 — validation failed

The body is missing a field required for its kind (e.g. an S3 destination without a bucket or credentials), the name is blank / over 80 chars or duplicates an existing destination, or you passed an unsupported kind. http_upload is defined but not yet supported. On the OAuth routes: a redirect URI that is not HTTPS (or localhost) or does not end in /backups/oauth/callback, or a state that is invalid, expired, or was started by another user.

400 — local destinations disabled

A local destination requires BACKUP_ALLOW_LOCAL_DESTINATION=true and a configured BACKUP_LOCAL_ROOT, and the endpoint must resolve to an absolute path inside that root. Both the root and the endpoint are symlink-resolved before the check, so a symlink that escapes the root is rejected. BACKUP_LOCAL_ROOT itself must be a dedicated directory — not /, /var, /var/lib, Synq's own state directory, or anything inside a system tree such as /etc or /var/lib/docker. Local destinations are always rejected in cloud mode.

404 on a per-destination route

The :id doesn't belong to your organization (or was deleted). List your destinations with GET /api/backup-destinations to get valid IDs.

Operations

OperationSDKREST API
List backup storage destinations.backupDestinations.list()GET /api/backup-destinations
backup_destination:list
Create a backup destination.backupDestinations.create(input)POST /api/backup-destinations
backup_destination:write
Test destination configuration before saving it.backupDestinations.preflightDraft(input)POST /api/backup-destinations/preflight
backup_destination:write
List every volume backup schedule with its destination and last run.backupDestinations.volumeBackups()GET /api/backup-destinations/volume-backups
backup_destination:list
Read a destination with credentials protected.backupDestinations.get(id)GET /api/backup-destinations/:id
backup_destination:read
Read destination storage usage.backupDestinations.usage(id)GET /api/backup-destinations/:id/usage
backup_destination:read
Update destination settings.backupDestinations.update(id, input)PATCH /api/backup-destinations/:id
backup_destination:write
Remove an eligible destination.backupDestinations.remove(id)DELETE /api/backup-destinations/:id
backup_destination:admin
Test a saved destination and record its verification state.backupDestinations.preflight(id)POST /api/backup-destinations/:id/preflight
backup_destination:write
Start connecting a Dropbox or Google Drive destination; returns the provider URL to open.backupDestinations.oauthStart(id, input)POST /api/backup-destinations/:id/oauth/start
backup_destination:write
Finish connecting with the code and state the provider sent back.backupDestinations.oauthComplete(input)POST /api/backup-destinations/oauth/complete
backup_destination:write

On this page