Trilio Share Protection Backup and Recovery as a Service for OpenStack Manila shares
Guide

REST Endpoints

All HTTP endpoints with method, path, request schema, and response schema


Overview

This page is the complete HTTP API reference for Trilio Share Protection (Abacá). It covers every endpoint exposed by abaca-api — the single entry point for all product operations — organized by resource type: targets, policies, backups, restores, jobs, usage, and admin operations. All paths are relative to the /v1 base path. Use this reference to understand the exact HTTP method, path, request body fields, response shapes, and error codes for each operation before writing client code or automation scripts.


Prerequisites

Before calling the API you need:

  • A running Abacá control plane (abaca-api reachable, abaca-conductor running)
  • OpenStack ≥ 2023.1 (Antelope) or RHOSO 18 with Keystone, Manila, and Barbican available
  • A valid Keystone token scoped to your project (all tenant endpoints require it; admin endpoints additionally require the admin role)
  • An S3-compatible bucket (AWS S3, MinIO, Ceph RGW, ODF, or Wasabi) with object lock enabled at creation time — required before you can register a backup target
  • Barbican secrets for your S3 access key and secret key, and a Kopia repository password — Abacá only stores Barbican hrefs, never the raw credentials
  • A Keystone trust delegating the necessary permissions to the Abacá service account — obtain the service user ID from GET /v1/service_info before creating the trust
  • python-abacaclient if you prefer the openstack share protection … CLI instead of raw HTTP

Installation

The API is served by abaca-api. Installation of the control plane is covered in the OpenShift deployment guide. To verify the API is reachable and confirm the service identity before enrollment:

  1. Discover the Abacá service user (unauthenticated — no token required):
curl -s https://<abaca-api-host>/v1/service_info | python3 -m json.tool

Expected response:

{
  "service_user": {
    "id": "a1b2c3d4e5f6...",
    "name": "abaca"
  },
  "worker_project": {
    "id": "f6e5d4c3b2a1...",
    "name": "abaca-workers"
  }
}

Record service_user.id — you need it when creating the Keystone trust during enrollment.

  1. Obtain a Keystone token scoped to your project:
export OS_TOKEN=$(openstack token issue -f value -c id)
  1. Confirm authenticated access by listing your backup targets (empty list is expected on a fresh deployment):
curl -s -H "X-Auth-Token: $OS_TOKEN" \
  https://<abaca-api-host>/v1/targets | python3 -m json.tool

Expected response:

{
  "targets": [],
  "pagination": {
    "limit": 100,
    "count": 0,
    "next_marker": null
  }
}

Configuration

Authentication

All tenant endpoints require a Keystone token passed as X-Auth-Token. Admin endpoints (under /v1/admin/…) additionally require the caller to hold the admin role in Keystone. The GET /v1/service_info endpoint is unauthenticated — it exists specifically so clients can self-bootstrap without needing user-list privileges.

Base URL

All paths below are relative to /v1. The API does not currently version-negotiate; pin your client to /v1.

Pagination

All list endpoints use keyset (cursor) pagination. The parameters are the same across every collection:

ParameterTypeDefaultMaximumDescription
limitinteger1001000Maximum items to return in one page
markerstringReturn only items whose id sorts after this value; use pagination.next_marker from the previous response

Every list response body wraps the collection in a pagination object:

{
  "<resource_plural>": [ … ],
  "pagination": {
    "limit": 100,
    "count": 12,
    "next_marker": null
  }
}

When next_marker is non-null, pass it as ?marker=<value> on the next request to fetch the following page.

Expired backups

GET /v1/backups hides backups with status=expired by default. A backup becomes expired when its Kopia manifest has aged out under the policy's retention rules — it is a tombstone and is no longer restorable. To include expired records, pass ?include_expired=true. To scope to a specific status exactly, pass ?status=<value>.

Retention keys

The retention object on a policy accepts only the following keys (all map directly to Kopia --keep-* flags); values must be non-negative integers:

