Skip to main content
RapidDev - Software Development Agency
v0-integrationsNext.js API Route

How to Integrate Thinkific with V0

To integrate Thinkific with V0 by Vercel, create a Next.js API route at app/api/thinkific/route.ts that calls the Thinkific REST API using an API key stored in THINKIFIC_API_KEY. V0 generates custom course storefronts and student dashboard components that fetch from your API route. Thinkific's API lets you display courses, enrollments, and user progress in a fully branded experience outside the default Thinkific theme.

What you'll learn

  • How to generate a Thinkific API key and find your subdomain for API calls
  • How to create a Next.js API route that fetches courses and enrollment data from Thinkific
  • How to prompt V0 to generate a custom course catalog and enrollment dashboard
  • How to display course pricing, descriptions, and enrollment counts in your V0 app
  • How to create enrollment links that redirect users to Thinkific's checkout flow
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read25 minutesOtherApril 2026RapidDev Engineering Team
TL;DR

To integrate Thinkific with V0 by Vercel, create a Next.js API route at app/api/thinkific/route.ts that calls the Thinkific REST API using an API key stored in THINKIFIC_API_KEY. V0 generates custom course storefronts and student dashboard components that fetch from your API route. Thinkific's API lets you display courses, enrollments, and user progress in a fully branded experience outside the default Thinkific theme.

Building Custom Thinkific Course Storefronts with V0

Thinkific's default themes work well for getting started, but many course creators want a fully custom storefront that matches their brand, integrates with their marketing site, or shows their courses alongside other content. The Thinkific API makes this possible by exposing course catalog data, enrollment information, and user details that you can display in any interface you build with V0.

The integration creates a Next.js API route that authenticates with Thinkific using your API key. V0 generates the front-end components — course cards, enrollment CTAs, student dashboards, or progress trackers — that fetch data from your secure API route. The Thinkific API key never reaches the browser, preventing unauthorized access to your student and enrollment data.

Thinkific's API supports the full course lifecycle: listing published courses with pricing and descriptions, checking enrollment status for a specific user, fetching a student's course progress, and triggering enrollment via free enrollment or linking to Thinkific's checkout. For most custom storefronts, the courses endpoint is the starting point — fetch the catalog, display it with your own design, and link each course to Thinkific's native checkout or enrollment page.

Integration method

Next.js API Route

V0 generates custom course storefront and student portal components. A Next.js API route on Vercel proxies requests to the Thinkific REST API, keeping your API key server-side. The React components fetch from /api/thinkific/courses rather than Thinkific directly, enabling fully branded course pages without the limitations of Thinkific's built-in themes.

Prerequisites

  • A V0 account with a Next.js project at v0.dev
  • A Thinkific account with at least one published course
  • A Thinkific API key from your Thinkific admin settings
  • Your Thinkific school subdomain (e.g., 'yourschool' from yourschool.thinkific.com)
  • A Vercel account with your V0 project connected via GitHub

Step-by-step guide

1

Get Your Thinkific API Key and Subdomain

Thinkific uses API key authentication and routes requests through your school's subdomain. You need both before writing any code. To get your API key, log into your Thinkific admin dashboard at yourschool.thinkific.com/admin. Click on your profile icon or navigate to Settings in the admin menu. Look for 'API' or 'Developer' settings — in the Thinkific admin, this is typically under Settings → Code & Analytics → API (or similar navigation depending on your plan). Generate a new API key and copy it. Thinkific API keys look like a long random alphanumeric string. Note that Thinkific's API key access depends on your plan. The API is available on Growth and higher plans. If you are on the Basic or Start plan, you may not have API access and will need to upgrade. Your subdomain is the part of your Thinkific school URL before '.thinkific.com'. If your school is at myacademy.thinkific.com, your subdomain is 'myacademy'. Every Thinkific API call uses this subdomain in the request headers (X-Auth-Subdomain) to identify which school you are querying. All Thinkific API endpoints use the base URL https://api.thinkific.com/api/public/v1. Authentication requires both the API key in the Authorization header (as Bearer token) and the subdomain in the X-Auth-Subdomain header. Omitting either header will result in authentication errors.

