AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

AAP 2.6 REST API Guide: Automate the Automation Platform

By Luca Berton · Published 2024-01-01 · Category: troubleshooting

Master the AAP 2.6 REST API for programmatic management. Launch jobs, manage inventories, create credentials, query job results, and integrate AAP with CI/CD.

AAP 2.6 API Overview

Every action available in the AAP web UI is also available through the REST API. The API enables:

  • Programmatic job execution — trigger automation from scripts, CI/CD, or external tools
  • Integration with ITSM platforms — ServiceNow, Jira, PagerDuty
  • Self-service portals — build custom UIs on top of AAP
  • Bulk operations — manage hundreds of hosts, credentials, or templates
  • Monitoring and reporting — extract job data for dashboards

Authentication

Personal Access Tokens (PAT)

The recommended authentication method:

# Create a token via API
curl -s -k -X POST \
  -H "Content-Type: application/json" \
  -u "admin:password" \
  "https://gateway.example.org/api/controller/v2/tokens/" \
  -d '{"description": "CI/CD Pipeline Token", "scope": "write"}'

# Response
{
  "id": 1,
  "token": "abc123def456...",
  "description": "CI/CD Pipeline Token",
  "scope": "write"
}

Using Tokens

# Bearer token (recommended)
curl -s -k -H "Authorization: Bearer abc123def456..." \
  "https://gateway.example.org/api/controller/v2/ping/"

# Or via OAuth2 header
curl -s -k -H "Authorization: Bearer abc123def456..." \
  "https://gateway.example.org/api/controller/v2/me/"

OAuth2 Application Tokens

For service-to-service integration:

- name: Create OAuth2 application
  ansible.builtin.uri:
    url: "https://gateway.example.org/api/controller/v2/applications/"
    method: POST
    headers:
      Authorization: "Bearer {{ admin_token }}"
      Content-Type: "application/json"
    body_format: json
    body:
      name: "ServiceNow Integration"
      organization: 1
      authorization_grant_type: "password"
      client_type: "confidential"
    validate_certs: false

See also: AAP 2.6 CI/CD Pipeline Integration: GitOps Workflows with Jenkins, GitLab, and GitHub Actions

API Endpoints Reference

Core Resources

EndpointDescription
/api/controller/v2/ping/Health check (no auth required)
/api/controller/v2/me/Current user info
/api/controller/v2/config/Platform configuration
/api/controller/v2/dashboard/Summary statistics

Automation Resources

EndpointDescription
/api/controller/v2/job_templates/Job templates CRUD
/api/controller/v2/workflow_job_templates/Workflow templates
/api/controller/v2/jobs/Job history and status
/api/controller/v2/workflow_jobs/Workflow job history
/api/controller/v2/inventories/Inventories
/api/controller/v2/hosts/Hosts
/api/controller/v2/groups/Host groups
/api/controller/v2/projects/SCM projects
/api/controller/v2/credentials/Credentials (metadata only)
/api/controller/v2/schedules/Job schedules
/api/controller/v2/notification_templates/Notifications

Platform Resources

EndpointDescription
/api/controller/v2/organizations/Organizations
/api/controller/v2/teams/Teams
/api/controller/v2/users/Users
/api/controller/v2/roles/RBAC roles
/api/controller/v2/instances/Mesh instances
/api/controller/v2/instance_groups/Instance groups
/api/controller/v2/execution_environments/EE images
/api/controller/v2/activity_stream/Audit log

Common API Operations

Launch a Job Template

# Launch with defaults
curl -s -k -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "https://gateway.example.org/api/controller/v2/job_templates/15/launch/"

# Launch with parameters
curl -s -k -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "extra_vars": {"app_version": "2.4.1", "deploy_env": "staging"},
    "limit": "webservers",
    "inventory": 5,
    "credentials": [3, 7]
  }' \
  "https://gateway.example.org/api/controller/v2/job_templates/15/launch/"

# Response includes job ID
{
  "id": 423,
  "url": "/api/controller/v2/jobs/423/",
  "status": "pending",
  ...
}

Wait for Job Completion

# Poll job status
JOB_ID=423
while true; do
  STATUS=$(curl -s -k -H "Authorization: Bearer $TOKEN" \
    "https://gateway.example.org/api/controller/v2/jobs/$JOB_ID/" | \
    jq -r '.status')
  echo "Job $JOB_ID: $STATUS"
  case $STATUS in
    successful|failed|error|canceled) break ;;
  esac
  sleep 5
done

Get Job Output

# Job stdout
curl -s -k -H "Authorization: Bearer $TOKEN" \
  "https://gateway.example.org/api/controller/v2/jobs/423/stdout/?format=txt"

# Job events (structured)
curl -s -k -H "Authorization: Bearer $TOKEN" \
  "https://gateway.example.org/api/controller/v2/jobs/423/job_events/?page_size=50" | \
  jq '.results[] | {counter: .counter, event: .event, task: .task, host: .host_name, changed: .changed}'

Create a Host

