Your First Request
Example API call with curl or HTTP client
This page walks you through making your first authenticated HTTP request to the Trilio Site Recovery API. You will discover the API version, obtain a Keystone token, and call a real resource endpoint — all using curl. By the end you will understand the request shape every subsequent API call follows: where to send it, which headers are required, and how to read the response envelope.
Before you begin, ensure you have:
- Two OpenStack deployments running Victoria or later, with the Trilio Site Recovery service deployed on each via the Juju charm
curl(or any HTTP client such as HTTPie or Pythonrequests)python-openstackclientinstalled viapip— not the snap; the snap is incompatible with thetriliositerecoveryCLI plugin- Keystone credentials for a user who has the
memberanddr_operatorroles granted on their project on both sites — these roles must be pre-created before any cross-site operation - Network access to the primary site's Keystone endpoint (port 5000) and protector-api endpoint (port 8788)
- The Tenant (Project) ID of your OpenStack project — used in every versioned API URL
Tip: Export your environment variables from your site's
openrcfile (source primary-site-openrc.sh) before running the examples below. The commands reference$OS_AUTH_URL,$OS_USERNAME,$OS_PASSWORD,$OS_PROJECT_NAME, and$OS_USER_DOMAIN_NAMEthroughout.
The CLI plugin is a separate Python package from the server. Installing the server package does not install the CLI.
-
Install
python-openstackclientvia pip (not the snap):pip install python-openstackclient -
Install the
triliositerecoveryCLI plugin from theclient/subdirectory of the repository:pip install -e ./clientThis registers the
openstack drcommand group with the OpenStack CLI via thetriliositerecoveryentry point. -
Verify the plugin is loaded:
openstack --help | grep '^ dr' openstack help drYou should see a list of
drsubcommands includingsite,protection group,failover, andfailback. -
Source your site credentials:
source primary-site-openrc.sh
Note: The
openstackclientsnap is incompatible with thetriliositerecoveryplugin. If you have the snap installed, remove it and use the pip-installed package instead.
Every request to the protector-api shares the same three configuration concerns:
API base URL
The protector-api listens on port 8788 by default. The bind address and port are controlled by the [api] section of protector.conf:
| Option | Section | Default | Effect |
|---|---|---|---|
bind_host | [api] | 0.0.0.0 | Interface the API binds to |
bind_port | [api] | 8788 | TCP port the API listens on |
workers | [api] | 4 (compiled-in); 2 in Juju charm deployments | Number of WSGI worker processes |
In Juju charm deployments, do not edit protector.conf directly — the charm overwrites it on every config-changed event. Use juju config with hyphenated option names instead:
juju config trilio-site-recovery bind-port=8788
URL structure
All versioned resource endpoints require the /v1/{tenant_id}/ prefix. A URL such as /{tenant_id}/sites returns 404 — the /v1/ segment is mandatory.
The only endpoints that do not require a tenant ID prefix are:
GET /— version discovery (no authentication required)
Authentication header
All resource endpoints require an X-Auth-Token header containing a token obtained from Keystone (port 5000). The protector-api has no authentication endpoint of its own.
Microversion header
The OpenStack-API-Version: protector <version> header is optional. When omitted, the middleware defaults to microversion 1.0. The supported range is 1.0–1.1. Sending a value outside this range returns HTTP 400.
| Header | Required | Default when omitted |
|---|---|---|
X-Auth-Token | Yes (on resource endpoints) | — |
OpenStack-API-Version | No | protector 1.0 |
Content-Type: application/json | On POST/PUT | — |
Making an API request to Trilio Site Recovery follows three steps every time:
- Discover the API version — confirm the service is reachable and learn the supported microversion range.
- Obtain a Keystone token — authenticate with Keystone on port 5000 and capture the
X-Subject-Tokenfrom the response headers. - Call a resource endpoint — send your authenticated request with the token in
X-Auth-Token.
Step 1 — Discover the API version
The version discovery endpoint requires no authentication and returns the supported microversion range:
PROTECTOR_URL="http://<primary-site-host>:8788"
curl -s -X GET "${PROTECTOR_URL}/" \
-H "Accept: application/json" | python3 -m json.tool
The response wraps version information in a versions array:
{
"versions": [
{
"id": "v1.0",
"status": "CURRENT",
"version": "1.1",
"min_version": "1.0",
"links": [
{
"rel": "self",
"href": "http://<primary-site-host>:8788/v1/"
}
]
}
]
}
Key fields:
version— the maximum supported microversion (1.1)min_version— the minimum supported microversion (1.0)links[0].href— the versioned root URL to use for all resource requests
Step 2 — Obtain a Keystone token
Authenticate with Keystone on port 5000. The token is returned in the X-Subject-Token response header:
KEYSTONE_URL="http://<primary-site-host>:5000"
TOKEN=$(curl -s -i -X POST "${KEYSTONE_URL}/v3/auth/tokens" \
-H "Content-Type: application/json" \
-d '{
"auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": "'"${OS_USERNAME}"'",
"domain": {"name": "'"${OS_USER_DOMAIN_NAME}"'"},
"password": "'"${OS_PASSWORD}"'"
}
}
},
"scope": {
"project": {
"name": "'"${OS_PROJECT_NAME}"'",
"domain": {"name": "Default"}
}
}
}
}' | grep -i 'x-subject-token' | awk '{print $2}' | tr -d '\r')
echo "Token: ${TOKEN}"
Note: Authentication is handled entirely by Keystone. The protector-api has no
/author token endpoint.
Step 3 — Call a resource endpoint
With your token set, call any versioned resource endpoint. Substitute your project UUID for {tenant_id}:
TENANT_ID=$(openstack project show "${OS_PROJECT_NAME}" -f value -c id)
curl -s -X GET "${PROTECTOR_URL}/v1/${TENANT_ID}/sites" \
-H "X-Auth-Token: ${TOKEN}" \
-H "Accept: application/json" \
-H "OpenStack-API-Version: protector 1.1" | python3 -m json.tool
Example 1 — Version discovery
Confirm the service is reachable and read the supported microversion range. No authentication needed.
curl -s -X GET "http://<primary-site-host>:8788/" \
-H "Accept: application/json" | python3 -m json.tool
Expected response (HTTP 200):
{
"versions": [
{
"id": "v1.0",
"status": "CURRENT",
"version": "1.1",
"min_version": "1.0",
"links": [
{
"rel": "self",
"href": "http://<primary-site-host>:8788/v1/"
}
]
}
]
}
Example 2 — Full first-request flow using environment variables
This script sources your OpenRC, obtains a token from Keystone, resolves your tenant ID, and lists Protection Groups.
#!/bin/bash
set -euo pipefail
# 1. Source credentials
source primary-site-openrc.sh
PROTECTOR_URL="http://<primary-site-host>:8788"
KEYSTONE_URL="${OS_AUTH_URL}" # e.g. http://<host>:5000
# 2. Obtain Keystone token
echo "Authenticating with Keystone..."
TOKEN=$(curl -s -i -X POST "${KEYSTONE_URL}/v3/auth/tokens" \
-H "Content-Type: application/json" \
-d "{
\"auth\": {
\"identity\": {
\"methods\": [\"password\"],
\"password\": {
\"user\": {
\"name\": \"${OS_USERNAME}\",
\"domain\": {\"name\": \"${OS_USER_DOMAIN_NAME}\"},
\"password\": \"${OS_PASSWORD}\"
}
}
},
\"scope\": {
\"project\": {
\"name\": \"${OS_PROJECT_NAME}\",
\"domain\": {\"name\": \"Default\"}
}
}
}
}" | grep -i 'x-subject-token' | awk '{print $2}' | tr -d '\r')
[ -z "${TOKEN}" ] && { echo "ERROR: Failed to obtain token"; exit 1; }
echo "Token obtained."
# 3. Resolve tenant ID
TENANT_ID=$(openstack project show "${OS_PROJECT_NAME}" -f value -c id)
echo "Tenant ID: ${TENANT_ID}"
# 4. List Protection Groups
echo "Listing Protection Groups..."
curl -s -X GET "${PROTECTOR_URL}/v1/${TENANT_ID}/protection-groups" \
-H "X-Auth-Token: ${TOKEN}" \
-H "Accept: application/json" \
-H "OpenStack-API-Version: protector 1.1" | python3 -m json.tool
Expected response when no Protection Groups exist yet (HTTP 200):
{
"protection_groups": []
}
Example 3 — List registered sites
Once you have a token and tenant ID, list the DR sites registered with the service:
curl -s -X GET "http://<primary-site-host>:8788/v1/${TENANT_ID}/sites" \
-H "X-Auth-Token: ${TOKEN}" \
-H "Accept: application/json" \
-H "OpenStack-API-Version: protector 1.1" | python3 -m json.tool
Expected response (HTTP 200):
{
"sites": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "primary-cluster",
"site_type": "PRIMARY",
"status": "ACTIVE",
"auth_url": "http://<primary-site-host>:5000"
},
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"name": "secondary-cluster",
"site_type": "SECONDARY",
"status": "ACTIVE",
"auth_url": "http://<secondary-site-host>:5000"
}
]
}
Example 4 — Send an unsupported microversion (expected HTTP 400)
If you send a microversion outside the supported 1.0–1.1 range, the middleware rejects the request:
curl -s -X GET "http://<primary-site-host>:8788/v1/${TENANT_ID}/sites" \
-H "X-Auth-Token: ${TOKEN}" \
-H "OpenStack-API-Version: protector 9.9" | python3 -m json.tool
Expected response (HTTP 400):
{
"error": {
"code": 400,
"message": "Invalid API version '9.9'. Supported versions: 1.0 to 1.1"
}
}
Note that the response also carries OpenStack-API-Version and OpenStack-API-Minimum-Version headers telling you the valid range.
Example 5 — Equivalent CLI call
The same list-sites operation via the openstack dr CLI (which handles token acquisition automatically):
source primary-site-openrc.sh
openstack dr site list
Add -f json for machine-readable output:
openstack dr site list -f json
HTTP 404 on a resource URL
Symptom: curl returns HTTP 404 for a URL like http://<host>:8788/<tenant_id>/sites.
Cause: The /v1/ prefix is missing. All versioned endpoints require the /v1/{tenant_id}/ prefix. URLs without /v1/ return 404.
Fix: Change the URL to http://<host>:8788/v1/<tenant_id>/sites.
HTTP 400 — Invalid API version
Symptom: Response body contains "Invalid API version" and HTTP status is 400.
Cause: The OpenStack-API-Version header contains a version string outside the supported 1.0–1.1 range, or is malformed (e.g. missing the service name prefix protector).
Fix: Use OpenStack-API-Version: protector 1.1 or omit the header entirely (the service defaults to 1.0).
HTTP 401 — Unauthorized
Symptom: Resource endpoint returns HTTP 401.
Cause: The X-Auth-Token header is missing, expired, or was issued for a different project.
Fix: Re-authenticate with Keystone and capture a fresh token. Ensure the token was scoped to the same project whose UUID you are using in the URL path.
HTTP 403 — Forbidden
Symptom: Request returns HTTP 403 on a site or operation endpoint.
Cause: Your user lacks the required Keystone role. DR users need member + dr_operator on their project. Site-level operations additionally require dr_site_admin.
Fix: Ask your cloud administrator to grant the missing roles. Both dr_operator and dr_site_admin must be pre-created on both sites before any cross-site operation.
Token obtained but tenant ID is wrong
Symptom: API returns HTTP 404 or empty results even though the service is running.
Cause: The tenant ID in the URL does not match the project the token is scoped to.
Fix: Resolve the project UUID from the same credentials you used to authenticate:
TENANT_ID=$(openstack project show "${OS_PROJECT_NAME}" -f value -c id)
Use this value in the URL path (/v1/${TENANT_ID}/...).
openstack dr commands not found
Symptom: Running openstack dr site list returns 'dr' is not an openstack command.
Cause: The triliositerecovery CLI plugin is not installed, or you installed the server package (pip install -e . from the repo root) instead of the client package.
Fix: Install the client package separately:
pip install -e ./client
Also ensure you are using the pip-installed openstack binary, not the snap — the snap is incompatible with the plugin.
Connection refused on port 8788
Symptom: curl: (7) Failed to connect to <host> port 8788: Connection refused.
Cause: The protector-api service is not running, or the host/port is incorrect.
Fix: On the Juju-deployed host, check the service status:
juju status trilio-site-recovery
If the unit is in an error state, check logs:
juju debug-log --include trilio-site-recovery
Confirm that port 8788 is open between your client and the API host.
Cross-site operations fail with RemoteSiteUnreachable
Symptom: Failover or metadata sync operations return a RemoteSiteUnreachable error.
Cause: Port 8788 between the two sites is blocked in one direction. Cross-site connectivity on port 8788 must be bidirectional — one-way connectivity causes metadata sync and tenant mapping failures.
Fix: Ensure both sites can reach each other on port 8788. Verify with:
curl -s -X GET "http://<secondary-site-host>:8788/"
from the primary site's host, and vice versa.