Skip to content

Webhook Triggers (API Automation) ​

Trigger backup jobs programmatically via the REST API. Perfect for CI/CD pipelines, cron jobs, Ansible playbooks, and custom automation scripts.

Overview ​

DBackup exposes a simple REST API that allows you to:

  1. Trigger backup jobs on demand
  2. Poll execution status until completion
  3. Retrieve execution logs and results

All API calls require an API Key with appropriate permissions.

Quick Start ​

1. Create an API Key ​

Navigate to Users & Groups → API keys, click New API key and pick Run jobs from CI/CD. The key gets exactly these permissions:

  • jobs:execute - Trigger backup jobs
  • history:read - Poll execution status

2. Trigger a Backup ​

bash
curl -X POST "https://your-instance.com/api/jobs/JOB_ID/run" \
  -H "Authorization: Bearer dbackup_your_api_key"

Response:

json
{
  "success": true,
  "executionId": "clx1abc..."
}

To auto-lock the created backup so it is excluded from all retention policies, pass lock: true in a JSON body:

bash
curl -X POST "https://your-instance.com/api/jobs/JOB_ID/run" \
  -H "Authorization: Bearer dbackup_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"lock":true}'

Locked backups are skipped by the retention policy and must be unlocked manually on the Backups page before they can be deleted.

3. Poll Execution Status ​

bash
curl "https://your-instance.com/api/executions/EXECUTION_ID" \
  -H "Authorization: Bearer dbackup_your_api_key"

Response:

json
{
  "success": true,
  "data": {
    "id": "clx1abc...",
    "jobId": "clx0xyz...",
    "jobName": "Daily MySQL Backup",
    "type": "Backup",
    "status": "Running",
    "progress": 45,
    "stage": "Uploading",
    "startedAt": "2025-01-15T10:30:00.000Z",
    "endedAt": null,
    "duration": null,
    "size": null,
    "path": null,
    "error": null
  }
}

Execution Status Values ​

StatusDescription
PendingJob is queued, waiting for an execution slot
RunningJob is actively running
SuccessJob completed successfully
PartialJob completed, but part of it failed, like the upload to one destination
FailedJob failed - check error field for details
CancelledA user cancelled the run

A run ends with one of the last four, so a script waits until it sees one of them.

Include Execution Logs ​

Add ?includeLogs=true to get full log entries:

bash
curl "https://your-instance.com/api/executions/EXECUTION_ID?includeLogs=true" \
  -H "Authorization: Bearer dbackup_your_api_key"

The API Trigger Dialog ​

Trigger by API in the menu of a job opens a dialog with everything a script needs for that job. The list on its left has these parts:

PartWhat it shows
OverviewThe URL that starts a run, the one that follows it, the job ID and the header, each with its own copy button, and what the requests answer
SetupThree steps: an API key, the request that starts the job and the one that follows its run
ScriptscURL, Bash, Python, TypeScript and Go, with the address and the job filled in
PipelinesGitHub Actions, GitLab CI and Azure DevOps with the skyfay/dbackup:ci image, and an Ansible playbook

For a user who may manage API keys, Create key in the Setup opens the editor of New API key with the Run jobs from CI/CD task, which holds jobs:execute and history:read. The new key shows once and fills into every example until the dialog closes. Until then the examples carry the placeholder dbackup_YOUR_API_KEY, marked in amber, and the pipelines list the two secrets they read with their values.

The scripts end with exit code 0 after Success, 2 after Partial and 1 after Failed or Cancelled, so whatever runs them can tell the outcomes apart.

Finding the Job ID ​

You can find a job's ID in two ways:

  1. In the UI: Open the menu of a job on the Jobs page and pick Trigger by API. The dialog shows the job ID, and every example in it has the ID filled in
  2. Via API: List all jobs with a GET /api/jobs request:
bash
curl "https://your-instance.com/api/jobs" \
  -H "Authorization: Bearer dbackup_your_api_key"

Integration Examples ​

Bash Script (Trigger + Wait) ​

This is the same script used inside the skyfay/dbackup:ci container. It triggers a backup job and polls until completion, with retry logic and optional TLS skip support.

Environment variables:

VariableRequiredDescription
DBACKUP_URLYesBase URL of your DBackup instance (no trailing slash)
JOB_IDYesID of the backup job to trigger
DBACKUP_API_KEYYesAPI key with jobs:execute and history:read permissions
DBACKUP_SKIP_TLS_VERIFYNoSet to 1 to skip TLS certificate verification (self-signed certs)
DBACKUP_AUTO_LOCKNoSet to 1 to lock the backup immediately after creation, excluding it from retention policies
DBACKUP_TIMEOUTNoSeconds to wait for the run before giving up, 3600 by default. The run goes on in DBackup.
DBACKUP_POLL_INTERVALNoSeconds between two questions about the run, 10 by default
bash
#!/usr/bin/env bash

