# Where to keep API configuration

Keep configuration outside code in .ini files that reload with no restarts.

Your services read configuration from `.ini` files, instantly from memory. These files hold API endpoints, mapping tables, business rules and any other values that do not belong in code.

Configuration files go in the `config/user-conf` directory of your project on the host machine. When you deploy with Docker, this directory is mounted into the container, so Zato can read the files at startup. Each file becomes accessible through `self.config` in your services, using the file name (without extension) as the first key.

For instance, if you create a file called **airports.ini**, in your service it is accessible as **self.config.airports**, and sections work as its keys, accessed with dot notation, so **\[api\]** with **timeout = 30** becomes **self.config.airports.api.timeout**.

The files are edited in the dashboard on the [Config tables screen](https://zato.io/docs/dev/config/dashboard.html) or with any editor of your own, and the [configuration overview](https://zato.io/docs/dev/config/index.html) shows where each kind of value belongs.

## Create a configuration file {#create-a-configuration-file}

On your host machine, create an `.ini` file in your project's `config/user-conf` directory. For example, `config/user-conf/airports.ini`:

```ini
[api]
base_url = https://api.airport-ops.example.com
timeout = 30
max_retries = 3

[security]
token_sec_def_name = Airport.Ops.Token

[terminals]
domestic = T1
international = T2
cargo = T3

[[gate_prefixes]]
T1 = A
T2 = B
T3 = C
```

The file uses a simple structure:

- **Sections** are defined with single brackets `[section_name]`
- **Nested sections** use additional brackets - double `[[subsection]]`, triple `[[[deeper]]]`, and so on for arbitrary nesting depth
- **Key-value pairs** are written as `key = value`
- **Comments** start with `#`

## Access configuration in services {#access-configuration-in-services}

In your service, access the configuration through `self.config` using dot notation. The first key is the file name (without `.ini`), followed by section names and keys:

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

# Zato
from zato.server.service import Service

class GetFlightInfo(Service):

    def handle(self):

        # Access simple values
        base_url = self.config.airports.api.base_url
        timeout = self.config.airports.api.timeout

        # Access nested sections
        t1_prefix = self.config.airports.terminals.gate_prefixes.T1

        self.logger.info(f'Using API at {base_url} with timeout {timeout}')
        self.logger.info(f'Terminal 1 gate prefix: {t1_prefix}')
```

You can nest sections as deep as you need. Each level of nesting in the `.ini` file becomes another level of dot access in your code.

## Changes apply immediately {#changes-apply-immediately}

Like services, configuration files are hot-deployed - saving an `.ini` file in the mounted project reloads its values in RAM within a couple of seconds, with no restarts and no redeployments. A service reading `self.config` always obtains the current contents of the file, which makes configuration files the right place for values that change while an environment runs, such as mapping tables and feature toggles. [Hot deployment](https://zato.io/docs/dev/hot-deploy.html) describes the mechanism.

## Where the files can live {#where-the-files-can-live}

The `config/user-conf` directory of your project is the default place, and two more are available:

- Any directory listed in the `Zato_User_Conf_Dir` environment variable, which accepts multiple colon-separated paths
- Additional projects pointed to by `Zato_Project_Root_*` variables, each with its own `config/user-conf`

All of them are loaded into the same `self.config` object and watched for changes, and [deploying projects](https://zato.io/tutorials/devops/deployment.html) covers the variables in detail.

## Use sections as dictionaries {#use-sections-as-dictionaries}

Each configuration section also behaves like a Python dictionary. You can iterate over keys, look up values with square brackets, check for key existence, and use all the standard dict methods:

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

# Zato
from zato.server.service import Service

class ListTerminals(Service):

    def handle(self):

        terminals = self.config.airports.terminals

        # Iterate over all keys
        for terminal_name in terminals:
            terminal_code = terminals[terminal_name]
            self.logger.info(f'Terminal {terminal_name} -> {terminal_code}')

        # Check if a key exists
        if 'cargo' in terminals:
            cargo_code = terminals['cargo']
            self.logger.info(f'Cargo terminal: {cargo_code}')

        # Get with default
        vip_terminal = terminals.get('vip', 'T1')

        # Get all keys
        all_terminal_names = list(terminals.keys())

        # Get all values
        all_terminal_codes = list(terminals.values())

        # Get all items
        for name, code in terminals.items():
            self.logger.info(f'{name} = {code}')
```

This means you can use configuration sections both for direct access when you know the key names, and for dynamic lookups when you need to iterate or search.

## Value types {#value-types}

Zato automatically converts configuration values to appropriate Python types.

For instance, let's say you have this config:

```ini
[api]
base_url = https://api.airport-ops.example.com
timeout = 30
max_retries = 3
debug_mode = False

[terminals]
active_gates = A1, A2, A3, B1, B2
gate_capacity = {'A1': 200, 'A2': 150, 'B1': 300}
```

Here's how it would be converted:

- **Integers** - `timeout = 30` becomes Python `int` 30
- **Booleans** - `debug_mode = False` becomes Python `bool` False
- **Lists** - `active_gates = A1, A2, A3, B1, B2` becomes Python list `['A1', 'A2', 'A3', 'B1', 'B2']`
- **Dicts** - `gate_capacity = {'A1': 200, 'A2': 150, 'B1': 300}` becomes Python dict
- **Strings** - `base_url = https://api.airport-ops.example.com` remains a Python `str`

## Nested sections {#nested-sections}

Nested sections become nested objects. Each level of brackets adds another level of dot access, so you can organize complex configuration hierarchically and access it naturally in code.

```ini
[terminal_t1]

[[gates]]

[[[gate_a1]]]
capacity = 200
airlines = FI, AF

[[[gate_a2]]]
capacity = 150
airlines = BA
```

In your service:

- `self.config.airports.terminal_t1` - the `[terminal_t1]` section
- `self.config.airports.terminal_t1.gates` - the `[[gates]]` subsection
- `self.config.airports.terminal_t1.gates.gate_a1` - the `[[[gate_a1]]]` subsection
- `self.config.airports.terminal_t1.gates.gate_a1.capacity` - the value `200` (as int)
- `self.config.airports.terminal_t1.gates.gate_a1.airlines` - the list `['FI', 'AF']`

## Mapping tables {#mapping-tables}

A common pattern is storing mapping tables - translating codes from external systems to your internal values:

```ini
[mappings]

[[aircraft_types]]
738 = Boeing 737-800
763 = Boeing 767-300
388 = Airbus A380-800
320 = Airbus A320
321 = Airbus A321

[[delay_reasons]]
WX = Weather
TC = Technical
OP = Operational
SC = Security
CR = Crew

[[airline_codes]]
FI = Icelandair
AF = Air France
BA = British Airways
```

Use these mappings in your service:

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

# Zato
from zato.server.service import Service

class TranslateFlightData(Service):

    def handle(self):

        # Get the mapping tables
        aircraft_types = self.config.airports.mappings.aircraft_types
        delay_reasons = self.config.airports.mappings.delay_reasons

        # Translate codes
        aircraft_code = '738'
        aircraft_name = aircraft_types[aircraft_code]

        delay_code = 'WX'
        delay_description = delay_reasons[delay_code]

        self.logger.info(f'Aircraft: {aircraft_name}')
        self.logger.info(f'Delay reason: {delay_description}')
```

## Translate and validate codes with config tables {#translate-and-validate-codes-with-config-tables}

Plain dict access covers a mapping you own. When each party you integrate with has its own codes for the same things, and when a value has to be checked before it is accepted, the same `.ini` files give you two methods on top of the dot access:

```python
# Is this value one we accept?
if self.config.statuses.validate(status):
    ...

# What does this party's value mean here?
status = self.config.partners.translate(source='ALPINE_TELECOM', code='NON_PAYMENT', codes='statuses')
```

A file with a `[codes]` section is a list of the values you accept, a file with any other sections is a mapping table with one section per source. See [Config tables](https://zato.io/docs/dev/examples/config-tables.html) for a complete description, including examples for healthcare and telecommunications.

## Environment variables for secrets {#environment-variables-for-secrets}

For sensitive values like [API keys](https://zato.io/docs/security/api-keys.html), passwords, and tokens, don't put them directly in the config file. Instead, reference environment variables using the `$` prefix:

```ini
[credentials]
api_key = $Airport_API_Key
client_secret = $Airport_Client_Secret

[database]
password = $Airport_DB_Password
```

When Zato loads the configuration, it automatically replaces `$Variable_Name` with the value from the corresponding environment variable. If the variable isn't set, a warning is logged.

In your service, you access these values through `self.config`, like any other config value:

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

# Zato
from zato.server.service import Service

class ConnectToAirportAPI(Service):

    def handle(self):

        # These values are resolved from environment variables when the file is loaded
        api_key = self.config.airports.credentials.api_key
        client_secret = self.config.airports.credentials.client_secret

        # Use them in your API calls, logging no more than a hint of the key
        key_hint = api_key[:4]
        self.logger.info(f'Connecting with API key: {key_hint}...')
```

## DevOps and container deployments {#devops-and-container-deployments}

When deploying with Docker, you pass environment variables to the container and mount your configuration directory. The [DevOps deployment tutorial](https://zato.io/tutorials/devops/deployment.html) covers this in detail.

The typical setup:

1. **Mount the config directory** from your host to `/opt/hot-deploy/yourproject/config/user-conf` inside the container
2. **Pass environment variables** either directly with `-e` flags or through an `env.ini` file mounted at `/opt/hot-deploy/enmasse/env.ini`

Example from a deployment script:

```bash
docker run \
    --mount type=bind,source=$host_root_dir/config/user-conf,target=$target/myproject/config/user-conf \
    --mount type=bind,source=$host_root_dir/config/auto-generated/env.ini,target=$target/enmasse/env.ini \
    $package_address
```

The `env.ini` file contains the environment variables that your config files reference:

```ini
[env]
Airport_API_Key=your-api-key-here
Airport_Client_Secret=your-secret-here
Airport_DB_Password=your-password-here
```

This separation means your configuration structure is in version control, but the actual secrets are injected at deployment time from your secrets management system.

## Override single values per environment {#override-single-values-per-environment}

Beyond the `$Variable_Name` references inside a file, any single key can be overridden from the outside, without touching the file at all, with a variable named after the file, section and key:

```text
Zato_Config_User_Config_<file_name>_<section>_<key>
```

Dots and dashes in the names become double underscores. For instance, to override `timeout` in the `[api]` section of `airports.ini` for one environment only:

```bash
export Zato_Config_User_Config_airports__ini_api_timeout=60
```

The file in git stays the same everywhere and the environments differ only in the variables they set.

## Real-world example {#real-world-example}

Here's a more complete example showing how configuration fits into a real service that integrates with an airport operations API:

```ini
[api]
base_url = https://api.airport-ops.example.com
timeout = 30
token_sec_def_name = Airport.Ops.Token

[defaults]
days_to_check = 7
max_results = 100

[terminals]
domestic = T1
international = T2

[[gate_ranges]]
T1 = A1-A20
T2 = B1-B40

[credentials]
api_key = $Airport_API_Key
```

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

# stdlib
from dataclasses import dataclass

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

@dataclass(init=False)
class FlightSearchRequest(Model):
    terminal: str
    date: str

@dataclass(init=False)
class Flight(Model):
    flight_id: str
    gate: str
    status: str

class SearchFlights(Service):

    input = FlightSearchRequest

    def handle(self):
        request:'FlightSearchRequest' = self.request.input

        # Get configuration
        config = self.config.airports

        # Build API request
        base_url = config.api.base_url
        timeout = config.api.timeout
        max_results = config.defaults.max_results

        # Translate terminal name to code
        terminal_code = config.terminals[request.terminal]

        # Get gate range for this terminal
        gate_range = config.terminals.gate_ranges[terminal_code]

        self.logger.info(f'Searching flights in terminal {terminal_code} (gates {gate_range})')

        # Make the API call
        conn = self.rest['Airport Ops API']
        api_response = conn.get(params={
            'terminal': terminal_code,
            'date': request.date,
            'limit': max_results
        })
        flights_data = api_response.data

        # Process results
        flights = []
        for item in flights_data:
            flight = Flight()
            flight.flight_id = item['FlightID']
            flight.gate = item['Gate']
            flight.status = item['Status']
            flights.append(flight)

        self.response.payload = {'flights': flights}
```

## See also {#see-also}

- [Config tables](https://zato.io/docs/dev/examples/config-tables.html) - Translate each party's codes into yours and validate accepted values
- [Config tables in the dashboard](https://zato.io/docs/dev/config/dashboard.html) - Edit, check and save the files on one screen
- [Hot deployment](https://zato.io/docs/dev/hot-deploy.html) - The mechanism that reloads the files with no restarts

## 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