Pro tip: Thinkific's API requires both the Authorization: Bearer header AND the X-Auth-Subdomain header. The subdomain header identifies which school to query — without it, authentication fails even with a valid API key.

Expected result: You have your Thinkific API key and school subdomain. You understand that both are required for every API call and are ready to add them to Vercel environment variables.

2

Add Thinkific Credentials to Vercel

Store your Thinkific API key and subdomain in Vercel's environment variables so your Next.js API route can authenticate with the Thinkific API securely. Open your Vercel Dashboard, navigate to your project, click 'Settings', and select 'Environment Variables'. Add the following two variables: THINKIFIC_API_KEY: Your Thinkific API key. This is a secret — do not add a NEXT_PUBLIC_ prefix. Set it for Production, Preview, and Development environments. THINKIFIC_SUBDOMAIN: Your school subdomain (the part before .thinkific.com). For example, if your school is at learnwithme.thinkific.com, set this to 'learnwithme'. This is not a secret but is best kept in an environment variable for easy configuration. Save both variables and trigger a redeployment. For local development, add these same variables to your .env.local file so your local Next.js server can authenticate with Thinkific during development.

.env.local
1# .env.local local development only, excluded from Git
2THINKIFIC_API_KEY=your_thinkific_api_key_here
3THINKIFIC_SUBDOMAIN=yourschool

Pro tip: Test your credentials locally before deploying by running the Next.js dev server and calling http://localhost:3000/api/thinkific/courses in your browser.

Expected result: Vercel Dashboard shows THINKIFIC_API_KEY and THINKIFIC_SUBDOMAIN saved as environment variables. Local .env.local is configured for development testing.

3

Create the Thinkific Courses API Route

Create the Next.js API route that fetches courses from Thinkific. This route runs server-side on Vercel and uses both the API key and subdomain to authenticate with the Thinkific API. Create app/api/thinkific/courses/route.ts. The Thinkific courses endpoint is GET https://api.thinkific.com/api/public/v1/courses and returns a paginated list of all courses in your school. Each course object includes id, name, slug, description, card_image_url, price (in your currency's smallest unit), administrator_user_id, product_id, published, and instructor details. Thinkific uses meta.pagination for pagination, which returns page, per_page, total_pages, and total objects. The default page size is 25. For most schools with under 50 courses, two pages cover the complete catalog. You can request up to 250 courses per page using the per_page query parameter. Filter for published courses only by checking the published field — draft and private courses should not appear in a public storefront. You can either filter on the Thinkific API side (Thinkific does not have a direct published=true filter parameter but you can filter after fetching) or filter in your route before returning the response. The price field in Thinkific's API is returned as a decimal number (e.g., 49.99 for a $49.99 course), not in cents. A price of 0.0 means the course is free. Format this for display in your component rather than in the API route.

V0 Prompt

Create a Next.js API route at app/api/thinkific/courses/route.ts. Use THINKIFIC_API_KEY and THINKIFIC_SUBDOMAIN from environment variables. Fetch from https://api.thinkific.com/api/public/v1/courses with headers Authorization: Bearer {key} and X-Auth-Subdomain: {subdomain}. Filter to only return published courses. Return an array with id, name, slug, description, card_image_url, price, and instructor name. Handle pagination to fetch all pages if total_pages > 1. Handle errors with appropriate status codes.

Paste this in V0 chat