set -o pipefail

require_env() {
  local name="$1"
  if [ -z "${!name}" ]; then
    echo "Missing required environment variable: ${name}" >&2
    exit 1
  fi
}

api_request() {
  local method="$1"
  local url="$2"
  local body="${3:-}"
  local response_file
  local http_code
  local curl_exit
  local curl_args=()

  response_file="$(mktemp)"

  echo "Request: ${method} ${url}" >&2

  if [ "${DBACKUP_SKIP_TLS_VERIFY:-0}" = "1" ]; then
    echo "TLS certificate verification: disabled" >&2
    curl_args+=(--insecure)
  fi

  if [ -n "${body}" ]; then
    curl_args+=(-H "Content-Type: application/json" --data "${body}")
  fi

  http_code=$(curl "${curl_args[@]}" \
    --connect-timeout 10 \
    --max-time 60 \
    --retry 3 \
    --retry-delay 2 \
    --retry-all-errors \
    -sS -o "${response_file}" -w "%{http_code}" -X "${method}" "${url}" \
    -H "Authorization: Bearer ${DBACKUP_API_KEY}")
  curl_exit=$?

  echo "Response HTTP status: ${http_code}" >&2

  if [ "${curl_exit}" -ne 0 ]; then
    echo "curl failed with exit code ${curl_exit}" >&2
    echo "Response body:" >&2
    cat "${response_file}" >&2
    rm -f "${response_file}"
    return "${curl_exit}"
  fi

  if [ "${http_code}" -lt 200 ] || [ "${http_code}" -ge 300 ]; then
    echo "Request failed with HTTP status ${http_code}" >&2
    echo "Response body:" >&2
    cat "${response_file}" >&2
    rm -f "${response_file}"
    return 1
  fi

  cat "${response_file}"
  rm -f "${response_file}"
}

require_seconds() {
  local name="$1"
  local value="$2"
  if ! [[ "${value}" =~ ^[0-9]+$ ]]; then
    echo "${name} must be a whole number of seconds, got: ${value}" >&2
    exit 1
  fi
}

json_value() {
  local response="$1"
  local filter="$2"
  local description="$3"
  local value

  if ! value=$(echo "${response}" | jq -er "${filter}"); then
    echo "Could not read ${description} from response JSON" >&2
    echo "Response body:" >&2
    echo "${response}" >&2
    return 1
  fi

  echo "${value}"
}

require_env "DBACKUP_URL"
require_env "JOB_ID"
require_env "DBACKUP_API_KEY"

# How long to wait for the run, one hour unless the pipeline says otherwise. The run itself goes
# on in DBackup when the script gives up.
TIMEOUT="${DBACKUP_TIMEOUT:-3600}"
POLL_INTERVAL="${DBACKUP_POLL_INTERVAL:-10}"
require_seconds "DBACKUP_TIMEOUT" "${TIMEOUT}"
require_seconds "DBACKUP_POLL_INTERVAL" "${POLL_INTERVAL}"

TRIGGER_BODY=""
if [ "${DBACKUP_AUTO_LOCK:-0}" = "1" ]; then
  echo "Auto-lock enabled: backup will be locked after creation" >&2
  TRIGGER_BODY='{"lock":true}'
fi

RESPONSE=$(api_request "POST" "${DBACKUP_URL}/api/jobs/${JOB_ID}/run" "${TRIGGER_BODY}") || exit 1

EXECUTION_ID=$(json_value "${RESPONSE}" '.executionId' "execution id") || exit 1
echo "Execution started: $EXECUTION_ID"

# Ends with 0 after Success, 2 after Partial and 1 after Failed, Cancelled, a timeout or an error.
DEADLINE=$(( $(date +%s) + TIMEOUT ))
ATTEMPT=0
while true; do
  ATTEMPT=$(( ATTEMPT + 1 ))
  RESPONSE=$(api_request "GET" "${DBACKUP_URL}/api/executions/${EXECUTION_ID}") || exit 1

  STATUS=$(json_value "${RESPONSE}" '.data.status' "execution status") || exit 1
  echo "Attempt ${ATTEMPT}: Status=${STATUS}"

  case "${STATUS}" in
    "Success")
      echo "Backup completed!"
      exit 0
      ;;
    "Partial")
      echo "Backup completed, but part of it failed. See the run in DBackup."
      echo "Response body:"
      echo "${RESPONSE}"
      exit 2
      ;;
    "Failed")
      ERROR=$(echo "${RESPONSE}" | jq -r '.data.error // "Unknown"')
      echo "Backup failed: ${ERROR}"
      echo "Response body:"
      echo "${RESPONSE}"
      exit 1
      ;;
    "Cancelled")
      echo "Backup cancelled in DBackup"
      exit 1
      ;;
    "Pending"|"Running")
      ;;
    *)
      echo "Unknown status: ${STATUS}"
      exit 1
      ;;
  esac

  if [ "$(date +%s)" -ge "${DEADLINE}" ]; then
    echo "Backup still ${STATUS} after ${TIMEOUT} seconds, giving up. The run goes on in DBackup."
    exit 1
  fi
  sleep "${POLL_INTERVAL}"
done

Requirements: curl, jq

The script ends with exit code 0 after Success, 2 after Partial and 1 after Failed, Cancelled, a timeout or an error.

Ansible Playbook ​

yaml
- name: Run the DBackup job and wait for it
  hosts: localhost
  vars:
    dbackup_url: "https://your-instance.com"
    dbackup_api_key: "dbackup_your_api_key"  # Better kept in Ansible Vault
    job_id: "your-job-id"

  tasks:
    - name: Start the job
      ansible.builtin.uri:
        url: "{{ dbackup_url }}/api/jobs/{{ job_id }}/run"
        method: POST
        headers:
          Authorization: "Bearer {{ dbackup_api_key }}"
        status_code: 200
      register: run

    - name: Wait until the run is done
      ansible.builtin.uri:
        url: "{{ dbackup_url }}/api/executions/{{ run.json.executionId }}"
        headers:
          Authorization: "Bearer {{ dbackup_api_key }}"
      register: poll
      until: poll.json.data.status in ['Success', 'Partial', 'Failed', 'Cancelled']
      retries: 360
      delay: 10

    - name: Fail unless the backup succeeded
      ansible.builtin.fail:
        msg: "Backup {{ poll.json.data.status }}: {{ poll.json.data.error | default('see the run in DBackup', true) }}"
      when: poll.json.data.status != 'Success'

The playbook waits up to an hour for the run.

CI/CD Pipelines ​

DBackup provides a lightweight helper container image - skyfay/dbackup:ci - that bundles everything needed to trigger a job and wait for its completion. No manual curl/jq scripting required in your pipeline.

The image is available on Docker Hub and GHCR:

  • skyfay/dbackup:ci
  • ghcr.io/skyfay/dbackup:ci

Environment variables:

VariableRequiredDescription
DBACKUP_URLYesBase URL of your DBackup instance (no trailing slash)
JOB_IDYesID of the backup job to trigger
DBACKUP_API_KEYYesAPI key with jobs:execute and history:read permissions
DBACKUP_SKIP_TLS_VERIFYNoSet to 1 to skip TLS certificate verification (self-signed certs)
DBACKUP_AUTO_LOCKNoSet to 1 to lock the backup immediately after creation, excluding it from retention policies
DBACKUP_TIMEOUTNoSeconds to wait for the run before giving up, 3600 by default. The run goes on in DBackup.
DBACKUP_POLL_INTERVALNoSeconds between two questions about the run, 10 by default

GitHub Actions ​

Add DBACKUP_URL and DBACKUP_API_KEY as repository secrets under Settings → Secrets and variables → Actions. Add DBACKUP_JOB_ID as a repository variable.

yaml
# .github/workflows/backup.yml
name: Trigger Database Backup

on:
  schedule:
    - cron: "0 2 * * *" # Daily at 2:00 AM UTC
  workflow_dispatch: # Allow manual trigger

jobs:
  backup:
    runs-on: ubuntu-latest
    container: skyfay/dbackup:ci
    steps:
      - name: Trigger and wait for backup
        run: /backup/execute.sh
        env:
          DBACKUP_URL: ${{ secrets.DBACKUP_URL }}
          JOB_ID: ${{ vars.DBACKUP_JOB_ID }}
          DBACKUP_API_KEY: ${{ secrets.DBACKUP_API_KEY }}
          # DBACKUP_SKIP_TLS_VERIFY: "1" # Uncomment if using self-signed certificates

GitLab CI ​

Add DBACKUP_URL and DBACKUP_API_KEY as CI/CD variables under Settings → CI/CD → Variables (mark DBACKUP_API_KEY as masked).

yaml
# .gitlab-ci.yml
trigger-backup:
  image: skyfay/dbackup:ci
  script:
    - /backup/execute.sh
  variables:
    DBACKUP_URL: $DBACKUP_URL
    JOB_ID: $DBACKUP_JOB_ID
    DBACKUP_API_KEY: $DBACKUP_API_KEY
    # DBACKUP_SKIP_TLS_VERIFY: "1" # Uncomment if using self-signed certificates
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
    - if: $CI_PIPELINE_SOURCE == "web" # Allow manual trigger from GitLab UI

Azure DevOps ​

Add DBACKUP_URL and DBACKUP_API_KEY as pipeline variables under Pipelines → Edit → Variables (mark DBACKUP_API_KEY as secret). Requires a self-hosted agent pool with Docker support.

yaml
# azure-pipelines.yml
trigger: none

schedules:
  - cron: "0 2 * * *" # Daily at 2:00 AM UTC
    displayName: Daily backup
    branches:
      include:
        - main
    always: true

stages:
  - stage: Backup
    jobs:
      - job: TriggerBackup
        displayName: Trigger dbackup job
        container: skyfay/dbackup:ci
        steps:
          - script: /backup/execute.sh
            displayName: Trigger and wait for backup
            env:
              DBACKUP_URL: $(DBACKUP_URL)
              JOB_ID: $(DBACKUP_JOB_ID)
              DBACKUP_API_KEY: $(DBACKUP_API_KEY)
              # DBACKUP_SKIP_TLS_VERIFY: "1" # Uncomment if using self-signed certificates

Docker Compose Healthcheck Integration ​

Trigger a backup before deploying a new database version:

bash
#!/bin/bash
# pre-deploy-backup.sh
set -euo pipefail

echo "Creating pre-deploy backup..."
RESPONSE=$(curl -sf -X POST "${DBACKUP_URL}/api/jobs/${BACKUP_JOB_ID}/run" \
  -H "Authorization: Bearer ${DBACKUP_API_KEY}")

EXECUTION_ID=$(echo "$RESPONSE" | jq -r '.executionId')

# Quick wait (max 5 minutes)
for i in $(seq 1 30); do
  STATUS=$(curl -sf "${DBACKUP_URL}/api/executions/${EXECUTION_ID}" \
    -H "Authorization: Bearer ${DBACKUP_API_KEY}" | jq -r '.data.status')

  if [ "$STATUS" = "Success" ]; then
    echo "Backup complete - safe to deploy"
    exit 0
  elif [ "$STATUS" = "Partial" ] || [ "$STATUS" = "Failed" ] || [ "$STATUS" = "Cancelled" ]; then
    echo "Backup ended as $STATUS - aborting deploy!"
    exit 1
  fi
  sleep 10
done

echo "Backup timed out - aborting deploy!"
exit 1

API Reference ​

POST /api/jobs/:id/run ​

Trigger a backup job execution.

Required permission: jobs:execute

Headers:

Authorization: Bearer dbackup_your_api_key

Response (200):

json
{
  "success": true,
  "executionId": "clx1abc..."
}

Error Responses:

StatusBodyCause
401{ "error": "..." }Invalid/disabled/expired API key
403{ "error": "..." }Missing jobs:execute permission
404{ "error": "Job not found" }Invalid job ID

GET /api/executions/:id ​

Poll execution status and progress.

Required permission: history:read

Query Parameters:

ParameterTypeDefaultDescription
includeLogsbooleanfalseInclude full execution log entries

Headers:

Authorization: Bearer dbackup_your_api_key

Response (200):

json
{
  "success": true,
  "data": {
    "id": "string",
    "jobId": "string",
    "jobName": "string",
    "type": "Backup | Restore",
    "status": "Pending | Running | Success | Partial | Failed | Cancelled",
    "progress": "number | null",
    "stage": "string | null",
    "startedAt": "ISO 8601 | null",
    "endedAt": "ISO 8601 | null",
    "duration": "number (ms) | null",
    "size": "number (bytes) | null",
    "path": "string | null",
    "error": "string | null",
    "logs": "[...] (only with includeLogs=true)"
  }
}

Rate Limits ​

API requests are subject to the same rate limits as the web interface:

TypeLimit
GET requests100/min per IP
POST/PUT/DELETE20/min per IP

Troubleshooting ​

401 Unauthorized ​

  • Verify the API key is correct and starts with dbackup_
  • Check if the key is enabled (not disabled)
  • Check if the key has expired

403 Forbidden ​

  • The key is valid but lacks the required permission
  • Add the missing permission under Users & Groups → API keys
  • The group of the key's owner lacks it. A key never uses more than its owner may do, its panel lists such permissions as paused

Job not starting (Pending) ​

  • The job may be queued due to the max concurrent jobs setting
  • A run of the same job may still be going, the new one starts right after it
  • Check Runs at the same time under Settings → General

Released under the GNU General Public License. | Privacy · Legal Notice