Ansible uri Module: Make HTTP/REST API Calls from Playbooks (Guide)
By Luca Berton · Published 2024-01-01 · Category: troubleshooting
Complete guide to Ansible uri module. Make GET, POST, PUT, DELETE requests, authenticate with APIs, upload files, handle JSON responses, and implement health.
The ansible.builtin.uri module sends HTTP and HTTPS requests from Ansible playbooks. It's the Ansible equivalent of curl — use it for REST API calls, health checks, webhooks, and any HTTP interaction.
Basic HTTP Requests
GET Request
- name: Simple GET
ansible.builtin.uri:
url: https://api.example.com/status
register: response
- name: Show response
ansible.builtin.debug:
msg: "Status: {{ response.status }}, Body: {{ response.json }}"
POST Request (JSON)
- name: Create resource via API
ansible.builtin.uri:
url: https://api.example.com/users
method: POST
body:
name: "John Doe"
email: "john@example.com"
role: admin
body_format: json
status_code: 201
register: create_result
PUT Request
- name: Update resource
ansible.builtin.uri:
url: "https://api.example.com/users/{{ user_id }}"
method: PUT
body:
name: "Jane Doe"
role: superadmin
body_format: json
status_code: 200
DELETE Request
- name: Delete resource
ansible.builtin.uri:
url: "https://api.example.com/users/{{ user_id }}"
method: DELETE
status_code: [200, 204]
PATCH Request
- name: Partial update
ansible.builtin.uri:
url: "https://api.example.com/users/{{ user_id }}"
method: PATCH
body:
role: admin
body_format: json
See also: Ansible 2.17.0-rc1: Elevating Automation with ‘Gallows Pole’
Authentication
Bearer Token
- name: API call with bearer token
ansible.builtin.uri:
url: https://api.example.com/data
headers:
Authorization: "Bearer {{ api_token }}"
Basic Auth
- name: Basic authentication
ansible.builtin.uri:
url: https://api.example.com/data
url_username: "{{ api_user }}"
url_password: "{{ api_password }}"
force_basic_auth: true
API Key
- name: API key in header
ansible.builtin.uri:
url: https://api.example.com/data
headers:
X-API-Key: "{{ api_key }}"
Custom Headers
- name: Request with custom headers
ansible.builtin.uri:
url: https://api.example.com/data
headers:
Content-Type: application/json
Accept: application/json
X-Request-ID: "{{ ansible_date_time.iso8601 }}"
User-Agent: "Ansible/{{ ansible_version.full }}"
See also: Ansible systemd Module: Manage Services, Timers & Units (Guide)
Handle Responses
Parse JSON Response
- name: Get user list
ansible.builtin.uri:
url: https://api.example.com/users
return_content: true
register: users_response
- name: Show user count
ansible.builtin.debug:
msg: "Found {{ users_response.json | length }} users"
- name: Show first user
ansible.builtin.debug:
msg: "{{ users_response.json[0].name }}"
- name: Filter active users
ansible.builtin.debug:
msg: "{{ users_response.json | selectattr('active', 'equalto', true) | list }}"
Check Status Code
- name: Expect specific status codes
ansible.builtin.uri:
url: https://api.example.com/resource
status_code: [200, 201, 204]
register: result
# Fails if response code is not in the list
- name: Don't fail on error codes
ansible.builtin.uri:
url: https://api.example.com/maybe-missing
status_code: [200, 404]
register: result
- name: Handle 404
ansible.builtin.debug:
msg: "Resource not found — creating it"
when: result.status == 404
Save Response to File
- name: Download file
ansible.builtin.uri:
url: https://example.com/archive.tar.gz
dest: /tmp/archive.tar.gz
creates: /tmp/archive.tar.gz # Skip if file exists
Health Checks & Monitoring
Simple Health Check
- name: Check application health
ansible.builtin.uri:
url: "http://localhost:{{ app_port }}/health"
status_code: 200
timeout: 10
Health Check with Retries
- name: Wait for service to be healthy
ansible.builtin.uri:
url: "http://localhost:8080/health"
status_code: 200
register: health
retries: 30
delay: 5
until: health.status == 200
Verify SSL Certificate
- name: Check SSL certificate
ansible.builtin.uri:
url: https://www.example.com
validate_certs: true
status_code: 200
register: ssl_check
- name: Show certificate info
ansible.builtin.debug:
msg: "SSL OK, response time: {{ ssl_check.elapsed }}s"
See also: Ansible wait_for Module: Wait for Conditions, Ports & Files (Guide)
Real-World Patterns
Webhook Notifications
- name: Send Slack notification
ansible.builtin.uri:
url: "{{ slack_webhook_url }}"
method: POST
body:
text: "Deployment complete: {{ app_version }} on {{ inventory_hostname }}"
channel: "#deployments"
body_format: json
delegate_to: localhost
run_once: true
- name: Send PagerDuty event
ansible.builtin.uri:
url: https://events.pagerduty.com/v2/enqueue
method: POST
body:
routing_key: "{{ pagerduty_key }}"
event_action: trigger
payload:
summary: "Deployment failed on {{ inventory_hostname }}"
severity: critical
source: ansible
body_format: json
delegate_to: localhost
Create-or-Update Pattern
- name: Check if resource exists
ansible.builtin.uri:
url: "https://api.example.com/items/{{ item_name }}"
status_code: [200, 404]
register: check
- name: Create resource (if not found)
ansible.builtin.uri:
url: https://api.example.com/items
method: POST
body:
name: "{{ item_name }}"
config: "{{ item_config }}"
body_format: json
status_code: 201
when: check.status == 404
- name: Update resource (if exists)
ansible.builtin.uri:
url: "https://api.example.com/items/{{ item_name }}"
method: PUT
body:
config: "{{ item_config }}"
body_format: json
when: check.status == 200
Pagination
- name: Fetch all pages of results
ansible.builtin.uri:
url: "https://api.example.com/items?page={{ item }}&per_page=100"
return_content: true
register: page_results
loop: "{{ range(1, total_pages + 1) | list }}"
- name: Combine all results
ansible.builtin.set_fact:
all_items: "{{ page_results.results | map(attribute='json') | flatten }}"
Upload File
- name: Upload file via multipart form
ansible.builtin.uri:
url: https://api.example.com/upload
method: POST
src: /tmp/report.pdf
headers:
Content-Type: application/pdf
status_code: [200, 201]
Parameters Reference
| Parameter | Description | Default |
|-----------|-------------|---------|
| url | Target URL (required) | — |
| method | HTTP method | GET |
| body | Request body | — |
| body_format | json, form-urlencoded, raw | raw |
| headers | Custom headers (dict) | — |
| status_code | Expected status code(s) | 200 |
| return_content | Include body in response | false |
| timeout | Request timeout (seconds) | 30 |
| validate_certs | Verify SSL certificates | true |
| dest | Save response body to file | — |
| creates | Skip if file exists | — |
| follow_redirects | none, safe, all | safe |
FAQ
What is the Ansible equivalent of curl?
The ansible.builtin.uri module is the Ansible equivalent of curl. It supports GET, POST, PUT, DELETE, PATCH, custom headers, authentication, file uploads, and response parsing — all natively in YAML.
How do I make a POST request with JSON body?
Use method: POST, body_format: json, and pass the body as a YAML dictionary: body: {key: value}. Ansible automatically serializes it to JSON and sets the Content-Type header.
How do I handle API errors without failing the playbook?
Add the error status codes to status_code: status_code: [200, 404]. Then check result.status in subsequent tasks to handle each case differently.
How do I wait for an API to be ready?
Use the uri module with retries and until: retries: 30, delay: 5, until: result.status == 200. This polls the endpoint every 5 seconds for up to 150 seconds.
Can I use Ansible uri module with self-signed certificates?
Yes, set validate_certs: false to skip SSL verification. For production, use ca_path to specify a custom CA certificate instead of disabling validation entirely.
Conclusion
• GET/POST/PUT/DELETE — Full HTTP method support •body_format: json — Auto-serialize YAML to JSON
• status_code: — Define expected response codes
• retries + until — Health check polling
• return_content: true — Access response.json for parsed JSON
• Always use delegate_to: localhost for API calls from the controller
Related Articles
• Ansible wait_for Module: Wait for Conditions • Ansible register: Save Task Output • Ansible delegate_to: Run Tasks on Different HostsCategory: troubleshooting