diff --git a/.optimize-cache.json b/.optimize-cache.json
index bba480b445..ed5cd56343 100644
--- a/.optimize-cache.json
+++ b/.optimize-cache.json
@@ -1587,6 +1587,26 @@
"static/images/docs/network/edges-map.png": "12ecc1ea200905ba75eb7cfd17055a156fd51fccf746869a1058f923dfd7ac1b",
"static/images/docs/network/pops-map.png": "205ead599703cf47d0df316db8fcc4f48d5eed01508109fc740d17914275e9ab",
"static/images/docs/network/regions-map.png": "c65f1423ab19c3048bf8bf93117e8f2e1d13a2bc705c00307de7ee821e5668a1",
+ "static/images/docs/oauth-server/dark/oauth2-server-apps-empty.png": "b81617a77c6489bc93cfe3c118c2fd075eebda850fc5803cba4b1b62792269f1",
+ "static/images/docs/oauth-server/dark/oauth2-server-apps-list.png": "a0b1a9bcc5708bb48f7d4684d754cfc8af8e666f808e11d00fc42182ee7ccf6e",
+ "static/images/docs/oauth-server/dark/oauth2-server-create-app.png": "8237a7890f6246661086cf80b7eb6cdbf0b27b4bc1a5cbcf485209ef9a27dfcf",
+ "static/images/docs/oauth-server/dark/oauth2-server-discovery.png": "e688439f87c4ae73086b76ee094e036d5bddb14b9431aaad5901d281ab3ca413",
+ "static/images/docs/oauth-server/dark/oauth2-server-secret-created.png": "d675fe574706d58b224ce823c8904145482472f64ab940a8bb37b92fcc33008e",
+ "static/images/docs/oauth-server/dark/oauth2-server-settings-disabled.png": "ad51d531eaa3736839859097c024bca41a28d1339ceef773e376c06936e040ee",
+ "static/images/docs/oauth-server/dark/oauth2-server-settings.png": "18da0340416739f803b8c04d66c191e92e7eafa70cd301acb1e269d8dd5a0c81",
+ "static/images/docs/oauth-server/dark/oauth2-server-token-lifetimes.png": "46743e2f0a3e8c126d590809668a37c2beb1f1c5483002839d9a15eca9e110d4",
+ "static/images/docs/oauth-server/guide/taskflow-consent.png": "5c4f4cbc6b225c60d5d6e2f1616abe45f0e6b66a5014438961fca98db349bd18",
+ "static/images/docs/oauth-server/guide/taskflow-login.png": "5d0013b31b211efe4d3a2f6a9f224a5b547d3ef1370e507b9a183bb276fc90e1",
+ "static/images/docs/oauth-server/guide/vantage-dashboard.png": "b2e95c34ec9c19be090e5f3384e0c3278dd840059b3f015ccdb13a16c135dde1",
+ "static/images/docs/oauth-server/guide/vantage-landing.png": "dc597717bcc93de02b2e5336a0f05d743ea66e6d91cef40fbdc61f8380577bec",
+ "static/images/docs/oauth-server/oauth2-server-apps-empty.png": "6f7b5c2021df2db7a3a1c4da3dfcf9b2ff119e98450e755eb64f03263030da0a",
+ "static/images/docs/oauth-server/oauth2-server-apps-list.png": "e3f38509e45e502f742e7532ae1a19bdd35b0891821b6a81601b7960ff16b38c",
+ "static/images/docs/oauth-server/oauth2-server-create-app.png": "182c207475ebd8f386d3d1ec522058f23f59cb47d85f4b681889d0dd08eb8cd8",
+ "static/images/docs/oauth-server/oauth2-server-discovery.png": "5b75f8e780cac92a68fda24a9686ba89ce83d89ac66e5f651428b4c62bb81cbd",
+ "static/images/docs/oauth-server/oauth2-server-secret-created.png": "5a6d48595018a8faaa26aeafe099c3dda23613a35e49884decbecb0029449168",
+ "static/images/docs/oauth-server/oauth2-server-settings-disabled.png": "648091d9d81308ab93dd456f172fa1c1977c2fbce9b8aa03893398bdefe10595",
+ "static/images/docs/oauth-server/oauth2-server-settings.png": "914aa6511caedb568f199eacef837366c523f78125c551afa992e39098707431",
+ "static/images/docs/oauth-server/oauth2-server-token-lifetimes.png": "895bf62c7f5171c8f244fda37b516760266c47447f2c66c93c3c812139823b96",
"static/images/docs/platform/add-platform.png": "5a05bb9d75a8d5270bfa5e67df7e6de20a9fad174476a112b5bdab72e7bdad30",
"static/images/docs/platform/create-api-key.png": "7661b3845e13704643f8ff4f763faa8e61efb90878c3ffa7466ece0910b8ecab",
"static/images/docs/platform/create-webhook.png": "77e08173da6ac534524e025433cf75e532d853a135944a7c6ba2278357d88b2d",
diff --git a/src/routes/docs/Sidebar.svelte b/src/routes/docs/Sidebar.svelte
index 1dd3ea69ec..b1c7a1edfa 100644
--- a/src/routes/docs/Sidebar.svelte
+++ b/src/routes/docs/Sidebar.svelte
@@ -101,6 +101,13 @@
href: '/docs/partners/project',
icon: 'icon-briefcase',
isParent: true
+ },
+ {
+ label: 'OAuth server',
+ href: '/docs/partners/oauth-server',
+ icon: 'icon-key',
+ isParent: true,
+ new: isNewUntil('31 Aug 2026')
}
]
},
diff --git a/src/routes/docs/partners/oauth-server/+layout.svelte b/src/routes/docs/partners/oauth-server/+layout.svelte
new file mode 100644
index 0000000000..409e8402dc
--- /dev/null
+++ b/src/routes/docs/partners/oauth-server/+layout.svelte
@@ -0,0 +1,64 @@
+
+
+
+
+
+
diff --git a/src/routes/docs/partners/oauth-server/+page.markdoc b/src/routes/docs/partners/oauth-server/+page.markdoc
new file mode 100644
index 0000000000..b3ec80274e
--- /dev/null
+++ b/src/routes/docs/partners/oauth-server/+page.markdoc
@@ -0,0 +1,68 @@
+---
+layout: article
+title: OAuth server
+description: Turn your Appwrite project into an OAuth 2.1 and OpenID Connect provider so third-party apps can sign in with your product.
+back: /docs
+---
+
+Appwrite can act as an **OAuth 2.1 and OpenID Connect provider**. When you enable the OAuth server on a project, third-party apps register as clients, send your users to a consent screen you host, and receive tokens your project issues. The users in your project become an identity provider that any standards-compliant OAuth or OIDC library can integrate with, the same way apps integrate with "Sign in with Google" or "Sign in with GitHub".
+
+{% only_dark %}
+
+{% /only_dark %}
+{% only_light %}
+
+{% /only_light %}
+
+This is the inverse of the [OAuth providers](/docs/partners/project/oauth) feature. OAuth providers let your users sign in to your project *with* an external service. The OAuth server lets external services sign their users in *with your project*.
+
+# How it works {% #how-it-works %}
+
+The OAuth server has three moving parts.
+
+1. **The authorization server.** Enabling the server on your project exposes a full set of OIDC endpoints: authorization, token, userinfo, discovery, JWKS, revocation, and introspection. A keypair is generated for your project on first enable and used to sign tokens.
+2. **Clients.** Each third-party app registers as a [client](/docs/partners/oauth-server/clients), either confidential (it has a backend that can hold a secret) or public (a browser or mobile app that cannot). Clients declare the redirect URIs they are allowed to return to.
+3. **The consent screen.** You host a consent screen at an authorization URL you configure. When a user authorizes a client, Appwrite redirects them to your screen, your screen confirms the grant, and Appwrite issues an authorization code the client exchanges for tokens.
+
+The flow follows the OAuth 2.1 authorization code grant with PKCE, plus the OpenID Connect layer for identity. Because the server is spec-compliant, integrators can point any OAuth or OIDC client library at your project's discovery URL and it works without Appwrite-specific code.
+
+# Standards support {% #standards %}
+
+The server implements the current OAuth 2.1 and OpenID Connect security practices:
+
+- **Authorization code grant with PKCE.** PKCE uses `S256` and is required for public clients. The password grant and the implicit access-token grant are not supported.
+- **Refresh tokens with rotation.** Every refresh issues a new refresh token and invalidates the previous one. Reusing an old refresh token revokes the whole token family.
+- **Exact redirect URI matching.** A returned redirect URI must exactly match a registered one, with a narrow loopback-port exception for public native clients (RFC 8252).
+- **OpenID Connect.** The server issues `id_token` values, serves a discovery document at `/.well-known/openid-configuration`, and publishes signing keys at `/.well-known/jwks.json`.
+- **Device authorization grant** (RFC 8628) for input-constrained devices like TVs and CLIs.
+
+# Concepts {% #concepts %}
+
+{% cards %}
+{% cards_item href="/docs/partners/oauth-server/quick-start" title="Quick start" %}
+Enable the server, register a client, and run your first sign-in end to end.
+{% /cards_item %}
+{% cards_item href="/docs/partners/oauth-server/clients" title="Clients" %}
+Register confidential and public OAuth clients and manage their secrets.
+{% /cards_item %}
+{% cards_item href="/docs/partners/oauth-server/authorization" title="Authorization" %}
+The authorization code flow, PKCE, and hosting your own consent screen.
+{% /cards_item %}
+{% cards_item href="/docs/partners/oauth-server/tokens" title="Tokens" %}
+Access, refresh, and ID tokens, their lifetimes, introspection, and revocation.
+{% /cards_item %}
+{% cards_item href="/docs/partners/oauth-server/scopes" title="Scopes" %}
+The built-in OpenID scopes and the custom scopes your clients can request.
+{% /cards_item %}
+{% cards_item href="/docs/partners/oauth-server/device-flow" title="Device flow" %}
+Authorize TVs, CLIs, and other input-constrained devices.
+{% /cards_item %}
+{% /cards %}
+
+# Guide {% #guide %}
+
+{% cards %}
+{% cards_item href="/docs/partners/oauth-server/sign-in-with-your-product/step-1" title="Sign in with your product" %}
+Build a full sign-in with your product experience end to end, from the consent screen to the token exchange.
+{% /cards_item %}
+{% /cards %}
diff --git a/src/routes/docs/partners/oauth-server/authorization/+page.markdoc b/src/routes/docs/partners/oauth-server/authorization/+page.markdoc
new file mode 100644
index 0000000000..be7872d8d9
--- /dev/null
+++ b/src/routes/docs/partners/oauth-server/authorization/+page.markdoc
@@ -0,0 +1,522 @@
+---
+layout: article
+title: Authorization
+description: The OAuth 2.1 authorization code flow with PKCE, and how to host a consent screen for your Appwrite OAuth server.
+back: /docs/partners/oauth-server
+---
+
+Authorization is the step where a user allows a client to act on their behalf. Appwrite's OAuth server uses the **authorization code flow with PKCE**, the flow the OAuth 2.1 draft recommends for every client type. This page covers the flow end to end and how to host the consent screen it depends on.
+
+# The authorization code flow {% #flow %}
+
+1. The client sends the user to the **authorization endpoint** with its client ID, a registered redirect URI, `response_type=code`, and the scopes it wants.
+2. Appwrite checks for a user session on your project. With a session, it creates a grant and redirects to your **authorization URL** with a `grant_id`. Without one, it redirects to your authorization URL with the full request so you can sign the user in first.
+3. Your **consent screen** loads the grant, shows the user what is being requested, and approves or rejects it.
+4. On approval, Appwrite redirects back to the client's redirect URI with a short-lived authorization `code`.
+5. The client exchanges the code for tokens at the token endpoint. See [Tokens](/docs/partners/oauth-server/tokens).
+
+# PKCE {% #pkce %}
+
+PKCE (Proof Key for Code Exchange) binds an authorization request to the client that started it, so an intercepted code cannot be redeemed by anyone else. The client generates a random `code_verifier`, hashes it with SHA-256 into a `code_challenge`, and sends the challenge on the authorize request. When it later redeems the code, it presents the original verifier, and the server checks that they match.
+
+The server supports the `S256` challenge method only. PKCE is **always required for public clients**. For confidential clients it is optional and controlled per project by the **Require PKCE** setting, since those clients already authenticate with a secret.
+
+# Start authorization {% #authorize %}
+
+Send the user to the authorization endpoint. A public client includes its `code_challenge`; a confidential client includes it when PKCE is enabled.
+
+{% multicode %}
+```client-web
+import { Client, Oauth2 } from 'appwrite';
+
+const client = new Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject(''); // Your project ID
+
+const oauth2 = new Oauth2(client);
+
+const result = await oauth2.authorize({
+ clientId: '',
+ redirectUri: 'https://example.com',
+ responseType: 'code',
+ scope: '', // optional
+ state: '', // optional
+ nonce: '', // optional
+ codeChallenge: '', // optional
+ codeChallengeMethod: 's256', // optional
+ prompt: '', // optional
+ maxAge: 0, // optional
+ authorizationDetails: '', // optional
+ resource: '' // optional
+});
+
+console.log(result);
+```
+```client-flutter
+import 'package:appwrite/appwrite.dart';
+
+Client client = Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject(''); // Your project ID
+
+Oauth2 oauth2 = Oauth2(client);
+
+Oauth2Authorize result = await oauth2.authorize(
+ clientId: '',
+ redirectUri: 'https://example.com',
+ responseType: 'code',
+ scope: '', // optional
+ state: '', // optional
+ nonce: '', // optional
+ codeChallenge: '', // optional
+ codeChallengeMethod: 's256', // optional
+ prompt: '', // optional
+ maxAge: 0, // optional
+ authorizationDetails: '', // optional
+ resource: '', // optional
+);
+```
+```client-apple
+import Appwrite
+
+let client = Client()
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject("") // Your project ID
+
+let oauth2 = Oauth2(client)
+
+let oauth2Authorize = try await oauth2.authorize(
+ client_id: "",
+ redirect_uri: "https://example.com",
+ response_type: "code",
+ scope: "", // optional
+ state: "", // optional
+ nonce: "", // optional
+ code_challenge: "", // optional
+ code_challenge_method: "s256", // optional
+ prompt: "", // optional
+ max_age: 0, // optional
+ authorization_details: "", // optional
+ resource: "" // optional
+)
+
+```
+```client-android-kotlin
+import io.appwrite.Client
+import io.appwrite.coroutines.CoroutineCallback
+import io.appwrite.services.Oauth2
+
+val client = Client(context)
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject("") // Your project ID
+
+val oauth2 = Oauth2(client)
+
+val result = oauth2.authorize(
+ client_id = "",
+ redirect_uri = "https://example.com",
+ response_type = "code",
+ scope = "", // (optional)
+ state = "", // (optional)
+ nonce = "", // (optional)
+ code_challenge = "", // (optional)
+ code_challenge_method = "s256", // (optional)
+ prompt = "", // (optional)
+ max_age = 0, // (optional)
+ authorization_details = "", // (optional)
+ resource = "", // (optional)
+)
+```
+```client-android-java
+import io.appwrite.Client;
+import io.appwrite.coroutines.CoroutineCallback;
+import io.appwrite.services.Oauth2;
+
+Client client = new Client(context)
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject(""); // Your project ID
+
+Oauth2 oauth2 = new Oauth2(client);
+
+oauth2.authorize(
+ "", // client_id
+ "https://example.com", // redirect_uri
+ "code", // response_type
+ "", // scope (optional)
+ "", // state (optional)
+ "", // nonce (optional)
+ "", // code_challenge (optional)
+ "s256", // code_challenge_method (optional)
+ "", // prompt (optional)
+ 0, // max_age (optional)
+ "", // authorization_details (optional)
+ "", // resource (optional)
+ new CoroutineCallback<>((result, error) -> {
+ if (error != null) {
+ error.printStackTrace();
+ return;
+ }
+
+ Log.d("Appwrite", result.toString());
+ })
+);
+
+```
+```client-react-native
+import { Client, Oauth2 } from "react-native-appwrite";
+
+const client = new Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject(''); // Your project ID
+
+const oauth2 = new Oauth2(client);
+
+const result = await oauth2.authorize({
+ clientId: '',
+ redirectUri: 'https://example.com',
+ responseType: 'code',
+ scope: '', // optional
+ state: '', // optional
+ nonce: '', // optional
+ codeChallenge: '', // optional
+ codeChallengeMethod: 's256', // optional
+ prompt: '', // optional
+ maxAge: 0, // optional
+ authorizationDetails: '', // optional
+ resource: '' // optional
+});
+
+console.log(result);
+```
+{% /multicode %}
+
+Without a session on the project, Appwrite redirects to your authorization URL with the original request parameters attached. Sign the user in there (with your normal [Appwrite authentication](/docs/products/auth)), then send them back through the authorize endpoint so a grant can be created.
+
+# Host the consent screen {% #consent %}
+
+Your authorization URL is a page you host. When Appwrite redirects a signed-in user to it with a `grant_id`, the page reads the grant, presents it, and records the user's decision. The consent endpoints authenticate with the user's session on your project.
+
+**Load the grant** to see which client, scopes, and resources are being requested, then render your UI.
+
+{% info title="Required scope" %}
+Reading a grant requires the `oauth2.read` scope. Approving or rejecting one requires `oauth2.write`.
+{% /info %}
+
+{% multicode %}
+```client-web
+import { Client, Oauth2 } from 'appwrite';
+
+const client = new Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject(''); // Your project ID
+
+const oauth2 = new Oauth2(client);
+
+const result = await oauth2.getGrant({
+ grantId: ''
+});
+
+console.log(result);
+```
+```client-flutter
+import 'package:appwrite/appwrite.dart';
+
+Client client = Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject(''); // Your project ID
+
+Oauth2 oauth2 = Oauth2(client);
+
+Oauth2Grant result = await oauth2.getGrant(
+ grantId: '',
+);
+```
+```client-apple
+import Appwrite
+
+let client = Client()
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject("") // Your project ID
+
+let oauth2 = Oauth2(client)
+
+let oauth2Grant = try await oauth2.getGrant(
+ grant_id: ""
+)
+
+```
+```client-android-kotlin
+import io.appwrite.Client
+import io.appwrite.coroutines.CoroutineCallback
+import io.appwrite.services.Oauth2
+
+val client = Client(context)
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject("") // Your project ID
+
+val oauth2 = Oauth2(client)
+
+val result = oauth2.getGrant(
+ grant_id = "",
+)
+```
+```client-android-java
+import io.appwrite.Client;
+import io.appwrite.coroutines.CoroutineCallback;
+import io.appwrite.services.Oauth2;
+
+Client client = new Client(context)
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject(""); // Your project ID
+
+Oauth2 oauth2 = new Oauth2(client);
+
+oauth2.getGrant(
+ "", // grant_id
+ new CoroutineCallback<>((result, error) -> {
+ if (error != null) {
+ error.printStackTrace();
+ return;
+ }
+
+ Log.d("Appwrite", result.toString());
+ })
+);
+
+```
+```client-react-native
+import { Client, Oauth2 } from "react-native-appwrite";
+
+const client = new Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject(''); // Your project ID
+
+const oauth2 = new Oauth2(client);
+
+const result = await oauth2.getGrant({
+ grantId: ''
+});
+
+console.log(result);
+```
+{% /multicode %}
+
+**Approve** the grant to continue. Appwrite responds with the redirect URL back to the client, carrying the authorization code. Send the user there.
+
+{% multicode %}
+```client-web
+import { Client, Oauth2 } from 'appwrite';
+
+const client = new Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject(''); // Your project ID
+
+const oauth2 = new Oauth2(client);
+
+const result = await oauth2.approve({
+ grantId: '',
+ authorizationDetails: '', // optional
+ scope: '' // optional
+});
+
+console.log(result);
+```
+```client-flutter
+import 'package:appwrite/appwrite.dart';
+
+Client client = Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject(''); // Your project ID
+
+Oauth2 oauth2 = Oauth2(client);
+
+Oauth2Approve result = await oauth2.approve(
+ grantId: '',
+ authorizationDetails: '', // optional
+ scope: '', // optional
+);
+```
+```client-apple
+import Appwrite
+
+let client = Client()
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject("") // Your project ID
+
+let oauth2 = Oauth2(client)
+
+let oauth2Approve = try await oauth2.approve(
+ grant_id: "",
+ authorization_details: "", // optional
+ scope: "" // optional
+)
+
+```
+```client-android-kotlin
+import io.appwrite.Client
+import io.appwrite.coroutines.CoroutineCallback
+import io.appwrite.services.Oauth2
+
+val client = Client(context)
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject("") // Your project ID
+
+val oauth2 = Oauth2(client)
+
+val result = oauth2.approve(
+ grant_id = "",
+ authorization_details = "", // (optional)
+ scope = "", // (optional)
+)
+```
+```client-android-java
+import io.appwrite.Client;
+import io.appwrite.coroutines.CoroutineCallback;
+import io.appwrite.services.Oauth2;
+
+Client client = new Client(context)
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject(""); // Your project ID
+
+Oauth2 oauth2 = new Oauth2(client);
+
+oauth2.approve(
+ "", // grant_id
+ "", // authorization_details (optional)
+ "", // scope (optional)
+ new CoroutineCallback<>((result, error) -> {
+ if (error != null) {
+ error.printStackTrace();
+ return;
+ }
+
+ Log.d("Appwrite", result.toString());
+ })
+);
+
+```
+```client-react-native
+import { Client, Oauth2 } from "react-native-appwrite";
+
+const client = new Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject(''); // Your project ID
+
+const oauth2 = new Oauth2(client);
+
+const result = await oauth2.approve({
+ grantId: '',
+ authorizationDetails: '', // optional
+ scope: '' // optional
+});
+
+console.log(result);
+```
+{% /multicode %}
+
+**Reject** the grant if the user declines. Appwrite responds with the redirect URL carrying an error instead of a code.
+
+{% multicode %}
+```client-web
+import { Client, Oauth2 } from 'appwrite';
+
+const client = new Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject(''); // Your project ID
+
+const oauth2 = new Oauth2(client);
+
+const result = await oauth2.reject({
+ grantId: ''
+});
+
+console.log(result);
+```
+```client-flutter
+import 'package:appwrite/appwrite.dart';
+
+Client client = Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject(''); // Your project ID
+
+Oauth2 oauth2 = Oauth2(client);
+
+Oauth2Reject result = await oauth2.reject(
+ grantId: '',
+);
+```
+```client-apple
+import Appwrite
+
+let client = Client()
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject("") // Your project ID
+
+let oauth2 = Oauth2(client)
+
+let oauth2Reject = try await oauth2.reject(
+ grant_id: ""
+)
+
+```
+```client-android-kotlin
+import io.appwrite.Client
+import io.appwrite.coroutines.CoroutineCallback
+import io.appwrite.services.Oauth2
+
+val client = Client(context)
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject("") // Your project ID
+
+val oauth2 = Oauth2(client)
+
+val result = oauth2.reject(
+ grant_id = "",
+)
+```
+```client-android-java
+import io.appwrite.Client;
+import io.appwrite.coroutines.CoroutineCallback;
+import io.appwrite.services.Oauth2;
+
+Client client = new Client(context)
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject(""); // Your project ID
+
+Oauth2 oauth2 = new Oauth2(client);
+
+oauth2.reject(
+ "", // grant_id
+ new CoroutineCallback<>((result, error) -> {
+ if (error != null) {
+ error.printStackTrace();
+ return;
+ }
+
+ Log.d("Appwrite", result.toString());
+ })
+);
+
+```
+```client-react-native
+import { Client, Oauth2 } from "react-native-appwrite";
+
+const client = new Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject(''); // Your project ID
+
+const oauth2 = new Oauth2(client);
+
+const result = await oauth2.reject({
+ grantId: ''
+});
+
+console.log(result);
+```
+{% /multicode %}
+
+# Redirect URI matching {% #redirect-uris %}
+
+The `redirect_uri` on an authorization request must exactly match one of the client's registered redirect URIs, character for character. This is what stops an attacker from redirecting a code to a URL they control.
+
+There is one narrow exception. For **public** clients, an `http` loopback address (`localhost`, `127.0.0.1`, or `[::1]`) matches on everything except the port. Native and CLI apps bind an unpredictable local port at runtime and cannot register it ahead of time (RFC 8252). Confidential clients get no such exception; their redirect URIs must match exactly, port included.
diff --git a/src/routes/docs/partners/oauth-server/clients/+page.markdoc b/src/routes/docs/partners/oauth-server/clients/+page.markdoc
new file mode 100644
index 0000000000..d5e364907f
--- /dev/null
+++ b/src/routes/docs/partners/oauth-server/clients/+page.markdoc
@@ -0,0 +1,1578 @@
+---
+layout: article
+title: Clients
+description: Register confidential and public OAuth clients against your Appwrite project's OAuth server and manage their secrets.
+back: /docs/partners/oauth-server
+---
+
+A **client** is a third-party app that authenticates users through your project's OAuth server. Each client registers the redirect URIs it is allowed to return to, and, depending on its type, receives a client secret. Clients are managed from the **Auth > OAuth2 server > Apps** tab in the Console, or with a [Server SDK](/docs/sdks#server).
+
+{% only_dark %}
+
+{% /only_dark %}
+{% only_light %}
+
+{% /only_light %}
+
+# Confidential and public clients {% #client-types %}
+
+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.
+
+{% only_dark %}
+
+{% /only_dark %}
+{% only_light %}
+
+{% /only_light %}
+
+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 | Allowed | Not allowed |
+| 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 {% #register %}
+
+Create a client from the **Apps** tab, or with the `create` method of the `Apps` service. Registering a confidential client returns its secret once. Store it immediately, because the full value cannot be retrieved again.
+
+{% only_dark %}
+
+{% /only_dark %}
+{% only_light %}
+
+{% /only_light %}
+
+{% info title="Required scope" %}
+The API key used for this call needs the `apps.write` scope.
+{% /info %}
+
+{% multicode %}
+```server-nodejs
+import { Client, Apps } from 'node-appwrite';
+
+const client = new Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject('') // Your project ID
+ .setKey(''); // Your secret API key
+
+const apps = new Apps(client);
+
+const result = await apps.create({
+ appId: '',
+ name: '',
+ redirectUris: [],
+ description: '', // optional
+ clientUri: 'https://example.com', // optional
+ logoUri: 'https://example.com', // optional
+ privacyPolicyUrl: 'https://example.com', // optional
+ termsUrl: 'https://example.com', // optional
+ contacts: [], // optional
+ tagline: '', // optional
+ tags: [], // optional
+ images: [], // optional
+ supportUrl: 'https://example.com', // optional
+ dataDeletionUrl: 'https://example.com', // optional
+ postLogoutRedirectUris: [], // optional
+ enabled: false, // optional
+ type: 'public', // optional
+ deviceFlow: false, // optional
+ teamId: '' // optional
+});
+```
+```server-deno
+import { Client, Apps } from "npm:node-appwrite";
+
+const client = new Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject('') // Your project ID
+ .setKey(''); // Your secret API key
+
+const apps = new Apps(client);
+
+const result = await apps.create({
+ appId: '',
+ name: '',
+ redirectUris: [],
+ description: '', // optional
+ clientUri: 'https://example.com', // optional
+ logoUri: 'https://example.com', // optional
+ privacyPolicyUrl: 'https://example.com', // optional
+ termsUrl: 'https://example.com', // optional
+ contacts: [], // optional
+ tagline: '', // optional
+ tags: [], // optional
+ images: [], // optional
+ supportUrl: 'https://example.com', // optional
+ dataDeletionUrl: 'https://example.com', // optional
+ postLogoutRedirectUris: [], // optional
+ enabled: false, // optional
+ type: 'public', // optional
+ deviceFlow: false, // optional
+ teamId: '' // optional
+});
+```
+```server-php
+```php
+setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ ->setProject('') // Your project ID
+ ->setKey(''); // Your secret API key
+
+$apps = new Apps($client);
+
+$result = $apps->create(
+ appId: '',
+ name: '',
+ redirectUris: [],
+ description: '', // optional
+ clientUri: 'https://example.com', // optional
+ logoUri: 'https://example.com', // optional
+ privacyPolicyUrl: 'https://example.com', // optional
+ termsUrl: 'https://example.com', // optional
+ contacts: [], // optional
+ tagline: '', // optional
+ tags: [], // optional
+ images: [], // optional
+ supportUrl: 'https://example.com', // optional
+ dataDeletionUrl: 'https://example.com', // optional
+ postLogoutRedirectUris: [], // optional
+ enabled: false, // optional
+ type: 'public', // optional
+ deviceFlow: false, // optional
+ teamId: '' // optional
+);```
+```
+```server-python
+from appwrite.client import Client
+from appwrite.services.apps import Apps
+from appwrite.models import App
+
+client = Client()
+client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint
+client.set_project('') # Your project ID
+client.set_key('') # Your secret API key
+
+apps = Apps(client)
+
+result: App = apps.create(
+ app_id = '',
+ name = '',
+ redirect_uris = [],
+ description = '', # optional
+ client_uri = 'https://example.com', # optional
+ logo_uri = 'https://example.com', # optional
+ privacy_policy_url = 'https://example.com', # optional
+ terms_url = 'https://example.com', # optional
+ contacts = [], # optional
+ tagline = '', # optional
+ tags = [], # optional
+ images = [], # optional
+ support_url = 'https://example.com', # optional
+ data_deletion_url = 'https://example.com', # optional
+ post_logout_redirect_uris = [], # optional
+ enabled = False, # optional
+ type = 'public', # optional
+ device_flow = False, # optional
+ team_id = '' # optional
+)
+
+print(result.model_dump())
+```
+```server-ruby
+require 'appwrite'
+
+include Appwrite
+
+client = Client.new
+ .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint
+ .set_project('') # Your project ID
+ .set_key('') # Your secret API key
+
+apps = Apps.new(client)
+
+result = apps.create(
+ app_id: '',
+ name: '',
+ redirect_uris: [],
+ description: '', # optional
+ client_uri: 'https://example.com', # optional
+ logo_uri: 'https://example.com', # optional
+ privacy_policy_url: 'https://example.com', # optional
+ terms_url: 'https://example.com', # optional
+ contacts: [], # optional
+ tagline: '', # optional
+ tags: [], # optional
+ images: [], # optional
+ support_url: 'https://example.com', # optional
+ data_deletion_url: 'https://example.com', # optional
+ post_logout_redirect_uris: [], # optional
+ enabled: false, # optional
+ type: 'public', # optional
+ device_flow: false, # optional
+ team_id: '' # optional
+)
+```
+```server-dart
+import 'package:dart_appwrite/dart_appwrite.dart';
+
+Client client = Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject('') // Your project ID
+ .setKey(''); // Your secret API key
+
+Apps apps = Apps(client);
+
+App result = await apps.create(
+ appId: '',
+ name: '',
+ redirectUris: [],
+ description: '', // (optional)
+ clientUri: 'https://example.com', // (optional)
+ logoUri: 'https://example.com', // (optional)
+ privacyPolicyUrl: 'https://example.com', // (optional)
+ termsUrl: 'https://example.com', // (optional)
+ contacts: [], // (optional)
+ tagline: '', // (optional)
+ tags: [], // (optional)
+ images: [], // (optional)
+ supportUrl: 'https://example.com', // (optional)
+ dataDeletionUrl: 'https://example.com', // (optional)
+ postLogoutRedirectUris: [], // (optional)
+ enabled: false, // (optional)
+ type: 'public', // (optional)
+ deviceFlow: false, // (optional)
+ teamId: '', // (optional)
+);
+```
+```server-dotnet
+```csharp
+using Appwrite;
+using Appwrite.Models;
+using Appwrite.Services;
+
+Client client = new Client()
+ .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .SetProject("") // Your project ID
+ .SetKey(""); // Your secret API key
+
+Apps apps = new Apps(client);
+
+App result = await apps.Create(
+ appId: "",
+ name: "",
+ redirectUris: new List(),
+ description: "", // optional
+ clientUri: "https://example.com", // optional
+ logoUri: "https://example.com", // optional
+ privacyPolicyUrl: "https://example.com", // optional
+ termsUrl: "https://example.com", // optional
+ contacts: new List(), // optional
+ tagline: "", // optional
+ tags: new List(), // optional
+ images: new List(), // optional
+ supportUrl: "https://example.com", // optional
+ dataDeletionUrl: "https://example.com", // optional
+ postLogoutRedirectUris: new List(), // optional
+ enabled: false, // optional
+ type: "public", // optional
+ deviceFlow: false, // optional
+ teamId: "" // optional
+);```
+```
+```server-kotlin
+import io.appwrite.Client
+import io.appwrite.coroutines.CoroutineCallback
+import io.appwrite.services.Apps
+
+val client = Client()
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject("") // Your project ID
+ .setKey("") // Your secret API key
+
+val apps = Apps(client)
+
+val response = apps.create(
+ appId = "",
+ name = "",
+ redirectUris = listOf(),
+ description = "", // optional
+ clientUri = "https://example.com", // optional
+ logoUri = "https://example.com", // optional
+ privacyPolicyUrl = "https://example.com", // optional
+ termsUrl = "https://example.com", // optional
+ contacts = listOf(), // optional
+ tagline = "", // optional
+ tags = listOf(), // optional
+ images = listOf(), // optional
+ supportUrl = "https://example.com", // optional
+ dataDeletionUrl = "https://example.com", // optional
+ postLogoutRedirectUris = listOf(), // optional
+ enabled = false, // optional
+ type = "public", // optional
+ deviceFlow = false, // optional
+ teamId = "" // optional
+)
+```
+```server-java
+import io.appwrite.Client;
+import io.appwrite.coroutines.CoroutineCallback;
+import io.appwrite.services.Apps;
+
+Client client = new Client()
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject("") // Your project ID
+ .setKey(""); // Your secret API key
+
+Apps apps = new Apps(client);
+
+apps.create(
+ "", // appId
+ "", // name
+ List.of(), // redirectUris
+ "", // description (optional)
+ "https://example.com", // clientUri (optional)
+ "https://example.com", // logoUri (optional)
+ "https://example.com", // privacyPolicyUrl (optional)
+ "https://example.com", // termsUrl (optional)
+ List.of(), // contacts (optional)
+ "", // tagline (optional)
+ List.of(), // tags (optional)
+ List.of(), // images (optional)
+ "https://example.com", // supportUrl (optional)
+ "https://example.com", // dataDeletionUrl (optional)
+ List.of(), // postLogoutRedirectUris (optional)
+ false, // enabled (optional)
+ "public", // type (optional)
+ false, // deviceFlow (optional)
+ "", // teamId (optional)
+ new CoroutineCallback<>((result, error) -> {
+ if (error != null) {
+ error.printStackTrace();
+ return;
+ }
+
+ System.out.println(result);
+ })
+);
+
+```
+```server-swift
+import Appwrite
+
+let client = Client()
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject("") // Your project ID
+ .setKey("") // Your secret API key
+
+let apps = Apps(client)
+
+let app = try await apps.create(
+ appId: "",
+ name: "",
+ redirectUris: [],
+ description: "", // optional
+ clientUri: "https://example.com", // optional
+ logoUri: "https://example.com", // optional
+ privacyPolicyUrl: "https://example.com", // optional
+ termsUrl: "https://example.com", // optional
+ contacts: [], // optional
+ tagline: "", // optional
+ tags: [], // optional
+ images: [], // optional
+ supportUrl: "https://example.com", // optional
+ dataDeletionUrl: "https://example.com", // optional
+ postLogoutRedirectUris: [], // optional
+ enabled: false, // optional
+ type: "public", // optional
+ deviceFlow: false, // optional
+ teamId: "" // optional
+)
+
+```
+```server-go
+package main
+
+import (
+ "fmt"
+ "github.com/repoowner/reponame/client"
+ "github.com/repoowner/reponame/apps"
+)
+
+client := client.New(
+ client.WithEndpoint("https://.cloud.appwrite.io/v1")
+ client.WithProject("")
+ appwrite.WithKey("")
+)
+
+service := apps.New(client)
+
+response, error := service.Create(
+ "",
+ "",
+ []string{},
+ apps.WithCreateDescription(""),
+ apps.WithCreateClientUri("https://example.com"),
+ apps.WithCreateLogoUri("https://example.com"),
+ apps.WithCreatePrivacyPolicyUrl("https://example.com"),
+ apps.WithCreateTermsUrl("https://example.com"),
+ apps.WithCreateContacts([]string{}),
+ apps.WithCreateTagline(""),
+ apps.WithCreateTags([]string{}),
+ apps.WithCreateImages([]string{}),
+ apps.WithCreateSupportUrl("https://example.com"),
+ apps.WithCreateDataDeletionUrl("https://example.com"),
+ apps.WithCreatePostLogoutRedirectUris([]string{}),
+ apps.WithCreateEnabled(false),
+ apps.WithCreateType("public"),
+ apps.WithCreateDeviceFlow(false),
+ apps.WithCreateTeamId(""),
+)
+```
+```server-rust
+use appwrite::Client;
+use appwrite::services::Apps;
+
+#[tokio::main]
+async fn main() -> Result<(), Box> {
+ let client = Client::new();
+ client.set_endpoint("https://.cloud.appwrite.io/v1"); // Your API Endpoint
+ client.set_project(""); // Your project ID
+ client.set_key(""); // Your secret API key
+
+ let apps = Apps::new(&client);
+
+ let result = apps.create(
+ "",
+ "",
+ vec![],
+ Some(""), // optional
+ Some("https://example.com"), // optional
+ Some("https://example.com"), // optional
+ Some("https://example.com"), // optional
+ Some("https://example.com"), // optional
+ Some(vec![]), // optional
+ Some(""), // optional
+ Some(vec![]), // optional
+ Some(vec![]), // optional
+ Some("https://example.com"), // optional
+ Some("https://example.com"), // optional
+ Some(vec![]), // optional
+ Some(false), // optional
+ Some("public"), // optional
+ Some(false), // optional
+ Some("") // optional
+ ).await?;
+
+ let _ = result;
+
+ Ok(())
+}
+```
+{% /multicode %}
+
+The `type` parameter accepts `confidential` (the default) or `public`. Set `deviceFlow` to `true` to let the client use the [device authorization flow](/docs/partners/oauth-server/device-flow). The branding fields (`logoUri`, `tagline`, `privacyPolicyUrl`, `termsUrl`) appear on the consent screen your users see.
+
+# Manage client secrets {% #secrets %}
+
+A confidential client authenticates with a secret. Generate a new secret with the `createSecret` method. The plaintext value is returned only in this response.
+
+{% only_dark %}
+
+{% /only_dark %}
+{% only_light %}
+
+{% /only_light %}
+
+{% multicode %}
+```server-nodejs
+import { Client, Apps } from 'node-appwrite';
+
+const client = new Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject('') // Your project ID
+ .setKey(''); // Your secret API key
+
+const apps = new Apps(client);
+
+const result = await apps.createSecret({
+ appId: ''
+});
+```
+```server-deno
+import { Client, Apps } from "npm:node-appwrite";
+
+const client = new Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject('') // Your project ID
+ .setKey(''); // Your secret API key
+
+const apps = new Apps(client);
+
+const result = await apps.createSecret({
+ appId: ''
+});
+```
+```server-php
+```php
+setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ ->setProject('') // Your project ID
+ ->setKey(''); // Your secret API key
+
+$apps = new Apps($client);
+
+$result = $apps->createSecret(
+ appId: ''
+);```
+```
+```server-python
+from appwrite.client import Client
+from appwrite.services.apps import Apps
+from appwrite.models import AppSecretPlaintext
+
+client = Client()
+client.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint
+client.set_project('') # Your project ID
+client.set_key('') # Your secret API key
+
+apps = Apps(client)
+
+result: AppSecretPlaintext = apps.create_secret(
+ app_id = ''
+)
+
+print(result.model_dump())
+```
+```server-ruby
+require 'appwrite'
+
+include Appwrite
+
+client = Client.new
+ .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint
+ .set_project('') # Your project ID
+ .set_key('') # Your secret API key
+
+apps = Apps.new(client)
+
+result = apps.create_secret(
+ app_id: ''
+)
+```
+```server-dart
+import 'package:dart_appwrite/dart_appwrite.dart';
+
+Client client = Client()
+ .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint
+ .setProject('') // Your project ID
+ .setKey(''); // Your secret API key
+
+Apps apps = Apps(client);
+
+AppSecretPlaintext result = await apps.createSecret(
+ appId: '',
+);
+```
+```server-dotnet
+```csharp
+using Appwrite;
+using Appwrite.Models;
+using Appwrite.Services;
+
+Client client = new Client()
+ .SetEndPoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .SetProject("") // Your project ID
+ .SetKey(""); // Your secret API key
+
+Apps apps = new Apps(client);
+
+AppSecretPlaintext result = await apps.CreateSecret(
+ appId: ""
+);```
+```
+```server-kotlin
+import io.appwrite.Client
+import io.appwrite.coroutines.CoroutineCallback
+import io.appwrite.services.Apps
+
+val client = Client()
+ .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint
+ .setProject("") // Your project ID
+ .setKey("") // Your secret API key
+
+val apps = Apps(client)
+
+val response = apps.createSecret(
+ appId = ""
+)
+```
+```server-java
+import io.appwrite.Client;
+import io.appwrite.coroutines.CoroutineCallback;
+import io.appwrite.services.Apps;
+
+Client client = new Client()
+ .setEndpoint("https://