---
title: Routes
description: One exact host and path prefix to one backend, how overlap is refused, and what a change does to live traffic.
type: explanation
product: network
summary: The address a request arrives on, the backend it reaches, and how the two stay unambiguous
updated: 2026-10-01
order: 0
---

<Callout tone="note" title="Today this runs on the management API">
A hostname is attached to a service with `POST /v1/projects/{id}/domains/{domain_id}/hostnames` (`hostname`, `serviceId`); see the [Network quickstart](/docs/network/quickstart). The `routes` collection is not served at api.sylphx.com.
</Callout>

A Route sends one exact host and path prefix to one backend. It is the whole of
the platform's request routing: there is no implicit fallback, no precedence
between Routes, and no Route that covers a name you did not write down.

## The address is exact

`host` is one hostname. A wildcard grants no authority, so a Route for
`shop.example.com` says nothing about `www.shop.example.com` — every host that
serves traffic has its own Route, and its own line in the spec. The host must be
a verified [Domain](/docs/network/domains) or a subdomain of one, which is what
keeps one project from claiming another's name.

`path_prefix` is the second half of the address, and it defaults to `/`. A
prefix of `/api` takes `/api` and everything under it; the rest of the host
needs its own Route or nothing at all.

<PropertyTable
	properties={[
		{
			name: 'host',
			type: 'string',
			required: true,
			description: 'The exact host: a verified Domain or a subdomain of one.',
		},
		{
			name: 'path_prefix',
			type: 'string',
			description: 'The path prefix; default /.',
		},
		{
			name: 'service',
			type: 'string',
			description: 'A Hosting Service. One of the backend group.',
		},
		{
			name: 'redirect',
			type: 'RouteRedirect',
			description: 'A redirect, with a target_uri and a status code. The other member of the backend group.',
		},
	]}
/>

A Route carries exactly one backend: either a Service or a redirect, never
both.

```bash
sylphx network routes create \
  --parent orgs/acme/projects/shop/envs/production \
  --spec.host shop.example.com \
  --spec.service shop
```

A redirect backend answers the same address without a workload behind it:
`target_uri` is the absolute URL to send the caller to, and `status_code` is
301, 302, 307 or 308, defaulting to 308.

## Overlap is refused

Two Routes may not claim the same host and path prefix. The refusal is at
creation, not at request time: a Route that would overlap an existing one is
rejected, so the address a request takes is never a matter of which Route was
written first. That is also why a wildcard grants no authority — a wildcard is
the one Route that would make every other Route's answer ambiguous.

## A Route belongs to an environment

A Route lives under an environment, at
`orgs/{org}/projects/{project}/envs/{env}/routes/{route}`. The same host in
production and in staging is two Routes, and pointing one at another Service is
a change in one environment only.

## What changing one does

Creating, updating or deleting a Route answers an Operation: the call returns
before the change is everywhere, and the Operation settles when it is. The Route
is your desired state, which means a change is a complete statement of what
should be true — the previous backend is replaced, not merged.

The edge's own view is on the Route, in `status.edge`:

<PropertyTable
	properties={[
		{
			name: 'hostname_state',
			type: 'EdgeState',
			description: 'Whether the edge accepts the hostname. One of pending, active, error.',
		},
		{
			name: 'tls_state',
			type: 'EdgeState',
			description: 'Whether the edge serves TLS for the hostname. One of pending, active, error.',
		},
	]}
/>

So the sequence for a hostname is visible rather than guessed: the Route exists,
the edge picks up the hostname, and TLS follows. A Route that is not yet live
reads `pending` there, and a hostname the edge cannot use reads `error` — both
while the Route's own spec stays exactly what you wrote.

Deleting a Route is `destructive`, so the CLI asks before it runs, and the API
takes an `etag` that must match the current one.

<RelatedDocs
	links={[
		{
			href: '/docs/api/routes',
			label: 'The routes collection',
			description: 'Every method, its scope and its examples.',
		},
		{
			href: '/docs/network/quickstart',
			label: 'Quickstart',
			description: 'A domain, its record and its Route.',
		},
		{
			href: '/docs/network/domains',
			label: 'Domains',
			description: 'Why a host has to be a verified name.',
		},
	]}
/>
