API Reference¶
The orchestrator exposes a JSON-over-HTTP API implemented in orchestrator/src/api.py with FastAPI. Interactive documentation is generated automatically at /docs (Swagger UI) and /redoc, and the OpenAPI schema is at /openapi.json.
Base URL. In-cluster: http://orchestrator-service.orchestrator.svc:8000. Locally:
kubectl port-forward -n orchestrator svc/orchestrator-service 8000:8000
export MANA=http://localhost:8000
No authentication
The API has no authentication or authorization. Anyone who can reach it can trigger rollbacks (POST /rollback) and submit forged webhooks. Keep it on a private network, restrict access with NetworkPolicies, or put it behind an authenticating proxy. See Security.
Conventions¶
- Request and response bodies are
application/json. - Validation errors return HTTP
422in FastAPI's standard shape ({"detail": [{"loc": [...], "msg": "...", "type": "..."}]}). - Unexpected errors return HTTP
500with{"detail": "<message>"}. - Timestamps in deployment records are Unix epoch seconds (float).
Endpoint Summary¶
| Method | Path | Purpose |
|---|---|---|
GET |
/ |
Service info and feature list |
GET |
/health |
Liveness and dependency health |
POST |
/webhook/deployment |
Receive deployment events from CI/CD |
POST |
/rollback |
Manually roll a Deployment back to a version |
POST |
/health-check |
Run a deployment health check on demand |
GET |
/deployment/{deployment_id} |
Fetch one stored deployment state |
GET |
/deployments/recent |
List recent deployment states |
GET |
/metrics/deployment/{namespace}/{deployment_name} |
Replica counts and conditions (see known issue) |
GET |
/config |
Effective orchestrator configuration |
GET |
/metrics/ |
Prometheus metrics |
GET /¶
Returns static service information.
curl -s $MANA/
{
"service": "Self-Healing Orchestrator",
"version": "1.0.0",
"status": "healthy",
"features": [
"Application health endpoint monitoring",
"Exponential backoff for retries",
"GitHub Actions retry integration",
"Comprehensive audit logging",
"Automatic rollback on failure",
"Previous version tracking"
],
"github_integration": true
}
github_integration is true only when both GITHUB_TOKEN and GITHUB_REPO are set.
GET /health¶
Checks Redis (PING) and Kubernetes (list_namespace(limit=1)). It always returns HTTP 200; inspect the body for status. This is the endpoint used by the container and Kubernetes probes.
{
"status": "healthy",
"redis": "healthy",
"kubernetes": "healthy",
"github_integration": true
}
status is healthy when both dependencies are healthy, otherwise degraded.
POST /webhook/deployment¶
The entry point for CI/CD events. See Data Flow for what happens next.
Request body¶
| Field | Type | Required | Description |
|---|---|---|---|
deployment_id |
string | yes | Unique ID for this deployment. Retry state is keyed on it |
namespace |
string | yes | Kubernetes namespace of the target |
app_name |
string | yes | Deployment name (also the app label and container name) |
version |
string | yes | Version or image tag being deployed |
status |
string | yes | in_progress, success or failed. Other values are rejected with HTTP 422 |
failure_type |
string | when status=failed |
One of build_failure, test_failure, deployment_failure, health_check_failure, smoke_test_failure, timeout |
metadata |
object | no | Free-form. Include workflow_run_id (string) for retries. Typical keys: commit, author, branch |
curl -s -X POST $MANA/webhook/deployment \
-H 'Content-Type: application/json' \
-d '{
"deployment_id": "deploy-123",
"namespace": "production",
"app_name": "sample-app",
"version": "v1a2b3c4-1760000000",
"status": "failed",
"failure_type": "health_check_failure",
"metadata": {"commit": "1a2b3c4", "branch": "main", "workflow_run_id": "9876543210"}
}'
Responses¶
status=failed (HTTP 200). action_taken depends on the decision:
{
"status": "processed",
"deployment_id": "deploy-123",
"action_taken": {
"action": "retry",
"retry_count": 1,
"backoff_seconds": 10.0,
"workflow_run_id": "9876543210",
"message": "Retry 1/3 scheduled in 10.0s"
}
}
{
"status": "processed",
"deployment_id": "deploy-123",
"action_taken": {
"action": "rollback",
"previous_version": "v0f9e8d7-1759990000",
"message": "Rolled back to version v0f9e8d7-1759990000"
}
}
{
"status": "processed",
"deployment_id": "deploy-123",
"action_taken": {
"action": "alert",
"message": "No previous version available - manual intervention required"
}
}
action |
Meaning | Other fields |
|---|---|---|
retry |
A workflow re-run has been scheduled after the backoff | retry_count, backoff_seconds, workflow_run_id, message |
rollback |
Rollback executed and verified healthy | previous_version, message |
skip |
The failed version is not the one running, so rolling back would change a healthy Deployment | live_version, message |
alert |
Cannot recover automatically (no workflow_run_id, no previous version, a build or test failure, the live version could not be checked, or rollback failed) |
message |
status=success (HTTP 200):
{
"status": "success",
"deployment_id": "deploy-123",
"message": "Deployment successful - version saved for future rollbacks"
}
status=in_progress (HTTP 200): {"status": "acknowledged", "deployment_id": "deploy-123"}
Errors¶
| Code | Cause |
|---|---|
422 |
Missing or mistyped required field, a status other than in_progress, success or failed, an unknown failure_type, or status: failed without a failure_type |
500 |
An internal error such as Redis being unavailable |
Note
The rollback branch runs synchronously inside the request, including the health verification wait (up to 300 s). Configure your CI client's HTTP timeout accordingly, or treat a timeout as "processing" and check GET /deployment/{id} afterwards.
POST /rollback¶
Rolls a Deployment back to a specific version without any retry logic. The image tag of the application container (named deployment_name, or the only container) is replaced and the version labels are updated, then health is verified.
| Field | Type | Required | Description |
|---|---|---|---|
namespace |
string | yes | Target namespace |
deployment_name |
string | yes | Deployment to roll back |
target_version |
string | yes | Image tag to restore |
curl -s -X POST $MANA/rollback \
-H 'Content-Type: application/json' \
-d '{"namespace":"production","deployment_name":"sample-app","target_version":"v0f9e8d7-1759990000"}'
{
"status": "success",
"deployment_id": "manual-rollback-6c1f0c0e-6f58-4a8b-9d5f-0d7a3e1b2c44",
"message": "Rolled back to version v0f9e8d7-1759990000"
}
Returns 500 if the rollback does not become healthy. The body is {"detail": "500: Rollback failed"} (the prefix is an artefact of the endpoint re-wrapping its own HTTPException). A successful call increments rollbacks_total{reason="manual"}. Manual rollbacks are logged under the generated manual-rollback-… ID but do not create a stored state record.
POST /health-check¶
Runs the deployment health check (all replicas ready plus application health) and records its duration.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
namespace |
string | yes | Target namespace | |
deployment_name |
string | yes | Deployment to check | |
timeout |
integer | no | 300 |
Maximum seconds to wait |
curl -s -X POST $MANA/health-check \
-H 'Content-Type: application/json' \
-d '{"namespace":"production","deployment_name":"sample-app","timeout":60}'
{
"deployment_name": "sample-app",
"namespace": "production",
"healthy": true,
"check_duration_seconds": 3.4
}
The call blocks until the check passes, fails or times out.
GET /deployment/{deployment_id}¶
Returns the stored state for one deployment, or 404 ({"detail": "Deployment not found"}) if there is none or it has expired (24 h TTL).
curl -s $MANA/deployment/deploy-123
{
"deployment_id": "deploy-123",
"namespace": "production",
"app_name": "sample-app",
"version": "v1a2b3c4-1760000000",
"status": "rolled_back",
"retry_count": 4,
"failure_type": "health_check_failure",
"previous_version": "v0f9e8d7-1759990000",
"timestamp": 1760000123.45,
"metadata": { "workflow_run_id": "9876543210" }
}
status is one of pending, in_progress, success, failed, rolling_back, rolled_back, retry_scheduled.
GET /deployments/recent¶
| Query parameter | Type | Default | Description |
|---|---|---|---|
namespace |
string | all | Only deployments in this namespace |
limit |
integer | 10 |
Maximum results |
curl -s "$MANA/deployments/recent?namespace=production&limit=5"
{
"count": 1,
"deployments": [
{
"deployment_id": "deploy-123",
"namespace": "production",
"app_name": "sample-app",
"version": "v1a2b3c4-1760000000",
"status": "rolled_back",
"retry_count": 4,
"timestamp": 1760000123.45
}
]
}
Results are sorted newest first. The list is built by scanning all deployment:* keys in Redis, so it is intended for dashboards and debugging rather than high-frequency polling.
GET /metrics/deployment/{namespace}/{deployment_name}¶
Intended to return live replica information straight from Kubernetes:
{
"replicas": 3,
"ready_replicas": 3,
"available_replicas": 3,
"unavailable_replicas": 0,
"conditions": [
{
"type": "Available",
"status": "True",
"reason": "MinimumReplicasAvailable"
}
]
}
Known issue: route shadowed by the Prometheus mount
The Prometheus ASGI app is mounted at /metrics before this route is registered, so requests to /metrics/deployment/<ns>/<name> are handled by the Prometheus app and return Prometheus text instead of the JSON above. Until the route is moved (for example to /deployment-metrics/{namespace}/{name}), use kubectl get deployment <name> -n <ns> -o json or the metrics endpoint for the same information.
GET /config¶
Shows the values the running process is using, which is the fastest way to confirm a configuration change took effect.
{
"max_retries": 3,
"initial_backoff": 10.0,
"max_backoff": 300.0,
"backoff_multiplier": 2.0,
"health_check_timeout": 5,
"max_app_health_failures": 3,
"github_integration_enabled": true
}
GET /metrics/¶
Prometheus text exposition format. A request to /metrics (without the trailing slash) returns a 307 redirect to /metrics/; Prometheus follows redirects, so scraping works with either form.
Application metrics¶
| Metric | Type | Labels | Updated |
|---|---|---|---|
deployments_total |
counter | namespace, status |
every webhook |
retries_total |
counter | namespace, retry_attempt |
when a retry is scheduled |
rollbacks_total |
counter | namespace, reason |
on a successful rollback (reason is the failure type or manual) |
health_check_duration_seconds |
histogram | namespace, deployment |
POST /health-check |
deployment_duration_seconds |
histogram | namespace |
declared, never observed |
active_deployments |
gauge | namespace |
declared, never set |
The standard python_* and process_* collector metrics are also exported. See Monitoring for queries and dashboards.
Using the API from GitHub Actions¶
The shipped workflows send webhooks with curl. A minimal reusable step:
- name: Notify MANA
if: always() && env.ORCHESTRATOR_URL != ''
run: |
curl -sS --fail-with-body --max-time 330 -X POST "$ORCHESTRATOR_URL/webhook/deployment" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg id "$DEPLOYMENT_ID" --arg ns "production" --arg app "sample-app" \
--arg ver "$VERSION" --arg st "${{ job.status == 'success' && 'success' || 'failed' }}" \
--arg run "${{ github.run_id }}" \
'{deployment_id:$id, namespace:$ns, app_name:$app, version:$ver, status:$st,
failure_type:(if $st=="failed" then "deployment_failure" else null end),
metadata:{workflow_run_id:$run}}')"
Using jq to build the JSON avoids quoting bugs when values contain special characters. See CI/CD Integration for the full setup.