Skip to main content
RapidDev - Software Development Agency
bubble-integrationsBubble API Connector

Teachable

Connect Bubble to Teachable by adding the free API Connector plugin and configuring a shared header with key `apiKey` — NOT `Authorization: Bearer` — marked Private. This single header name distinction is the most common cause of 401 errors when Bubble developers first connect to Teachable. API access requires a Pro plan ($59/mo) or above. Use Bubble to build a branded course storefront and enrollment analytics dashboard while Teachable handles all payments and content delivery.

What you'll learn

  • Why Teachable uses `apiKey` as the header key name instead of `Authorization: Bearer` and why this matters in Bubble
  • How to generate a Teachable API key and which plan tier unlocks API access
  • How to configure the Bubble API Connector with the correct `apiKey` Private header
  • How to filter courses by `is_published` and paginate results with `page` and `perPage` parameters
  • How to build a branded course catalog Repeating Group that links to Teachable's hosted checkout
  • How to retrieve enrollment and completion data per course using a second API Connector call
  • How to apply Bubble privacy rules to any stored student or transaction data
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner23 min read1–3 hoursEducationLast updated July 2026RapidDev Engineering Team
TL;DR

Connect Bubble to Teachable by adding the free API Connector plugin and configuring a shared header with key `apiKey` — NOT `Authorization: Bearer` — marked Private. This single header name distinction is the most common cause of 401 errors when Bubble developers first connect to Teachable. API access requires a Pro plan ($59/mo) or above. Use Bubble to build a branded course storefront and enrollment analytics dashboard while Teachable handles all payments and content delivery.

Quick facts about this guide
FactValue
ToolTeachable
CategoryEducation
MethodBubble API Connector
DifficultyBeginner
Time required1–3 hours
Last updatedJuly 2026

Bubble + Teachable: a branded course storefront with enrollment analytics

Teachable's core value for course creators is that it handles everything Bubble should not: video hosting, content delivery, payment processing, student authentication, completion tracking, and certificate generation. What Teachable cannot do is give you a fully branded storefront that matches your personal brand's visual identity, or aggregate enrollment and revenue data across your entire course library in a single analytics view.

This is the natural division of labor for the Bubble + Teachable integration. Teachable is the backend — the source of truth for your courses, students, and transactions. Bubble is the frontend — your custom-branded storefront, your aggregated ops dashboard, and your student-facing experience. Visitors browse your courses in Bubble, see instructor-curated descriptions and pricing, and click a 'Enroll Now' button that sends them to Teachable's hosted checkout. Teachable processes the payment, handles confirmation emails, and grants course access. Bubble never sees the payment information.

The integration's technical footprint is minimal. Teachable's API uses a single API key, one non-standard header name, and straightforward REST endpoints. There is no OAuth flow, no token expiration, no webhook complexity for the storefront use case. The entire setup fits in a single API Connector group with three to five API calls: one for listing courses, one for course detail, one for enrollment data per course, and optionally one for transaction history.

The one header name distinction that causes more failed integrations than any other single factor: Teachable's API key goes in a header named `apiKey` — not `Authorization`, not `Authorization: Bearer`, not `X-API-Key`. Just `apiKey`. In Bubble's API Connector shared headers, the key field must contain exactly `apiKey` with that capitalization. Using `Authorization: Bearer` with a valid Teachable API key still produces 401 errors every time.

For ops teams that want more than a storefront — enrollment counts per course, aggregate revenue, completion rates across the school — the same API Connector setup adds enrollment and transaction calls. Because Teachable's API paginates at 50 records per page, schools with many courses need explicit pagination handling using the `meta.total_count` field in the response to know how many additional pages to fetch. Storing this synced data in a Bubble Data Type with a scheduled Backend Workflow gives you a fast, always-available analytics view that does not depend on live Teachable API calls on every page load.

Integration method

Bubble API Connector

The Bubble API Connector (free plugin by Bubble) calls the Teachable REST API at `https://developers.teachable.com/v1` with a Private `apiKey` header — note the header key name is `apiKey`, NOT `Authorization: Bearer`.

