# Smart routing (Business+)

Source: https://developers.leonai.io/docs/guides/smart-routing/

Smart routing lets one short link resolve to **different destinations** based on rules
you attach to it — by geography, device, time of day, or an A/B split. Rules are
managed with a JWT (they are account-management operations) under the link's
`/routing` sub-resource.

Reads and deletes stay available even after a downgrade, so you can inspect and clean
up existing rules; **creating** rules requires the Business tier or above.

## Rule types

`conditions` is a JSON object whose shape depends on `type`:

| `type`    | Routes on            | Example `conditions`                                                                         |
| --------- | -------------------- | -------------------------------------------------------------------------------------------- |
| `geo`     | Visitor country/city | `{ "countries": ["US", "CA"], "cities": ["Austin"], "excludeCountries": ["RU"] }`            |
| `device`  | Device type          | `{ "devices": ["mobile", "tablet"] }` (types: `mobile`, `tablet`, `desktop`, `bot`)          |
| `time`    | Time window          | `{ "timezone": "UTC", "startTime": "09:00", "endTime": "17:00", "daysOfWeek": [1,2,3,4,5] }` |
| `ab_test` | A/B split            | `{ "percentage": 50 }` — matches ~50% of visitors                                            |

For `geo`, country codes are matched case-insensitively; for `time`, `startTime` /
`endTime` are `HH:mm` (overnight ranges are allowed) and `daysOfWeek` is `0`=Sunday …
`6`=Saturday. For `ab_test`, assignment is **deterministic per visitor IP** — a given
visitor always lands in the same bucket, so a rule with `percentage: 50` consistently
serves the same variant to the same person.

## Create a rule

```bash
curl -X POST https://api.short.leonai.io/api/v1/links/<linkId>/routing \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "US shoppers",
    "type": "geo",
    "priority": 10,
    "conditions": { "countries": ["US"] },
    "destinationUrl": "https://example.com/us",
    "weight": 100,
    "enabled": true
  }'
```

`REST: POST /api/v1/links/:linkId/routing`

| Field            | Notes                                                                                                             |
| ---------------- | ----------------------------------------------------------------------------------------------------------------- |
| `type`           | `geo` \| `device` \| `time` \| `ab_test` (required)                                                               |
| `conditions`     | JSON object interpreted per `type` (required)                                                                     |
| `destinationUrl` | Where matching traffic goes (required)                                                                            |
| `priority`       | `0`–`1000`; higher priority rules are evaluated first (default `0`)                                               |
| `weight`         | `0`–`100` (default `100`). Present on the model; A/B splitting is driven by `conditions.percentage`, not `weight` |
| `name`           | Optional label                                                                                                    |
| `enabled`        | Default `true`                                                                                                    |

## Manage rules

```bash
# List all rules on a link
curl https://api.short.leonai.io/api/v1/links/<linkId>/routing \
  -H "Authorization: Bearer <jwt>"

# Get one rule
curl https://api.short.leonai.io/api/v1/links/<linkId>/routing/<ruleId> \
  -H "Authorization: Bearer <jwt>"

# Update a rule
curl -X PATCH https://api.short.leonai.io/api/v1/links/<linkId>/routing/<ruleId> \
  -H "Authorization: Bearer <jwt>" -H "Content-Type: application/json" \
  -d '{ "enabled": false }'

# Delete a rule
curl -X DELETE https://api.short.leonai.io/api/v1/links/<linkId>/routing/<ruleId> \
  -H "Authorization: Bearer <jwt>"
```

## How resolution works

On each redirect, enabled rules are evaluated in priority order (highest first). The
first rule whose `conditions` match wins and its `destinationUrl` is served. If no rule
matches, the link's default `longUrl` is used.
