When your service needs to fetch data from or send data to an external system, you use outgoing REST connections. These connections are configured once in the Dashboard and reused across all your services - you never hardcode URLs or credentials in your Python code.
The pattern is always the same: get a connection by name, call a method like .get() or .post(), and work with the response. Zato handles connection pooling, serialization, and credential injection automatically.
Before calling an API, create an outgoing REST connection in the Dashboard.

Key fields:
https://api.example.com){placeholders} for parametersOnce the connection exists, use it in your service. The connection name you use in code must match exactly what you entered in Dashboard.
# -*- coding: utf-8 -*-
from zato.server.service import Service
class GetCustomer(Service):
name = 'my.api.get-customer'
def handle(self):
# Get the connection by name - must match Dashboard exactly
conn = self.out.rest['Customer API'].conn
# Call the API - self.cid is the correlation ID for tracing
response = conn.get(self.cid)
# Response data is automatically parsed from JSON
customer = response.data
self.response.payload = customer
To expose this service, create a REST channel pointing to /api/customer and then:
The service calls the external Customer API and returns whatever it received.
When you need to create resources or send data to an external API, use POST. Pass a Python dict as the second argument - Zato serializes it to JSON automatically.
# -*- coding: utf-8 -*-
from zato.server.service import Service
class CreateOrder(Service):
name = 'my.api.create-order'
def handle(self):
# Data to send - will be serialized to JSON automatically
payload = {
'customer_id': '12345',
'items': [
{'sku': 'ABC-001', 'quantity': 2},
{'sku': 'XYZ-999', 'quantity': 1}
]
}
conn = self.out.rest['Orders API'].conn
# POST the data - Zato converts the dict to JSON
response = conn.post(self.cid, payload)
self.response.payload = response.data
If this service is exposed on /api/orders, you invoke it like this:
curl -X POST http://localhost:11223/api/orders \
-d '{"customer_id": "12345", "items": [{"sku": "ABC-001", "quantity": 2}]}'
Your service receives the JSON, forwards it to the external Orders API, and returns the response.
REST APIs typically need two kinds of parameters: path parameters (like /customers/123/orders) and query parameters (like ?status=pending). Zato handles both through a single params dict.
If your outgoing connection's URL path contains placeholders like /customers/{customer_id}/orders, Zato substitutes matching keys from params into the path. Remaining keys become query string parameters.
# -*- coding: utf-8 -*-
from zato.server.service import Service
class GetCustomerOrders(Service):
name = 'my.api.get-customer-orders'
def handle(self):
# Parameters - customer_id goes in path, status goes in query string
params = {
'customer_id': '12345',
'status': 'pending'
}
conn = self.out.rest['Orders API'].conn
# Zato substitutes {customer_id} in the path
# and appends ?status=pending to the query string
response = conn.get(self.cid, params=params)
self.response.payload = response.data
Expose this on a channel at /api/customers/{customer_id}/orders and test with:
The customer_id comes from the URL path, status from the query string. Both are available in your service and passed to the external API.
Some APIs require custom headers for tracking, versioning, or localization. Pass a headers dict to include them in your outgoing request.
# -*- coding: utf-8 -*-
from zato.server.service import Service
class CallWithHeaders(Service):
name = 'my.api.call-with-headers'
def handle(self):
headers = {
'X-Request-ID': self.cid,
'X-Client-Version': '2.0',
'Accept-Language': 'en-US'
}
conn = self.out.rest['External API'].conn
response = conn.get(self.cid, headers=headers)
self.response.payload = response.data
Using self.cid as a request ID is useful for distributed tracing - the same correlation ID flows through your logs and the external system's logs.
External APIs return HTTP status codes. Always check them before assuming the response contains valid data. Use Python's HTTPStatus enum for readable, maintainable code.
# -*- coding: utf-8 -*-
from http import HTTPStatus
from zato.server.service import Service
class CheckResponseStatus(Service):
name = 'my.api.check-status'
def handle(self):
conn = self.out.rest['External API'].conn
response = conn.get(self.cid)
# Check HTTP status code before using the data
if response.status_code == HTTPStatus.OK:
self.response.payload = response.data
elif response.status_code == HTTPStatus.NOT_FOUND:
self.response.payload = {'error': 'Not found'}
self.response.status_code = HTTPStatus.NOT_FOUND
else:
raise Exception(f'API error: {response.status_code}')
When you raise an exception, Zato returns HTTP 500 to your caller. If you want to return a different status, set self.response.status_code explicitly as shown for the 404 case.
The connection object supports all standard HTTP methods:
conn.get(self.cid, params) # GET
conn.post(self.cid, payload) # POST
conn.put(self.cid, payload) # PUT
conn.patch(self.cid, payload) # PATCH
conn.delete(self.cid, params) # DELETE
conn.head(self.cid) # HEAD
conn.options(self.cid) # OPTIONS