KeyEffect
keep_latestKeep the N most recent snapshots regardless of schedule
keep_hourlyKeep N hourly snapshots
keep_dailyKeep N daily snapshots
keep_weeklyKeep N weekly snapshots
keep_monthlyKeep N monthly snapshots
keep_annualKeep N annual snapshots

An empty retention object ({}) or omitting the field entirely means Kopia keeps every snapshot.

Error shape

Every error response uses a consistent JSON body:

{
  "code": "abaca.not_found",
  "title": "Not Found",
  "detail": "target abc123 not found",
  "category": "tenant_action_required"
}

category is either tenant_action_required (you need to fix your configuration, credentials, or request) or operator_action_required (the infrastructure or service needs attention from an operator).


Usage

Tenant endpoints

The following sections describe the primary workflows you perform as a tenant. All examples assume $OS_TOKEN holds a valid Keystone token and $API is set to the base URL, for example https://abaca.example.com/v1.


Backup Targets

A backup target represents one S3-compatible bucket together with the Barbican secret references needed to access it. Each target holds exactly one Kopia repository.

Register a backup target

POST /v1/targets

Required fields: name, endpoint, bucket. Include trust_id, barbican_secret_map, and optionally repository_password_ref and ca_cert_ref to allow the enrollment job to run immediately. Without trust_id and barbican_secret_map the target is created with status: pending and no enrollment job is dispatched.

Request body fields:

FieldTypeRequiredDescription
namestringyesHuman-readable name
endpointstringyesS3 endpoint URL
bucketstringyesBucket name
trust_idstringnoKeystone trust ID delegating access to Abacá
barbican_secret_mapobjectno{"access_key": "<href>", "secret_key": "<href>"}
barbican_secret_refsarray of stringnoAdditional Barbican secret hrefs
ca_cert_refstringnoBarbican href for a custom CA certificate
addressing_modestringnoS3 addressing style (e.g. path or virtual-hosted)
regionstringnoS3 region hint stored in capabilities

Response: 202 Accepted with {"target": Target, "job": Job} when enrollment is dispatched, or {"target": Target} when inputs are insufficient to enroll.

List targets

GET /v1/targets[?limit=N&marker=M]

Response: 200 OK with paginated targets collection.

Show a target

GET /v1/targets/{target_id}

Response: 200 OK with a single Target object.

Check preflight status

GET /v1/targets/{target_id}/preflight

Returns {target_id, status, detail}. Preflight checks verify bucket reachability, read/write access, addressing style, object lock configuration, and the absence of expiration lifecycle rules.

Update a target

PUT /v1/targets/{target_id} or PATCH /v1/targets/{target_id}

Updatable fields: name, endpoint, bucket, trust_id, ca_cert_ref, barbican_secret_refs.

Response: 200 OK with the updated Target object.

Delete a target

DELETE /v1/targets/{target_id}

Fails with 409 Conflict if any policies or backups reference the target — detach or delete those first. Returns 204 No Content on success.


Protection Policies

A protection policy binds a Manila share to a backup target with a cron schedule and retention rules. One share can have at most one policy.

Create a policy

POST /v1/policies

Required fields: name, share_id, target_id, schedule.

FieldTypeRequiredDescription
namestringyesHuman-readable name
share_idstringyesManila share UUID
target_idstringyesRegistered backup target UUID
schedulestringyesCron expression (e.g. "0 2 * * *")
retentionobjectnokeep_* keys — see Configuration
enabledbooleannoDefault true; set false to pause scheduling
include_patternsarray of stringnoKopia glob patterns for files to include
exclude_patternsarray of stringnoKopia glob patterns for files to exclude

Response: 201 Created with the Policy object.

Returns 409 Conflict (abaca.conflict) when the share already has a policy.

List policies

GET /v1/policies[?target_id=T&limit=N&marker=M]

Pass ?target_id=<id> to filter to policies using a specific target.

Show a policy

GET /v1/policies/{policy_id}

Update a policy (partial update)

PATCH /v1/policies/{policy_id}

Editable fields: name, schedule, retention, enabled, include_patterns, exclude_patterns. Identity fields (share_id, target_id, id, project_id) are immutable — attempting to change them returns 400 abaca.field_immutable.