app/api/thinkific/courses/route.ts
1import { NextResponse } from 'next/server';
2
3interface ThinkificCourse {
4 id: number;
5 name: string;
6 slug: string;
7 description: string;
8 card_image_url: string | null;
9 price: number;
10 published: boolean;
11 instructor: {
12 id: number;
13 full_name: string;
14 avatar_url: string | null;
15 } | null;
16}
17
18interface ThinkificResponse {
19 items: ThinkificCourse[];
20 meta: {
21 pagination: {
22 page: number;
23 per_page: number;
24 total_pages: number;
25 total: number;
26 };
27 };
28}
29
30async function fetchCoursePage(
31 apiKey: string,
32 subdomain: string,
33 page: number
34): Promise<ThinkificResponse> {
35 const url = new URL('https://api.thinkific.com/api/public/v1/courses');
36 url.searchParams.set('page', String(page));
37 url.searchParams.set('limit', '50');
38
39 const response = await fetch(url.toString(), {
40 headers: {
41 Authorization: `Bearer ${apiKey}`,
42 'X-Auth-Subdomain': subdomain,
43 'Content-Type': 'application/json',
44 },
45 next: { revalidate: 300 },
46 });
47
48 if (!response.ok) {
49 throw new Error(`Thinkific API returned ${response.status}`);
50 }
51
52 return response.json();
53}
54
55export async function GET() {
56 const apiKey = process.env.THINKIFIC_API_KEY;
57 const subdomain = process.env.THINKIFIC_SUBDOMAIN;
58
59 if (!apiKey || !subdomain) {
60 return NextResponse.json(
61 { error: 'Thinkific configuration missing' },
62 { status: 500 }
63 );
64 }
65
66 try {
67 const firstPage = await fetchCoursePage(apiKey, subdomain, 1);
68 let allCourses = firstPage.items;
69
70 // Fetch additional pages if needed
71 const totalPages = firstPage.meta.pagination.total_pages;
72 for (let page = 2; page <= totalPages; page++) {
73 const additionalPage = await fetchCoursePage(apiKey, subdomain, page);
74 allCourses = allCourses.concat(additionalPage.items);
75 }
76
77 // Filter to published courses only
78 const publishedCourses = allCourses
79 .filter((course) => course.published)
80 .map((course) => ({
81 id: course.id,
82 name: course.name,
83 slug: course.slug,
84 description: course.description || '',
85 imageUrl: course.card_image_url,
86 price: course.price,
87 isFree: course.price === 0,
88 instructorName: course.instructor?.full_name || '',
89 instructorAvatar: course.instructor?.avatar_url || null,
90 enrollUrl: `https://${subdomain}.thinkific.com/courses/${course.slug}`,
91 }));
92
93 return NextResponse.json({ courses: publishedCourses, total: publishedCourses.length });
94 } catch (error) {
95 console.error('Thinkific courses fetch failed:', error);
96 return NextResponse.json(
97 { error: 'Failed to fetch courses' },
98 { status: 500 }
99 );
100 }
101}

