---
layout: article
title: Clients
description: Register confidential and public OAuth clients against your Appwrite project's OAuth2 server and manage them from your own developer platform.
back: /docs/products/auth/oauth-server
---

A **client** is a third-party app that authenticates users through your project's OAuth2 server. Each client registers the redirect URIs it is allowed to return to and the post-logout redirect URIs it can end sessions at, sets its type, and chooses whether the [device flow](/docs/products/auth/oauth-server/device-flow) is enabled. Its other attributes serve two surfaces: branding like the name, logo, and tagline can appear on your consent screen, while attributes like tags, images, and the privacy policy URL are for your project's apps marketplace.

Integrators never visit the Appwrite Console. You are expected to build a developer platform on your own website with the [Client SDKs](/docs/sdks#client)' `apps` service, which covers the full lifecycle: `create`, `update`, `get`, `list`, `delete`, along with `createSecret`, `listSecrets`, `getSecret`, `deleteSecret`, `updateTeam`, and `deleteTokens`. Any signed-in user on your project can call them; no API key is involved. The Console's **Auth > OAuth2 server > Apps** tab is your own administrative view of the same data.

![OAuth2 clients list in the Appwrite Console](/images/docs/oauth-server/oauth2-server-apps-list.avif)

Server SDKs matter in exactly two places: [introspecting](/docs/products/auth/oauth-server/tokens#introspect) a public client's tokens, which needs an API key because there is no client secret, and administrative operations like [curating labels](#labels).

# Confidential and public clients

![Confidential clients exchange the code from their backend with a client secret, while public clients exchange it from the device with PKCE](/images/docs/oauth-server/diagram-clients.avif)

Every client is one of two types, and the difference comes down to a single question: can the app keep a secret?

- A **confidential** client runs code on a server the developer controls, so it can store a `client_secret` that users never see. It authenticates to the token endpoint with that secret, which lets your server prove which client is calling.
- A **public** client runs entirely on the user's device (a single-page app, a native mobile app, a CLI), where any embedded secret would ship to the user and could be read. Public clients receive no secret and rely on PKCE instead.

![Token lifetimes for confidential and public clients](/images/docs/oauth-server/oauth2-server-token-lifetimes.avif)

The type a client uses changes what it can do:

| | Confidential | Public |
| --- | --- | --- |
| Client secret | Issued, sent on token requests | None issued |
| PKCE | Optional (configurable per project) | Always required |
| Token introspection | With its client secret | With a project API key holding the `oauth2.read` scope |
| Default access token lifetime | 8 hours | 1 hour |
| Default refresh token lifetime | 365 days | 30 days |

Choose confidential whenever the app has a backend. It is the safer default: the token exchange is protected by a secret, tokens never touch the browser, and sessions can last longer. Reserve public for apps that genuinely have no server to hold a secret.

# Register a client

Create a client with the `create` method. It needs only a name and a redirect URI; everything else can be filled in later with `update`. Registering a confidential client also calls for a [secret](#secrets) before it can exchange tokens.

```client-web
import { Client, Apps, ID } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.create({
    appId: ID.unique(),
    name: 'Custom App',
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

App app = await apps.create(
    appId: ID.unique(),
    name: 'Custom App',
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let app = try await apps.create(
    appId: ID.unique(),
    name: "Custom App",
    redirectUris: ["https://vantage.localhost/auth/redirect"],
    type: "confidential"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val app = apps.create(
    appId = ID.unique(),
    name = "Custom App",
    redirectUris = listOf("https://vantage.localhost/auth/redirect"),
    type = "confidential"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.create(
    ID.unique(), // appId
    "Custom App", // name
    List.of("https://vantage.localhost/auth/redirect"), // redirectUris
    null, // description (optional)
    null, // clientUri (optional)
    null, // logoUri (optional)
    null, // privacyPolicyUrl (optional)
    null, // termsUrl (optional)
    null, // contacts (optional)
    null, // tagline (optional)
    null, // tags (optional)
    null, // images (optional)
    null, // supportUrl (optional)
    null, // dataDeletionUrl (optional)
    null, // postLogoutRedirectUris (optional)
    null, // enabled (optional)
    "confidential", // type (optional)
    null, // deviceFlow (optional)
    null, // teamId (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps, ID } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.create({
    appId: ID.unique(),
    name: 'Custom App',
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
});
```

An app is owned by the user who created it. For team-oriented platforms, pass `teamId` on creation to make it team-owned instead: every team member can see it, while members with the `owner` or `developer` role manage it.

You can also create clients from the Console's **Apps** tab, which offers the same fields.

![Create an OAuth2 client dialog](/images/docs/oauth-server/oauth2-server-create-app.avif)

**Use Storage for logos and images**

`logoUri` and `images` accept URLs, not files. [Appwrite Storage](/docs/products/storage) pairs well here: upload the file to a bucket, take its preview URL, and store that URL on the app.

# Manage client secrets

A confidential client authenticates with a secret. Four methods manage them in one place: `createSecret`, `listSecrets`, `getSecret`, and `deleteSecret`.

Generate a new secret with `createSecret`. The plaintext value is returned only in this response.

![OAuth2 client secret shown once on creation](/images/docs/oauth-server/oauth2-server-secret-created.avif)

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const secret = await apps.createSecret({
    appId: '<APP_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

AppSecretPlaintext secret = await apps.createSecret(
    appId: '<APP_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let secret = try await apps.createSecret(
    appId: "<APP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val secret = apps.createSecret(
    appId = "<APP_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.createSecret(
    "<APP_ID>", // appId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const secret = await apps.createSecret({
    appId: '<APP_ID>',
});
```

A client can hold several secrets at once, which is how you rotate them without downtime: create the new secret, roll it out, then delete the old one. Each entry in `listSecrets` carries the metadata for deciding which secret can be removed safely: a `hint` of the value, who created it (`createdById`, `createdByName`), when it was created, and `lastAccessedAt` for when it last authenticated a request.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const secrets = await apps.listSecrets({
    appId: '<APP_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

AppSecretList secrets = await apps.listSecrets(
    appId: '<APP_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let secrets = try await apps.listSecrets(
    appId: "<APP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val secrets = apps.listSecrets(
    appId = "<APP_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.listSecrets(
    "<APP_ID>", // appId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const secrets = await apps.listSecrets({
    appId: '<APP_ID>',
});
```

`getSecret` reads a single entry by ID, and `deleteSecret` revokes it immediately.

# List clients

The `list` method drives three different screens, depending on the queries you pass:

- **A developer portal**: filter by the signed-in user with `Query.equal('userId', userId)` so developers manage their own apps.
- **Team settings**: filter with `Query.equal('teamId', teamId)` for the apps a team owns.
- **An apps marketplace**: list without an owner filter to show all registered apps. Filter by [labels](#labels), such as `Query.contains('labels', ['official'])`, when the marketplace should only show apps you have vetted, because labels cannot be self-assigned.

Always paginate with `Query.limit()` and `Query.cursorAfter()`; a marketplace can grow past any single page.

```client-web
import { Client, Apps, Query } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const portal = await apps.list({
    queries: [
        Query.equal('userId', '<SIGNED_IN_USER_ID>'),
        Query.limit(25),
    ],
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

AppList portal = await apps.list(
    queries: [
        Query.equal('userId', '<SIGNED_IN_USER_ID>'),
        Query.limit(25),
    ],
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let portal = try await apps.list(
    queries: [
        Query.equal("userId", value: "<SIGNED_IN_USER_ID>"),
        Query.limit(25)
    ]
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val portal = apps.list(
    queries = listOf(
        Query.equal("userId", "<SIGNED_IN_USER_ID>"),
        Query.limit(25)
    )
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.list(
    List.of(
        Query.Companion.equal("userId", "<SIGNED_IN_USER_ID>"),
        Query.Companion.limit(25)
    ), // queries (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps, Query } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const portal = await apps.list({
    queries: [
        Query.equal('userId', '<SIGNED_IN_USER_ID>'),
        Query.limit(25),
    ],
});
```

# Get a client

Read a single client with `get`. This backs the app detail page in a developer portal, and a [consent screen](/docs/products/auth/oauth-server/authorization#consent) uses it to show the requesting app's name and logo.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.get({
    appId: '<APP_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

App app = await apps.get(
    appId: '<APP_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let app = try await apps.get(
    appId: "<APP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val app = apps.get(
    appId = "<APP_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.get(
    "<APP_ID>", // appId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.get({
    appId: '<APP_ID>',
});
```

# Update a client

Change a client's redirect URIs, branding, or type with the `update` method.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.update({
    appId: '<APP_ID>',
    name: 'Vantage',
    description: 'A dashboard that signs in with TaskFlow.',
    clientUri: 'https://vantage.localhost',
    logoUri: 'https://vantage.localhost/logo.png',
    privacyPolicyUrl: 'https://vantage.localhost/privacy',
    termsUrl: 'https://vantage.localhost/terms',
    contacts: ['security@vantage.localhost'],
    tagline: 'Product analytics for modern teams',
    tags: ['analytics', 'productivity'],
    images: ['https://vantage.localhost/screenshot.png'],
    supportUrl: 'https://vantage.localhost/support',
    dataDeletionUrl: 'https://vantage.localhost/data-deletion',
    enabled: true,
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    postLogoutRedirectUris: ['https://vantage.localhost/signed-out'],
    type: 'confidential',
    deviceFlow: false,
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

App app = await apps.update(
    appId: '<APP_ID>',
    name: 'Vantage',
    description: 'A dashboard that signs in with TaskFlow.',
    clientUri: 'https://vantage.localhost',
    logoUri: 'https://vantage.localhost/logo.png',
    privacyPolicyUrl: 'https://vantage.localhost/privacy',
    termsUrl: 'https://vantage.localhost/terms',
    contacts: ['security@vantage.localhost'],
    tagline: 'Product analytics for modern teams',
    tags: ['analytics', 'productivity'],
    images: ['https://vantage.localhost/screenshot.png'],
    supportUrl: 'https://vantage.localhost/support',
    dataDeletionUrl: 'https://vantage.localhost/data-deletion',
    enabled: true,
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    postLogoutRedirectUris: ['https://vantage.localhost/signed-out'],
    type: 'confidential',
    deviceFlow: false,
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let app = try await apps.update(
    appId: "<APP_ID>",
    name: "Vantage",
    description: "A dashboard that signs in with TaskFlow.",
    clientUri: "https://vantage.localhost",
    logoUri: "https://vantage.localhost/logo.png",
    privacyPolicyUrl: "https://vantage.localhost/privacy",
    termsUrl: "https://vantage.localhost/terms",
    contacts: ["security@vantage.localhost"],
    tagline: "Product analytics for modern teams",
    tags: ["analytics", "productivity"],
    images: ["https://vantage.localhost/screenshot.png"],
    supportUrl: "https://vantage.localhost/support",
    dataDeletionUrl: "https://vantage.localhost/data-deletion",
    enabled: true,
    redirectUris: ["https://vantage.localhost/auth/redirect"],
    postLogoutRedirectUris: ["https://vantage.localhost/signed-out"],
    type: "confidential",
    deviceFlow: false
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val app = apps.update(
    appId = "<APP_ID>",
    name = "Vantage",
    description = "A dashboard that signs in with TaskFlow.",
    clientUri = "https://vantage.localhost",
    logoUri = "https://vantage.localhost/logo.png",
    privacyPolicyUrl = "https://vantage.localhost/privacy",
    termsUrl = "https://vantage.localhost/terms",
    contacts = listOf("security@vantage.localhost"),
    tagline = "Product analytics for modern teams",
    tags = listOf("analytics", "productivity"),
    images = listOf("https://vantage.localhost/screenshot.png"),
    supportUrl = "https://vantage.localhost/support",
    dataDeletionUrl = "https://vantage.localhost/data-deletion",
    enabled = true,
    redirectUris = listOf("https://vantage.localhost/auth/redirect"),
    postLogoutRedirectUris = listOf("https://vantage.localhost/signed-out"),
    type = "confidential",
    deviceFlow = false,
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.update(
    "<APP_ID>", // appId
    "Vantage", // name
    "A dashboard that signs in with TaskFlow.", // description
    "https://vantage.localhost", // clientUri
    "https://vantage.localhost/logo.png", // logoUri
    "https://vantage.localhost/privacy", // privacyPolicyUrl
    "https://vantage.localhost/terms", // termsUrl
    List.of("security@vantage.localhost"), // contacts
    "Product analytics for modern teams", // tagline
    List.of("analytics", "productivity"), // tags
    List.of("https://vantage.localhost/screenshot.png"), // images
    "https://vantage.localhost/support", // supportUrl
    "https://vantage.localhost/data-deletion", // dataDeletionUrl
    true, // enabled
    List.of("https://vantage.localhost/auth/redirect"), // redirectUris
    List.of("https://vantage.localhost/signed-out"), // postLogoutRedirectUris
    "confidential", // type
    false, // deviceFlow
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.update({
    appId: '<APP_ID>',
    name: 'Vantage',
    description: 'A dashboard that signs in with TaskFlow.',
    clientUri: 'https://vantage.localhost',
    logoUri: 'https://vantage.localhost/logo.png',
    privacyPolicyUrl: 'https://vantage.localhost/privacy',
    termsUrl: 'https://vantage.localhost/terms',
    contacts: ['security@vantage.localhost'],
    tagline: 'Product analytics for modern teams',
    tags: ['analytics', 'productivity'],
    images: ['https://vantage.localhost/screenshot.png'],
    supportUrl: 'https://vantage.localhost/support',
    dataDeletionUrl: 'https://vantage.localhost/data-deletion',
    enabled: true,
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    postLogoutRedirectUris: ['https://vantage.localhost/signed-out'],
    type: 'confidential',
    deviceFlow: false,
});
```

The `type` parameter accepts `confidential` (the default) or `public`. Set `deviceFlow` to `true` to let the client use the [device authorization flow](/docs/products/auth/oauth-server/device-flow). The branding fields (`logoUri`, `tagline`, `privacyPolicyUrl`, `termsUrl`) can appear on the consent screen, and they fill out the app's listing on your marketplace.

# Transfer to a team

Convert a user-owned app to team ownership, or move it between teams, with `updateTeam`. The member doing the transfer needs the `owner` or `developer` role in the app's current team, and at least membership in the new one.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.updateTeam({
    appId: '<APP_ID>',
    teamId: '<TEAM_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

App app = await apps.updateTeam(
    appId: '<APP_ID>',
    teamId: '<TEAM_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let app = try await apps.updateTeam(
    appId: "<APP_ID>",
    teamId: "<TEAM_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val app = apps.updateTeam(
    appId = "<APP_ID>",
    teamId = "<TEAM_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.updateTeam(
    "<APP_ID>", // appId
    "<TEAM_ID>", // teamId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.updateTeam({
    appId: '<APP_ID>',
    teamId: '<TEAM_ID>',
});
```

# Curate with labels

Labels are trust markers like `official`, `partner`, or `verified`. They are read-only for clients: only a [Server SDK](/docs/sdks#server) using a project API key with the `apps.write` scope can set them, so app owners cannot mark themselves as trusted. That is what makes them safe to filter a marketplace by.

```server-nodejs
import { Client, Apps } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const apps = new Apps(client);

const app = await apps.updateLabels({
    appId: '<APP_ID>',
    labels: ['official'],
});
```
```server-deno
import { Client, Apps } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const apps = new Apps(client);

const app = await apps.updateLabels({
    appId: '<APP_ID>',
    labels: ['official'],
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Apps;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    ->setProject('<YOUR_PROJECT_ID>') // Your project ID
    ->setKey('<YOUR_API_KEY>'); // Your secret API key

$apps = new Apps($client);

$app = $apps->updateLabels(
    appId: '<APP_ID>',
    labels: ['official']
);
```
```server-python
from appwrite.client import Client
from appwrite.services.apps import Apps

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
client.set_project('<YOUR_PROJECT_ID>') # Your project ID
client.set_key('<YOUR_API_KEY>') # Your secret API key

apps = Apps(client)

app = apps.update_labels(
    app_id = '<APP_ID>',
    labels = ['official']
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
    .set_project('<YOUR_PROJECT_ID>') # Your project ID
    .set_key('<YOUR_API_KEY>') # Your secret API key

apps = Apps.new(client)

app = apps.update_labels(
    app_id: '<APP_ID>',
    labels: ['official']
)
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

Apps apps = Apps(client);

App app = await apps.updateLabels(
    appId: '<APP_ID>',
    labels: ['official'],
);
```
```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .SetProject("<YOUR_PROJECT_ID>") // Your project ID
    .SetKey("<YOUR_API_KEY>"); // Your secret API key

Apps apps = new Apps(client);

App app = await apps.UpdateLabels(
    appId: "<APP_ID>",
    labels: new List<string> { "official" }
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

val apps = Apps(client)

val app = apps.updateLabels(
    appId = "<APP_ID>",
    labels = listOf("official")
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>"); // Your secret API key

Apps apps = new Apps(client);

apps.updateLabels(
    "<APP_ID>", // appId
    List.of("official"), // labels
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let apps = Apps(client)

let app = try await apps.updateLabels(
    appId: "<APP_ID>",
    labels: ["official"]
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/apps"
    "github.com/appwrite/sdk-for-go/client"
)

func main() {
    c := client.New(
        client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        client.WithProject("<YOUR_PROJECT_ID>"),
        client.WithKey("<YOUR_API_KEY>"),
    )

    service := apps.New(c)

    app, err := service.UpdateLabels(
        "<APP_ID>",
        []string{"official"},
    )
    if err != nil {
        panic(err)
    }

    fmt.Println(app)
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::apps::Apps;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new()
        .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
        .set_project("<YOUR_PROJECT_ID>") // Your project ID
        .set_key("<YOUR_API_KEY>"); // Your secret API key

    let apps = Apps::new(&client);

    let app = apps
        .update_labels(
            "<APP_ID>",
            vec!["official".into()],
        )
        .await?;

    let _ = app;

    Ok(())
}
```

Labels replace the previous set on every call. Up to 100 labels are allowed, each up to 36 alphanumeric characters.

# Revoke all tokens

`deleteTokens` invalidates every token issued to a client at once: a kill switch for all of its sessions. Reach for it when testing, since it forces the consent screen to reappear, or as an emergency response to a leaked secret, together with rotating the secret itself.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

await apps.deleteTokens({
    appId: '<APP_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

await apps.deleteTokens(
    appId: '<APP_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

try await apps.deleteTokens(
    appId: "<APP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

apps.deleteTokens(
    appId = "<APP_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.deleteTokens(
    "<APP_ID>", // appId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

await apps.deleteTokens({
    appId: '<APP_ID>',
});
```

# Delete a client

Deleting a client immediately invalidates every token issued to it.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

await apps.delete({
    appId: '<APP_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

await apps.delete(
    appId: '<APP_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

try await apps.delete(
    appId: "<APP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

apps.delete(
    appId = "<APP_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.delete(
    "<APP_ID>", // appId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

await apps.delete({
    appId: '<APP_ID>',
});
```

# Dynamic client registration

Clients can also register themselves over plain HTTP, without an Appwrite SDK or a signed-in user, through the registration endpoint ([RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)). This is what makes your OAuth2 server compatible with MCP servers and other software that provisions its own client on first contact. Registration is rate-limited per IP.

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/register' \
  -H 'Content-Type: application/json' \
  -d '{
    "client_name": "MCP Client",
    "redirect_uris": [
        "https://vantage.localhost/auth/redirect"
    ],
    "token_endpoint_auth_method": "none"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/register
Content-Type: application/json
{
    "client_name": "MCP Client",
    "redirect_uris": [
        "https://vantage.localhost/auth/redirect"
    ],
    "token_endpoint_auth_method": "none"
}
```

```json
{
    "client_id": "6a56677caf736a2310a2",
    "client_id_issued_at": 1784047484,
    "redirect_uris": ["https://vantage.localhost/auth/redirect"],
    "token_endpoint_auth_method": "none",
    "grant_types": ["authorization_code"],
    "response_types": ["code"],
    "client_name": "MCP Client"
}
```

`token_endpoint_auth_method: none` registers a public client for PKCE; `client_secret_basic` (the default) and `client_secret_post` register confidential clients. The registered app appears in your Console and in `list` like any other client.
