# Management SDK - Instance Users

> Source: https://agilitycms.com/docs/dotNet/management-sdk-dotnet-instance-users

`client.InstanceUsers` manages who can use an instance and which roles they have. This article also covers the signed-in user (`client.ServerUsers`), Personal Access Tokens (`client.PersonalAccessTokens`) and an instance's Fetch API keys (`client.OAuth`).

> **Note:** Upgrading from 1.x? `instanceUserMethods` is now `client.InstanceUsers`, and every method takes the instance GUID first. See [Migrating to 2.0](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-migrating-to-2).

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 user and token 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.

## Which token you need

Instance user management and token management need an **OAuth access token**. The API refuses a Personal Access Token for these calls.

| Area | Personal Access Token | OAuth access token |
|---|---|---|
| `client.InstanceUsers` | No | Yes |
| `client.PersonalAccessTokens` | No | Yes |

See the [Intro](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-intro) for how to sign in with OAuth and give the client a refresh token.

## The signed-in user

### Get the current user

`GetCurrentUserAsync` returns the user the access token belongs to, with the instances they can reach. It's a server-level call, so it doesn't take an instance GUID. Use it to find the GUIDs of the instances a token can reach.

```csharp
ServerUser me = await client.ServerUsers.GetCurrentUserAsync();
Console.WriteLine($"Signed in as {me.EmailAddress}");

foreach (var site in me.WebsiteAccess ?? [])
{
    Console.WriteLine($"{site.WebsiteName}: {site.Guid}");
}
```

**Signature:** `Task<ServerUser> GetCurrentUserAsync()`

## Instance users

Access via `client.InstanceUsers`.

### Get users

List every user of an instance.

```csharp
List<WebsiteUser> users = await client.InstanceUsers.GetUsersAsync(guid);

foreach (var user in users)
{
    Console.WriteLine($"{user.EmailAddress} - {user.FirstName} {user.LastName} (ID: {user.UserID})");
}
```

**Signature:** `Task<List<WebsiteUser>> GetUsersAsync(string instanceGuid)`

---

### Save a user

Add a user to an instance, or change an existing user's roles. The user is identified by email address.

```csharp
InstanceUser user = await client.InstanceUsers.SaveUserAsync(guid, "editor@example.com",
    [new InstanceRole { RoleID = editorRoleId }],
    firstName: "Sam", lastName: "Lee");

Console.WriteLine($"Saved user ID: {user.UserID}");
```

The roles you pass **replace** the user's current roles. To add a role, include the roles the user already has as well. `firstName` and `lastName` are used for a new user.

**Signature:** `Task<InstanceUser> SaveUserAsync(string instanceGuid, string emailAddress, IReadOnlyList<InstanceRole> roles, string? firstName = null, string? lastName = null)`

---

### Delete a user

Remove a user from an instance by their user ID.

```csharp
await client.InstanceUsers.DeleteUserAsync(guid, user.UserID);
```

The method returns nothing. If the delete fails, it throws an `AgilityManagementException`.

**Signature:** `Task DeleteUserAsync(string instanceGuid, int userId)`

## Personal Access Tokens

`client.PersonalAccessTokens` manages your own Personal Access Tokens (PATs): long-lived tokens for scripts, CI and server-side jobs. These are server-level calls, so they don't take an instance GUID. They need an OAuth access token: a PAT can't create, list or revoke tokens.

For what a PAT is and how to use one, see [Personal Access Tokens](https://agilitycms.com/docs/developers/personal-access-tokens).

### Create a token

```csharp
PersonalAccessTokenCreationResponse created = await client.PersonalAccessTokens.CreateTokenAsync(
    new PersonalAccessTokenRequest
    {
        Name = "nightly-sync",
        ExpiryDate = DateTime.UtcNow.AddYears(1),
    });

StoreSecret(created.Token!);   // the token value is returned only now
```

`Name` is required. `ExpiryDate` is optional: it can be at most two years away, and two years is the default.

> ⚠️ **Store the token value straight away.** `created.Token` is the only time the API returns it. Listing or reading a token later never includes its value.

**Signature:** `Task<PersonalAccessTokenCreationResponse> CreateTokenAsync(PersonalAccessTokenRequest request)`

---

### List your tokens

```csharp
PersonalAccessTokenListResponse mine = await client.PersonalAccessTokens.GetTokensAsync();

foreach (var token in mine.Tokens ?? [])
{
    Console.WriteLine($"{token.Name}: expires {token.ExpiryDate:d}, enabled: {token.Enabled}");
}
```

The list doesn't include token values.

**Signature:** `Task<PersonalAccessTokenListResponse> GetTokensAsync()`

---

### Get a token

```csharp
PersonalAccessTokenResponse token = await client.PersonalAccessTokens.GetTokenAsync(tokenId);
Console.WriteLine($"{token.Name}: {token.DaysUntilExpiration} days left, last used {token.LastUsedDate}");
```

**Signature:** `Task<PersonalAccessTokenResponse> GetTokenAsync(Guid tokenId)`

---

### Rename, enable or disable a token

Both properties of `PersonalAccessTokenUpdateRequest` are optional: set `Name` to rename the token, `Enabled` to turn it on or off, or both. The method returns the updated token.

```csharp
await client.PersonalAccessTokens.UpdateTokenAsync(created.TokenID,
    new PersonalAccessTokenUpdateRequest { Enabled = false });
```

**Signature:** `Task<PersonalAccessTokenResponse> UpdateTokenAsync(Guid tokenId, PersonalAccessTokenUpdateRequest request)`

---

### Revoke a token

```csharp
await client.PersonalAccessTokens.RevokeTokenAsync(created.TokenID);
```

**Signature:** `Task RevokeTokenAsync(Guid tokenId)`

---

### Token limits

The API allows each user 10 active tokens, and 5 token creations an hour. Beyond that, it returns HTTP 429, which the SDK throws as an `AgilityManagementException`. The SDK doesn't retry a token creation, because it never retries writes.

## Fetch API keys

`client.OAuth` returns an instance's Fetch API keys: one for published content and one for preview (staging) content. Use them to configure a website that reads content with the Fetch API.

```csharp
string fetchKey = await client.OAuth.GetFetchApiKeyAsync(guid);
string previewKey = await client.OAuth.GetPreviewApiKeyAsync(guid);
```

**Signatures:**

- `Task<string> GetFetchApiKeyAsync(string instanceGuid)`
- `Task<string> GetPreviewApiKeyAsync(string instanceGuid)`

> ⚠️ **Treat these keys as secrets.** Don't log them or commit them.

## 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. A call made with a Personal Access Token to an endpoint that needs OAuth fails this way too.

```csharp
try
{
    var users = await client.InstanceUsers.GetUsersAsync(guid);
}
catch (AgilityManagementException ex)
{
    Console.Error.WriteLine($"{ex.StatusCode}: {ex.ApiMessage} (request {ex.RequestId})");
}
```

See the [Intro](https://agilitycms.com/docs/dotNet/management-sdk-dotnet-intro) for the full list of exceptions and the retry rules.

## Coming from 1.x

| 1.x (`client.instanceUserMethods`) | 2.0 (`client.InstanceUsers`) |
|---|---|
| `GetUsers(guid)` | `GetUsersAsync(guid)` |
| `SaveUser(email, roles, guid, firstName, lastName)` | `SaveUserAsync(guid, email, roles, firstName, lastName)` |
| `DeleteUser(userID, guid)` | `DeleteUserAsync(guid, userId)`: returns `Task` instead of a message string |

The signed-in user, Personal Access Token management and Fetch API keys are new in 2.0.
