# REST adapter

The RESTAdapter base class - declarative API calls with response mapping and no boilerplate.

The REST adapter is a base class for services that call external REST APIs declaratively - a subclass of `RESTAdapter` names its connection and HTTP method as class attributes and implements no `handle` method. The adapter manages the connection, authentication, request serialization, response parsing and retries.

## Basic usage {#basic-usage}

Subclass `RESTAdapter` and set `conn_name` to the name of an [outgoing REST connection](https://zato.io/docs/dev/rest/outconns.html) - this is the only required attribute. The adapter invokes the configured endpoint when the service runs and the response becomes `self.response.payload`:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import RESTAdapter

class GetFlight(RESTAdapter):
    """ Retrieves flight data from the scheduling system.
    """
    name = 'airport.adapter.get-flight'
    conn_name = 'flight.ops'
```

The default method is GET. For POST, PUT, DELETE or another HTTP method, set the `method` attribute:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import RESTAdapter

class UpdateRunwayStatus(RESTAdapter):
    """ Updates runway availability status.
    """
    name = 'airport.adapter.update-runway-status'
    conn_name = 'runway.ops'
    method = 'POST'
```

An adapter is used in two ways:

- **Invoke it from another service:** an orchestration service calls the adapter through `self.invoke`, combines its result with data from other sources and returns one response - each adapter stays focused on one external system
- **Expose it on a [REST channel](https://zato.io/docs/dev/rest/channels.html):** external clients call your endpoint, the adapter forwards the request to the external system, transforms the response and returns it, with no code beyond the adapter class

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class GetFlightStatus(Service):
    name = 'demo.rest.get-flight-status'

    def handle(self) -> 'None':
        flight = self.invoke('airport.adapter.get-flight', id=self.request.input.flight_id)
        self.response.payload = flight
```

## Dynamic configuration {#dynamic-configuration}

When request parameters depend on input data or configuration, override the matching `get_*` method - the adapter calls it on each invocation and uses what it returns:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import RESTAdapter

# The airport the weather API reports on when the caller names none
_default_airport = 'BIKF'

class GetWeatherData(RESTAdapter):
    """ Fetches weather data for airport operations.
    """
    name = 'airport.adapter.get-weather-data'
    conn_name = 'weather.api'

    def get_headers(self):
        airport_code = self.request.input.get('airport_code', _default_airport)
        return {
            'X-Request-ID': self.cid,
            'X-Airport-Code': airport_code,
        }

    def get_query_string(self, params):
        params['unit'] = 'metric'
        return params
```

## Response mapping with models {#response-mapping-with-models}

The adapter deserializes JSON responses into [data models](https://zato.io/docs/dev/model/index.html). Set the `model` attribute to your Model class and the adapter parses the response into an instance of it, with type-safe access to every field:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Model, RESTAdapter

class AircraftModel(Model):
    id: int
    registration: str
    type_code: str

class GetAircraft(RESTAdapter):
    name = 'airport.adapter.get-aircraft'
    conn_name = 'fleet.api'
    model = AircraftModel
```

## Custom response transformation {#custom-response-transformation}

When the external format differs from your own - other field names, codes or structures - implement `map_response`. The method receives the parsed response data, already converted to your `model` class when one is set, and returns your canonical model:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Model, RESTAdapter

class ExternalFlightData(Model):
    flight_id: int
    carrier: str
    dep_time_utc: str
    status_code: str

class Flight(Model):
    id: int
    airline: str
    departure_time: str
    status: str

class GetFlightDetails(RESTAdapter):
    name = 'airport.adapter.get-flight-details'

    input = 'id'
    output = Flight

    has_query_string_id = True
    conn_name = 'Flight.Schedule'
    model = ExternalFlightData

    def map_response(self, data:'ExternalFlightData') -> 'Flight':

        # Configuration translates the external codes ..
        status = self.config.airport.status_codes[data.status_code]
        airline = self.config.airport.carriers[data.carrier]

        # .. and the canonical model receives the translated values.
        out = Flight()
        out.id = data.flight_id
        out.airline = airline
        out.departure_time = data.dep_time_utc
        out.status = status

        return out
```

## Path parameters {#path-parameters}

When the [outgoing connection's](https://zato.io/docs/dev/rest/outconns.html) URL contains placeholders like `/terminals/{terminal_id}/gates/{gate_id}`, implement `get_path_params` to return the values. The adapter substitutes them into the URL, and parameters the path does not use become query string parameters:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import RESTAdapter

class GetGateAssignment(RESTAdapter):
    name = 'airport.adapter.get-gate-assignment'

    input = 'terminal_id', 'gate_id'
    conn_name = 'Gate.Management'
    method = 'GET'

    def get_path_params(self, params):
        return {
            'terminal_id': self.request.input.terminal_id,
            'gate_id': self.request.input.gate_id,
        }
```

## Request data {#request-data}

For POST, PUT and PATCH requests, implement `get_request` to return the request body - the adapter serializes dicts and Model objects to JSON:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import RESTAdapter

class SubmitCargoManifest(RESTAdapter):
    name = 'airport.adapter.submit-cargo-manifest'

    input = 'manifest'
    conn_name = 'Cargo.System'
    method = 'POST'

    def get_request(self):
        return self.request.input.manifest
```

## Base adapters {#base-adapters}

When several adapters share configuration - the same authentication, headers or conventions - move it into a base class the others inherit from. When the external system changes its authentication, you update the base class and every adapter follows:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import RESTAdapter

class BaseAirportAdapter(RESTAdapter):
    sec_def_name = 'Airport.OAuth.Token'

class GetRunway(BaseAirportAdapter):
    name = 'airport.adapter.get-runway'
    conn_name = 'Airfield.Runways'
    input = 'id'
    has_query_string_id = True

class GetTaxiway(BaseAirportAdapter):
    name = 'airport.adapter.get-taxiway'
    conn_name = 'Airfield.Taxiways'
    input = 'id'
    has_query_string_id = True
```

## Invoke adapter services {#invoke-adapter-services}

Adapter services are regular Zato services - other services invoke them through `self.invoke`, with input parameters arriving through the standard `self.request.input`. The calling service receives the mapped result directly:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class SyncGateAssignments(Service):
    """ Assigns available gates to flights that have none.
    """
    name = 'demo.rest.sync-gate-assignments'

    def handle(self) -> 'None':

        flight_list = self.invoke('airport.adapter.get-flight-list')
        gate_list = self.invoke('airport.adapter.get-gate-list', status='available')

        for flight in flight_list:
            if flight.gate_id not in gate_list:
                self.invoke('airport.adapter.assign-gate', flight_id=flight.id)
```

Adapter services can also be exposed on [REST channels](https://zato.io/docs/dev/rest/channels.html), so external clients receive a transformed view of an external API directly.

## All configuration options {#all-configuration-options}

**Connection and method**

| Name | Default | Description |
| --- | --- | --- |
| conn\_name | '' | Name of the outgoing REST connection to use |
| method | 'GET' | HTTP method (GET, POST, PUT, DELETE, PATCH, etc.) |
| get\_conn\_name | None | Callable returning the connection name dynamically |
| get\_method | None | Callable returning the HTTP method dynamically |

**Request data**

| Name | Default | Description |
| --- | --- | --- |
| get\_request | None | Callable returning the request body data |
| get\_query\_string | None | Callable returning query string parameters as a dict |
| get\_path\_params | None | Callable returning path parameters as a dict |
| get\_headers | None | Callable returning HTTP headers as a dict |
| has\_query\_string\_id | False | If True, automatically adds an 'id' parameter to query string from input |
| query\_string\_id\_param | None | Custom name for the query string ID parameter (default: 'id') |
| has\_json\_id | False | If True, uses JSON ID parameter |
| json\_id\_param | None | Custom name for the JSON ID parameter |

**Authentication**

| Name | Default | Description |
| --- | --- | --- |
| sec\_def\_name | None | Name of the security definition to use |
| auth\_scopes | '' | OAuth scopes to request |
| get\_sec\_def\_name | None | Callable returning the security definition name dynamically |
| get\_auth\_scopes | None | Callable returning auth scopes dynamically |
| get\_auth\_bearer | None | Callable returning a [bearer token](https://zato.io/docs/security/bearer-tokens/index.html) to add to the Authorization header |

**Response handling**

| Name | Default | Description |
| --- | --- | --- |
| model | None | A Model class to map the response to |
| map\_response | None | Callable to transform the response data |
| log\_response | False | Whether to log the response |
| needs\_raw\_response | False | If True, returns a `RESTAdapterResponse` with both parsed data and raw response |

**Retry configuration**

| Name | Default | Description |
| --- | --- | --- |
| max\_retries | None | How many times a failed invocation is retried, a failure being a timeout, a connection error or an HTTP 429 response. The effective default is 0, i.e. no retries. |
| retry\_sleep\_time | None | How many seconds to wait before the first retry, 8 at most. The effective default is 2. |
| retry\_backoff\_multiplier | None | How many times longer each next wait is. The effective default is 2. |
| retry\_backoff\_threshold | None | How many seconds all the waits may take together, once that is reached, no more retries take place. The effective default is 60. |

When an adapter leaves these attributes as None, the retry config of the underlying outgoing connection applies - the one set in its Delivery tab in the Dashboard. Only when the connection has no retry config of its own do the defaults above take effect. Setting any of the attributes in an adapter subclass overrides both the connection's config and the defaults. To learn how the settings work, with examples, and how a `Retry-After` header is honoured, see [Delivery retries and dead-letter queues](https://zato.io/docs/dev/delivery.html).

## See also {#see-also}

- [Outgoing connections](https://zato.io/docs/dev/rest/outconns.html) - The connections adapters name in conn\_name
- [Data mapping](https://zato.io/docs/dev/rest/data-mapping.html) - The transformation patterns map\_response implements
- [Calling REST APIs](https://zato.io/docs/dev/rest/calling-apis.html) - Invoking connections directly, without an adapter
- [REST channels](https://zato.io/docs/dev/rest/channels.html) - Exposing adapter services to external clients

## Learn more {#learn-more}

- [REST integration overview](https://zato.io/integrations/rest/) - Start here to see how teams build REST integrations
- [In-depth API tutorial](https://zato.io/tutorials/main/01.html) - Master architectural patterns for production-ready integrations
- [Modern REST API tutorial](https://zato.io/tutorials/rest-api/python.html) - Design, build and secure REST APIs with Python
