Open API
Overview
Besides the web UI, Spug exposes a small set of HTTP interfaces so that a CI job, an in-house platform or any automation script can trigger it directly:
| Capability | Endpoint | Notes |
|---|---|---|
| Run an execution template | POST /api/apis/exec/<template id>/ | Runs the command of a template on the given hosts |
| Query the execution result | GET /api/apis/exec/result/<token>/ | Status and output of the run above |
| Trigger a pipeline | POST /api/apis/pipeline/<pipeline id>/ | Triggers a pipeline |
| Query the pipeline result | GET /api/apis/pipeline/result/<token>/ | Status and per-node output of the run above |
| Fetch the host inventory | GET /api/apis/host/ | Read-only host list, usable as an Ansible dynamic inventory |
| Fetch application configs | GET /api/apis/config/ | See configuration API |
| Trigger a deploy | POST /api/apis/deploy/<deploy id>/<kind>/ | See webhook auto deploy |
Authentication
Every open endpoint uses the access token from System / Settings / Open service. Pass it either way:
# Option 1: request header (recommended)
curl -H "X-Api-Key: JLV8IGO0DhoxcM7I" https://spug.example.com/api/apis/host/
# Option 2: query parameter
curl "https://spug.example.com/api/apis/host/?apiKey=JLV8IGO0DhoxcM7I"
A wrong or missing token returns 401. Responses use the {"data": ..., "error": null} shape; on failure error holds the message.
The access token is effectively a key that can run commands on all of your hosts. Never commit it to a public repository or ship it in frontend code — keep it in the encrypted variables of your CI and rotate it regularly.
Run an execution template
-
Endpoint:
/api/apis/exec/<template id>/ -
Method:
POST -
The template id is visible in
Batch execution / Templates -
Body (
JSON, may be omitted entirely):Name Type Required Notes host_ids array No Host ids to run on. Defaults to the hosts configured in the template; returns 400when the template has none eitherparams object No Template parameters keyed by variable name. Defaults defined in the template are applied; a missing required parameter returns 400 -
Returns
202and atokenused to query the result
curl -X POST -H "X-Api-Key: JLV8IGO0DhoxcM7I" -H "Content-Type: application/json" \
-d '{"host_ids": [1, 2], "params": {"who": "nolan"}}' \
https://spug.example.com/api/apis/exec/1/
{"data": {"token": "8a35cc6f1a0043bab1964148c977176b"}, "error": null}
Query the execution result
- Endpoint:
/api/apis/exec/result/<token>/ - Method:
GET - Results are kept for one hour; an expired or unknown token returns
404
{
"data": {
"status": "running",
"hosts": [
{
"id": 1,
"title": "web-01(10.0.0.11:22)",
"status": "success",
"exit_code": 0,
"output": "hello from web-01\r\n"
},
{
"id": 2,
"title": "web-02(10.0.0.12:22)",
"status": "running",
"exit_code": null,
"output": ""
}
]
},
"error": null
}
The top-level status aggregates the run: running while any host is still executing, success when all hosts finished without failure, failed otherwise. Per-host status uses the same three values and exit_code is the exit code of the command (Spug itself reports 130 for a timeout and 131 for an exception).
A typical polling loop:
TOKEN=$(curl -s -X POST -H "X-Api-Key: $SPUG_API_KEY" -H "Content-Type: application/json" \
-d '{"params": {"version": "v1.2.3"}}' "$SPUG_URL/api/apis/exec/1/" | jq -r .data.token)
while true; do
STATUS=$(curl -s -H "X-Api-Key: $SPUG_API_KEY" "$SPUG_URL/api/apis/exec/result/$TOKEN/" | jq -r .data.status)
[ "$STATUS" = "running" ] || break
sleep 3
done
echo "result: $STATUS"
[ "$STATUS" = "success" ] || exit 1
Trigger a pipeline
-
Endpoint:
/api/apis/pipeline/<pipeline id>/ -
Method:
POST -
Body (
JSON, may be omitted entirely):Name Type Required Notes params object No Dynamic parameters declared in the parameter node, plus _spug_git_tag/_spug_git_commitfor build nodes. Array values are joined with,. A missing required parameter returns400 -
Returns
202and atoken
curl -X POST -H "X-Api-Key: JLV8IGO0DhoxcM7I" -H "Content-Type: application/json" \
-d '{"params": {"ver": "v1.2.3"}}' \
https://spug.example.com/api/apis/pipeline/11/
A pipeline containing a data upload node cannot be triggered through the API because that node needs a file picked in the browser; such a request returns 400. Runs triggered through the API are recorded under the pipeline's creator.
Query the pipeline result
- Endpoint:
/api/apis/pipeline/result/<token>/ - Method:
GET - Results are kept for one hour
{
"data": {
"pipeline_id": 11,
"status": "success",
"nodes": {
"p1": {"status": "success", "output": "parsing parameters ..."},
"s1": {"status": "success", "output": ""},
"s1.1": {"status": "success", "output": "started ... finished"}
}
},
"error": null
}
The top-level status is running, success or failed. Keys of nodes are node ids; a key shaped like node id.host id carries the output of that node on that host. Node status values match the UI (processing / success / error).
Fetch the host inventory
-
Endpoint:
/api/apis/host/ -
Method:
GET -
Parameters:
Name Type Required Default Notes format string No ansible ansiblereturns anAnsibledynamic inventory,jsonreturns a plain array of hosts
The endpoint is read-only and returns host names, addresses, ports, usernames, groups and the asset information collected by host management. It never returns private keys, passwords or any other credential.
{
"_meta": {
"hostvars": {
"web-01": {
"ansible_host": "10.0.0.11",
"ansible_port": 22,
"ansible_user": "root",
"spug_id": 1,
"spug_groups": ["web"],
"spug_os_name": "CentOS 7.9",
"spug_private_ip_address": ["10.0.0.11"]
}
}
},
"prod": {"hosts": [], "children": ["web"]},
"web": {"hosts": ["web-01"], "children": []},
"all": {"children": ["ungrouped", "prod"]},
"ungrouped": {"hosts": []}
}
Use it as an Ansible dynamic inventory
Create an executable script that simply forwards the response:
#!/bin/sh
case "$1" in
--host) echo '{}' ;;
*) curl -sf -H "X-Api-Key: ${SPUG_API_KEY}" "${SPUG_URL}/api/apis/host/" ;;
esac
chmod +x spug_inventory.sh
export SPUG_API_KEY=JLV8IGO0DhoxcM7I SPUG_URL=https://spug.example.com
ansible-inventory -i ./spug_inventory.sh --graph
ansible -i ./spug_inventory.sh web -m ping
ansible-playbook -i ./spug_inventory.sh deploy.yml --limit prod
The group hierarchy of host management maps to Ansible groups and children; hosts without a group land in ungrouped. Group names may repeat in Spug but not in Ansible, so duplicated names get the group id appended (for example web_6).
Ansible only accepts group names made of letters, digits and underscores. Names containing - or non-ASCII characters trigger an Invalid characters were found in group names warning — it is only a warning and targeting still works. Set transform_invalid_group_chars = ignore in ansible.cfg to silence it.
Ansible connects with the SSH key of the machine it runs on; Spug does not hand out its own key. Use --private-key when needed.