From d179bcfc2ba234054475cb49b8c041379d83da01 Mon Sep 17 00:00:00 2001 From: capJavert Date: Thu, 10 Sep 2026 18:08:37 +0200 Subject: [PATCH 1/4] feat(api-access): open API access to all users, sell Plus on limits Personal Access Tokens no longer require Plus (dailydotdev/daily-api#4262). Any confirmed account can mint one, so the settings page stops gating Create behind a subscription and four surfaces stop selling Plus as "Get API Access". The page does no eligibility pre-check. The API is the single source of truth on who may mint a token, so Create stays clickable and the server's refusal is surfaced in the toast, which previously swallowed every failure into a generic message. That keeps emailConfirmed out of the boot payload and lets this ship independently of the API change. Plus is now an optional upsell rather than a gate: a plus-entry-gradient card on the settings page and a trailing line in Ask, both selling full API access and higher rate limits. The Ask page and FAQ claimed a Plus subscription was required, which stops being true. No request counts in the UI copy. They live in daily-api and would drift here unnoticed. Co-Authored-By: Claude Opus 5 (1M context) --- .../shared/src/components/PlusUserBadge.tsx | 2 +- .../shared/src/components/UpgradeToPlus.tsx | 5 +- .../shared/src/components/plus/PlusList.tsx | 8 +- .../sidebar/sections/MainSection.tsx | 5 +- .../sidebar/sections/ProfilePanelSection.tsx | 6 +- .../src/features/ask/components/AskFAQ.tsx | 2 +- .../features/ask/components/AskInstall.tsx | 32 +++--- packages/shared/src/lib/log.ts | 1 + packages/webapp/pages/agents/ask/index.tsx | 6 +- packages/webapp/pages/settings/api.tsx | 101 ++++++++++-------- 10 files changed, 95 insertions(+), 73 deletions(-) diff --git a/packages/shared/src/components/PlusUserBadge.tsx b/packages/shared/src/components/PlusUserBadge.tsx index 0c2d8ec339e..782055bf13b 100644 --- a/packages/shared/src/components/PlusUserBadge.tsx +++ b/packages/shared/src/components/PlusUserBadge.tsx @@ -29,7 +29,7 @@ export const PlusUserBadge = ({ size = IconSize.Size16, }: Props): ReactElement | null => { const { isPlus, logSubscriptionEvent } = usePlusSubscription(); - const plusCta = 'Get API Access'; + const plusCta = 'Full API Access and higher rate limits'; if (!user.isPlus) { return null; diff --git a/packages/shared/src/components/UpgradeToPlus.tsx b/packages/shared/src/components/UpgradeToPlus.tsx index 9e2e09019ef..a9c08fb353e 100644 --- a/packages/shared/src/components/UpgradeToPlus.tsx +++ b/packages/shared/src/components/UpgradeToPlus.tsx @@ -39,7 +39,10 @@ export const UpgradeToPlus = ({ const isFullCTAText = !isLaptop || isLaptopXL; const { isPlus, logSubscriptionEvent } = usePlusSubscription(); const { isActive: isSaleActive } = usePlusSale(); - const ctaCopy = { full: 'Get API Access', short: 'API access' }; + const ctaCopy = { + full: 'Full API access and higher rate limits', + short: 'Full API access', + }; const content = isFullCTAText ? ctaCopy.full : ctaCopy.short; const showSaleLabel = isSaleActive && !iconOnly; const defaultColor = ButtonColor.Bacon; diff --git a/packages/shared/src/components/plus/PlusList.tsx b/packages/shared/src/components/plus/PlusList.tsx index a329223b034..639a1df29d3 100644 --- a/packages/shared/src/components/plus/PlusList.tsx +++ b/packages/shared/src/components/plus/PlusList.tsx @@ -66,9 +66,9 @@ export const defaultFeatureList: Array = [ export const plusFeatureListControl: Array = [ { id: 'public-api', - label: 'Public API access', + label: 'Full public API access and higher rate limits', status: PlusItemStatus.Ready, - tooltip: `Build custom integrations and access your personalized feed programmatically. Full API access to feeds, bookmarks, search, and more.`, + tooltip: `Build custom integrations and access your personalized feed programmatically. Full API access to feeds, bookmarks, search, and more with higher rate limits.`, }, { id: 'presidential-briefing', @@ -242,10 +242,10 @@ const reframeControlItem = ( export const plusFeatureListApiFirst: Array = [ { id: 'public-api', - label: 'Public API access', + label: 'Full public API access and higher rate limits', status: PlusItemStatus.Ready, highlight: true, - tooltip: `Endpoints for your feed, search, posts, and bookmarks. Plus pre-built integrations for Claude Code, Cursor, and Codex.`, + tooltip: `Full API access across your feed, search, posts, and bookmarks, with higher rate limits. Plus pre-built integrations for Claude Code, Cursor, and Codex.`, }, reframeControlItem( 'custom feeds', diff --git a/packages/shared/src/components/sidebar/sections/MainSection.tsx b/packages/shared/src/components/sidebar/sections/MainSection.tsx index c1fa88f8b4d..453a1777447 100644 --- a/packages/shared/src/components/sidebar/sections/MainSection.tsx +++ b/packages/shared/src/components/sidebar/sections/MainSection.tsx @@ -48,7 +48,10 @@ export const MainSection = ({ const { isV2 } = useLayoutVariant(); const isPlus = user?.isPlus; const { isActive: isSaleActive } = usePlusSale(); - const ctaCopy = { full: 'Get API Access', short: 'API access' }; + const ctaCopy = { + full: 'Full API access and higher rate limits', + short: 'Full API access', + }; const { value: showYearInReview } = useConditionalFeature({ feature: featureYearInReview, shouldEvaluate: isLoggedIn, diff --git a/packages/shared/src/components/sidebar/sections/ProfilePanelSection.tsx b/packages/shared/src/components/sidebar/sections/ProfilePanelSection.tsx index 2d7eeb8469f..4355f43f673 100644 --- a/packages/shared/src/components/sidebar/sections/ProfilePanelSection.tsx +++ b/packages/shared/src/components/sidebar/sections/ProfilePanelSection.tsx @@ -90,10 +90,10 @@ export const ProfilePanelSection = ({ } /> ), }, - // Non-Plus only: a purple "Get API Access" upgrade CTA (API access is a - // Plus perk). Plus users already have it, so it's hidden for them. + // Non-Plus only: a purple upgrade CTA for the higher API rate limits + // Plus carries. Plus users already have them, so it's hidden for them. !isPlus && { - title: 'Get API Access', + title: 'Full API access and higher rate limits', path: plusUrl, isForcedLink: true, requiresLogin: true, diff --git a/packages/shared/src/features/ask/components/AskFAQ.tsx b/packages/shared/src/features/ask/components/AskFAQ.tsx index 90095c8ed84..a17fc330e91 100644 --- a/packages/shared/src/features/ask/components/AskFAQ.tsx +++ b/packages/shared/src/features/ask/components/AskFAQ.tsx @@ -24,7 +24,7 @@ const faq = [ { title: 'Is this free?', description: - 'daily-dev-ask requires a Plus subscription and an API token. You can get Plus at daily.dev/plus and create a token in your API settings.', + 'Yes. Any daily.dev account can create an API token in API settings and use daily-dev-ask. Plus adds full API access and higher rate limits, check it out at daily.dev/plus.', }, { title: 'What tools does it work with?', diff --git a/packages/shared/src/features/ask/components/AskInstall.tsx b/packages/shared/src/features/ask/components/AskInstall.tsx index af3954ee351..c89f28e7425 100644 --- a/packages/shared/src/features/ask/components/AskInstall.tsx +++ b/packages/shared/src/features/ask/components/AskInstall.tsx @@ -80,7 +80,7 @@ const methods: InstallMethod[] = [ export const AskInstall = (): ReactElement => { const { displayToast } = useToastNotification(); - const { isPlus } = usePlusSubscription(); + const { isPlus, logSubscriptionEvent } = usePlusSubscription(); const { isLoggedIn, showLogin } = useAuthContext(); const { logEvent } = useLogContext(); const [expandedTool, setExpandedTool] = useState(null); @@ -116,17 +116,14 @@ export const AskInstall = (): ReactElement => { color={TypographyColor.Secondary} center > - {isPlus ? ( + You'll need an{' '} + + API token + + . Pick your tool and install in seconds. + {!isPlus && ( <> - You'll need an{' '} - - API token - - . Pick your tool and install in seconds. - - ) : ( - <> - Requires a{' '} + {' '} { if (!isLoggedIn) { e.preventDefault(); showLogin({ trigger: AuthTriggers.Plus }); + return; } + logSubscriptionEvent({ + event_name: LogEvent.UpgradeSubscription, + target_id: TargetId.ApiAccess, + }); }} onKeyDown={(e) => { if (e.key === 'Enter' && !isLoggedIn) { @@ -145,14 +147,10 @@ export const AskInstall = (): ReactElement => { } }} > - Plus subscription + Upgrade to Plus {' '} - and an{' '} - - API token - - . Pick your tool and install in seconds. + for full API access and higher rate limits. )} diff --git a/packages/shared/src/lib/log.ts b/packages/shared/src/lib/log.ts index c6c4474f1e1..51f6326aec3 100644 --- a/packages/shared/src/lib/log.ts +++ b/packages/shared/src/lib/log.ts @@ -637,6 +637,7 @@ export enum TargetId { MyProfile = 'my profile', PlusBadge = 'plus badge', PlusPage = 'plus page', + ApiAccess = 'api access', Onboarding = 'onboarding', BlockedWords = 'block words', CustomFeed = 'custom feed', diff --git a/packages/webapp/pages/agents/ask/index.tsx b/packages/webapp/pages/agents/ask/index.tsx index ca09cfd1c52..b6bdd03438a 100644 --- a/packages/webapp/pages/agents/ask/index.tsx +++ b/packages/webapp/pages/agents/ask/index.tsx @@ -153,15 +153,15 @@ const AskPage = (): ReactElement => { {!isPlus && (
- Unlock daily-dev-ask with Plus + Full API access and higher rate limits - daily-dev-ask requires a Plus subscription. Upgrade to get API - access and connect your AI tools. + daily-dev-ask works on any account. Plus raises your request + limits and unlocks the Plus-only endpoints. - ) : undefined + } >
@@ -516,11 +514,41 @@ const ApiAccessPage = (): ReactElement => { type={TypographyType.Callout} color={TypographyColor.Tertiary} > - Use tokens to authenticate with the daily.dev API. Tokens provide - read-only access to your personalized feed and posts. + Use tokens to authenticate with the daily.dev API from agents, + scripts, and integrations.
+ {!isPlus && ( +
+ + Full API access and higher rate limits + + + Plus raises your request limits and unlocks the Plus-only + endpoints, including bookmark folders and clickbait-shielded + titles. + + +
+ )} + {isLoading && ( { type={TypographyType.Callout} color={TypographyColor.Tertiary} > - {tokenEmptyStateText} + No tokens yet. Create one to get started. - {isPlus ? ( - - ) : ( - - )} +
)} From 5319b072cf15c90fd32a52b2693612b10e6b47de Mon Sep 17 00:00:00 2001 From: capJavert Date: Fri, 11 Sep 2026 09:29:19 +0200 Subject: [PATCH 2/4] refactor(plus): single source for the API upsell copy The CTA string was hand-written in six places and had already drifted within one PR: PlusUserBadge carried title-case "Access" and rendered a trailing "!" left over from the verb-led "Get API Access!", which reads oddly on a noun phrase. Both card headings repeated the sentence too. Export it once from lib/constants and point every site at it. The create-token toast now surfaces the first error regardless of code. Matching only FORBIDDEN dropped the token-limit message, which is an apollo ValidationError and so arrives as GRAPHQL_VALIDATION_FAILED - "Maximum of 5 tokens allowed per user" was being replaced with the generic fallback. Co-Authored-By: Claude Opus 5 (1M context) --- packages/shared/src/components/PlusUserBadge.tsx | 6 +++--- packages/shared/src/components/UpgradeToPlus.tsx | 7 ++----- .../src/components/sidebar/sections/MainSection.tsx | 7 +++---- .../components/sidebar/sections/ProfilePanelSection.tsx | 9 +++++++-- packages/shared/src/lib/constants.ts | 2 ++ packages/webapp/pages/agents/ask/index.tsx | 4 ++-- packages/webapp/pages/settings/api.tsx | 9 ++++----- 7 files changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/shared/src/components/PlusUserBadge.tsx b/packages/shared/src/components/PlusUserBadge.tsx index 782055bf13b..d9210b0a7df 100644 --- a/packages/shared/src/components/PlusUserBadge.tsx +++ b/packages/shared/src/components/PlusUserBadge.tsx @@ -3,7 +3,7 @@ import React from 'react'; import type { PublicProfile } from '../lib/user'; import { SimpleTooltip } from './tooltips'; import { PlusUser } from './PlusUser'; -import { plusUrl } from '../lib/constants'; +import { plusApiCta, plusUrl } from '../lib/constants'; import Link from './utilities/Link'; import { Typography, @@ -29,7 +29,7 @@ export const PlusUserBadge = ({ size = IconSize.Size16, }: Props): ReactElement | null => { const { isPlus, logSubscriptionEvent } = usePlusSubscription(); - const plusCta = 'Full API Access and higher rate limits'; + const plusCta = plusApiCta; if (!user.isPlus) { return null; @@ -60,7 +60,7 @@ export const PlusUserBadge = ({ }); }} > - {plusCta}! + {plusCta} )} diff --git a/packages/shared/src/components/UpgradeToPlus.tsx b/packages/shared/src/components/UpgradeToPlus.tsx index a9c08fb353e..be2932327cd 100644 --- a/packages/shared/src/components/UpgradeToPlus.tsx +++ b/packages/shared/src/components/UpgradeToPlus.tsx @@ -5,7 +5,7 @@ import type { ButtonSize } from './buttons/Button'; import { ButtonColor, Button, ButtonVariant } from './buttons/Button'; import { DevPlusIcon } from './icons'; import Link from './utilities/Link'; -import { plusUrl } from '../lib/constants'; +import { plusApiCta, plusApiCtaShort, plusUrl } from '../lib/constants'; import { useViewSize, ViewSize } from '../hooks'; import { usePlusSubscription } from '../hooks/usePlusSubscription'; import { usePlusSale } from '../hooks/usePlusSale'; @@ -39,10 +39,7 @@ export const UpgradeToPlus = ({ const isFullCTAText = !isLaptop || isLaptopXL; const { isPlus, logSubscriptionEvent } = usePlusSubscription(); const { isActive: isSaleActive } = usePlusSale(); - const ctaCopy = { - full: 'Full API access and higher rate limits', - short: 'Full API access', - }; + const ctaCopy = { full: plusApiCta, short: plusApiCtaShort }; const content = isFullCTAText ? ctaCopy.full : ctaCopy.short; const showSaleLabel = isSaleActive && !iconOnly; const defaultColor = ButtonColor.Bacon; diff --git a/packages/shared/src/components/sidebar/sections/MainSection.tsx b/packages/shared/src/components/sidebar/sections/MainSection.tsx index 453a1777447..15ace6a9096 100644 --- a/packages/shared/src/components/sidebar/sections/MainSection.tsx +++ b/packages/shared/src/components/sidebar/sections/MainSection.tsx @@ -21,6 +21,8 @@ import { OtherFeedPage } from '../../../lib/query'; import type { SidebarSectionProps } from './common'; import { gameCenterMilestoneSectionId, + plusApiCta, + plusApiCtaShort, plusUrl, webappUrl, } from '../../../lib/constants'; @@ -48,10 +50,7 @@ export const MainSection = ({ const { isV2 } = useLayoutVariant(); const isPlus = user?.isPlus; const { isActive: isSaleActive } = usePlusSale(); - const ctaCopy = { - full: 'Full API access and higher rate limits', - short: 'Full API access', - }; + const ctaCopy = { full: plusApiCta, short: plusApiCtaShort }; const { value: showYearInReview } = useConditionalFeature({ feature: featureYearInReview, shouldEvaluate: isLoggedIn, diff --git a/packages/shared/src/components/sidebar/sections/ProfilePanelSection.tsx b/packages/shared/src/components/sidebar/sections/ProfilePanelSection.tsx index 4355f43f673..6fd1fe0fda0 100644 --- a/packages/shared/src/components/sidebar/sections/ProfilePanelSection.tsx +++ b/packages/shared/src/components/sidebar/sections/ProfilePanelSection.tsx @@ -15,7 +15,12 @@ import { } from '../../icons'; import type { SidebarSectionProps } from './common'; import { OtherFeedPage } from '../../../lib/query'; -import { plusUrl, settingsUrl, webappUrl } from '../../../lib/constants'; +import { + plusApiCta, + plusUrl, + settingsUrl, + webappUrl, +} from '../../../lib/constants'; import { LogEvent, TargetId } from '../../../lib/log'; import { useAuthContext } from '../../../contexts/AuthContext'; import { usePlusSubscription } from '../../../hooks'; @@ -93,7 +98,7 @@ export const ProfilePanelSection = ({ // Non-Plus only: a purple upgrade CTA for the higher API rate limits // Plus carries. Plus users already have them, so it's hidden for them. !isPlus && { - title: 'Full API access and higher rate limits', + title: plusApiCta, path: plusUrl, isForcedLink: true, requiresLogin: true, diff --git a/packages/shared/src/lib/constants.ts b/packages/shared/src/lib/constants.ts index 8e962b8f031..3d37a9ac243 100644 --- a/packages/shared/src/lib/constants.ts +++ b/packages/shared/src/lib/constants.ts @@ -63,6 +63,8 @@ export const webappUrl = process.env.NEXT_PUBLIC_WEBAPP_URL as string; export const gameCenterMilestoneSectionId = 'milestone-quests'; export const onboardingUrl = `${webappUrl}onboarding`; export const plusUrl = `${webappUrl}plus`; +export const plusApiCta = 'Full API access and higher rate limits'; +export const plusApiCtaShort = 'Full API access'; export const managePlusUrl = 'https://r.daily.dev/billing'; export const plusDetailsUrl = 'https://r.daily.dev/plus-onboarding'; export const plusSuccessUrl = `${plusUrl}/success`; diff --git a/packages/webapp/pages/agents/ask/index.tsx b/packages/webapp/pages/agents/ask/index.tsx index b6bdd03438a..dc52a5e72f3 100644 --- a/packages/webapp/pages/agents/ask/index.tsx +++ b/packages/webapp/pages/agents/ask/index.tsx @@ -16,7 +16,7 @@ import { ButtonVariant, } from '@dailydotdev/shared/src/components/buttons/Button'; import { DevPlusIcon } from '@dailydotdev/shared/src/components/icons'; -import { plusUrl } from '@dailydotdev/shared/src/lib/constants'; +import { plusApiCta, plusUrl } from '@dailydotdev/shared/src/lib/constants'; import { useLogContext } from '@dailydotdev/shared/src/contexts/LogContext'; import { LogEvent, TargetId } from '@dailydotdev/shared/src/lib/log'; import { useAuthContext } from '@dailydotdev/shared/src/contexts/AuthContext'; @@ -153,7 +153,7 @@ const AskPage = (): ReactElement => { {!isPlus && (
- Full API access and higher rate limits + {plusApiCta} { {!isPlus && (
- Full API access and higher rate limits + {plusApiCta} Date: Fri, 11 Sep 2026 14:16:57 +0200 Subject: [PATCH 3/4] feat(quests): route API quests to API settings The five API quest event types land in both destination maps so their cards link somewhere useful. `questDestinations` is shared by the daily and weekly renderers; intro keeps its own map. Daily rotations are global with no per-user eligibility, so a user with no token will be served an API quest they cannot complete. The destination is what makes that acceptable: the card becomes a route into token creation rather than a dead slot. Co-Authored-By: Claude Opus 5 (1M context) --- packages/shared/src/components/modals/IntroQuestModal.tsx | 8 ++++++++ packages/shared/src/components/quest/questDestinations.ts | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/packages/shared/src/components/modals/IntroQuestModal.tsx b/packages/shared/src/components/modals/IntroQuestModal.tsx index afc3dd3a4dd..b27102973e4 100644 --- a/packages/shared/src/components/modals/IntroQuestModal.tsx +++ b/packages/shared/src/components/modals/IntroQuestModal.tsx @@ -53,6 +53,14 @@ const introDestinationByEventType: Record = { label: 'Profile', path: '/settings/profile', }, + api_token_create: { + label: 'API settings', + path: '/settings/api', + }, + api_request: { + label: 'API settings', + path: '/settings/api', + }, }; const getExtensionIntroDestination = ( diff --git a/packages/shared/src/components/quest/questDestinations.ts b/packages/shared/src/components/quest/questDestinations.ts index 85cf622dc68..3a5e41c6764 100644 --- a/packages/shared/src/components/quest/questDestinations.ts +++ b/packages/shared/src/components/quest/questDestinations.ts @@ -68,6 +68,12 @@ export const getQuestDestination = ( return { label: 'Feedback', path: '/settings/feedback' }; case 'squad_join': return { label: 'Squads', path: '/squads/discover' }; + case 'api_token_create': + case 'api_request': + case 'api_feed_fetch': + case 'api_bookmark': + case 'api_search': + return { label: 'API settings', path: '/settings/api' }; default: return null; } From 184983cc8933ced416076272d818d5dd6d916ce1 Mon Sep 17 00:00:00 2001 From: capJavert Date: Fri, 11 Sep 2026 14:45:02 +0200 Subject: [PATCH 4/4] fix(quests): rename api_request to api_profile_fetch The intro quest became "fetch your profile through the API" so the publisher fires only on mapped routes instead of once per API request. Co-Authored-By: Claude Opus 5 (1M context) --- packages/shared/src/components/modals/IntroQuestModal.tsx | 2 +- packages/shared/src/components/quest/questDestinations.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/components/modals/IntroQuestModal.tsx b/packages/shared/src/components/modals/IntroQuestModal.tsx index b27102973e4..023633b63c4 100644 --- a/packages/shared/src/components/modals/IntroQuestModal.tsx +++ b/packages/shared/src/components/modals/IntroQuestModal.tsx @@ -57,7 +57,7 @@ const introDestinationByEventType: Record = { label: 'API settings', path: '/settings/api', }, - api_request: { + api_profile_fetch: { label: 'API settings', path: '/settings/api', }, diff --git a/packages/shared/src/components/quest/questDestinations.ts b/packages/shared/src/components/quest/questDestinations.ts index 3a5e41c6764..e7af07ad183 100644 --- a/packages/shared/src/components/quest/questDestinations.ts +++ b/packages/shared/src/components/quest/questDestinations.ts @@ -69,7 +69,7 @@ export const getQuestDestination = ( case 'squad_join': return { label: 'Squads', path: '/squads/discover' }; case 'api_token_create': - case 'api_request': + case 'api_profile_fetch': case 'api_feed_fetch': case 'api_bookmark': case 'api_search':