Schedule a demo

Calling external REST APIs

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.

Creating an outgoing connection

Before calling an API, create an outgoing REST connection in the Dashboard.

Key fields:

  • Name - how you reference this connection in code
  • Host - base URL of the API (e.g. https://api.example.com)
  • URL path - endpoint path, can include {placeholders} for parameters
  • Data format - typically JSON

Basic API call

Once 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:

curl http://localhost:11223/api/customer

The service calls the external Customer API and returns whatever it received.

Sending data with POST

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.

Path and query parameters

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:

curl "http://localhost:11223/api/customers/12345/orders?status=pending"

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.

Custom headers

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.

Checking response status

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.

All HTTP methods

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

Connection list in Dashboard

Learn more