diff --git a/projects/api/src/contracts/oauth/index.ts b/projects/api/src/contracts/oauth/index.ts
index 0cfd3ff4..1a26eab8 100644
--- a/projects/api/src/contracts/oauth/index.ts
+++ b/projects/api/src/contracts/oauth/index.ts
@@ -1,5 +1,6 @@
import { authMetadata, builder } from '../_internal/builder.ts';
import { z } from '../_internal/z.ts';
+import { clientSecretSchema } from './schema/request/clientSecretSchema.ts';
import { codeRequestSchema } from './schema/request/codeRequestSchema.ts';
import {
deviceTokenRequestSchema,
@@ -17,14 +18,19 @@ const authorizeQuerySchema = z.object({
client_id: z.string().optional(),
redirect_uri: z.string().optional(),
state: z.string().optional(),
+ code_challenge: z.string().optional(),
+ code_challenge_method: z.literal('S256').optional(),
});
const revokeRequestSchema = z.object({
token: z.string(),
client_id: z.string(),
- client_secret: z.string(),
+ client_secret: clientSecretSchema,
});
+const CLIENT_SECRET_NOTE = `> ### Note
+> _\`client_secret\` is deprecated in favor of [PKCE](https://developer.trakt.tv/docs/pkce). It is optional, and should only be sent from your own server, never from a website, mobile app, or desktop app._`;
+
const device = builder.router({
code: {
summary: 'Generate new device codes',
@@ -38,7 +44,8 @@ You might consider generating a QR code for the user to easily scan on their mob
#### JSON POST Data
| Key | Type | Value |
|---|---|---|
-| \`client_id\` * | string | Get this from your app settings. |`,
+| \`client_id\` * | string | Get this from your app settings. |
+`,
method: 'POST',
path: '/code',
body: codeRequestSchema,
@@ -58,7 +65,6 @@ When you receive a \`200\` success response, save the \`access_token\` so your a
|---|---|---|
| \`code\` * | string | \`device_code\` from the initial method. |
| \`client_id\` * | string | Get this from your app settings. |
-| \`client_secret\` * | string | Get this from your app settings. |
#### Status Codes
This method will send various HTTP status codes that you should handle accordingly.
@@ -126,7 +132,18 @@ When building the authorization URL, you can optionally include the following qu
| Parameter | Value | Description |
|---|---|---|
| \`signup\` | \`true\` | Prefer the account sign up page to be the default. |
-| \`prompt\` | \`login\` | Force the user to sign in and authorize your app. |`,
+| \`prompt\` | \`login\` | Force the user to sign in and authorize your app. |
+
+#### PKCE
+
+[PKCE](https://developer.trakt.tv/docs/pkce) is the recommended way to sign users in. Send these parameters, then send the matching \`code_verifier\` instead of a \`client_secret\` when you exchange the code.
+
+| Parameter | Value | Description |
+|---|---|---|
+| \`code_challenge\` | string | The base64url-encoded SHA-256 hash of your \`code_verifier\`. |
+| \`code_challenge_method\` | \`S256\` | The only supported method. |
+
+${CLIENT_SECRET_NOTE}`,
path: '/authorize',
method: 'GET',
query: authorizeQuerySchema,
@@ -140,6 +157,10 @@ When building the authorization URL, you can optionally include the following qu
description:
`Exchange an OAuth authorization code or refresh token for an access token.
+If you started the flow with [PKCE](https://developer.trakt.tv/docs/pkce), send the \`code_verifier\`.
+
+${CLIENT_SECRET_NOTE}
+
#### Refreshing Tokens
Refresh tokens are single-use. Each successful refresh returns a new \`refresh_token\` and invalidates the previous one. Store the new \`access_token\` and \`refresh_token\` from the response before the next refresh.
@@ -166,7 +187,8 @@ If the exchange cannot be completed, this endpoint returns a \`400\` response wi
|---|---|---|
| \`token\` * | string | A valid OAuth \`access_token\`. |
| \`client_id\` * | string | Get this from your app settings. |
-| \`client_secret\` * | string | Get this from your app settings. |`,
+
+${CLIENT_SECRET_NOTE}`,
path: '/revoke',
method: 'POST',
body: revokeRequestSchema,
diff --git a/projects/api/src/contracts/oauth/schema/request/clientSecretSchema.ts b/projects/api/src/contracts/oauth/schema/request/clientSecretSchema.ts
new file mode 100644
index 00000000..74cd79de
--- /dev/null
+++ b/projects/api/src/contracts/oauth/schema/request/clientSecretSchema.ts
@@ -0,0 +1,7 @@
+import { z } from '../../../_internal/z.ts';
+
+/** Zod schema for the deprecated, optional `client_secret` of OAuth requests. */
+export const clientSecretSchema = z.string({
+ description:
+ 'Deprecated. Only send it from your own server, never from a website, mobile app, or desktop app.',
+}).openapi({ deprecated: true }).optional();
diff --git a/projects/api/src/contracts/oauth/schema/request/deviceTokenRequestSchema.ts b/projects/api/src/contracts/oauth/schema/request/deviceTokenRequestSchema.ts
index 215fbe01..4d18fbf7 100644
--- a/projects/api/src/contracts/oauth/schema/request/deviceTokenRequestSchema.ts
+++ b/projects/api/src/contracts/oauth/schema/request/deviceTokenRequestSchema.ts
@@ -1,4 +1,5 @@
import { z } from '../../../_internal/z.ts';
+import { clientSecretSchema } from './clientSecretSchema.ts';
/** Zod schema for the device token request. */
export const deviceTokenRequestSchema = z.object({
@@ -10,8 +11,5 @@ export const deviceTokenRequestSchema = z.object({
description: `The client ID of the application.
You can find it in the application details here: https://app.trakt.tv/settings/apps`,
}),
- client_secret: z.string({
- description: `The client secret of the application.
- You can find it in the application details here: https://app.trakt.tv/settings/apps`,
- }),
+ client_secret: clientSecretSchema,
});
diff --git a/projects/api/src/contracts/oauth/schema/request/tokenBaseSchema.ts b/projects/api/src/contracts/oauth/schema/request/tokenBaseSchema.ts
index a5449e00..576d601b 100644
--- a/projects/api/src/contracts/oauth/schema/request/tokenBaseSchema.ts
+++ b/projects/api/src/contracts/oauth/schema/request/tokenBaseSchema.ts
@@ -1,4 +1,5 @@
import { z } from '../../../_internal/z.ts';
+import { clientSecretSchema } from './clientSecretSchema.ts';
/** Zod schema for token base. */
export const tokenBaseSchema = z.object({
@@ -6,10 +7,7 @@ export const tokenBaseSchema = z.object({
description: `The client ID of the application.
You can find it in the application details here: https://app.trakt.tv/settings/apps`,
}),
- client_secret: z.string({
- description: `The client secret of the application.
- You can find it in the application details here: https://app.trakt.tv/settings/apps`,
- }),
+ client_secret: clientSecretSchema,
redirect_uri: z.string({
description: 'URI specified in your app settings.',
}),
diff --git a/projects/api/src/contracts/oauth/schema/request/tokenRequestSchema.ts b/projects/api/src/contracts/oauth/schema/request/tokenRequestSchema.ts
index defff208..a546811a 100644
--- a/projects/api/src/contracts/oauth/schema/request/tokenRequestSchema.ts
+++ b/projects/api/src/contracts/oauth/schema/request/tokenRequestSchema.ts
@@ -10,4 +10,8 @@ export const tokenRequestSchema = tokenBaseSchema.extend({
grant_type: z.string({
description: 'Defines how an access token is obtained.',
}),
+ code_verifier: z.string({
+ description:
+ 'The PKCE code verifier whose SHA-256 hash was sent as `code_challenge`. Required when the flow started with a `code_challenge`. See https://developer.trakt.tv/docs/pkce',
+ }).optional(),
});
diff --git a/projects/developer/src/lib/features/apps/ApplicationForm.svelte b/projects/developer/src/lib/features/apps/ApplicationForm.svelte
index ab800697..d984d2cc 100644
--- a/projects/developer/src/lib/features/apps/ApplicationForm.svelte
+++ b/projects/developer/src/lib/features/apps/ApplicationForm.svelte
@@ -1,6 +1,8 @@
+
+
+
+
diff --git a/projects/developer/src/lib/features/apps/RedirectUriWarning.svelte b/projects/developer/src/lib/features/apps/RedirectUriWarning.svelte
new file mode 100644
index 00000000..ae9748c7
--- /dev/null
+++ b/projects/developer/src/lib/features/apps/RedirectUriWarning.svelte
@@ -0,0 +1,67 @@
+
+
+{#if uris.length > 0}
+
+ ⚠️ Insecure redirect URIs
+
+
+ Use https:// redirect URIs only. Any app on
+ the device can claim a custom scheme or a localhost port and receive your users'
+ authorization codes. Mobile apps should use iOS Universal Links and Android
+ verified App Links instead.
+
+
+ {#each uris as uri (uri)}
{uri}
{/each}
+
+ How to set up secure redirects →
+ {/if}
+
+
diff --git a/projects/developer/src/lib/features/apps/applicationSchema.ts b/projects/developer/src/lib/features/apps/applicationSchema.ts
index 27521096..0ac5cf05 100644
--- a/projects/developer/src/lib/features/apps/applicationSchema.ts
+++ b/projects/developer/src/lib/features/apps/applicationSchema.ts
@@ -5,7 +5,7 @@ export const applicationSchema = z.object({
name: z.string(),
description: z.string().nullish(),
client_id: z.string(),
- client_secret: z.string(),
+ client_secret: z.string().nullish(),
redirect_uri: z.string(),
origins: z.array(z.string()),
approved: z.boolean(),
diff --git a/projects/developer/src/lib/features/apps/githubConnectUrl.spec.ts b/projects/developer/src/lib/features/apps/githubConnectUrl.spec.ts
index 68d16fcf..35d673a0 100644
--- a/projects/developer/src/lib/features/apps/githubConnectUrl.spec.ts
+++ b/projects/developer/src/lib/features/apps/githubConnectUrl.spec.ts
@@ -1,9 +1,4 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
-vi.mock(
- '$env/static/public',
- () => ({ PUBLIC_GITHUB_CLIENT_ID: 'test-client' }),
-);
-
import { githubConnectUrl } from './githubConnectUrl.ts';
function fakeStorage(): Storage {
@@ -30,7 +25,9 @@ describe('githubConnectUrl', () => {
vi.stubGlobal('location', { origin: 'https://developer.trakt.tv' });
vi.stubGlobal('crypto', { randomUUID: () => 'fixed-state' });
- const url = new URL(githubConnectUrl('switch'));
+ const url = new URL(
+ githubConnectUrl({ intent: 'switch', clientId: 'test-client' }),
+ );
expect(url.origin + url.pathname).toBe(
'https://github.com/login/oauth/authorize',
diff --git a/projects/developer/src/lib/features/apps/githubConnectUrl.ts b/projects/developer/src/lib/features/apps/githubConnectUrl.ts
index 7d1c8b87..2f967daf 100644
--- a/projects/developer/src/lib/features/apps/githubConnectUrl.ts
+++ b/projects/developer/src/lib/features/apps/githubConnectUrl.ts
@@ -1,14 +1,20 @@
-import { PUBLIC_GITHUB_CLIENT_ID } from '$env/static/public';
import type { GithubConnectIntent } from './GithubConnectIntent.ts';
import { GITHUB_INTENT_KEY } from './GITHUB_INTENT_KEY.ts';
import { GITHUB_STATE_KEY } from './GITHUB_STATE_KEY.ts';
-export function githubConnectUrl(intent: GithubConnectIntent): string {
+type GithubConnectUrlParams = {
+ intent: GithubConnectIntent;
+ clientId: string;
+};
+
+export function githubConnectUrl(
+ { intent, clientId }: GithubConnectUrlParams,
+): string {
const state = globalThis.crypto.randomUUID();
globalThis.sessionStorage?.setItem(GITHUB_STATE_KEY, state);
globalThis.sessionStorage?.setItem(GITHUB_INTENT_KEY, intent);
const url = new URL('https://github.com/login/oauth/authorize');
- url.searchParams.set('client_id', PUBLIC_GITHUB_CLIENT_ID);
+ url.searchParams.set('client_id', clientId);
url.searchParams.set('redirect_uri', `${globalThis.location.origin}/apps`);
url.searchParams.set('state', state);
return url.toString();
diff --git a/projects/developer/src/lib/features/apps/unsafeRedirectUris.spec.ts b/projects/developer/src/lib/features/apps/unsafeRedirectUris.spec.ts
new file mode 100644
index 00000000..c67ad320
--- /dev/null
+++ b/projects/developer/src/lib/features/apps/unsafeRedirectUris.spec.ts
@@ -0,0 +1,64 @@
+import { describe, expect, it } from 'vitest';
+import { unsafeRedirectUris } from './unsafeRedirectUris.ts';
+
+describe('unsafeRedirectUris', () => {
+ it('accepts public https redirect URIs', () => {
+ expect(
+ unsafeRedirectUris(
+ 'https://example.com/callback\nhttps://app.example.com/auth',
+ ),
+ ).toEqual([]);
+ });
+
+ it('accepts public addresses next to the private ranges', () => {
+ expect(
+ unsafeRedirectUris(
+ [
+ 'https://172.15.0.1/callback',
+ 'https://172.32.0.1/callback',
+ 'https://11.0.0.1/callback',
+ 'https://[2001:db8::1]/callback',
+ 'https://localhost.example.com/callback',
+ ].join('\n'),
+ ),
+ ).toEqual([]);
+ });
+
+ it.each([
+ 'myapp://callback',
+ 'com.example.app:/oauth',
+ 'urn:ietf:wg:oauth:2.0:oob',
+ 'http://example.com/callback',
+ 'https://localhost:3000/callback',
+ 'http://localhost/callback',
+ 'https://dev.localhost/callback',
+ 'http://127.0.0.1:8080/callback',
+ 'http://[::1]/callback',
+ 'https://0.0.0.0/callback',
+ 'https://10.0.0.1/callback',
+ 'https://172.16.4.2/callback',
+ 'https://172.31.255.1/callback',
+ 'https://192.168.1.10/callback',
+ 'https://169.254.10.20/callback',
+ 'https://[fd12:3456::1]/callback',
+ 'https://[fc00::1]/callback',
+ 'https://[fe80::1]/callback',
+ 'https://printer.local/callback',
+ ])('flags %s', (uri) => {
+ expect(unsafeRedirectUris(uri)).toEqual([uri]);
+ });
+
+ it('returns only the unsafe lines, trimmed, and skips blank lines', () => {
+ expect(
+ unsafeRedirectUris(
+ ' https://example.com/callback \n\n myapp://callback \n',
+ ),
+ ).toEqual(['myapp://callback']);
+ });
+
+ it('lists a repeated unsafe URI once', () => {
+ expect(unsafeRedirectUris('myapp://callback\nmyapp://callback')).toEqual([
+ 'myapp://callback',
+ ]);
+ });
+});
diff --git a/projects/developer/src/lib/features/apps/unsafeRedirectUris.ts b/projects/developer/src/lib/features/apps/unsafeRedirectUris.ts
new file mode 100644
index 00000000..8744fb28
--- /dev/null
+++ b/projects/developer/src/lib/features/apps/unsafeRedirectUris.ts
@@ -0,0 +1,40 @@
+const LOCAL_HOSTS: ReadonlySet = new Set(['localhost', '0.0.0.0']);
+
+// Loopback, private (RFC 1918), and link-local IPv4 ranges.
+const PRIVATE_IPV4 =
+ /^(127\.|10\.|192\.168\.|169\.254\.|172\.(1[6-9]|2\d|3[01])\.)/;
+
+// Loopback, unique local (fc00::/7), and link-local (fe80::/10) IPv6.
+const PRIVATE_IPV6 = /^\[(::1|f[cd][0-9a-f]{0,2}:|fe[89ab][0-9a-f]?:)/;
+
+function isPrivateHost(hostname: string): boolean {
+ return LOCAL_HOSTS.has(hostname) || hostname.endsWith('.localhost') ||
+ hostname.endsWith('.local') || PRIVATE_IPV4.test(hostname) ||
+ PRIVATE_IPV6.test(hostname);
+}
+
+function parseUrl(uri: string): URL | null {
+ try {
+ return new URL(uri);
+ } catch {
+ return null;
+ }
+}
+
+function isSafe(uri: string): boolean {
+ const url = parseUrl(uri);
+ if (!url) return false;
+
+ return url.protocol === 'https:' && !isPrivateHost(url.hostname);
+}
+
+/**
+ * Redirect URIs, one per line, that are not a public `https://` address:
+ * custom schemes, `http://`, loopback, private-network, and link-local hosts,
+ * and the out-of-band URI. Each URI is listed once.
+ */
+export function unsafeRedirectUris(redirects: string): ReadonlyArray {
+ const lines = redirects.split('\n').map((line) => line.trim());
+
+ return [...new Set(lines.filter(Boolean).filter((uri) => !isSafe(uri)))];
+}
diff --git a/projects/developer/src/lib/guides/authentication-oauth.md b/projects/developer/src/lib/guides/authentication-oauth.md
index 93041114..a7e84c99 100644
--- a/projects/developer/src/lib/guides/authentication-oauth.md
+++ b/projects/developer/src/lib/guides/authentication-oauth.md
@@ -1,5 +1,5 @@
---
-updatedAt: 2026-09-09T02:34:16.068Z
+updatedAt: 2026-10-01T00:00:00.000Z
---
# Authentication
@@ -23,6 +23,12 @@ Trakt supports two OAuth flows:
Check each endpoint’s documentation to see whether OAuth is required, optional,
or not needed.
+> ### 🔐 Use PKCE
+>
+> Add [PKCE](/docs/pkce) to the Authorization Code Flow. It is the recommended
+> way to sign users in and does not need a `client_secret`. The Client Secret is
+> deprecated for user sign-in and should only be used server to server.
+
## Register Your Application
To obtain a `client_id` and `client_secret`, create an application on the Trakt
diff --git a/projects/developer/src/lib/guides/contents.md b/projects/developer/src/lib/guides/contents.md
index e2c8b5e4..2bd26cf3 100644
--- a/projects/developer/src/lib/guides/contents.md
+++ b/projects/developer/src/lib/guides/contents.md
@@ -6,6 +6,7 @@
- [Create an App](/docs/create-an-app)
- [API Use Policy](/docs/api-use-policy)
- [Authentication](/docs/authentication-oauth)
+- [PKCE](/docs/pkce)
## Request basics
diff --git a/projects/developer/src/lib/guides/create-an-app.md b/projects/developer/src/lib/guides/create-an-app.md
index b96fb2f4..6523296e 100644
--- a/projects/developer/src/lib/guides/create-an-app.md
+++ b/projects/developer/src/lib/guides/create-an-app.md
@@ -1,12 +1,13 @@
---
-updatedAt: 2026-09-23T00:00:00.000Z
+updatedAt: 2026-10-01T00:00:00.000Z
---
# ✅ Create an App
To use the Trakt API, you'll first need to [**create a new API app**](/apps).
-Once done, you'll have everything you need to use the API (Client ID and Client Secret).
+Once done, you'll have everything you need to use the API: your Client ID.
+Sign users in with [PKCE](/docs/pkce), which does not need a Client Secret.
Creating an app requires a verified GitHub account. Connect it once from the
Developer panel on My Apps, and every app you create afterwards uses it.
diff --git a/projects/developer/src/lib/guides/pkce.md b/projects/developer/src/lib/guides/pkce.md
new file mode 100644
index 00000000..e0d9f0a9
--- /dev/null
+++ b/projects/developer/src/lib/guides/pkce.md
@@ -0,0 +1,74 @@
+---
+updatedAt: 2026-10-01T00:00:00.000Z
+---
+
+# 🔐 PKCE
+
+PKCE (Proof Key for Code Exchange, pronounced "pixy") is the recommended way to
+sign users in to your app. It adds a one-time secret to every sign-in, so an
+authorization `code` is useless to anyone except the app that started the
+request. Your app never needs a `client_secret`.
+
+Use PKCE for every app that signs users in: websites, single-page apps, mobile
+apps, desktop apps, and CLI tools that use the Authorization Code Flow.
+
+> ### ⚠️ The Client Secret is deprecated for user sign-in
+>
+> A `client_secret` shipped inside a website, mobile app, or desktop app is not
+> secret: anyone can extract it and impersonate your app. Only use it for
+> server-to-server calls where it never leaves your backend. New integrations
+> should use PKCE, and future apps may not receive a Client Secret at all.
+
+## Why PKCE
+
+Your `client_id` is public. It is visible in every authorization URL, so anyone
+can copy it. Without PKCE, an attacker who copies your `client_id` and the
+bundled `client_secret`, or intercepts a `code` on its way back to your app, can
+exchange it for the user's tokens.
+
+With PKCE, the token exchange also needs the `code_verifier` that only your app
+knows. A stolen `client_id` or `code` cannot be turned into tokens, so copying
+your app's credentials stops being useful.
+
+## How It Works
+
+1. **Create a code verifier.** Generate a random string of 43-128 characters
+ from `A-Z`, `a-z`, `0-9`, and `-._~`. Keep it in memory or session storage
+ for this sign-in only.
+2. **Derive the code challenge.** Hash the verifier with SHA-256 and encode the
+ result as base64url without padding. Trakt only supports the `S256` method.
+3. **Redirect to authorize.** Send the user to
+ `https://auth.trakt.tv/oauth/authorize` with the usual parameters plus
+ `code_challenge` and `code_challenge_method=S256`.
+4. **Receive the code.** Trakt redirects back to your `redirect_uri` with a
+ `code`.
+5. **Exchange the code.** Call
+ [`POST /oauth/token`](/?section=reference&operation=postOauthToken) with the
+ `code` and the original `code_verifier`. Do not send a `client_secret`.
+ Trakt checks that the verifier matches the challenge from step 3.
+
+## Redirect URIs
+
+PKCE protects the `code`, but your app should still only receive it on an
+address it truly owns. Use `https://` redirect URIs only.
+
+- **Websites** use an `https://` callback on their own domain.
+- **iOS apps** use
+ [Universal Links](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app),
+ so iOS opens the callback only in your app.
+- **Android apps** use
+ [verified App Links](https://developer.android.com/training/app-links/verify-android-applinks),
+ so Android opens the callback only in your app.
+
+Avoid custom schemes such as `myapp://callback`, `localhost` addresses, and the
+`urn:ietf:wg:oauth:2.0:oob` out-of-band URI. Any app installed on the device can
+register the same custom scheme or listen on the same local port and receive
+your users' codes.
+
+## Checklist
+
+- Generate a new `code_verifier` for every sign-in and discard it afterwards.
+- Always send `code_challenge_method=S256`.
+- Never ship a `client_secret` in a website, mobile app, or desktop app.
+- Register `https://` redirect URIs only, backed by Universal Links or verified
+ App Links on mobile.
diff --git a/projects/developer/src/lib/openapi/parseOpenApiDocument.spec.ts b/projects/developer/src/lib/openapi/parseOpenApiDocument.spec.ts
index a07cff3e..f271e4d9 100644
--- a/projects/developer/src/lib/openapi/parseOpenApiDocument.spec.ts
+++ b/projects/developer/src/lib/openapi/parseOpenApiDocument.spec.ts
@@ -201,4 +201,39 @@ describe('parseOpenApiDocument', () => {
'{\n "rating": 9\n}',
);
});
+
+ it('should leave deprecated properties out of JSON body examples', () => {
+ const catalog = parseOpenApiDocument({
+ source: 'test',
+ document: {
+ openapi: '3.0.2',
+ info: { title: 'Trakt API', version: '2.0.0' },
+ paths: {
+ '/oauth/token': {
+ post: {
+ operationId: 'postOauthToken',
+ requestBody: {
+ content: {
+ 'application/json': {
+ schema: {
+ type: 'object',
+ properties: {
+ client_id: { type: 'string' },
+ client_secret: { type: 'string', deprecated: true },
+ },
+ },
+ },
+ },
+ },
+ responses: { '200': { description: 'OK' } },
+ },
+ },
+ },
+ },
+ });
+
+ expect(catalog.endpoints.at(0)?.requestBody?.example).toBe(
+ '{\n "client_id": ""\n}',
+ );
+ });
});
diff --git a/projects/developer/src/lib/openapi/parseOpenApiDocument.ts b/projects/developer/src/lib/openapi/parseOpenApiDocument.ts
index ae5fc19e..4866712f 100644
--- a/projects/developer/src/lib/openapi/parseOpenApiDocument.ts
+++ b/projects/developer/src/lib/openapi/parseOpenApiDocument.ts
@@ -126,15 +126,19 @@ function sampleFromSchema({
switch (schemaType(schema)) {
case 'object':
return Object.fromEntries(
- Object.entries(asObject(schema.properties)).map(([name, property]) => [
- name,
- sampleFromSchema({
- root,
- schemaValue: property,
- depth: depth + 1,
- references: nextReferences,
- }),
- ]),
+ Object.entries(asObject(schema.properties))
+ .filter(([, property]) =>
+ !asBoolean(resolveReference({ root, value: property }).deprecated)
+ )
+ .map(([name, property]) => [
+ name,
+ sampleFromSchema({
+ root,
+ schemaValue: property,
+ depth: depth + 1,
+ references: nextReferences,
+ }),
+ ]),
);
case 'array':
return [
diff --git a/projects/developer/src/lib/redirects/guideRedirects.json b/projects/developer/src/lib/redirects/guideRedirects.json
index c09078ea..23bda38b 100644
--- a/projects/developer/src/lib/redirects/guideRedirects.json
+++ b/projects/developer/src/lib/redirects/guideRedirects.json
@@ -3,6 +3,7 @@
"/docs/stay-connected": "stay-connected",
"/docs/create-an-app": "create-an-app",
"/docs/authentication-oauth": "authentication-oauth",
+ "/docs/pkce": "pkce",
"/docs/api-url": "api-url",
"/docs/required-headers": "required-headers",
"/docs/rate-limiting": "rate-limiting",