> For the complete documentation index, see [llms.txt](https://docs.kangal.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kangal.dev/gateway/services-routes.md).

# Services and routes

Services describe where traffic should go. Routes describe which requests are allowed to use that destination. Keeping them separate lets multiple routes share timeouts, retries, and an upstream pool without duplicating destination configuration.

## Choose a service target mode

| Mode          | Use when                                                             | Required fields                                              |
| ------------- | -------------------------------------------------------------------- | ------------------------------------------------------------ |
| Direct URL    | One stable HTTP endpoint is sufficient.                              | `url`, or `protocol` plus `host` and optional `port`/`path`. |
| Upstream pool | The service needs multiple targets, health state, or load balancing. | `upstream_id` or `upstream_name`.                            |

The Admin API requires at least one of `url`, `host`, `upstream_id`, or `upstream_name`. The Console presents the choice as **Direct URL / host** or **Upstream pool**.

## Create a service in the Console

1. Open a gateway and select **Services**.
2. Select **Create service**.
3. Enter a unique service name for this gateway.
4. Choose the target mode.
5. For a direct target, enter either a full URL or host components.
6. For a pool, select an existing upstream.
7. Set retries and connect, read, and write timeouts.
8. Save, then create a route that references the service.

Creating the same service name again in the same tenant and gateway returns the existing resource instead of creating a duplicate.

## Service field reference

| Field                | Default        | Semantics and validation                                                                                            |
| -------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------- |
| `tenant_id`          | none           | Required and must match the administrator tenant.                                                                   |
| `name`               | none           | Required. The Console only checks that it is non-empty.                                                             |
| `url`                | none           | Full base URL, including `http://` or `https://`. Its existing path is preserved.                                   |
| `protocol`           | `http`         | Used with `host`; supported values are `http` and `https`.                                                          |
| `host`               | none           | Hostname or address. A full URL in this field is also accepted at runtime.                                          |
| `port`               | scheme default | Positive integer in the Console. Ports `80` and `443` are omitted from the assembled URL; other ports are appended. |
| `path`               | none           | Base path appended to the assembled host URL. Kangal adds a leading slash when needed.                              |
| `upstream_id`        | none           | Preferred stable reference to an upstream in the same gateway.                                                      |
| `upstream_name`      | none           | Name fallback used to resolve an upstream.                                                                          |
| `retries`            | `5`            | Supported runtime range is 0-5 retries after the first attempt.                                                     |
| `connect_timeout_ms` | `60000`        | Console range 100-300000 ms. Time allowed to establish the upstream connection.                                     |
| `read_timeout_ms`    | `60000`        | Console range 100-300000 ms. Also becomes the overall timeout when no separate `timeout_ms` exists.                 |
| `write_timeout_ms`   | `60000`        | Console range 100-300000 ms. Time allowed while writing the request upstream.                                       |
| `labels`             | `{}`           | Metadata used by API filtering and automation.                                                                      |

Timeout and retry values are evaluated in the order route, service, then upstream when those fields are present in imported configuration. The Console edits the service-level values.

### Retry behavior

The runtime defaults to retrying `502`, `503`, and `504` only for replay-safe `GET`, `HEAD`, `OPTIONS`, `PUT`, and `DELETE` requests. The configured `retries` value means retries after the first attempt, so `retries: 2` allows at most three attempts.

Enable retries only when the complete request is safe to replay. For untrusted request bodies, require an explicit request-size policy at both the public edge and route before enabling retries. Use an idempotency strategy for business operations.

## Create a route in the Console

1. Open **Routes** for the gateway and select **New route**.
2. Enter the public path.
3. Select a service. Use **Legacy upstream** only for a direct upstream name or URL that is not represented by a service.
4. Enter comma-separated HTTP methods and optional hosts.
5. Choose `exact`, `prefix`, or `regex` path matching.
6. Decide whether to strip the matched path and preserve the incoming host.
7. Set explicit priority when routes overlap.
8. Assign route plugins and save.

## Route field reference

| Field                         | Default   | Semantics and validation                                                                                                                 |
| ----------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `tenant_id`                   | none      | Required and tenant-scoped.                                                                                                              |
| `name`                        | none      | Optional display name.                                                                                                                   |
| `path`                        | none      | Required. Add a leading `/` for predictable display and rewriting.                                                                       |
| `path_type`                   | `exact`   | `exact`, `prefix`, or `regex`. A path beginning with `~` is also treated as regex by the matcher.                                        |
| `methods`                     | `["GET"]` | Case-insensitive. `HEAD` also matches a route allowing `GET`. `*` and `ANY` match every method.                                          |
| `hosts`                       | `[]`      | Case-insensitive host patterns. Port numbers are ignored. `*` wildcards are supported. Empty means any host.                             |
| `snis`                        | `[]`      | Case-insensitive TLS server-name patterns with wildcard support. Empty means any SNI.                                                    |
| `headers`                     | `{}`      | Every named header must match one allowed value. Header names and values are compared case-insensitively; `*` accepts any present value. |
| `query_params`                | `{}`      | Every named query parameter must match one allowed value. Query values are case-sensitive; `*` accepts any present value.                |
| `service_id` / `service_name` | none      | Resolve a service in the same tenant and gateway. Prefer the ID and include the name for readability.                                    |
| `upstream`                    | none      | Logical upstream ID/name or a full HTTP/HTTPS URL. Exactly one target strategy should be used.                                           |
| `strip_path`                  | `true`    | Removes the matched route prefix before joining the request path to the destination base path.                                           |
| `preserve_host`               | `false`   | When true, forwards the original `Host`; otherwise the HTTP client derives the upstream host.                                            |
| `priority`                    | `0`       | Integer. The Console accepts 0-1,000,000. Higher values win before specificity scoring.                                                  |
| `plugins`                     | `[]`      | Validated route plugin assignments. Updating this field replaces the route's complete plugin array.                                      |
| `labels`                      | `{}`      | Metadata for ownership and automation.                                                                                                   |

The create API requires at least one of `upstream`, `service_id`, or `service_name`. Verify that referenced resources belong to the same tenant and gateway before creation; an unresolved reference fails on the request path with `502`.

## Advanced API example

This route accepts `GET` only when the host, version header, and query parameter all match:

```json
{
  "tenant_id": "tenant-acme",
  "name": "orders-v2-read",
  "path": "/orders",
  "path_type": "prefix",
  "methods": ["GET"],
  "hosts": ["api.example.com"],
  "snis": ["api.example.com"],
  "headers": {"X-API-Version": ["2"]},
  "query_params": {"region": ["eu", "us"]},
  "service_id": "service-id",
  "service_name": "orders-service",
  "strip_path": true,
  "preserve_host": false,
  "priority": 200,
  "plugins": [],
  "labels": {"version": "v2"}
}
```

```bash
curl -sS -X POST \
  "$KANGAL_API_URL/api/v1/admin/gateways/$GATEWAY_ID/routes" \
  -H "Authorization: Bearer $KANGAL_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d @route.json
```

All configured match dimensions are combined with AND. Lists inside one header, query, host, SNI, or method dimension are alternatives.

## How route selection works

Kangal loads active routes for the request tenant and gateway, evaluates every configured matcher, and sorts matches in this order:

1. Higher explicit `priority`.
2. Path type: exact, then regex, then prefix.
3. Longer matched path.
4. A route with host restrictions.
5. A route with SNI restrictions.
6. More header and query restrictions.
7. A specific method rather than a wildcard.
8. Stable route identity as the final deterministic tie-breaker.

Because explicit priority is first, a broad high-priority route can override a more specific low-priority route. Use priority only where overlap is intentional.

### Path behavior

| Route                   | Request              | Match                                                                                  |
| ----------------------- | -------------------- | -------------------------------------------------------------------------------------- |
| `exact /orders`         | `/orders`            | yes                                                                                    |
| `exact /orders`         | `/orders/42`         | no                                                                                     |
| `prefix /orders`        | `/orders`            | yes                                                                                    |
| `prefix /orders`        | `/orders/42`         | yes                                                                                    |
| `prefix /orders`        | `/orders-old`        | no                                                                                     |
| `regex ^/orders/[0-9]+` | `/orders/42/details` | yes, because regex matching starts at the beginning and need not consume the full path |

Invalid stored regular expressions are skipped during matching rather than raising an error to the caller. Validate regexes before rollout so a typo does not silently produce `404`.

## Path rewriting examples

Destination service URL: `https://orders.internal/api`

| Route path | Request path | `strip_path` | Upstream path    |
| ---------- | ------------ | ------------ | ---------------- |
| `/orders`  | `/orders/42` | `true`       | `/api/42`        |
| `/orders`  | `/orders/42` | `false`      | `/api/orders/42` |
| `/`        | `/orders/42` | `true`       | `/api/orders/42` |

The incoming query string is appended to any query already present on the base URL. Avoid putting permanent secrets in either location.

## Safe change workflow

1. List routes and identify overlaps before changing path, host, or priority.
2. Patch only the intended fields. An empty patch returns `400`.
3. For plugin changes, include the complete desired plugin array.
4. Test a positive request and a negative request for every matcher dimension.
5. Test path rewriting at the route root and at least one nested path.
6. In an independent deployment, roll out and confirm ACKs before concluding that the new route is live everywhere.
7. Watch request IDs, status codes, upstream latency, and error rate.

## Deletion behavior

Service and route deletes are hard deletes in the scoped Admin API. Deleting a service does not search for or rewrite dependent routes. Those routes remain and return `502 Route service not resolved`. Repoint or delete routes first.

## Troubleshooting

### `404 Route not found`

Verify tenant and gateway scope, path type, host without its port, SNI forwarding, header value case, query value case, and route active state. For a custom domain, also confirm that the edge forwards a host or scope that Kangal can resolve.

### `405 Method not allowed for route`

The path and non-method matchers succeeded. Add the method intentionally; do not replace the list with `*` merely to hide the error.

### Wrong route wins

Compare explicit priorities first. Then compare exact/regex/prefix type and matched path length. Reduce broad-route priority or make its matchers narrower.

### Wrong upstream path

Inspect the service base path and `strip_path`. Kangal joins paths; it does not discard a path already present in the service URL.

### Original host reaches the backend unexpectedly

`preserve_host` is enabled. Disable it unless the backend intentionally routes on the public host.

### Updated timeouts are not active

Confirm the active data-plane bundle contains the updated service and that a route- or upstream-level imported value is not taking precedence. Retry values above five are capped at five by the proxy runtime.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.kangal.dev/gateway/services-routes.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
