Connect your Bubble app to Teamwork using the API Connector plugin with HTTP Basic Auth — your API token in the username field and the literal letter 'x' as the password. Every Teamwork endpoint requires a .json suffix in the path. The killer use case is building a client portal where clients see their project progress and time logs without needing a Teamwork seat.
| Fact | Value |
|---|---|
| Tool | Teamwork |
| Category | Productivity |
| Method | Bubble API Connector |
| Difficulty | Beginner |
| Time required | 1–2 hours |
| Last updated | July 2026 |
Build client portals on top of Teamwork — without extra Teamwork seats
Teamwork is built for agencies managing multiple client projects, but its native interface requires every stakeholder to have a Teamwork user seat. The most valuable Bubble integration is a custom client portal: your clients log in to your Bubble app, see only their own project's tasks, milestone dates, and billable time — all pulled from Teamwork in real time. They get transparency without cluttering your Teamwork workspace. Two quirks define the Teamwork API and catch almost every developer by surprise: first, authentication uses HTTP Basic Auth where the API token is the username and the literal string 'x' (not your account password, not blank — exactly the character x) is the password. Second, every API endpoint requires a '.json' suffix — missing it returns Teamwork's HTML login page with a 200 status code, which Bubble attempts to parse as JSON and fails silently. Get these two things right and the rest of the integration is straightforward.
Integration method
Calls Teamwork's REST API server-side using Bubble's API Connector plugin with HTTP Basic Auth credentials stored as Private fields.
Prerequisites
- A Bubble account on any plan (basic read access); paid Bubble plan required if you need Backend Workflows for multi-tenant dynamic API calls
- A Teamwork account on any paid plan (Free plan includes 2 projects; Starter and above for full API access across all projects)
- Your Teamwork API token (from your Teamwork profile settings) and your account subdomain (e.g., yourcompany.teamwork.com)
- The 'API Connector' plugin by Bubble installed in your app (Plugins tab → Add plugins → search 'API Connector')
Step-by-step guide
Find your Teamwork API token and account subdomain
Log in to your Teamwork account. Click your profile avatar in the top-right corner, then click 'Edit My Details'. In the profile settings page, look for the 'API' tab or 'API & Mobile' section (exact wording varies by Teamwork version). Click it to reveal your personal API token — a long alphanumeric string. Copy this token and keep it somewhere safe, as you'll paste it into Bubble shortly. Your account subdomain is the company-specific part of your Teamwork URL — for example, if your Teamwork URL is `https://acme.teamwork.com`, your subdomain is `acme` and your base URL will be `https://acme.teamwork.com`. Note this down — you'll need the full subdomain URL as the base URL in Bubble's API Connector. Teamwork API tokens are user-scoped, meaning they see only the projects and data that the user has permission to view. For a client portal that needs to show data from all your client projects, generate the token from an admin account with access to all projects.
Pro tip: Use an admin account's API token for your Bubble integration if you want to display data from multiple client projects. A token from a limited user account silently returns fewer projects, which can be confusing to debug.
Expected result: You have your Teamwork API token and your full Teamwork account URL (https://yourcompany.teamwork.com) ready to use.
Install the API Connector and configure Teamwork Basic Auth
In your Bubble editor, click the Plugins tab in the left sidebar. If you don't have the 'API Connector' plugin already, click 'Add plugins', search for 'API Connector' (by Bubble), and install it — it's free. After installation, click the API Connector in your Plugins tab. Click 'Add another API' and name it 'Teamwork'. In the base URL field, enter your full Teamwork account URL: `https://yourcompany.teamwork.com` (replace 'yourcompany' with your actual subdomain). Do not add a trailing slash. For authentication, select 'Basic Auth' from the authentication type dropdown. In the 'Username' field, paste your Teamwork API token. In the 'Password' field, type the single letter `x` — that is the literal character x, nothing else, not your Teamwork account password and not blank. Check 'Private' on both the username and password fields. This is the documented Teamwork authentication convention: the API ignores the password value but requires a non-empty string, and `x` is the established placeholder. Using your actual Teamwork password causes a 401 and using a blank password causes a 401 — only `x` works as documented.
1{2 "api_name": "Teamwork",3 "base_url": "https://yourcompany.teamwork.com",4 "auth": "Basic Auth",5 "username": "<your_teamwork_api_token>",6 "password": "x",7 "private": true8}Pro tip: The password is literally the letter x — single character, lowercase. This is documented by Teamwork as their Basic Auth convention. It is not a placeholder for something else.
Expected result: The 'Teamwork' API entry is configured in your API Connector with your subdomain as base URL and Basic Auth credentials (API token as username, 'x' as password) both marked Private.
Add the projects call and run Initialize
Within the Teamwork API entry in the API Connector, click 'Add a call'. Name this call 'Get Projects'. Set the method to GET. In the URL path field, type `/projects.json` — the path must start with a forward slash and end with `.json`. This is not optional: Teamwork's API requires the `.json` suffix on every endpoint. Without it, the request returns Teamwork's HTML login page with an HTTP 200 status, which Bubble attempts to parse as JSON and silently fails. Add a URL parameter: key = `status`, value = `active` to filter to only active projects. Add another: key = `pageSize`, value = `100`. Set 'Use as' to 'Data'. Click 'Initialize call'. Bubble sends a real GET request to `https://yourcompany.teamwork.com/projects.json?status=active&pageSize=100` using your Basic Auth credentials. If your token and subdomain are correct, you'll see a response with a `projects` array and a `STATUS: OK` field. Bubble auto-maps all response fields including the hyphenated ones like `last-changed-on` and `start-date` — do not rename these in the mapper, as Bubble handles them correctly when accessed via the response body.
1GET https://yourcompany.teamwork.com/projects.json?status=active&pageSize=1002Auth: Basic <token>:xPro tip: If Initialize fails with 'There was an issue setting up your call' and returns HTML content, you're missing the .json suffix. If it returns 401, recheck your API token — copy it fresh from your Teamwork profile API tab rather than retyping from memory.
Expected result: Initialize succeeds and Bubble shows a preview of your Teamwork project list with fields like id, name, start-date, and last-changed-on auto-mapped in the response body.
Build a project-listing Repeating Group and add child data calls
On your Bubble page, add a Repeating Group element. Set its Type of content to the data type returned by your 'Get Projects' call. Set the data source to 'Get data from an external API' → select your Teamwork API → select the 'Get Projects' call. Inside the Repeating Group, add Text elements to display project information: bind one to `Current cell's name`, one to `Current cell's start-date`, one to `Current cell's last-changed-on`. Note the hyphenated field names — Bubble's expression builder shows them exactly as returned from Teamwork, so you'll see `last-changed-on` in the field picker; use it as-is. Now add additional API Connector calls for tasks and milestones. For tasks, add a call named 'Get Tasks' with path `/tasklists/<task_list_id>/tasks.json` where `<task_list_id>` is a dynamic parameter. You'll first need a separate call to `GET /projects/<project_id>/tasklists.json` to retrieve the tasklist IDs for a given project — each project has one or more tasklists that contain tasks. For milestones, add `GET /projects/<project_id>/milestones.json`. Set both as dynamic calls with the project ID as an input parameter. For time entries, use `GET /projects/<project_id>/time_entries.json`. Each of these calls must also end in `.json`.
1{2 "calls": [3 {4 "name": "Get Projects",5 "method": "GET",6 "path": "/projects.json",7 "params": { "status": "active", "pageSize": "100" }8 },9 {10 "name": "Get Task Lists",11 "method": "GET",12 "path": "/projects/<project_id>/tasklists.json",13 "dynamic_params": ["project_id"]14 },15 {16 "name": "Get Tasks",17 "method": "GET",18 "path": "/tasklists/<tasklist_id>/tasks.json",19 "dynamic_params": ["tasklist_id"]20 },21 {22 "name": "Get Milestones",23 "method": "GET",24 "path": "/projects/<project_id>/milestones.json",25 "dynamic_params": ["project_id"]26 },27 {28 "name": "Get Time Entries",29 "method": "GET",30 "path": "/projects/<project_id>/time_entries.json",31 "dynamic_params": ["project_id"]32 }33 ]34}Pro tip: Bubble's 50-item Repeating Group cap means you'll see only the first 50 tasks for task-heavy projects. Add a 'Load more' button that passes a `page` or `startPage` parameter to the tasks call and appends results to a Custom State list.
Expected result: Your Bubble page shows a live list of your active Teamwork projects. Clicking a project loads its tasklists, tasks, milestones, and time entries via additional API calls.
Build the client portal with privacy rules and user-scoped project access
The real value of the Teamwork-Bubble integration is giving each client access to only their own project data. To build this, first create a Data Type in Bubble called 'Client Project Link' with two fields: 'User' (User type, linking to a Bubble user account) and 'Teamwork Project ID' (text field storing the project's numeric ID from Teamwork). When a client logs into your Bubble app, a page load workflow fetches their linked Teamwork Project ID from the database, then uses that ID to call the Teamwork API for project-specific data. Set Privacy rules on your Client Project Link data type: in the Data tab → Privacy → Client Project Link → add a rule 'This user is the current user' with view access only. This ensures clients can only ever load their own project ID, not another client's. For multi-client portals with many simultaneous users, each Teamwork API call in Bubble runs server-side via the API Connector — but since the base URL is the same Teamwork subdomain for all clients of the same agency, you don't need separate API Connector entries per client. The project ID parameter in each call is what scopes the data. If you serve multiple agencies each with their own Teamwork subdomain and credentials, that's a different multi-tenant scenario that requires a Backend Workflow approach with per-agency stored credentials (paid Bubble plan).
Pro tip: Test your privacy rules by creating two test user accounts in your Bubble app, linking them to different Teamwork project IDs, and verifying in preview mode that logging in as each user shows only their linked project data.
Expected result: Clients log in and see only their own Teamwork project data. The Privacy rules on Client Project Link prevent any user from accessing another client's project ID through Bubble's data layer.
Add task creation from a Bubble intake form
To complete the two-way integration, add a POST call to create Teamwork tasks from your Bubble app. In the API Connector, add a new call named 'Create Task'. Set the method to POST. Set the path to `/tasks.json`. Set 'Use as' to 'Action' (since this call performs a write operation, not a data read). Set the body type to 'JSON'. The POST body for Teamwork task creation is wrapped in a `todo-item` key: set the body to `{"todo-item": {"content": "<task_name>", "tasklist-id": "<tasklist_id>", "due-date": "<YYYYMMDD>", "assigned-to-user-ids": "<user_id>"}}`. Mark `<task_name>`, `<tasklist_id>`, `<due-date>`, and `<user_id>` as dynamic parameters. The date format for Teamwork's due-date field is YYYYMMDD with no hyphens — `20260715`, not `2026-07-15`. Using a hyphenated date format returns a 200 response but the due date is silently ignored. Run Initialize on this call with a sample task body pointing to a real tasklist ID in your Teamwork account. Once initialized, add a workflow in Bubble triggered by your intake form's submit button: run the 'Create Task' API call with the form fields mapped to the dynamic parameters. On success, show a confirmation message and optionally refresh the task list Repeating Group.
1POST https://yourcompany.teamwork.com/tasks.json2Auth: Basic <token>:x3Content-Type: application/json45{6 "todo-item": {7 "content": "<task_name>",8 "tasklist-id": "<tasklist_id>",9 "due-date": "<YYYYMMDD>",10 "assigned-to-user-ids": "<user_id>"11 }12}Pro tip: Teamwork task due dates must be formatted as YYYYMMDD with no separators. In Bubble, format a date field using the ':formatted as' operator with the custom format string YYYYMMDD before passing it to the API call.
Expected result: Submitting the intake form in your Bubble app creates a new task in Teamwork with the correct title, tasklist, due date, and assignee. The task appears in Teamwork immediately.
Common use cases
Client project portal with task and milestone visibility
Give clients a branded login portal built in Bubble where they can see their active tasks, upcoming milestones, and recent activity — all pulled from Teamwork — without requiring a Teamwork seat for each client.
Build a Bubble page that shows a client their Teamwork project details: project name, active task count, next milestone date, and a list of recently completed tasks. Each client's portal should show only their own project data based on which Teamwork project is linked to their Bubble user account.
Copy this prompt to try it in Bubble
Time tracking and billing dashboard for agency operations
Pull time entries from Teamwork into an internal Bubble dashboard that shows billable hours per project per month, compares actuals to estimates, and flags projects approaching their budget limit.
Create a Bubble dashboard that pulls Teamwork time entries for all active projects in the current month, groups them by project, and shows each project's total billable hours alongside the estimated hours. Highlight projects where logged hours exceed 80% of the estimate.
Copy this prompt to try it in Bubble
Project intake form that creates Teamwork tasks automatically
Build a Bubble intake form for new client requests that automatically creates a task in the correct Teamwork project and tasklist, eliminating manual copy-paste between your intake channel and Teamwork.
Add a Bubble workflow triggered when a client submits a support request form. It should call the Teamwork API to create a new task in the client's designated tasklist, set the title to the request subject, assign it to our project manager, and set a due date 3 business days from today.
Copy this prompt to try it in Bubble
Troubleshooting
API Connector Initialize returns HTML content instead of JSON
Cause: The endpoint path is missing the .json suffix. Teamwork returns its web interface HTML (with HTTP 200) for any API path that doesn't end in .json, which Bubble cannot parse as a valid API response.
Solution: Edit your API Connector call and ensure every path ends with .json — for example /projects.json, /tasks.json, /milestones.json. Save and re-run Initialize. The HTML response is the unmistakable sign of this specific error.
1GET https://yourcompany.teamwork.com/projects.jsonAPI calls return 401 Unauthorized
Cause: Either the API token is incorrect, the password field contains something other than the letter x, or the Basic Auth credentials were not marked Private and are being sent improperly.
Solution: Return to your Teamwork profile → Edit My Details → API tab and copy a fresh API token. In Bubble's API Connector, confirm the username field contains only the API token (no 'Bearer' prefix, no extra spaces) and the password field contains only the single character x. Ensure both are marked Private. Do not use your Teamwork account login password in the password field.
'There was an issue setting up your call' during Initialize
Cause: The Initialize step requires a real successful API response. This error means the call returned an error status or invalid JSON. Common causes: wrong subdomain in the base URL, missing .json suffix, or an empty project list (Initialize needs at least one item to map fields).
Solution: Confirm your base URL matches exactly your Teamwork subdomain (https://yourcompany.teamwork.com). Add the .json suffix to the call path. If your Teamwork account has zero active projects, temporarily change the status filter to 'all' to ensure the Initialize response contains at least one project.
Repeating Group shows no projects even though Initialize succeeded
Cause: The Teamwork projects response wraps the array in a root key: `{"projects": [...], "STATUS": "OK"}`. If the Repeating Group is bound to the full result rather than the `projects` key, Bubble finds no list to display.
Solution: In your Repeating Group data source, set the data to `[API result]'s projects`. The root-level result is an object with a projects array inside — bind to the array, not the object.
Task due date is not saving even though the Create Task call returns 200
Cause: Teamwork requires the due-date field in YYYYMMDD format with no hyphens or slashes. Passing a date as '2026-07-15' or '07/15/2026' results in a 200 response but the due date is silently ignored.
Solution: In Bubble, format date values for Teamwork using the :formatted as operator with the custom date format YYYYMMDD before passing them to the Create Task API call body.
Backend Workflows or multi-tenant dynamic subdomain features are unavailable
Cause: Backend Workflows (API Workflows) are only available on paid Bubble plans. The free plan does not include this feature, which is required for multi-tenant setups where different users have different Teamwork account subdomains.
Solution: Upgrade to a paid Bubble plan (Starter or higher) to access Backend Workflows. For multi-tenant scenarios, store each agency's subdomain and API token in an encrypted Bubble Data Type and use Backend Workflow logic to dynamically construct API calls per user.
Best practices
- Always mark the Teamwork API token and the 'x' password as Private in the API Connector — this keeps credentials on Bubble's servers and prevents them from appearing in browser network requests.
- Generate your Teamwork API token from an admin account if your Bubble integration needs to display data across all client projects. User-scoped tokens silently omit projects the token owner can't see, which is a subtle and hard-to-debug data limitation.
- Set Privacy rules on every Bubble Data Type that stores Teamwork project IDs or client associations. Bubble's default 'everyone can see' setting can expose which clients are linked to which projects — use privacy rules to scope data to the current user.
- Never forget the .json suffix on Teamwork API paths. Build a mental habit: every Teamwork API call path ends in .json. If a call suddenly stops working after editing, the missing suffix is the first thing to check.
- Use Teamwork's project ID (numeric, returned in the API response) as the reliable identifier for linking Bubble users to projects — not the project name, which can be changed by admins at any time.
- Minimize API calls in your client portal by fetching only the fields you display. For project listing, use the `fields` parameter if Teamwork supports field filtering for your version — this reduces response payload size and keeps WU consumption lower.
- Test your client portal with multiple user accounts before going live. Log in as each test client to confirm the privacy rules are correctly scoping project data and no client can see another's tasks or time entries.
- For high-frequency task creation (e.g., a public intake form that many users submit), consider batching requests or adding a queue to avoid hitting Teamwork's rate limits. Monitor your Bubble Logs tab for 429 errors as a sign the rate limit is being approached.
Alternatives
Asana uses modern Bearer token authentication with clean JSON field names and a GID-based object model, making it more straightforward to configure in Bubble than Teamwork's Basic Auth + .json suffix pattern. Asana is better for general-purpose work management and cross-functional team portfolios; Teamwork is purpose-built for agency client billing with time tracking and invoicing tightly coupled to project tasks.
Jira uses Basic Auth with an API token and email address rather than Teamwork's API token + 'x' pattern. Jira is better for software development teams using agile workflows, sprints, and bug tracking; Teamwork is better for client-services agencies that need time-billing and milestone tracking as first-class features.
Harvest is a dedicated time tracking and invoicing tool that overlaps with Teamwork's billing features but without the project and task management layer. If your agency already uses Teamwork for project management and only needs to surface time and invoice data in Bubble, a Harvest integration alongside Teamwork provides richer financial reporting.
Frequently asked questions
Why is the Teamwork API password literally the letter x?
Teamwork uses HTTP Basic Auth with the API token as the username. The Basic Auth standard requires a password value — Teamwork ignores whatever you put there, but the field must not be blank. Teamwork's documentation specifies using x as the placeholder password. It's a convention from Teamwork's API design, not a security feature. Using any other non-blank value also works, but x is the documented standard.
Can I connect multiple clients' separate Teamwork accounts to one Bubble app?
Yes, but it requires a paid Bubble plan. When each client uses the same Teamwork account (your agency's single account), one API Connector entry with your admin token handles everything, and project-level data scoping is done in your Bubble workflows. When different clients have separate Teamwork accounts with different subdomains, you need to store each client's subdomain and token in your Bubble database and build Backend Workflows that dynamically construct API calls using those stored credentials — this requires Bubble's paid Backend Workflows feature.
Does the Teamwork API token expire?
Teamwork personal API tokens do not expire automatically — they remain active until you regenerate them in your profile settings. However, if you're building an internal ops tool where multiple Bubble users will use the same token (your admin token), document where the token is stored and set a reminder to rotate it periodically as a security practice. If the developer who created the token leaves your team, regenerate it immediately from the admin account.
How do I show time entries billed to a specific client in Bubble?
Use the Teamwork API call GET /projects/{project_id}/time_entries.json to fetch all time entries for a project. Filter to billable entries by adding the URL parameter billable=true. Each time entry object includes hours, description, the user who logged it, and the date. Display these in a Bubble Repeating Group and sum the hours using Bubble's :sum operator on the Repeating Group to show a total billable hours figure.
What if I need to receive real-time Teamwork webhook notifications in Bubble?
Teamwork supports webhooks for events like task creation, milestone completion, and time entry logging. To receive these in Bubble, you need a Backend Workflow endpoint — go to Settings → API → enable 'This app exposes a Workflow API', then create a Backend Workflow with 'Detect request data' to map Teamwork's webhook payload. Register your Bubble endpoint URL in Teamwork's webhook settings. Backend Workflows require a paid Bubble plan. On a free plan, you can only poll Teamwork for changes.
Our agency is building a complex client portal with Teamwork data across multiple projects. Where can we get help?
RapidDev's team has built agency-facing client portals on Bubble with complex project management integrations. If you need help with multi-tenant Teamwork setups, custom dashboards, or billing integrations, book a free scoping call at rapidevelopers.com/contact.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation