Skip to content
Marc Pope edited this page Mar 30, 2026 · 2 revisions

Admin API

BBS provides a REST API for automated provisioning of clients, repositories, and backup plans. This enables infrastructure-as-code workflows with tools like Ansible, Terraform, or CI pipelines.

Authentication

All API requests require a Bearer token in the Authorization header:

Authorization: Bearer bbs_tok_...

Creating Tokens

Via Web UI: Settings > API tab > Create Token

Via CLI (useful for headless bootstrap):

sudo /var/www/bbs/bin/bbs-token create --name "ansible-provisioner"
# Output: bbs_tok_abc123...

sudo /var/www/bbs/bin/bbs-token list
sudo /var/www/bbs/bin/bbs-token revoke "ansible-provisioner"

Tokens have full admin access. The token value is shown once at creation and cannot be retrieved later.


Endpoints

All endpoints accept and return JSON. Base URL: https://your-bbs-server

Clients

List Clients

GET /api/v1/clients

Response:

{
  "clients": [
    {
      "id": 1,
      "name": "web-server-01",
      "hostname": "web01.example.com",
      "ip_address": "10.0.1.5",
      "status": "online",
      "agent_version": "2.18.7",
      "borg_version": "1.4.3",
      "last_heartbeat": "2026-03-30 12:00:00",
      "created_at": "2026-03-01 10:00:00",
      "owner": "admin"
    }
  ]
}

Create Client

POST /api/v1/clients

Request:

{
  "name": "web-server-01"
}

Response (201):

{
  "id": 42,
  "name": "web-server-01",
  "api_key": "a5b8c9d0e1f2...",
  "status": "setup",
  "install_command": "curl -s https://your-server/get-agent | sudo bash -s -- --server https://your-server --key a5b8c9d0e1f2..."
}

The api_key is the agent install key. Use the install_command to install the agent on the target machine.

Get Client Detail

GET /api/v1/clients/{id}

Returns client info including repositories and plans arrays.

Update Client

PUT /api/v1/clients/{id}

Request:

{
  "name": "new-client-name"
}

Delete Client

DELETE /api/v1/clients/{id}

Removes the client, deprovisions SSH access, and deletes storage.


Repositories

List Repositories

GET /api/v1/clients/{id}/repositories

Create Repository (Local)

POST /api/v1/clients/{id}/repositories

Request:

{
  "name": "daily-backup",
  "encryption": "repokey-blake2",
  "passphrase": "optional-custom-passphrase"
}

If passphrase is omitted, one is auto-generated and returned in the response.

Optional fields:

  • storage_location_id - Use a specific storage location (default: server default)
  • encryption - One of: none, repokey, repokey-blake2, authenticated, authenticated-blake2 (default: repokey-blake2)

Response (201):

{
  "id": 15,
  "name": "daily-backup",
  "path": "ssh://bbs-user@host/./daily-backup",
  "encryption": "repokey-blake2",
  "storage_type": "local",
  "passphrase": "A1B2-C3D4-E5F6-G7H8-I9J0"
}

Create Repository (Remote SSH)

POST /api/v1/clients/{id}/repositories

Request:

{
  "name": "offsite-backup",
  "encryption": "repokey-blake2",
  "storage_type": "remote_ssh",
  "remote_ssh_config_id": 3
}

Use GET /api/v1/storage to find available remote_ssh_config_id values.

Rename Repository

PUT /api/v1/clients/{id}/repositories/{repo_id}

Request:

{
  "name": "new-repo-name"
}

Only local repos can be renamed. Blocked while jobs are active.

Delete Repository

DELETE /api/v1/clients/{id}/repositories/{repo_id}

Blocked if backup plans reference the repo or if jobs are active.


Backup Plans

List Plans

GET /api/v1/clients/{id}/plans

Create Plan

POST /api/v1/clients/{id}/plans

Request:

{
  "name": "daily-full",
  "repository_id": 15,
  "directories": "/home\n/etc\n/var/www",
  "excludes": "*.tmp\n*.log",
  "advanced_options": "--compression lz4 --exclude-caches --noatime",
  "frequency": "daily",
  "times": "02:00",
  "prune_days": 7,
  "prune_weeks": 4,
  "prune_months": 6,
  "plugins": [
    {"plugin_config_id": 5}
  ]
}

Schedule fields:

Field Description Default
frequency hourly, daily, weekly, monthly, manual daily
times Time(s) to run, comma-separated (e.g. 02:00,14:00) 02:00
day_of_week 0-6 (Sun-Sat), required for weekly -
day_of_month 1-31 or last, required for monthly -

Prune retention fields:

Field Default
prune_minutes 0
prune_hours 0
prune_days 7
prune_weeks 4
prune_months 6
prune_years 0

Plugin attachment:

Two formats supported:

"plugins": [{"plugin_config_id": 5}, {"plugin_config_id": 8}]

or map format (plugin_id: config_id):

"plugins": {"1": 5, "2": 8}

Update Plan

PUT /api/v1/clients/{id}/plans/{plan_id}

All fields are optional — only provided fields are updated:

{
  "name": "updated-name",
  "directories": "/home\n/etc",
  "excludes": "*.tmp",
  "advanced_options": "--compression zstd --noatime",
  "repository_id": 15,
  "frequency": "daily",
  "times": "03:00,15:00",
  "day_of_week": null,
  "day_of_month": null,
  "timezone": "America/New_York",
  "prune_days": 14,
  "prune_weeks": 8,
  "plugins": [{"plugin_config_id": 5}]
}

Delete Plan

DELETE /api/v1/clients/{id}/plans/{plan_id}

Pause Plan

POST /api/v1/clients/{id}/plans/{plan_id}/pause

Disables the schedule. The plan can still be triggered manually.