Prerequisites

  • A Teachable school on the Pro plan ($59/mo or equivalent current pricing) or above — Free and Basic plan schools have no API section in Teachable Settings
  • Your Teachable API key from Teachable Settings → API (requires school owner account — co-owners may also have access depending on plan)
  • A Bubble app (any plan for read-only course display; paid Bubble plan required for Backend Workflows and scheduled data syncs)
  • The free API Connector plugin by Bubble installed (Plugins → Add plugins → search 'API Connector')
  • At least one published course in your Teachable school so the Initialize call returns a real response with detectable field names

Step-by-step guide

1

Step 1 — Generate your Teachable API key

Log in to your Teachable school as the school owner. In the left sidebar, click 'Settings' → in the Settings navigation, look for 'API.' If the API section does not appear, your Teachable school is on the Free or Basic plan — API access is restricted to Pro plan and above. On the API settings page, you will see an option to generate or view your API key. Click 'Generate API key' (or reveal the existing key if one was previously created). Copy the key immediately and save it in a password manager. Teachable generates one API key per school. This single key is used for all API integrations — there are no scoped keys and no way to create separate keys for different integrations. Before rotating or regenerating the key, check whether any other systems (zapier automations, other Bubble apps, custom code) use the same key, as regenerating invalidates the old key for all of them simultaneously. The Teachable API key is a static credential — it does not expire unless manually regenerated. Treat it with the same care as a password. Also note the base URL for all Teachable API calls: `https://developers.teachable.com/v1`.

teachable-api-reference.txt
1// Teachable API reference
2// Base URL: https://developers.teachable.com/v1
3//
4// Authentication: custom header (NOT Bearer token)
5// Header Key: apiKey ← camelCase, exactly this spelling
6// Header Value: your_teachable_api_key
7//
8// Common mistake:
9// Key: Authorization ← WRONG — causes 401 errors
10// Value: Bearer your_key ← WRONG — wrong format for Teachable
11//
12// Key endpoints:
13// GET /courses — list all courses
14// GET /courses/{id}/enrollments — enrollments per course
15// GET /courses/{id}/transactions — transactions per course
16// GET /users — student list
17//
18// Plan requirement: Pro plan or above for API access
19// One key per school — no scoped keys

Pro tip: If you are managing a client's Teachable school, confirm with the client which other tools (if any) are using the current API key before building the Bubble integration. A key rotation would break all other integrations simultaneously. If the client uses no other API integrations, document this fact so future team members know the Bubble app is the sole key consumer.

Expected result: You have your Teachable API key copied and stored securely. You have confirmed that your school is on the Pro plan or above. You have noted the API base URL `https://developers.teachable.com/v1` and the header key name `apiKey`.

2

Step 2 — Install the API Connector and configure the Teachable API group

Open your Bubble app editor and click 'Plugins' in the left sidebar. If the API Connector by Bubble is not already installed, click 'Add plugins,' search for 'API Connector,' and install it — it is free. Return to the Plugins tab, click 'API Connector,' and click 'Add another API.' Name this API group 'Teachable.' In the 'Root URL' field, enter `https://developers.teachable.com/v1`. Under 'Shared headers across all calls,' click 'Add a shared header.' In the key field, type exactly: `apiKey` — this is case-sensitive. In the value field, paste your Teachable API key. Check the 'Private' checkbox. This marks the `apiKey` header as server-side only — Bubble will use it when making API calls from its servers, and the key value will never appear in a browser's DevTools Network tab for any visitor to your app. Do NOT add an `Authorization` header or use `Bearer` format — Teachable's API does not use the Authorization header name for authentication. If you add `Authorization: Bearer {key}`, every call returns 401 even with a valid key. Add a second shared header: key `Accept`, value `application/json` — no Private checkbox needed. This completes the API group setup.

