Skip to main content
The subscription endpoint allows authenticated users to retrieve their current subscription information, including plan details, status, and billing cycle.

Endpoint

GET /api/payments/subscription

Authentication

This endpoint requires authentication. The user must have an active session with a valid JWT token.

Request

No request body is required. Simply make a GET request to the endpoint with valid authentication cookies.

Example Request

const response = await fetch('/api/payments/subscription', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
  }
})

const data = await response.json()
console.log(data.subscription)
cURL
curl -X GET https://yourdomain.com/api/payments/subscription \
  -H "Content-Type: application/json" \
  -H "Cookie: better-auth.session_token=..."

Response

subscription
object | null
The user’s subscription details, or null if they don’t have an active subscription.

Success Response (200)

{
  "subscription": {
    "id": "sub_abc123",
    "userId": "user_xyz789",
    "customerId": "cus_def456",
    "provider": "stripe",
    "providerSubscriptionId": "sub_1234567890",
    "status": "active",
    "plan": "pro",
    "interval": "month",
    "amount": "29.90",
    "currency": "usd",
    "currentPeriodStart": "2024-01-01T00:00:00.000Z",
    "currentPeriodEnd": "2024-02-01T00:00:00.000Z",
    "cancelAtPeriodEnd": false,
    "canceledAt": null,
    "trialStart": null,
    "trialEnd": null,
    "createdAt": "2024-01-01T00:00:00.000Z",
    "updatedAt": "2024-01-01T00:00:00.000Z"
  }
}

No Subscription Response (200)

If the user doesn’t have a subscription, the response will be:
{
  "subscription": null
}

Error Responses

Returned when the user is not authenticated.
{
  "error": "Unauthorized"
}
Returned when an unexpected error occurs.
{
  "error": "Internal Server Error"
}

Implementation Details

The endpoint retrieves the most recently updated subscription for the authenticated user:
export async function GET(req: Request) {
  const session = await auth.api.getSession({
    headers: await headers(),
  })

  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const userSubscription = await db.query.subscription.findFirst({
    where: eq(subscription.userId, session.user.id),
    orderBy: (subscriptions, { desc }) => [desc(subscriptions.updatedAt)],
  })

  return NextResponse.json({ subscription: userSubscription || null })
}

Source Code Reference

The implementation can be found in src/app/api/payments/subscription/route.ts:9-32.

Usage Examples

React Hook

'use client'

import { useEffect, useState } from 'react'

interface Subscription {
  id: string
  plan: string
  status: string
  currentPeriodEnd: string
  // ... other fields
}

export function useSubscription() {
  const [subscription, setSubscription] = useState<Subscription | null>(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    const fetchSubscription = async () => {
      try {
        const response = await fetch('/api/payments/subscription')
        const data = await response.json()
        setSubscription(data.subscription)
      } catch (error) {
        console.error('Failed to fetch subscription:', error)
      } finally {
        setLoading(false)
      }
    }

    fetchSubscription()
  }, [])

  return { subscription, loading }
}

Server Component

import { headers } from 'next/headers'
import { auth } from '@/lib/auth'
import { db } from '@/database'
import { eq } from 'drizzle-orm'
import { subscription } from '@/database/schema'

export default async function SubscriptionPage() {
  const session = await auth.api.getSession({
    headers: await headers(),
  })

  if (!session) {
    return <div>Please log in</div>
  }

  const userSubscription = await db.query.subscription.findFirst({
    where: eq(subscription.userId, session.user.id),
    orderBy: (subscriptions, { desc }) => [desc(subscriptions.updatedAt)],
  })

  if (!userSubscription) {
    return <div>No active subscription</div>
  }

  return (
    <div>
      <h1>Your Subscription</h1>
      <p>Plan: {userSubscription.plan}</p>
      <p>Status: {userSubscription.status}</p>
      <p>Renews: {new Date(userSubscription.currentPeriodEnd).toLocaleDateString()}</p>
    </div>
  )
}

Subscription Status Values

active

Subscription is active and in good standing

trialing

Subscription is in trial period

past_due

Payment failed, subscription at risk

canceled

Subscription has been canceled

incomplete

Initial payment failed or pending

Next Steps