Resume Plan

POST /api/v1/clients/{id}/plans/{plan_id}/resume

Re-enables the schedule.

Trigger Backup (Run Now)

POST /api/v1/clients/{id}/plans/{plan_id}/trigger

Queues an immediate backup. Blocked if a backup is already queued/running for this plan.

Response:

{
  "status": "ok",
  "job_id": 456,
  "message": "Backup queued for plan \"daily-full\""
}

Jobs & Queue

List Jobs (Backup History)

GET /api/v1/clients/{id}/jobs

Query parameters:

  • limit — max results (default 50, max 200)
  • offset — pagination offset
  • status — filter by status: queued, sent, running, completed, failed, cancelled

Response:

{
  "jobs": [
    {
      "id": 123,
      "task_type": "backup",
      "status": "completed",
      "plan_name": "daily-full",
      "repository_name": "main-repo",
      "files_total": 15000,
      "files_processed": 15000,
      "bytes_total": 5368709120,
      "bytes_processed": 5368709120,
      "duration_seconds": 342,
      "queued_at": "2026-03-30 02:00:00",
      "started_at": "2026-03-30 02:00:05",
      "completed_at": "2026-03-30 02:05:47"
    }
  ],
  "total": 150,
  "limit": 50,
  "offset": 0
}

Get Job Detail

GET /api/v1/clients/{id}/jobs/{job_id}

Returns full job record including error_log and status_message.

Global Queue

GET /api/v1/queue

Returns all currently queued, sent, and running jobs across all clients.


Plugins

List Plugins

GET /api/v1/plugins

Returns all available plugins (mysql_dump, pg_dump, shell_hook, s3_sync).

Get Plugin Schemas

GET /api/v1/plugins/schema

Returns field definitions for each plugin type, useful for building config forms programmatically.

List Plugin Configs

GET /api/v1/clients/{id}/plugin-configs

Returns named plugin configurations for a client. Sensitive fields (passwords, keys) are masked.

Create Plugin Config

POST /api/v1/clients/{id}/plugin-configs

Request (MySQL example):

{
  "plugin": "mysql_dump",
  "name": "Production MySQL",
  "config": {
    "host": "localhost",
    "port": 3306,
    "user": "bbs_backup",
    "password": "secret123",
    "databases": "*",
    "dump_dir": "/home/bbs/mysql",
    "compress": true,
    "cleanup_after": true
  }
}

Response (201):

{
  "id": 12,
  "plugin": "mysql_dump",
  "name": "Production MySQL"
}

The plugin is automatically enabled for the client when a config is created.


Storage

List Storage Locations

GET /api/v1/storage

Returns both local storage locations and remote SSH configurations:

{
  "local": [
    {"id": 1, "name": "Default", "path": "/var/bbs/home", "is_default": 1}
  ],
  "remote_ssh": [
    {"id": 3, "name": "rsync.net", "remote_host": "ch-s011.rsync.net", "remote_user": "12345", ...}
  ]
}

Example: Full Ansible Workflow

# 1. Generate API token (run once after BBS install)
- name: Generate BBS admin API token
  command: /var/www/bbs/bin/bbs-token create --name "ansible"
  register: bbs_token
  delegate_to: backup_server

# 2. Create client
- name: Create BBS client
  uri:
    url: "https://backup.example.com/api/v1/clients"
    method: POST
    headers:
      Authorization: "Bearer {{ bbs_token.stdout }}"
    body_format: json
    body:
      name: "{{ inventory_hostname }}"
  register: bbs_client

# 3. Install agent
- name: Install BBS agent
  shell: "{{ bbs_client.json.install_command }}"
  args:
    creates: /etc/bbs-agent/config.ini

# 4. Create repository
- name: Create repository
  uri:
    url: "https://backup.example.com/api/v1/clients/{{ bbs_client.json.id }}/repositories"
    method: POST
    headers:
      Authorization: "Bearer {{ bbs_token.stdout }}"
    body_format: json
    body:
      name: "{{ inventory_hostname }}-main"
      encryption: repokey-blake2
  register: bbs_repo

# 5. Create MySQL plugin config
- name: Configure MySQL backup
  uri:
    url: "https://backup.example.com/api/v1/clients/{{ bbs_client.json.id }}/plugin-configs"
    method: POST
    headers:
      Authorization: "Bearer {{ bbs_token.stdout }}"
    body_format: json
    body:
      plugin: mysql_dump
      name: "Production DB"
      config:
        host: localhost
        user: bbs_backup
        password: "{{ mysql_backup_password }}"
        databases: "*"
        dump_dir: /home/bbs/mysql
  register: mysql_config

# 6. Create backup plan with MySQL plugin
- name: Create backup plan
  uri:
    url: "https://backup.example.com/api/v1/clients/{{ bbs_client.json.id }}/plans"
    method: POST
    headers:
      Authorization: "Bearer {{ bbs_token.stdout }}"
    body_format: json
    body:
      name: daily-backup
      repository_id: "{{ bbs_repo.json.id }}"
      directories: "/home\n/etc\n/var/www"
      excludes: "*.tmp\n*.log\n*.cache"
      advanced_options: "--compression lz4 --exclude-caches --noatime"
      frequency: daily
      times: "02:00"
      prune_days: 7
      prune_weeks: 4
      prune_months: 6
      plugins:
        - plugin_config_id: "{{ mysql_config.json.id }}"

Error Responses

Code Meaning
400 Bad request (missing required fields)
401 Invalid or missing API token
403 Token does not belong to an admin user
404 Resource not found
409 Conflict (duplicate name/path)
429 Rate limited (too many failed auth attempts)
500 Server error

All errors return:

{"error": "Description of the problem"}

Clone this wiki locally