api-connector-teachable-group.json
1// Bubble API Connector — Teachable group configuration
2{
3 "api_group_name": "Teachable",
4 "root_url": "https://developers.teachable.com/v1",
5 "shared_headers": [
6 {
7 "key": "apiKey",
8 "value": "your_teachable_api_key_here",
9 "private": true,
10 "note": "camelCase 'apiKey' — NOT 'Authorization'"
11 },
12 {
13 "key": "Accept",
14 "value": "application/json"
15 }
16 ]
17}
18
19// MOST COMMON ERROR — wrong header setup:
20// key: 'Authorization'
21// value: 'Bearer your_key'
22// Result: 401 Unauthorized on every call
23//
24// Teachable API does NOT use Authorization: Bearer format
25// The correct header key is ONLY: apiKey

Pro tip: The 'Private' checkbox on the `apiKey` header is what makes this integration secure. Without it, Bubble includes the header in client-side API calls and it becomes visible in the browser's DevTools Network tab. With it, Bubble makes the API call from its own servers and only the response data reaches the browser — the API key stays hidden from any visitor who inspects network traffic.

Expected result: The Teachable API group appears in your API Connector with root URL `https://developers.teachable.com/v1` and two shared headers: `apiKey` (Private) and `Accept`. The header key is spelled exactly `apiKey` with that capitalization.

3

Step 3 — Add the 'Get Courses' API call and initialize it

Inside the Teachable API group, click 'Add call.' Name it 'Get Courses.' Set the method to GET. Endpoint: `/courses`. Set 'Use as' to 'Data' — this tells Bubble the response should be mappable to a list data type for binding to Repeating Groups. Add URL parameters: click 'Add parameter.' Add `is_published` with value `true` — this filters out draft courses so only published courses appear in your storefront. Add `page` with value `1` and `per` with value `50` (50 is Teachable's maximum per page). The `per` parameter controls how many courses Teachable returns; omitting it uses Teachable's default, which may return fewer courses than your school has. Now click 'Initialize call.' Bubble fires a real GET request to Teachable using your Private `apiKey` header. If your key is valid and your school has at least one published course, Teachable returns a JSON response with a `courses` array and a `meta` object. Bubble detects the response fields: `id`, `name`, `heading`, `image_url`, `is_published`, and the `meta` object containing `total_count`, `page`, and `per`. Click 'Save.' The `meta.total_count` field tells you the total number of courses matching the filter — if it is greater than 50, you will need pagination to fetch additional pages. The total count divided by 50 (rounded up) tells you how many pages exist.

teachable-get-courses-response.json
1// GET /courses — Teachable API response structure
2{
3 "courses": [
4 {
5 "id": 12345,
6 "name": "Advanced Photography",
7 "heading": "Master light, composition, and editing",
8 "image_url": "https://cdn.teachable.com/images/...",
9 "is_published": true,
10 "price": 4900,
11 "sale_price": null
12 },
13 {
14 "id": 12346,
15 "name": "Brand Photography Masterclass",
16 "heading": "Build your photography business",
17 "image_url": "https://cdn.teachable.com/images/...",
18 "is_published": true,
19 "price": 9700,
20 "sale_price": 7500
21 }
22 ],
23 "meta": {
24 "total_count": 12,
25 "page": 1,
26 "per": 50
27 }
28}
29
30// IMPORTANT: price is in CENTS
31// $49.00 course → price = 4900
32// Display: Current cell's course's price / 100 :formatted as $#,##0.00
33//
34// Pagination: if meta.total_count > 50, request page 2 with page=2

Pro tip: If 'Initialize call' returns an error or shows no courses, confirm that your Teachable school has at least one published course. Unpublished draft courses are excluded when `is_published=true` is set. Temporarily remove the `is_published` parameter during initialization if all your courses are drafts, then re-add it after initialization detects the field schema.

Expected result: The 'Get Courses' call is initialized with a green checkmark. Bubble shows detected fields including course `id`, `name`, `heading`, `image_url`, `is_published`, `price`, and `meta.total_count`. The call is set to 'Use as: Data.'

4

Step 4 — Build the branded course catalog Repeating Group

With the Get Courses call initialized, build the public-facing course catalog page. In your Bubble page editor, add a Repeating Group element. Set 'Type of content' to 'Teachable - Get Courses - course' (the name Bubble generates from your call name). Set 'Data source' to 'Get data from an external API → Teachable → Get Courses.' In the parameters, set `is_published = true`, `per = 50`, `page = 1`. Inside the Repeating Group's first cell, add the course card components: an Image element bound to `Current cell's course's image_url`; a Text element for `Current cell's course's name` (styled as a heading); a Text element for `Current cell's course's heading` (subtitle description); a Text element for price using the expression `Current cell's course's price / 100 :formatted as $#,##0.00` — this converts the cents integer to a dollar display. If Teachable provides a `sale_price` field, add a conditional: when `Current cell's course's sale_price is not empty`, show the sale price in a highlighted color and the original price with strikethrough styling. Add a Button labeled 'Enroll Now.' This button should NOT submit any form or call any Bubble workflow related to payments. Its action should be: 'Open an external website' → link to Teachable's school URL for that specific course (the URL is typically `https://yourschool.teachable.com/p/course-slug` — note that the API response `id` is a numeric ID, not the slug, so you may need to construct the URL or include the `url` field in the API response if Teachable returns it). Important: Bubble must never handle Teachable payment processing. The 'Enroll Now' button opens Teachable's native checkout — that is where payment, confirmation, and course access all happen.

teachable-storefront-rg.txt
1// Bubble Repeating Group — Teachable course catalog
2// Type of content: Teachable - Get Courses - course
3// Data source: Get data from external API → Teachable → Get Courses
4// Parameters: is_published=true, per=50, page=1
5
6// Inside RG first cell:
7// Image element:
8// Source = Current cell's course's image_url
9//
10// Text (course name):
11// Current cell's course's name
12//
13// Text (heading/subtitle):
14// Current cell's course's heading
15//
16// Text (price):
17// Current cell's course's price / 100 :formatted as $#,##0.00
18//
19// Sale price (conditional — show when sale_price is not empty):
20// Current cell's course's sale_price / 100 :formatted as $#,##0.00
21// Style: highlighted, original price with strikethrough
22//
23// 'Enroll Now' button:
24// Action: Open an external website
25// URL: Teachable checkout URL for this course
26// (Never handle payment in Bubble — always redirect to Teachable)
27//
28// Pagination — if more than 50 courses:
29// Add 'Load More' button that increments page parameter
30// Check meta.total_count > 50 to determine if button should show

Pro tip: To build a search or filter on the course catalog page, add a text input and a category dropdown. Pass the input's value as a `name` or `search` query parameter to the Get Courses call (check Teachable's current API docs for supported filter parameters). For client-side filtering without a second API call, use Bubble's ':filtered' condition on the Repeating Group data source — this fetches all courses and filters locally.

