# Management SDK - URL Redirections

> Source: https://agilitycms.com/docs/dotNet/management-sdk-dotnet-url-redirections

`client.UrlRedirections` creates, updates and deletes an instance's URL redirections, and exports and imports them as Excel spreadsheets.

> **Note:** URL redirection management is new in 2.0. If you're upgrading from 1.x, see [Migrating to 2.0](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-migrating-to-2).

These are the same redirections editors manage in Agility under **Settings → URL Redirections**. For how they work, see [URL Redirections](https://agilitycms.com/docs/editors/url-redirections).

The examples assume a `client` created as shown in the [Intro](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-intro), and `guid` set to your instance GUID. The redirection types are in `Agility.Management.Sdk.Models`:

```csharp
using Agility.Management.Sdk;
using Agility.Management.Sdk.Models;
```

Every method also takes an optional `CancellationToken cancellationToken` as its last parameter. The signatures below leave it out.

## Creating and updating redirections

### Save redirections

`SaveUrlRedirectionsAsync` creates and updates up to 250 redirections in one call. Set `UrlRedirectionID` to `0` to create a redirection, or to an existing ID to update it.

```csharp
UrlRedirectionSaveResult saved = await client.UrlRedirections.SaveUrlRedirectionsAsync(guid,
[
    new UrlRedirection { UrlRedirectionID = 0, OriginUrl = "/old-page", DestinationUrl = "/new-page", HttpCode = 301 },
    new UrlRedirection { UrlRedirectionID = 0, OriginUrl = "/promo", DestinationUrl = "https://example.com/sale", HttpCode = 302 },
]);
```

An update **replaces** the whole redirection, so send every property you want to keep.

| Property | What it holds |
|---|---|
| `UrlRedirectionID` | `0` to create, or the ID of the redirection to update |
| `OriginUrl` | The URL or path to redirect from. It must be unique in the instance. |
| `DestinationUrl` | The URL or path to redirect to |
| `HttpCode` | `301` (permanent) or `302` (temporary) |
| `DestinationLocale` | Optional. The locale code of the destination URL, such as `en-us`. |
| `OriginLocales` | Optional. The locale codes the origin URL applies to. Leave it `null` or empty to apply it to every locale. |
| `Content` | Optional. Content served for the redirect instead of a `Location` header. |

**Signature:** `Task<UrlRedirectionSaveResult> SaveUrlRedirectionsAsync(string instanceGuid, IReadOnlyList<UrlRedirection> redirections)`

---

### Check the result of a save

A save doesn't fail as a whole because of one bad item. Items that fail validation, or whose origin URL is already used by another redirection, are skipped. The result lists every item under `Created`, `Updated` or `Skipped`:

```csharp
foreach (var item in saved.Created ?? [])
{
    Console.WriteLine($"Created {item.OriginUrl} (ID: {item.UrlRedirectionID})");
}

foreach (var item in saved.Skipped ?? [])
{
    Console.WriteLine($"Skipped item {item.Index} ({item.OriginUrl}): {item.Reason}");
}
```

`Index` is the item's zero-based position in the list you sent, and `Reason` says why a skipped item was skipped. Use the `UrlRedirectionID` of a created item to update or delete it later.

## Deleting redirections

### Delete redirections

`DeleteUrlRedirectionsAsync` deletes up to 250 redirections by ID.

```csharp
UrlRedirectionDeleteResult deleted = await client.UrlRedirections.DeleteUrlRedirectionsAsync(guid, [12, 13]);

Console.WriteLine($"Deleted: {string.Join(", ", deleted.Deleted ?? [])}");
Console.WriteLine($"Not found: {string.Join(", ", deleted.NotFound ?? [])}");
```

`Deleted` lists the IDs that were deleted. `NotFound` lists the IDs that didn't match an active redirection. Passing an empty list throws an `ArgumentException` before any request is sent.

**Signature:** `Task<UrlRedirectionDeleteResult> DeleteUrlRedirectionsAsync(string instanceGuid, IEnumerable<int> redirectionIds)`

## Spreadsheets

The Management API has no call that lists redirections. To get them all, with their IDs, export them as a spreadsheet. You can edit the spreadsheet and import it back to update many redirections at once.

### Export redirections

`ExportUrlRedirectionsAsync` returns every active redirection as an Excel (`.xlsx`) file. Dispose the stream when you're done with it.

```csharp
await using (var export = await client.UrlRedirections.ExportUrlRedirectionsAsync(guid))
await using (var file = File.Create("redirections.xlsx"))
{
    await export.CopyToAsync(file);
}
```

**Signature:** `Task<Stream> ExportUrlRedirectionsAsync(string instanceGuid)`

---

### Import redirections

`ImportUrlRedirectionsAsync` imports redirections from an Excel (`.xlsx`) file of up to 5,000 rows. It uses the same columns as the export, so you can export, edit and re-import.

```csharp
await using var edited = File.OpenRead("redirections.xlsx");
UrlRedirectionSaveResult imported = await client.UrlRedirections.ImportUrlRedirectionsAsync(guid, edited);

foreach (var row in imported.Skipped ?? [])
{
    Console.WriteLine($"Row {row.Index} skipped: {row.Reason}");
}
```

| Column | Notes |
|---|---|
| `OriginURL` | Required |
| `DestinationURL` | Required |
| `HTTPCode` | Optional. Defaults to `301`. |
| `OriginLangCodes` | Optional. Locale codes, separated by semicolons. |
| `DestinationLangCode` | Optional |
| `Content` | Optional |
| `UrlRedirectionID` | Optional. A row with an ID updates that redirection; a row without one creates a redirection. |

The result has the same shape as a save. Rows that fail are skipped and reported, and `Index` is the row number in the file. The header is row 1.

The SDK doesn't dispose the stream you pass. `fileName` sets the file name sent with the upload; the default is `redirections.xlsx`.

**Signature:** `Task<UrlRedirectionSaveResult> ImportUrlRedirectionsAsync(string instanceGuid, Stream file, string fileName = "redirections.xlsx")`

## Error handling

Every error from the API or the network throws an `AgilityManagementException`. It carries the HTTP status code, the API's own message and a request ID. Items the API skips are reported in the result, not thrown.

```csharp
try
{
    var saved = await client.UrlRedirections.SaveUrlRedirectionsAsync(guid, redirections);
}
catch (AgilityManagementException ex)
{
    Console.Error.WriteLine($"{ex.StatusCode}: {ex.ApiMessage} (request {ex.RequestId})");
}
```

Saves, deletes and imports are never retried. The export is a read, so it's retried automatically on temporary failures. See the [Intro](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-intro) for the full list of exceptions and the retry rules.
