# URL path matching

How a request finds its channel - path parameters, methods, Accept headers and match priorities.

A request is routed to a channel by three things - its URL path, its HTTP method and its `Accept` header. This page covers how each is matched, what happens when several channels match one request, and every option that affects the outcome.

## Before matching starts {#before-matching-starts}

The server accepts these HTTP methods: `GET, POST, DELETE, PUT, PATCH, HEAD, OPTIONS`. A request with any other method is answered 405 before URL matching runs.

## Path parameters {#path-parameters}

Curly braces in a channel's URL path capture parameters:

```text
/api/customers/{customer_id}/orders/{order_id}
```

Each parameter matches one or more characters from this set: letters, digits and underscore, plus `$ . - : | = ~ ^ % @ + , ; ! ( )` and space. The captured values reach your service as `self.request.http.params`:

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

# Zato
from zato.server.service import Service

class GetOrder(Service):

    name = 'demo.rest.get-order'

    def handle(self) -> 'None':

        customer_id = self.request.http.params['customer_id']
        order_id = self.request.http.params['order_id']
```

`self.request.http.params` holds path parameters only. Query string values are in `self.request.http.GET` - a single-valued key points at its value directly and a repeated key at a list of values.

## The match\_slash option {#the-match_slash-option}

By default a path parameter can also match slashes, so `{path}` in `/files/{path}` matches `reports/2026/summary.csv` in one parameter. Turning `match_slash` off on the channel makes each parameter stop at the next slash, so one parameter is one path segment.

## Method and Accept matching {#method-and-accept-matching}

A channel may name one HTTP method, in which case only that method reaches it, or leave the method empty to accept any method the server allows. Likewise a channel may name an `Accept` value through its `http_accept` option - a channel configured with `*/*` or nothing accepts any `Accept` header.

## When several channels match {#when-several-channels-match}

Two channels may share a URL path as long as they differ in HTTP method or in their `http_accept` value. Creating a channel whose path, method and Accept value all equal an existing channel's fails with an error naming that other channel.

A create also fails when two channels would match the same requests with equal specificity but different security - the error names both channels and states that they secure the same requests differently.

When several channels legitimately match one request, the most specific one wins. Specificity is decided in order by the longest literal path prefix, the total literal length of the path, whether the channel names an Accept value, and whether it names a method.

## Parameter priorities {#parameter-priorities}

Two settings on the channel decide how parameters merge:

- `url_params_pri` - when one name appears both in the path and in the query string, `qs-over-path` (the default) lets the query string win and `path-over-qs` lets the path parameter win.
- `merge_url_params_req` - on by default. When on, path and query parameters are merged into `self.request.input` for keys the payload does not already contain, and `self.request.http.GET` and `self.request.http.POST` are populated. When off, none of that happens and only the payload reaches the service.

## When self.request.http.POST is populated {#when-selfrequesthttppost-is-populated}

`self.request.http.POST` is filled in two cases, both requiring `merge_url_params_req` to be on:

- The channel's data format is `form` and the request's content type is `application/x-www-form-urlencoded` or `multipart/form-data` - the parsed form is both `POST` and the service's payload
- The channel has no data format at all, in which case the raw body is parsed as a query string into `POST`

A channel with any other data format, JSON included, leaves `POST` empty.

## Data format {#data-format}

A new channel has no default data format - a channel that should parse JSON bodies into `self.request.payload` must set its data format to JSON explicitly, in the Dashboard or in [enmasse YAML](https://zato.io/docs/dev/rest/enmasse.html). Without it, the service receives the raw request bytes.

## An inactive channel {#an-inactive-channel}

An inactive channel answers every request with 404, the same status a caller receives when no channel exists at all - deactivation is indistinguishable from absence on purpose.

## See also {#see-also}

- [REST channels](https://zato.io/docs/dev/rest/channels.html) - Creating channels and reading parameters in services
- [HTTP verbs](https://zato.io/docs/dev/rest/verbs.html) - One service responding differently to GET, POST, PUT, PATCH and DELETE
- [Error catalog](https://zato.io/docs/dev/rest/error-catalog.html) - Every status code a channel returns, including 404 and 405
- [Enmasse reference](https://zato.io/docs/dev/rest/enmasse.html) - match\_slash, http\_accept and every other channel\_rest key in YAML

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