curl -s -k -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "web05.example.com",
    "inventory": 3,
    "enabled": true,
    "variables": "{\"ansible_host\": \"192.168.1.15\", \"http_port\": 8080}"
  }' \
  "https://gateway.example.org/api/controller/v2/hosts/"

Search and Filter

# Search job templates by name
curl -s -k -H "Authorization: Bearer $TOKEN" \
  "https://gateway.example.org/api/controller/v2/job_templates/?search=patching"

# Filter hosts by group
curl -s -k -H "Authorization: Bearer $TOKEN" \
  "https://gateway.example.org/api/controller/v2/hosts/?group__name=webservers"

# Filter jobs by status and date
curl -s -k -H "Authorization: Bearer $TOKEN" \
  "https://gateway.example.org/api/controller/v2/jobs/?status=failed&finished__gte=2026-04-01"

# Order by field
curl -s -k -H "Authorization: Bearer $TOKEN" \
  "https://gateway.example.org/api/controller/v2/jobs/?order_by=-finished&page_size=10"

Python API Client

Using requests

#!/usr/bin/env python3
"""AAP API client example"""
import requests
import time
import json

class AAPClient:
    def __init__(self, gateway_url, token, verify_ssl=False):
        self.base_url = f"{gateway_url}/api/controller/v2"
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json"
        })
        self.session.verify = verify_ssl

    def launch_job(self, template_id, extra_vars=None, limit=None):
        """Launch a job template and return job ID"""
        payload = {}
        if extra_vars:
            payload["extra_vars"] = extra_vars
        if limit:
            payload["limit"] = limit

        resp = self.session.post(
            f"{self.base_url}/job_templates/{template_id}/launch/",
            json=payload
        )
        resp.raise_for_status()
        return resp.json()

    def wait_for_job(self, job_id, timeout=600, interval=5):
        """Wait for job completion, return final status"""
        elapsed = 0
        while elapsed < timeout:
            resp = self.session.get(f"{self.base_url}/jobs/{job_id}/")
            resp.raise_for_status()
            job = resp.json()
            status = job["status"]
            if status in ("successful", "failed", "error", "canceled"):
                return job
            time.sleep(interval)
            elapsed += interval
        raise TimeoutError(f"Job {job_id} did not complete in {timeout}s")

    def get_job_output(self, job_id):
        """Get job stdout"""
        resp = self.session.get(
            f"{self.base_url}/jobs/{job_id}/stdout/",
            params={"format": "txt"}
        )
        return resp.text

    def list_inventories(self):
        """List all inventories"""
        resp = self.session.get(f"{self.base_url}/inventories/")
        resp.raise_for_status()
        return resp.json()["results"]

    def add_host(self, inventory_id, hostname, variables=None):
        """Add a host to an inventory"""
        payload = {
            "name": hostname,
            "inventory": inventory_id,
            "enabled": True
        }
        if variables:
            payload["variables"] = json.dumps(variables)
        resp = self.session.post(f"{self.base_url}/hosts/", json=payload)
        resp.raise_for_status()
        return resp.json()


# Usage
client = AAPClient("https://gateway.example.org", "your-token")

# Launch and wait
job = client.launch_job(15, extra_vars={"app_version": "2.4.1"})
print(f"Launched job {job['id']}")

result = client.wait_for_job(job["id"])
print(f"Job finished: {result['status']}")

if result["status"] == "failed":
    print(client.get_job_output(job["id"]))

Using awxkit (Official SDK)

from awxkit import api
from awxkit.config import config

config.base_url = "https://gateway.example.org"
config.credentials = {"default": {"username": "admin", "password": "password"}}

root = api.Api()
root.load_session().get()

# Launch job
jt = root.get("/api/controller/v2/job_templates/15/").results
job = jt.launch(extra_vars={"env": "staging"})
job.wait_until_completed()
print(f"Status: {job.status}")

See also: Ansible Tower API: Automate Jobs, Inventories & Workflows via REST (Guide)

CI/CD Integration Examples

GitHub Actions

# .github/workflows/deploy.yml
name: Deploy via AAP
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Trigger AAP deployment
        run: |
          RESPONSE=$(curl -s -k -X POST \
            -H "Authorization: Bearer ${{ secrets.AAP_TOKEN }}" \
            -H "Content-Type: application/json" \
            -d '{"extra_vars": {"app_version": "${{ github.sha }}"}}' \
            "${{ secrets.AAP_URL }}/api/controller/v2/job_templates/${{ vars.DEPLOY_TEMPLATE_ID }}/launch/")

          JOB_ID=$(echo $RESPONSE | jq -r '.id')
          echo "Launched AAP job $JOB_ID"

          # Wait for completion
          while true; do
            STATUS=$(curl -s -k \
              -H "Authorization: Bearer ${{ secrets.AAP_TOKEN }}" \
              "${{ secrets.AAP_URL }}/api/controller/v2/jobs/$JOB_ID/" | \
              jq -r '.status')
            echo "Status: $STATUS"
            case $STATUS in
              successful) exit 0 ;;
              failed|error|canceled) exit 1 ;;
            esac
            sleep 10
          done