Expected result: The Repeating Group on your page displays all published Teachable courses. Each card shows the course image, name, heading, and correct price in dollars (not cents). The 'Enroll Now' button opens Teachable's hosted checkout in a new tab. Sale prices display correctly when present.

5

Step 5 — Add enrollment and completion data API calls

To build an ops dashboard showing enrollment counts and completion rates per course, add a second API call in the Teachable group. Click 'Add call' → name it 'Get Course Enrollments.' Method: GET. Endpoint: `/courses/<id>/enrollments` — add a URL parameter named `id` (type: dynamic). Add query parameters: `perPage` with value `100` (Teachable's pagination for enrollments), `page` with value `1`. Set 'Use as' to 'Data.' Click 'Initialize call' — you will need to provide a real course `id` value during initialization (use a numeric ID from one of your courses, visible in your Teachable dashboard URL or from a Get Courses call result). The response returns an array of enrollment objects with fields including `percentage_completed` (a decimal number from 0 to 100, e.g., `67.5` means 67.5% complete), `lectureable_completions_count`, and student information. Store this in a Bubble Data Type: create a type named `TeachableEnrollment` with fields `course_id` (number), `course_name` (text), `student_email` (text), `percentage_completed` (number), `enrolled_at` (date). Build a Backend Workflow named 'Sync Teachable Enrollments' (requires paid Bubble plan for Backend Workflows). Inside, for each course fetched by Get Courses, call Get Course Enrollments with the course's `id` as the parameter, then create TeachableEnrollment records for each returned enrollment. Schedule this to run nightly. Your ops dashboard queries from Bubble's database for fast performance.

teachable-enrollments-call.json
1// GET /courses/{id}/enrollments — response structure
2{
3 "enrollments": [
4 {
5 "id": 98765,
6 "enrolled_at": "2026-05-15T09:22:00Z",
7 "percentage_completed": 67.5,
8 "lectureable_completions_count": 12,
9 "user": {
10 "id": 55555,
11 "email": "student@example.com",
12 "name": "Jane Smith"
13 }
14 }
15 ],
16 "meta": {
17 "total_count": 145,
18 "page": 1,
19 "per": 100
20 }
21}
22
23// Bubble Data Type: TeachableEnrollment
24// course_id (number) — Teachable course numeric ID
25// course_name (text)
26// student_email (text)
27// percentage_completed (number) — 0 to 100, e.g., 67.5
28// enrolled_at (date)
29//
30// Ops dashboard expressions:
31// Total enrollments: Search for TeachableEnrollments:count
32// Average completion: Search for TeachableEnrollments where course_id = X
33// :sum of percentage_completed / :count
34// Completed students: Search for TeachableEnrollments
35// :filtered where percentage_completed = 100
36// :count

Pro tip: Teachable's `percentage_completed` field uses a decimal from 0 to 100 (not 0 to 1). A student who has completed 67.5% of a course has `percentage_completed = 67.5`. Use this directly in display expressions without multiplying by 100 — unlike many other APIs that use a 0-to-1 decimal for percentages. Add a `%` suffix in your Bubble text element display.

Expected result: The 'Get Course Enrollments' API call initializes successfully with a course ID provided during setup. Bubble detects enrollment fields including `percentage_completed`, `enrolled_at`, and student `email`. A scheduled Backend Workflow syncs enrollment data nightly into the TeachableEnrollment Data Type.

6

Step 6 — Apply privacy rules to student and transaction data

Student enrollment records, email addresses, completion rates, and transaction amounts are sensitive personal and financial data. Storing any of this in Bubble's database without privacy rules creates a security risk: Bubble's default behavior allows any logged-in user of your app to query data types via Bubble's native API. If your Bubble app has any public-facing signup, this could expose student data to non-admin users. Go to Bubble's Data tab → Privacy. For the 'TeachableEnrollment' Data Type (and any other type storing student or transaction data): click 'Define rules' → 'Add a rule.' Set the condition: 'Current User's is_admin = yes' (add an `is_admin` yes/no field to your Bubble User data type if it does not exist, with a default of `no`). Under 'When this rule applies,' check both 'Find this in searches' and 'See these fields → All fields.' Under 'When this rule does NOT apply,' ensure both are unchecked — this blocks all access for non-admin users. Set `is_admin = yes` only for your own account by going to Data tab → App data → User records → find your user → edit → set `is_admin = yes`. Apply the same privacy rule to any transaction data types. For compliance note: if your school has students from the EU, GDPR obligations apply to storing student email addresses in Bubble. Consider storing only aggregated metrics (completion rate, enrollment count) rather than individual student PII if full GDPR compliance is a concern. If RapidDev can help you design the appropriate data minimization approach for your compliance requirements, reach out for a free scoping call at rapidevelopers.com/contact.

teachable-privacy-rules.txt
1// Bubble Privacy Rules — TeachableEnrollment Data Type
2// Data tab → Privacy → TeachableEnrollment
3//
4// Rule: 'Admin Only'
5// Condition: Current User's is_admin = yes
6//
7// When rule applies:
8// [x] Find this in searches
9// [x] See these fields → All Fields
10//
11// When rule does NOT apply:
12// [ ] Find this in searches (blocked)
13// [ ] See any fields (blocked)
14//
15// Bubble User type — add field:
16// is_admin (yes/no, default: no)
17//
18// Set is_admin = yes for your account:
19// Data tab → App data → User records → find your email → edit → is_admin = yes
20//
21// Also apply to:
22// TeachableTransaction (if you create this type for revenue data)
23// Any type storing student email addresses
24//
25// Note: transaction amounts in Teachable API are in cents
26// GET /courses/{id}/transactions → amount field in cents → divide by 100

Pro tip: Bubble's privacy rules protect client-side database queries. Your scheduled Backend Workflow (the nightly sync) operates server-side and is not restricted by privacy rules — it can write all enrollment data regardless of user role. This is the intended behavior: the server-side sync needs unrestricted write access, while client-side user queries are restricted to authorized admin users.

Expected result: The TeachableEnrollment Data Type has a privacy rule limiting access to admin users only. Non-admin logged-in users and anonymous visitors cannot query or see student enrollment or completion data. Your own admin account can access all enrollment data for the ops dashboard.

Common use cases

Branded course catalog storefront

A course creator wants a custom website that matches their personal brand — fonts, colors, layout — rather than Teachable's default school page. Bubble fetches all published courses from Teachable's API and displays them in a card-based grid with the creator's branding. Each card shows the course name, heading, image, and price (in dollars, converted from cents). An 'Enroll Now' button links to Teachable's hosted checkout URL for that specific course. The creator manages all course content and pricing in Teachable; Bubble automatically reflects any changes on the next API fetch.

Bubble Prompt

On page load, call Teachable API 'Get Courses' with is_published=true and perPage=50; display in Repeating Group sorted by name; show price as Current cell's course's price / 100 formatted as $#,##0.00; 'Enroll Now' button opens Teachable checkout URL in new tab

Copy this prompt to try it in Bubble

Enrollment analytics operations dashboard

A course creator running multiple courses wants a private Bubble page showing enrollment counts, completion rates, and revenue per course — data that Teachable's reporting shows only one course at a time. A Backend Workflow runs nightly, calls Teachable's enrollment and transaction endpoints for each course, and stores aggregated data in Bubble Data Types. The ops dashboard page queries from Bubble's database and displays total enrollments, percentage_completed averages, and monthly revenue across all courses in one view, with sorting by enrollment count or revenue.

Bubble Prompt

Schedule nightly Backend Workflow: for each TeachableCourse, call 'Get Enrollments' and store average percentage_completed and total count; on Ops Dashboard, display Search for TeachableCourses sorted by enrollment_count descending; headline: sum of all enrollment_counts and sum of revenue_cents / 100

Copy this prompt to try it in Bubble

Student completion tracking for corporate training

A company uses Teachable to deliver internal training courses and needs a manager dashboard showing which employees have completed required courses. Bubble calls Teachable's enrollment endpoint per course, stores completion data in a Bubble `EmployeeTrainingRecord` Data Type, and displays a manager view filtered by department. Managers see each employee's `percentage_completed` for each required course, with a visual indicator for completion (100%) versus in-progress. Employees who have not started a required course appear in an alert list.

Bubble Prompt

On Manager Dashboard, search EmployeeTrainingRecords where department = Current User's department and percentage_completed < 100; display in Repeating Group; highlight rows where percentage_completed = 0 as 'Not Started'; show completion rate header: count of completed / total count as percentage

Copy this prompt to try it in Bubble

Troubleshooting

Every Teachable API call returns 401 Unauthorized even though the API key is copied correctly

Cause: The API Connector shared header is using `Authorization` as the key name (or `Authorization: Bearer`) instead of `apiKey`. Teachable's API does not use the standard Authorization header — it requires a custom `apiKey` header. A valid API key in the wrong header name produces 401 on every call.

Solution: In Bubble's Plugins tab → API Connector → Teachable group, click the shared headers section. Check the header key name exactly. It must be `apiKey` — lowercase 'api', uppercase 'K', no spaces, no prefix. Delete any `Authorization` header if present. Add a new header with key = `apiKey`, value = your Teachable API key, Private checkbox checked. Click Initialize call again on any API call to verify the fix. The 401 error should resolve immediately once the header key is corrected.

typescript
1// WRONG — causes 401 on Teachable:
2// Key: Authorization
3// Value: Bearer your_teachable_api_key
4//
5// CORRECT — works with Teachable:
6// Key: apiKey
7// Value: your_teachable_api_key
8// Private: checked
9//
10// Note: no 'Bearer' prefix, no 'Authorization' key name
11// The Teachable API key is passed as-is as the header value

No 'API' section appears in Teachable Settings — cannot find where to generate the API key

Cause: The Teachable school is on the Free or Basic plan. API access is gated to Pro plan and above in Teachable's pricing. The API settings section does not appear in the left navigation for lower-tier plans.

Solution: Log in to Teachable and go to Settings → Billing to check your current plan. If on Free or Basic, you must upgrade to Pro to access the API. If you are managing a client's Teachable school, verify their plan tier before beginning any API integration work. If upgrading is not immediately possible, note that Teachable's affiliate or partner programs may provide alternative data access paths — check Teachable's current offerings.

The course catalog Repeating Group shows fewer courses than exist in the Teachable school

Cause: Teachable's API returns a maximum of 50 courses per page when `per=50`. Schools with more than 50 courses require pagination — subsequent pages must be fetched with `page=2`, `page=3`, etc. Omitting pagination silently truncates results.

Solution: After the first API call, check the `meta.total_count` field from the response. If it is greater than 50, add pagination. For a simple implementation, add a 'Load More' button that triggers a second API call with `page=2` and appends results to a Bubble Data Type. For automatic full sync, use a Backend Workflow that loops from page 1 until no more results exist (when the page size response is less than `per`). The `meta.total_count` divided by 50 (rounded up) gives you the number of pages to fetch.

typescript
1// Check if more pages exist:
2// meta.total_count > (meta.page * meta.per)
3// → true: there are more courses to fetch
4// → false: all courses retrieved
5//
6// Example: total_count=120, page=1, per=50
7// 120 > (1 × 50) → true → fetch page 2
8// After page 2: 120 > (2 × 50) → true → fetch page 3
9// After page 3: 120 > (3 × 50) = 120 > 150 → false → done

Course prices display as large numbers like '9700' instead of '$97.00'

Cause: Teachable returns the `price` field as an integer in cents. The raw value `9700` represents $97.00. Displaying the field value without dividing by 100 shows the cents amount as if it were dollars.

Solution: In every Bubble expression that displays a Teachable price, add division by 100: `Current cell's course's price / 100 :formatted as $#,##0.00`. In Bubble's expression editor, after selecting the `price` field, use the arithmetic operator to divide by 100, then apply currency formatting. Apply this fix to every price field in your storefront — both regular price and sale_price fields. This is the most common display error in Teachable integrations.

typescript
1// Correct price display in Bubble:
2// Current cell's Teachable - Get Courses - course's price / 100
3// :formatted as $#,##0.00
4//
5// Examples:
6// price = 4900 → 4900 / 100 = 49.00 → displays as $49.00
7// price = 9700 → 9700 / 100 = 97.00 → displays as $97.00
8// sale_price = 7500 → 7500 / 100 = 75.00 → displays as $75.00

'Initialize call' in Bubble shows no fields or returns 'There was an issue setting up your call' for the courses endpoint

Cause: Either the `apiKey` header is missing or incorrect (causing a 401 before Bubble captures the response), or your Teachable school has no published courses and returns an empty array (Bubble cannot detect field names from empty arrays).

Solution: First, verify the header: in API Connector → Teachable group → shared headers, confirm `apiKey` is the header key and your API key value is correctly pasted with no extra spaces. Remove any `is_published=true` parameter temporarily during initialization — if all your courses are unpublished drafts, this parameter filters them all out and returns an empty response. After initializing without the filter (so Bubble can detect field names from a draft course), re-add `is_published=true` for production use.

Best practices

  • Use exactly `apiKey` as the Bubble API Connector header key name for Teachable — not `Authorization`, not `X-API-Key`, not `Bearer`. This single distinction prevents the most common 401 authentication error in Teachable integrations
  • Mark the `apiKey` header as 'Private' in Bubble's API Connector so the key is used only on Bubble's servers and never appears in a visitor's browser DevTools network tab
  • Divide all Teachable `price` and `sale_price` fields by 100 before displaying — Teachable returns monetary amounts as integers in cents, and the raw values produce prices 100× larger than actual amounts
  • Never process Teachable payments in Bubble — link all purchase buttons to Teachable's hosted checkout URL (`product.url` or your school's course-specific checkout URL). Teachable handles payment, confirmation, and course access; Bubble handles discovery and display only
  • Document which systems use the Teachable API key before regenerating it — Teachable provides only one key per school, and regenerating it breaks all integrations using the old key simultaneously
  • Add `per=50` and `page=1` parameters explicitly to the Get Courses call, and check `meta.total_count` to determine whether pagination is needed for schools with more than 50 courses — missing pagination silently truncates your course catalog
  • Cache enrollment and analytics data in Bubble Data Types via a nightly Backend Workflow rather than querying Teachable's API per page load — Bubble's native database is faster and does not consume WU for reads in the same way API calls do
  • Apply Bubble Data tab Privacy rules to TeachableEnrollment and any other Data Types storing student email addresses, completion rates, or transaction data — restrict access to admin users only to protect student privacy