Delete a policy

DELETE /v1/policies/{policy_id}

Fails with 409 abaca.policy_has_inflight_backup if a backup triggered by this policy is still in creating state. Historical backup records are preserved (their policy_id becomes null). Returns 204 No Content on success.


Backups

A backup is a point-in-time snapshot of a Manila share stored as an encrypted, deduplicated Kopia snapshot in your S3 bucket. Creating a backup is asynchronous — poll the returned job for progress.

Create an on-demand backup

POST /v1/backups

Required fields: share_id, target_id.

FieldTypeRequiredDescription
share_idstringyesManila share UUID to back up
target_idstringyesRegistered backup target UUID
policy_idstringnoAssociate this backup with a policy (set automatically for scheduled backups)

Response: 202 Accepted with {"backup": Backup, "job": Job}.

List backups

GET /v1/backups[?share_id=S&target_id=T&policy_id=P&status=X&include_expired=true&limit=N&marker=M]

All filter parameters compose with AND. Expired backups (status=expired) are hidden by default.

Show a backup

GET /v1/backups/{backup_id}

Delete a backup

DELETE /v1/backups/{backup_id}

Deletes the backup record. Returns 204 No Content.


Restores

A restore recreates files from a backup onto a new share (the default) or back onto the original share. Restoring to the original share is destructive and requires force: true.

Create a restore

POST /v1/restores

Required field: backup_id.

FieldTypeRequiredDescription
backup_idstringyesUUID of the backup to restore
modestringno"new_share" (default) or "in_place"
forcebooleannoRequired true when mode is "in_place"
sub_pathstringnoRestore only this path within the backup
target_share_idstringnoDestination share UUID for new_share mode

Returns 400 abaca.force_required if mode=in_place and force is not true.

Response: 202 Accepted with {"restore": Restore, "job": Job}.

List restores

GET /v1/restores[?limit=N&marker=M]

Show a restore

GET /v1/restores/{restore_id}

Delete a restore

DELETE /v1/restores/{restore_id}

Deletes the restore record (does NOT undo the data written to the share). Fails with 409 abaca.restore_in_progress if the restore is still pending. Returns 204 No Content on success.


Jobs

Jobs are the unit of work for every asynchronous operation (backup, restore, enrollment, maintenance). They are read-only for tenants.

List jobs

GET /v1/jobs[?target_id=T&resource_id=R&limit=N&marker=M]

Pass ?resource_id=<backup_id> or ?resource_id=<restore_id> to fetch jobs for a specific resource.

Show a job

GET /v1/jobs/{job_id}

Response fields:

FieldTypeDescription
idstringJob UUID
job_typestringbackup, restore, target_enroll, or maintenance type
statestringCurrent state (see job state machine below)
progressintegerPercent complete (0–100)
resource_idstringUUID of the associated backup, restore, or target
share_idstringAssociated Manila share UUID
target_idstringAssociated backup target UUID
worker_idstringWorker VM executing the job
attemptsintegerNumber of execution attempts
executed_bystringconductor:<host> in production
errorobject|null{code, detail, category} on failure
created_atstring (ISO 8601)Creation timestamp

Job state machine

Every job moves through these states in order:

queued → provisioning_network → provisioning_source → connecting_repository
       → transferring → finalizing → releasing → available
                                                          ↘ error

Usage

Tenant usage summary

GET /v1/usage

Returns per-tenant metrics for chargeback and capacity planning:

{
  "project_id": "<uuid>",
  "protected_shares": 4,
  "backup_count": 28,
  "protected_bytes": 107374182400,
  "stored_bytes": 21474836480
}

Service Info (unauthenticated)

Get service identity

GET /v1/service_info

No authentication required. Returns the Keystone user ID and project of the Abacá service account. Use service_user.id as the trustee when creating the Keystone trust during target enrollment.


Admin endpoints

All /v1/admin/… endpoints require the admin Keystone role.

Fleet workers