GitLab CI

# .gitlab-ci.yml
deploy:
  stage: deploy
  script:
    - |
      JOB_ID=$(curl -s -k -X POST \
        -H "Authorization: Bearer $AAP_TOKEN" \
        -H "Content-Type: application/json" \
        -d "{\"extra_vars\": {\"version\": \"$CI_COMMIT_SHA\"}}" \
        "$AAP_URL/api/controller/v2/job_templates/$DEPLOY_TEMPLATE_ID/launch/" | \
        jq -r '.id')
      echo "AAP Job: $JOB_ID"

Jenkins Pipeline

pipeline {
    agent any
    stages {
        stage('Deploy via AAP') {
            steps {
                script {
                    def response = httpRequest(
                        url: "${AAP_URL}/api/controller/v2/job_templates/${DEPLOY_TEMPLATE_ID}/launch/",
                        httpMode: 'POST',
                        customHeaders: [[name: 'Authorization', value: "Bearer ${AAP_TOKEN}"]],
                        contentType: 'APPLICATION_JSON',
                        requestBody: '{"extra_vars": {"version": "' + env.BUILD_TAG + '"}}',
                        ignoreSslErrors: true
                    )
                    def jobId = readJSON(text: response.content).id
                    echo "Launched AAP job ${jobId}"
                }
            }
        }
    }
}

ServiceNow Integration

Trigger AAP from ServiceNow Incident

# ServiceNow Business Rule or Flow Designer script
import requests

AAP_URL = "https://gateway.example.org"
AAP_TOKEN = "your-service-token"

def trigger_remediation(incident):
    """Auto-remediate based on incident category"""
    template_map = {
        "disk_full": 15,      # Disk Cleanup template
        "service_down": 22,   # Service Restart template
        "cert_expiry": 31,    # Certificate Renewal template
    }

    template_id = template_map.get(incident.category)
    if not template_id:
        return

    response = requests.post(
        f"{AAP_URL}/api/controller/v2/job_templates/{template_id}/launch/",
        headers={"Authorization": f"Bearer {AAP_TOKEN}"},
        json={
            "extra_vars": {
                "incident_number": incident.number,
                "target_host": incident.cmdb_ci,
                "priority": incident.priority
            }
        },
        verify=False
    )

    # Update incident with AAP job link
    job_id = response.json()["id"]
    incident.work_notes = f"Automated remediation triggered: AAP Job #{job_id}"
    incident.update()

See also: Automate Ansible Collection Testing with GitHub Actions

Webhook Receivers

AAP can receive webhooks from GitHub, GitLab, and other systems:

- name: Configure GitHub webhook on job template
  ansible.platform.job_template:
    controller_host: "{{ gateway_url }}"
    controller_username: "{{ controller_user }}"
    controller_password: "{{ controller_pass }}"
    name: "Deploy on Push"
    webhook_service: "github"
    webhook_credential: "GitHub Webhook Secret"
    state: present

The webhook URL is:

POST https://gateway.example.org/api/controller/v2/job_templates/{id}/github/

Configure this URL in GitHub repository Settings → Webhooks.

Pagination and Bulk Operations

Handle Paginated Results

def get_all_results(client, endpoint):
    """Fetch all pages of a paginated API response"""
    results = []
    url = f"{client.base_url}{endpoint}"
    while url:
        resp = client.session.get(url)
        resp.raise_for_status()
        data = resp.json()
        results.extend(data["results"])
        url = data.get("next")
    return results

# Get all hosts across all pages
all_hosts = get_all_results(client, "/hosts/?page_size=200")
print(f"Total hosts: {len(all_hosts)}")

FAQ

What is the API rate limit?

AAP does not enforce a hard rate limit by default, but rapid API calls can impact Controller performance. For bulk operations, add small delays between requests. For monitoring, scrape the metrics endpoint instead of polling job status.

Can I use the API to export/import configurations?

Yes. Use the API to export job templates, inventories, credentials (metadata only), and workflows as JSON. The awx-manage export_assets and awx-manage import_assets CLI commands provide bulk export/import functionality.

Is the API backward compatible?

The /api/v2/ endpoint has been stable since Ansible Tower 3.x. AAP 2.6 adds new endpoints under /api/controller/v2/ but maintains backward compatibility. Check the API changelog in the release notes for deprecations.

How do I find the template ID for API calls?

Browse the API in your browser at https://gateway.example.org/api/controller/v2/ — it provides a browsable interface. Or search by name: /api/controller/v2/job_templates/?search=patching.

Can I use GraphQL instead of REST?

No. AAP provides a REST API only. There is no GraphQL endpoint.

Conclusion

The AAP 2.6 REST API transforms the platform from a UI-driven tool into a programmable automation engine. Whether integrating with CI/CD pipelines, ITSM platforms, or building custom self-service portals, the API provides complete programmatic access to every AAP capability.

Category: troubleshooting

Browse all Ansible tutorials · AnsiblePilot Home