Pro tip: Include enrollUrl in your API response (https://{subdomain}.thinkific.com/courses/{slug}) so your V0 components can link directly to Thinkific's enrollment flow without constructing the URL in the browser.

Expected result: Calling /api/thinkific/courses returns a JSON array of published courses with name, slug, imageUrl, price, isFree, instructorName, and enrollUrl for each course.

4

Generate Course Catalog Components with V0

With the courses API route working, prompt V0 to generate the React components that render your course catalog. The most effective approach is to first check the JSON output of /api/thinkific/courses to see the exact field names, then reference those in your V0 prompt. For a course catalog page, describe the card layout you want: the image placement, typography for course name and instructor, price display format, and the enrollment CTA. Tell V0 whether you want a grid (2-3 columns) or a list layout. Specify how you want to handle missing course images — either a placeholder gradient or your brand logo. Ask V0 to add category filtering or sorting if your course catalog is large. Thinkific's API supports collections (course bundles) which you can fetch separately to use as filter categories. If you only have a handful of courses, simple sorting by price (free first) or alphabetical order may be sufficient. For the price display, instruct V0 to show 'Free' for courses with price of 0 and to format paid prices as '$XX' or with your local currency symbol. You can also ask V0 to add a 'sale' badge if you want to manually flag certain courses as discounted. Ask V0 to generate a loading skeleton that matches your card layout — this appears while the fetch is in progress and prevents layout shift. The skeleton should have the same dimensions as a loaded card so the page does not jump when data arrives.

V0 Prompt

Create a course catalog page that fetches from /api/thinkific/courses. The response has courses array with name, slug, description, imageUrl (nullable), price, isFree, instructorName, and enrollUrl. Display as a 3-column responsive grid (2 on tablet, 1 on mobile). Each card has: course image with aspect-ratio 16/9 (grey gradient placeholder if imageUrl is null), course name as bold heading (2 lines max), instructorName in muted text, description truncated to 3 lines, a green 'Free' badge or '$XX' price badge, and a full-width 'Enroll Now' button linking to enrollUrl. Show 8 loading skeleton cards while fetching.

Paste this in V0 chat

Pro tip: Thinkific course descriptions can include HTML markup. Use dangerouslySetInnerHTML or a sanitized HTML renderer in your React component rather than rendering the description as plain text.

Expected result: V0 generates a polished course catalog grid with cards showing images, names, instructor names, prices, and enrollment buttons. Loading skeleton cards display during the initial data fetch.

5

Add an Enrollments Route for Student Dashboards

If you want to build a student-facing dashboard showing enrolled courses and progress, you need an additional API route that fetches enrollments for a specific user. This requires authenticating as an admin and looking up the user's enrollments by their email address. Create app/api/thinkific/enrollments/route.ts. The Thinkific enrollments endpoint is GET https://api.thinkific.com/api/public/v1/enrollments. You can filter by user email using the query parameter query[user_email]={email}. The response includes enrollment records with course_name, percentage_completed, completed, activated_at, expiry_date, and course_id. For user-specific dashboards where each visitor should only see their own enrollments, you will need a way to identify the current user. If your V0 app has authentication (via Clerk, Auth0, or similar), the logged-in user's email can be passed as a query parameter to the API route. The route should validate that the requester is authenticated before looking up enrollments — never allow arbitrary email lookups without authentication. For internal admin tools where you want to look up any student's enrollments, you can accept the email as a query parameter with admin authentication protecting the route. For complex use cases like syncing Thinkific enrollments with your own database, triggering enrollment from external events, or building a full white-label course platform on top of Thinkific, RapidDev can help design a more comprehensive integration architecture.

V0 Prompt

Add an enrollments section to the student dashboard that fetches from /api/thinkific/enrollments?email={userEmail}. The response has enrollments array with course_name, percentage_completed (0-100), completed (boolean), activated_at (ISO date string), and expiry_date (ISO date string or null). Show a 'My Courses' heading with the total enrollment count. Display enrollments as a vertical list of cards with: course name as heading, progress bar, percentage text, start date formatted as 'Started Mar 2026', expiry badge if expiry_date is set, and a 'Continue' link button.

Paste this in V0 chat

app/api/thinkific/enrollments/route.ts
1import { NextRequest, NextResponse } from 'next/server';
2
3export async function GET(request: NextRequest) {
4 const apiKey = process.env.THINKIFIC_API_KEY;
5 const subdomain = process.env.THINKIFIC_SUBDOMAIN;
6
7 if (!apiKey || !subdomain) {
8 return NextResponse.json(
9 { error: 'Thinkific configuration missing' },
10 { status: 500 }
11 );
12 }
13
14 const { searchParams } = new URL(request.url);
15 const userEmail = searchParams.get('email');
16
17 if (!userEmail) {
18 return NextResponse.json({ error: 'Email parameter required' }, { status: 400 });
19 }
20
21 const enrollmentsUrl = new URL(
22 'https://api.thinkific.com/api/public/v1/enrollments'
23 );
24 enrollmentsUrl.searchParams.set('query[user_email]', userEmail);
25 enrollmentsUrl.searchParams.set('limit', '50');
26
27 try {
28 const response = await fetch(enrollmentsUrl.toString(), {
29 headers: {
30 Authorization: `Bearer ${apiKey}`,
31 'X-Auth-Subdomain': subdomain,
32 'Content-Type': 'application/json',
33 },
34 next: { revalidate: 60 },
35 });
36
37 if (!response.ok) {
38 console.error('Thinkific enrollments error:', response.status);
39 return NextResponse.json(
40 { error: `Thinkific returned ${response.status}` },
41 { status: response.status }
42 );
43 }
44
45 const data = await response.json();
46
47 const enrollments = (data.items || []).map(
48 (e: {
49 course_name: string;
50 percentage_completed: number;
51 completed: boolean;
52 activated_at: string;
53 expiry_date: string | null;
54 course_id: number;
55 }) => ({
56 courseName: e.course_name,
57 percentageCompleted: e.percentage_completed,
58 completed: e.completed,
59 activatedAt: e.activated_at,
60 expiryDate: e.expiry_date,
61 courseId: e.course_id,
62 courseUrl: `https://${subdomain}.thinkific.com/courses/take/${e.course_id}`,
63 })
64 );
65
66 return NextResponse.json({ enrollments, total: enrollments.length });
67 } catch (error) {
68 console.error('Thinkific enrollments fetch failed:', error);
69 return NextResponse.json(
70 { error: 'Failed to fetch enrollments' },
71 { status: 500 }
72 );
73 }
74}

Pro tip: The Thinkific API returns enrollment progress as percentage_completed (a 0-100 integer). A value of 100 does not always mean the enrollment.completed field is true — students may reach 100% lesson completion without submitting a final quiz that marks the course complete.

Expected result: Calling /api/thinkific/enrollments?email=student@example.com returns that student's enrolled courses with completion percentages, dates, and direct links to continue their courses.

Common use cases

Custom Course Catalog on a Marketing Site

A course creator wants to display their Thinkific course catalog on their main marketing website with a design that matches their brand identity, not Thinkific's default look. V0 generates a course grid page that fetches published courses from the Thinkific API and displays each with a custom card layout including course image, title, instructor name, price, and an enrollment CTA button.

V0 Prompt

Build a course catalog page that fetches data from /api/thinkific/courses. The response has courses array with name, description, slug, price (number in cents), product_id, card_image_url, and instructor.name. Display courses as cards in a 3-column grid. Each card has the course image at the top, course name as a bold heading, instructor name in grey, a 2-line description preview, price formatted as '$XX' (or 'Free' if 0), and a blue 'Enroll Now' button that links to 'https://your-school.thinkific.com/courses/{slug}'.

Copy this prompt to try it in V0

Student Progress Dashboard

An educational startup wants a student portal where enrolled learners can see their course progress, completion percentages, and continue where they left off. V0 generates a dashboard showing each enrolled course as a card with a progress bar, completion status, and a 'Continue Learning' button linking back to the course.

V0 Prompt

Create a student dashboard that fetches enrollments from /api/thinkific/enrollments. Each enrollment has course_name, percentage_completed (0-100), completed (boolean), activated_at (ISO date), and course_url. Display enrollments as cards sorted by activated_at descending. Each card shows the course name, a progress bar filled to percentage_completed, percentage as text (e.g., '67% complete'), the enrolled date, and a button: 'Continue Learning' if not completed, 'Review Course' if completed (green checkmark). Show 'No courses enrolled yet' if the array is empty.

Copy this prompt to try it in V0

Course Landing Page with Enrollment Button

A course creator wants individual landing pages for each course that display the full course description, curriculum outline, instructor bio, and enrollment button — all styled to match their brand instead of Thinkific's default page. V0 generates a dynamic course detail page that pulls course data from the Thinkific API.

V0 Prompt

Build a course detail page that fetches a single course from /api/thinkific/courses/{slug}. The response has name, description (HTML string, render as rich text), price, card_image_url, instructor.name, instructor.bio, and chapters array (each with name and lessons_count). Show the course image as a hero banner, course name as a large heading, price badge, full description, instructor section with bio, and a curriculum list of chapter names with lesson counts. Include a prominent 'Start Learning — $X' button at the top right.

Copy this prompt to try it in V0

Troubleshooting

API returns 401 Unauthorized even with correct API key

Cause: Thinkific requires both the Authorization: Bearer {key} header AND the X-Auth-Subdomain: {subdomain} header. Omitting either header causes a 401 error, even if the API key is valid.

Solution: Ensure both headers are present in every API call. Verify the subdomain is just the prefix without '.thinkific.com' (e.g., 'myschool' not 'myschool.thinkific.com'). Confirm your Thinkific plan supports API access — Basic and Start plans may not have API access.

typescript
1headers: {
2 Authorization: `Bearer ${process.env.THINKIFIC_API_KEY}`,
3 'X-Auth-Subdomain': process.env.THINKIFIC_SUBDOMAIN!,
4 'Content-Type': 'application/json',
5}

Course descriptions render as raw HTML tags in the browser

Cause: Thinkific stores course descriptions as HTML, not plain text. Rendering them directly in a React component outputs the HTML as escaped text rather than formatted content.

Solution: Use dangerouslySetInnerHTML={{ __html: course.description }} in your React component to render HTML content. If user-submitted content is involved, sanitize the HTML with DOMPurify (client-side) or a server-side sanitizer before rendering to prevent XSS attacks.

typescript
1// Render Thinkific HTML descriptions safely
2<div
3 className="prose max-w-none"
4 dangerouslySetInnerHTML={{ __html: course.description }}
5/>

API route returns courses but images do not display

Cause: Thinkific course images are hosted on CDN URLs that may require specific image domains to be whitelisted in Next.js's image configuration. If you are using next/image, the Thinkific CDN domain must be added to next.config.js.

Solution: Add the Thinkific CDN domain to your next.config.js images.remotePatterns array. Thinkific hosts images on cdn.thinkific.com and assets.thinkific.com. Alternatively, use a standard img HTML tag with the src attribute if you do not need Next.js image optimization.

typescript
1// next.config.js
2module.exports = {
3 images: {
4 remotePatterns: [
5 {
6 protocol: 'https',
7 hostname: 'cdn.thinkific.com',
8 },
9 {
10 protocol: 'https',
11 hostname: 'assets.thinkific.com',
12 },
13 ],
14 },
15};

Best practices

  • Cache course catalog responses for at least 5 minutes (next: { revalidate: 300 }) since course data changes infrequently — this prevents unnecessary API calls on every page load.
  • Always include the X-Auth-Subdomain header in every Thinkific API request; forgetting it causes authentication errors that look identical to invalid API key errors.
  • Filter for published=true courses in your API route before returning results — draft and archived courses should never appear in a public storefront.
  • Include the enrollment URL in your API response rather than constructing it in the frontend, keeping your school subdomain server-side and making the component simpler.
  • Handle missing course images gracefully with a branded placeholder — not all Thinkific courses have a card image set, and a broken image element damages the storefront appearance.
  • Use Thinkific's built-in checkout for payments rather than building your own — Thinkific handles payment processing, tax, and refunds. Your V0 storefront should link to Thinkific's checkout, not replace it.
  • When displaying enrollment progress, account for the difference between percentage_completed (lesson views) and the completed flag (formal course completion with certificate eligibility).

Alternatives

Frequently asked questions

Does Thinkific's API require a paid plan?

Yes, Thinkific's REST API is available on Growth and higher plans. The Basic and Start plans do not include API access. If you need API access for a custom storefront, verify your plan supports it before building the integration. Plan pricing and feature details are available at thinkific.com/pricing.

Can I create enrollments via the Thinkific API?

Yes. The Thinkific API supports creating free enrollments via POST /api/public/v1/enrollments with a user_id or user_email and course_id. For paid courses, Thinkific's checkout handles payment, and the enrollment is created automatically after successful payment. You cannot process payments via the API directly — always redirect to Thinkific's checkout for paid courses.

How do I display a single course detail page?

Create a dynamic API route at app/api/thinkific/courses/[slug]/route.ts that fetches a single course using the slug from the URL. The Thinkific endpoint for a single course is GET /api/public/v1/courses/{id} — you may need to first search by slug using GET /api/public/v1/courses?query[slug]={slug} to find the course ID, then fetch the full details.

Can V0 generate a fully white-labeled course player?

You can display course metadata, syllabi, and progress with V0, but the actual video player and lesson content are served by Thinkific's platform. Thinkific does not expose a direct video streaming API — enrolled students must watch lessons within the Thinkific interface or the Thinkific mobile app. V0 is best used for the storefront, catalog, and progress dashboard rather than a custom lesson player.

How do I handle Thinkific webhook events in my V0 app?

Thinkific supports webhooks for events like enrollment.created, course.completed, and payment.received. Create a POST handler at app/api/thinkific/webhook/route.ts on your Vercel deployment. In Thinkific admin settings, add your webhook URL pointing to your deployed app. Your webhook handler can update a database, send a notification, or trigger other actions when students enroll or complete courses.

Can I use the Thinkific API to issue course completion certificates?

Thinkific handles certificate generation natively when a course is marked complete. The API allows you to check enrollment.completed status and provides the certificate URL when available. You cannot generate custom certificates via the API, but you can display or link to Thinkific's generated certificates from your custom student dashboard.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.