MethodPathDescription
GET/v1/admin/workersList all worker VMs in the fleet
POST/v1/admin/workers/{worker_id}/drainStop assigning new jobs to the worker; let running jobs finish
POST/v1/admin/workers/{worker_id}/retireMark the worker as retired; the conductor will not dispatch to it

All-tenant jobs

MethodPathDescription
GET/v1/admin/jobsList jobs across all tenants
POST/v1/admin/jobs/{job_id}/retryRe-queue a job that is in error state
POST/v1/admin/jobs/{job_id}/cancelCancel an in-progress job

Coverage

MethodPathDescription
GET/v1/admin/coveragePer-share protection coverage across all tenants

All-tenant targets

MethodPathDescription
GET/v1/admin/targetsList backup targets across all tenants

Scheduling

MethodPathDescription
GET/v1/admin/schedulingGet scheduling status ({"paused": false})
POST/v1/admin/scheduling/pausePause policy-triggered scheduled backups
POST/v1/admin/scheduling/resumeResume scheduled backups

Reconciliation

MethodPathDescription
GET/v1/admin/reconciliationGet reconciliation loop status
POST/v1/admin/reconciliation/triggerManually trigger a reconciliation run

Examples

Register a backup target and poll enrollment

This is the first step in any Abacá workflow. You register the bucket, supply your Barbican secret references and Keystone trust, then poll the enrollment job until it reaches available.

# Step 1 — Register the target
curl -s -X POST "$API/targets" \
  -H "X-Auth-Token: $OS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-production-target",
    "endpoint": "https://s3.us-east-1.amazonaws.com",
    "bucket": "my-backup-bucket",
    "trust_id": "e3b0c44298fc...",
    "barbican_secret_map": {
      "access_key": "https://barbican.example.com/v1/secrets/aaaa-1111",
      "secret_key": "https://barbican.example.com/v1/secrets/bbbb-2222"
    }
  }' | python3 -m json.tool

Expected response (202 Accepted):

{
  "target": {
    "id": "tgt-0001-uuid",
    "name": "my-production-target",
    "endpoint": "https://s3.us-east-1.amazonaws.com",
    "bucket": "my-backup-bucket",
    "status": "pending",
    "trust_id": "e3b0c44298fc...",
    "preflight_at": null,
    "created_at": "2024-06-01T10:00:00"
  },
  "job": {
    "id": "job-enroll-uuid",
    "job_type": "target_enroll",
    "state": "queued",
    "progress": 0
  }
}
# Step 2 — Poll the enrollment job
JOB_ID="job-enroll-uuid"
until [ "$(curl -s -H "X-Auth-Token: $OS_TOKEN" "$API/jobs/$JOB_ID" | python3 -c "import sys,json; print(json.load(sys.stdin)['state'])" 2>/dev/null)" = "available" ]; do
  echo "Waiting…"
  sleep 5
done
echo "Enrollment complete"

Create a protection policy with retention

Once the target is enrolled, bind a Manila share to it with a daily backup schedule keeping 7 daily and 4 weekly snapshots.

curl -s -X POST "$API/policies" \
  -H "X-Auth-Token: $OS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "daily-7d4w",
    "share_id": "manila-share-uuid",
    "target_id": "tgt-0001-uuid",
    "schedule": "0 2 * * *",
    "retention": {
      "keep_daily": 7,
      "keep_weekly": 4
    },
    "enabled": true
  }' | python3 -m json.tool

Expected response (201 Created):

{
  "id": "pol-0001-uuid",
  "name": "daily-7d4w",
  "share_id": "manila-share-uuid",
  "target_id": "tgt-0001-uuid",
  "schedule": "0 2 * * *",
  "retention": { "keep_daily": 7, "keep_weekly": 4 },
  "enabled": true,
  "include_patterns": [],
  "exclude_patterns": [],
  "last_run": null,
  "created_at": "2024-06-01T10:05:00"
}

Run an on-demand backup

Trigger a backup immediately outside the scheduled window.

curl -s -X POST "$API/backups" \
  -H "X-Auth-Token: $OS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "share_id": "manila-share-uuid",
    "target_id": "tgt-0001-uuid"
  }' | python3 -m json.tool