Alternatives

Frequently asked questions

Why does Teachable use `apiKey` instead of the standard `Authorization: Bearer` header?

Teachable's API was designed before `Authorization: Bearer` became the dominant convention for API key authentication. The `apiKey` custom header is a legacy design choice that Teachable has maintained for backward compatibility. While it functions identically to a Bearer token in terms of authentication security (both are secrets in HTTP headers), the non-standard header name is a common trip-up for developers familiar with modern REST API conventions. Teachable's current API documentation confirms `apiKey` as the correct header key — always verify against their latest docs.

Can I use this Bubble integration to also enroll students programmatically through the API?

Check Teachable's current API documentation for enrollment write operations — Teachable's API has evolved over time and write capabilities (POST /enrollments) may or may not be available on your plan tier. If enrollment write endpoints are available, configure a POST call in Bubble's API Connector and use it from a Backend Workflow. However, for most use cases, student enrollment should happen through Teachable's hosted checkout (via the 'Enroll Now' button linking to Teachable's checkout URL) rather than through a Bubble API call, since Teachable's checkout handles payment processing, receipt emails, and course access grants atomically.

Does Teachable have webhooks I can receive in Bubble for real-time enrollment notifications?

Yes, Teachable supports webhooks for events like enrollment creation, course completion, and payment success. To receive these in Bubble, go to Settings → API → enable 'This app exposes a Workflow API' in Bubble (paid plan required), then create a Backend Workflow with 'Detect request data' enabled. Use the Backend Workflow endpoint URL (without the `/initialize` suffix for production) as the webhook destination in Teachable's Settings → Webhooks. Teachable sends a POST payload to Bubble on each event, and Bubble's Backend Workflow processes it — for example, creating a notification or updating a student record in Bubble's database.

How does the `percentage_completed` field work — is it 0 to 1 or 0 to 100?

Teachable's `percentage_completed` is a decimal from 0 to 100 — not 0 to 1. A value of `67.5` means 67.5% complete. A value of `100` means fully completed. You can display this directly as a percentage by adding a `%` suffix in your Bubble text element without any multiplication. This differs from some other platforms that use a 0-to-1 decimal for percentages and require multiplication by 100 before display.

Can I use the Teachable API on Bubble's free plan?

Yes, for the course catalog storefront. Displaying Teachable courses in a Repeating Group using client-side API Connector calls works on Bubble's free plan — no Backend Workflow is needed for read-only display. The free plan limitations apply to: Backend Workflows (needed for nightly enrollment syncs), scheduled recurring workflows (needed for automated data refresh), and actions that should run server-side for security reasons. A production ops dashboard with enrollment analytics requires a paid Bubble plan for the sync workflow infrastructure.

RapidDev

Talk to an Expert

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

Book a free consultation

Integrations are where projects stall

We wire Bubble integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

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.