Expected response (202 Accepted):

{
  "backup": {
    "id": "bak-0001-uuid",
    "share_id": "manila-share-uuid",
    "target_id": "tgt-0001-uuid",
    "status": "creating",
    "kopia_snapshot_id": null,
    "crash_consistent": false,
    "size_bytes": null,
    "created_at": "2024-06-01T10:10:00"
  },
  "job": {
    "id": "job-backup-uuid",
    "job_type": "backup",
    "state": "queued",
    "progress": 0
  }
}

Poll progress by watching job.state advance through the state machine:

curl -s -H "X-Auth-Token: $OS_TOKEN" "$API/jobs/job-backup-uuid" | python3 -m json.tool

Example in-progress response:

{
  "id": "job-backup-uuid",
  "job_type": "backup",
  "state": "transferring",
  "progress": 42,
  "error": null
}

Restore a backup to a new share

curl -s -X POST "$API/restores" \
  -H "X-Auth-Token: $OS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "backup_id": "bak-0001-uuid",
    "mode": "new_share"
  }' | python3 -m json.tool

Expected response (202 Accepted):

{
  "restore": {
    "id": "rst-0001-uuid",
    "backup_id": "bak-0001-uuid",
    "mode": "new_share",
    "force": false,
    "sub_path": null,
    "status": "pending",
    "created_at": "2024-06-01T11:00:00"
  },
  "job": {
    "id": "job-restore-uuid",
    "job_type": "restore",
    "state": "queued",
    "progress": 0
  }
}

Restore a single subdirectory in-place

In-place restores are destructive and require force: true.

curl -s -X POST "$API/restores" \
  -H "X-Auth-Token: $OS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "backup_id": "bak-0001-uuid",
    "mode": "in_place",
    "force": true,
    "sub_path": "data/reports/2024"
  }' | python3 -m json.tool

List backups for a specific share

curl -s -H "X-Auth-Token: $OS_TOKEN" \
  "$API/backups?share_id=manila-share-uuid&limit=5" | python3 -m json.tool

Expected response:

{
  "backups": [
    {
      "id": "bak-0001-uuid",
      "share_id": "manila-share-uuid",
      "target_id": "tgt-0001-uuid",
      "status": "available",
      "kopia_snapshot_id": "kc9a3f2e1b...",
      "crash_consistent": true,
      "size_bytes": 2147483648,
      "stored_bytes": 536870912,
      "files_count": 14023,
      "created_at": "2024-06-01T10:15:32"
    }
  ],
  "pagination": {
    "limit": 5,
    "count": 1,
    "next_marker": null
  }
}

Pause and resume scheduled backups (admin)

# Pause all scheduled backups across all tenants
curl -s -X POST "$API/admin/scheduling/pause" \
  -H "X-Auth-Token: $ADMIN_TOKEN" | python3 -m json.tool
# {"paused": true}

# Resume
curl -s -X POST "$API/admin/scheduling/resume" \
  -H "X-Auth-Token: $ADMIN_TOKEN" | python3 -m json.tool
# {"paused": false}

Paginating through all jobs (admin)

MARKER=""
while true; do
  URL="$API/admin/jobs?limit=100"
  [ -n "$MARKER" ] && URL="${URL}&marker=${MARKER}"
  RESP=$(curl -s -H "X-Auth-Token: $ADMIN_TOKEN" "$URL")
  MARKER=$(echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['pagination']['next_marker'] or '')")
  echo "$RESP" | python3 -c "import sys,json; [print(j['id'], j['state']) for j in json.load(sys.stdin)['jobs']]"
  [ -z "$MARKER" ] && break
done

Troubleshooting

422 Preflight failed when registering a target

Symptom: POST /v1/targets returns HTTP 422 with code: abaca.preflight_failed.

Likely causes:

  • The S3 bucket is not reachable from the Abacá control plane
  • Object lock is not enabled on the bucket (it must be enabled at bucket creation; it cannot be added later)
  • The bucket has expiration lifecycle rules that would delete backup objects within the retention window
  • The Barbican secret hrefs are invalid or the Keystone trust lacks the necessary permissions to read them
  • Incorrect addressing mode (path vs virtual-hosted) for the bucket's S3 provider

Fix: Review preflight_detail in the error response or call GET /v1/targets/{target_id}/preflight after the target is created. Correct the bucket configuration or credentials, then re-run enrollment (update the target via PATCH and re-POST or trigger re-enrollment through abaca-dev).


409 Conflict when creating a policy

Symptom: POST /v1/policies returns 409 with code: abaca.conflict and the message references share already has a protection policy.

Likely cause: The Manila share UUID you supplied already has a policy in this project. One share can bind to at most one policy.

Fix: Retrieve the existing policy with GET /v1/policies?share_id=<share_id> (once filtering by share_id is available) or list all policies and search for the share ID. Update the existing policy via PATCH, or delete it first if you want to change the target.


400 abaca.field_immutable when patching a policy

Symptom: PATCH /v1/policies/{policy_id} returns 400 with code: abaca.field_immutable.

Likely cause: The request body includes share_id, target_id, id, project_id, or created_at, or an unknown field. These fields are immutable once the policy is created because they are baked into every historical backup the policy has created.

Fix: Remove the immutable or unknown fields from the request body. Only name, schedule, retention, enabled, include_patterns, and exclude_patterns can be changed.


400 abaca.force_required when creating an in-place restore

Symptom: POST /v1/restores returns 400 with code: abaca.force_required.

Likely cause: You specified mode: in_place without setting force: true. In-place restore overwrites the existing share and requires an explicit acknowledgement.

Fix: Add "force": true to the request body. Ensure you understand that this operation is destructive and irreversible.


409 abaca.restore_in_progress when deleting a restore record

Symptom: DELETE /v1/restores/{restore_id} returns 409.

Likely cause: The restore is still in a non-terminal state (pending). A running restore holds a live worker reservation and its record cannot be deleted while the job is active.

Fix: Wait for the restore job to reach available or error (poll GET /v1/jobs/{job_id}), then retry the delete. If the job appears stuck, an operator can cancel it via POST /v1/admin/jobs/{job_id}/cancel or trigger reconciliation via POST /v1/admin/reconciliation/trigger.


409 abaca.policy_has_inflight_backup when deleting a policy

Symptom: DELETE /v1/policies/{policy_id} returns 409.

Likely cause: A backup triggered by this policy is still in creating state.

Fix: Wait for the in-flight backup job to reach a terminal state (available or error), then retry the delete. Reconciliation's stuck-job sweep will walk hung jobs to error on its own cadence if the job is genuinely stuck.


Job stuck in queued indefinitely

Symptom: A backup or restore job stays in state: queued and never advances.

Likely causes:

  1. The abaca-conductor service is not running or cannot reach the message broker
  2. The target associated with the job is not fully enrolled (trust_id or barbican_secret_map was never set — check target status)
  3. All worker VMs are at capacity (all slots occupied)

Fix:

  1. Verify abaca-conductor health and RabbitMQ connectivity with your operator
  2. Check GET /v1/targets/{target_id} — if status is pending, complete enrollment by patching the target with trust_id and barbican_secret_map
  3. An operator can inspect fleet capacity via GET /v1/admin/workers and retire or drain workers that are stuck

error_category: operator_action_required on a failed job

Symptom: GET /v1/jobs/{job_id} returns state: error and error.category: operator_action_required.

Likely cause: The infrastructure or the Abacá service itself encountered a problem — for example, Nova could not boot a worker VM, a network port could not be attached, or the conductor lost its database connection.

Fix: You do not need to change your configuration. Share the job ID and error.detail with your operator. An operator can retry the job via POST /v1/admin/jobs/{job_id}/retry once the underlying infrastructure issue is resolved.


401 Authentication required

Symptom: Any endpoint returns 401.

Likely cause: The X-Auth-Token header is missing, the token has expired, or the token is not scoped to the correct project.

Fix: Re-issue a token with openstack token issue scoped to your project and retry. Keystone tokens have a finite lifetime; ensure your automation refreshes the token before it expires.