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

Robinhood API

Robinhood has no official public API — every integration uses unofficial reverse-engineered endpoints that violate Robinhood's Terms of Service and risk permanent account suspension. This tutorial shows the Plaid Investment Holdings API as the recommended alternative, then walks through the unofficial path for those who understand and accept the risks involved.

What you'll learn

  • Why Robinhood has no official API and what the ToS risk means for your app and account
  • How Plaid Investment Holdings API is the recommended official alternative
  • How to configure Bubble API Connector with a Private bearer token header
  • How to call GET /positions/ and resolve instrument URLs to ticker symbols
  • How to handle 24-hour token expiry with a Backend Workflow on a paid Bubble plan
  • Why the MFA challenge flow breaks Bubble's Initialize Call and how to work around it
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced18 min read3–5 hoursFinance & AccountingLast updated July 2026RapidDev Engineering Team
TL;DR

Robinhood has no official public API — every integration uses unofficial reverse-engineered endpoints that violate Robinhood's Terms of Service and risk permanent account suspension. This tutorial shows the Plaid Investment Holdings API as the recommended alternative, then walks through the unofficial path for those who understand and accept the risks involved.

Quick facts about this guide
FactValue
ToolRobinhood API
CategoryFinance & Accounting
MethodBubble API Connector
DifficultyAdvanced
Time required3–5 hours
Last updatedJuly 2026

The honest reality: Robinhood does not offer a public API

If you searched for 'Robinhood API Bubble integration', you found this page because you want to show portfolio data — your holdings, positions, or portfolio value — inside a Bubble app. That's a completely reasonable goal. But here's what you need to know before building anything: Robinhood has never released an official public API, never opened a developer program, and their Terms of Service explicitly prohibit use of their platform through automated means or scripts.

The unofficial endpoints at api.robinhood.com were reverse-engineered from Robinhood's mobile app. They work today, they may stop working next week, and using them exposes your Robinhood account to permanent suspension. If your Bubble app serves end users who connect their own Robinhood accounts, those users' accounts are equally at risk.

**Recommended alternative: Plaid Investment Holdings.** Plaid is an official financial data API with proper OAuth, documented rate limits, institutional-grade security, and no Terms of Service risk. Plaid's GET /investments/holdings endpoint returns the same data you want from Robinhood — securities, quantities, values — from Robinhood accounts and hundreds of other brokerages through a single integration. Unless you have a specific reason to use the unofficial Robinhood endpoint, Plaid is the right choice.

This tutorial covers the Plaid alternative first, then walks through the unofficial Robinhood path for those who understand the risks and choose to proceed anyway.

Integration method

Bubble API Connector

REST calls to unofficial api.robinhood.com endpoints with Authorization: Bearer [token] in a Private header. MFA handling and token refresh require Backend Workflows on a paid Bubble plan.

Prerequisites

  • A Bubble account on the Starter plan or higher (Backend Workflows required for token refresh — free plan cannot complete this integration)
  • A Robinhood account with 2FA enabled (SMS or authenticator) — required for any Robinhood login
  • OR a Plaid developer account at dashboard.plaid.com (recommended alternative, free sandbox)
  • API Connector plugin installed in your Bubble app (Plugins → Add plugins → search 'API Connector')
  • A clear understanding that using unofficial Robinhood endpoints violates Robinhood's Terms of Service

Step-by-step guide

1

Step 1: Understand your options and choose the right path

Before touching Bubble, make an informed decision about which path you're taking. Here are your two options: **Option A — Plaid (recommended):** Sign up at dashboard.plaid.com, create an application, and get your client_id and sandbox secret. Plaid's Link widget handles the OAuth flow to connect Robinhood (and other brokerages) on behalf of your users. After linking, you call GET /investments/holdings with your plaid-client-id and plaid-secret headers (server-side, both Private in Bubble's API Connector). Plaid returns security names, tickers, quantities, and values in a well-documented JSON format. Rate limits are documented, support is available, and there is zero ToS risk. The main trade-off is that Plaid's production tier requires business approval and carries per-call costs — the sandbox is free and unlimited for development. **Option B — Unofficial Robinhood (understand the risks):** Using the unofficial endpoints at https://api.robinhood.com works today. But Robinhood's Terms of Service section 3 prohibits automated access through scripts. Your account can be suspended without warning. Robinhood has banned the third-party client_id that was historically used for OAuth. If you are building an app where end users connect their own Robinhood accounts, those users are exposed to the same risk. We are documenting this path for educational purposes — not recommending it for production apps. If you are building for users beyond yourself, use Plaid. If you are building a personal tool and accept the risk, continue to Step 2.

plaid_holdings_call.json
1// Plaid Investment Holdings API (recommended)
2// Headers for all Plaid data calls:
3{
4 "PLAID-CLIENT-ID": "<your_client_id>", // Private in Bubble API Connector
5 "PLAID-SECRET": "<your_sandbox_secret>", // Private in Bubble API Connector
6 "Content-Type": "application/json"
7}
8
9// POST /investments/holdings
10// Body:
11{
12 "client_id": "<your_client_id>",
13 "secret": "<your_sandbox_secret>",
14 "access_token": "<user_plaid_access_token>"
15}

Pro tip: If choosing Plaid: their sandbox includes a pre-configured institution called 'Robinhood' in the test environment. You can test the full holdings flow without a real Robinhood account.

Expected result: You have a clear plan for which integration path you are pursuing. If using Plaid, your client_id and secret are ready.

2

Step 2: Configure API Connector with base URL and Private token header

In your Bubble editor, click on the **Plugins** tab in the left sidebar. If you do not already have API Connector installed, click **Add plugins**, search for 'API Connector', find the one labeled 'API Connector by Bubble' and click Install. Once installed, click API Connector in your plugins list to open its configuration panel. Click **Add another API** to create a new API group. Name it 'Robinhood' (or 'Plaid' if using that path). Set the authentication type to 'None or self-handled' — you will manage the bearer token manually in the header. In the **Shared headers** section of the API group (not on individual calls), click the + to add a header. Set the header name to `Authorization`. Set the value to `Bearer <access_token>` where `<access_token>` is a parameter placeholder you will fill at call time. Critically, tick the **Private** checkbox on this header. The Private checkbox tells Bubble's API Connector to substitute this value server-side and never expose it in browser network requests. Without Private, the token appears in your app's client-side JavaScript. Set the base URL for the API group to `https://api.robinhood.com`. Click Save.

api_connector_config.json
1// API Connector configuration (conceptual)
2{
3 "api_name": "Robinhood",
4 "base_url": "https://api.robinhood.com",
5 "shared_headers": [
6 {
7 "name": "Authorization",
8 "value": "Bearer <access_token>",
9 "private": true
10 }
11 ]
12}

Pro tip: The Private checkbox on the Authorization header is your security layer. With it checked, Bubble substitutes the real token on the server before the request leaves Bubble's infrastructure. Without it, the token is visible to anyone who opens browser DevTools on your app.

Expected result: API Connector shows a 'Robinhood' API group with base URL https://api.robinhood.com and a Private Authorization header. The configuration is saved.

3

Step 3: Add the Initialize Call with GET /user/

The API Connector requires a successful Initialize Call before you can use any API call in your Bubble workflows. This means you need to provide a real access token to test with during configuration. To get a test access token from the unofficial Robinhood API, you need to perform the OAuth2 token request manually using Postman or your browser. The token endpoint is `POST https://api.robinhood.com/oauth2/token/`. The request body must include your Robinhood username, password, and Robinhood's own app client_id. Important: the client_id for the Robinhood mobile app is a hardcoded value — using it to authenticate means you are technically impersonating Robinhood's own client application, which is part of why this is a Terms of Service violation. Once you have a valid bearer token (the token field from the OAuth2 response), return to your Bubble API Connector. Inside the Robinhood API group, click **Add a call**. Set the call name to 'Get User Info'. Set the method to GET and the path to `/user/`. In the test value field for the `access_token` parameter, paste your real bearer token. Click **Initialize call**. If the token is valid, you will see a 200 response with user account information including username, email, and account details. Bubble will auto-detect the response fields, making them available to use in workflows and page bindings. Click **Save**. If you get a 401 error, the token has expired (they last 24 hours) or was entered incorrectly. If you get an MFA challenge response, the Initialize Call cannot proceed interactively — see the troubleshooting section for MFA handling.

get_user_call.json
1// Initialize Call configuration
2{
3 "call_name": "Get User Info",
4 "method": "GET",
5 "path": "/user/",
6 "use_as": "Data",
7 "headers": {
8 "Authorization": "Bearer <access_token>" // Private, from shared header
9 }
10}
11
12// Expected successful response shape:
13{
14 "username": "jsmith",
15 "first_name": "John",
16 "last_name": "Smith",
17 "email": "jsmith@example.com",
18 "id": "abc123-def456-..."
19}

Pro tip: If the Initialize Call fails with a 401, your token has likely expired. The unofficial Robinhood tokens last approximately 24 hours. You need to re-authenticate outside of Bubble to get a fresh token for testing.

Expected result: The Initialize Call returns a 200 with user data. Bubble shows the auto-detected fields (username, email, id) available for binding. The call is saved and marked as initialized.

4

Step 4: Add calls for GET /positions/ and GET /portfolios/ with instrument URL resolution

Inside your Robinhood API group, click **Add a call** to add a positions call. Name it 'Get Positions', method GET, path `/positions/`. Initialize this call with your test token. The response is a paginated `results` array — each item contains fields like `quantity`, `average_buy_price`, and `instrument` (a URL string like `https://api.robinhood.com/instruments/abc123/`). This is the most important gotcha with the unofficial Robinhood API: the positions endpoint does NOT return ticker symbols or company names. It returns instrument URL strings. To get the ticker (e.g. AAPL, TSLA), you must make a second API call to that instrument URL for each position. This means one position in your portfolio = two API calls. For a portfolio with 20 positions, that is 40 API calls. At Bubble's Workload Unit (WU) pricing, these add up fast. To handle this, add a second call named 'Get Instrument Detail', method GET, path `/instruments/[instrument_id]/`. The path parameter `[instrument_id]` will be the UUID extracted from the instrument URL. In your Bubble workflow, when iterating through positions, call this endpoint for each position to retrieve `symbol` and `simple_name` fields, then use those to populate your display. Also add a 'Get Portfolios' call: GET `/portfolios/`. This returns the total portfolio value in `equity` and `extended_hours_equity` fields — useful for the portfolio summary display. In your Bubble page, create a Repeating Group bound to the 'Get Positions' data. For each row, trigger a 'When page is loaded' workflow that calls 'Get Instrument Detail' for that row's instrument ID and saves the ticker to the database to avoid re-fetching.

positions_calls.json
1// GET /positions/ — response shape (simplified)
2{
3 "next": null,
4 "previous": null,
5 "results": [
6 {
7 "quantity": "10.00000",
8 "average_buy_price": "142.50000",
9 "instrument": "https://api.robinhood.com/instruments/450dfc6d-5510-4d40-abfb-f633b7d9be3e/",
10 "account": "https://api.robinhood.com/accounts/AB1234/"
11 }
12 ]
13}
14
15// GET /instruments/{id}/ — response shape
16{
17 "symbol": "AAPL",
18 "simple_name": "Apple",
19 "name": "Apple Inc. Common Stock",
20 "tradeable": true,
21 "type": "stock"
22}
23
24// GET /portfolios/ — response shape
25{
26 "equity": "14523.45",
27 "extended_hours_equity": "14489.12",
28 "market_value": "14123.00"
29}

Pro tip: Cache instrument details in a Bubble database table (Instruments: symbol, name, robinhood_id). The instrument IDs never change for a given security — fetch once, store permanently. This cuts your WU consumption dramatically on repeat visits.

Expected result: You have three initialized API calls: Get Positions (returns instrument URLs), Get Instrument Detail (resolves symbol and name), and Get Portfolios (returns total equity). They appear in your API Connector and can be used in workflows.

5

Step 5: Store the access token securely and build token expiry handling

The unofficial Robinhood access token expires in approximately 24 hours. Once it expires, every API call will return 401 until a new token is obtained. If your Bubble app is for personal use only, you may be able to manually refresh the token once a day. For any multi-user or automated setup, you need a token refresh Backend Workflow — which requires a paid Bubble plan (Starter or higher). First, token storage. In the Bubble Data tab, add a field to your User data type: `robinhood_token` (text), `robinhood_token_issued_at` (date). After successfully authenticating outside Bubble and obtaining a token, store it in the User record. When API calls are made, the workflow reads `Current User's robinhood_token` and uses it as the `access_token` parameter. For token expiry detection, in any workflow that calls a Robinhood API, add a first step: check if `Current User's robinhood_token_issued_at` is more than 23 hours ago. If yes, trigger the 'Refresh Robinhood Token' Backend Workflow before the API call. However, re-authentication in the unofficial Robinhood API is not a simple refresh token exchange. The unofficial endpoint does not provide long-lived refresh tokens in the same way as standard OAuth. For automated refresh, you need to store the user's Robinhood username and password in Bubble's database — which is a significant security risk and is not recommended. For most use cases, plan for users to re-authenticate manually once per day. Privacy rules are critical: in the Data tab, click Privacy, select your User type, and add a rule: 'This User is Current User' → allow field robinhood_token to be read only by the user themselves. Without this rule, any user of your app could potentially read another user's token field through data queries.

token_storage.json
1// Bubble Data type: User (additional fields)
2{
3 "robinhood_token": "text",
4 "robinhood_token_issued_at": "date"
5}
6
7// Workflow logic for token age check:
8// Condition: Current User's robinhood_token_issued_at < Current date/time - 23 hours
9// Action: Show "Please re-authenticate" popup
10
11// Privacy Rule (Data tab → Privacy → User):
12// Rule: When This User is Current User
13// Allow access to field: robinhood_token (read only for the token owner)

Pro tip: Consider adding a 'Token expires in X hours' notice to your app UI, calculated from `robinhood_token_issued_at`. This gives users advance warning to re-authenticate before the token dies mid-session.

Expected result: User data type has robinhood_token and robinhood_token_issued_at fields. Privacy rules restrict token access to the token's owner only. Workflows check token age before calling Robinhood endpoints.

6

Step 6: Build the portfolio display UI with Repeating Groups

With your API calls configured and tokens stored, build the portfolio display in your Bubble page editor. Drag a **Repeating Group** onto your page. In the Type of content, set it to match your cached positions data type (if using a database caching approach) or set it to 'Robinhood → Get Positions' (the API call result) for live fetching. Set the Data source to 'Get data from an external API' → Robinhood - Get Positions → Results. Set a list constraint or fixed count to avoid runaway WU consumption on large portfolios. Inside the Repeating Group, add text elements for the position data you want to display. For the ticker symbol: if you are using cached database records, reference `Current cell's Instrument's symbol`. For the quantity: `Current cell's quantity` (format as number). For average buy price: `Current cell's average_buy_price` (format as currency). Add a separate section above the Repeating Group for portfolio summary. Use a text element bound to 'Get data from an external API → Robinhood - Get Portfolios → equity', formatted as currency. This shows the total portfolio value. For the instrument URL resolution: add a workflow 'When page is loaded → Schedule API Workflow on a list' (paid plan) to batch-resolve all instrument URLs and save tickers to your database. This runs once on each page load but only writes to the database when the instrument is not already cached. All subsequent visits read from the database cache instead of calling the Robinhood instrument endpoint per row. RapidDev's team has built Bubble apps integrating financial APIs of this complexity — if you want a scoping call to review your architecture before building, visit rapidevelopers.com/contact.

portfolio_ui_bindings.json
1// Repeating Group data source configuration:
2// Type of content: Robinhood Position (database type, if caching)
3// Data source: Do a search for Robinhood Positions where User = Current User
4
5// OR live API (higher WU cost):
6// Data source: Get data from external API → Robinhood - Get Positions → results
7
8// Text element binding examples:
9// Ticker: Current cell's instrument_symbol (from cached database)
10// Qty: Current cell's quantity formatted as number (decimal: 2)
11// Avg price: Current cell's average_buy_price formatted as currency
12// Total value: Current cell's quantity * Current cell's current_price (if stored)
13
14// Portfolio total text:
15// Get data from external API → Robinhood - Get Portfolios → equity
16// formatted as currency

Pro tip: Use Bubble's 'Repeating Group fixed row count' option and add a 'Load more' button rather than loading all positions at once. The /positions/ endpoint supports pagination via the `next` URL field — load the next page on button click to keep initial page load fast and WU costs low.

Expected result: A Repeating Group on your page displays positions with ticker symbols, quantities, and average buy prices. A portfolio total text element shows the equity value from GET /portfolios/. The UI loads data from the cached database table on repeat visits.

Common use cases

Portfolio dashboard showing brokerage holdings

Display a user's stock positions, quantities, and current values in a Bubble Repeating Group. The Plaid path supports Robinhood and 100+ other brokerages. The unofficial Robinhood path shows only Robinhood accounts.

Bubble Prompt

Copy this prompt to try it in Bubble

Net worth tracker aggregating investment accounts

Pull portfolio value from GET /portfolios/ (unofficial) or Plaid /investments/holdings to show a user's total brokerage balance alongside bank accounts and other assets in a unified Bubble dashboard.

Bubble Prompt

Copy this prompt to try it in Bubble

Automated token refresh for unattended portfolio sync

Use a Bubble Backend Workflow scheduled daily to check if the stored access token is approaching its 24-hour expiry and re-authenticate before the token dies, keeping the data fresh without requiring the user to log in again.

Bubble Prompt

Copy this prompt to try it in Bubble

Troubleshooting

Initialize Call returns an MFA challenge response instead of user data

Cause: Robinhood's login enforces MFA for all accounts. The standard OAuth2 token request triggers an SMS or authenticator code challenge that cannot be handled interactively inside Bubble's API Connector Initialize Call — there is no way to pause the call and inject a dynamic code mid-request.

Solution: To get a token for Initialize Call testing: use Postman or a temporary Python script to complete the full MFA flow outside of Bubble, then copy the resulting access_token into the Initialize Call test field. For the production Bubble app, the MFA flow needs to be handled in a multi-step Backend Workflow (paid plan): first call /oauth2/token/ → receive MFA challenge → show an input popup to the user → submit the MFA code in a second call. This is complex to implement in Bubble and is another argument for using Plaid instead.

GET /positions/ returns instrument URLs instead of ticker symbols like AAPL or TSLA

Cause: This is the expected Robinhood API response — the positions endpoint intentionally returns instrument resource URLs rather than ticker symbols. You must resolve each URL with a secondary GET call to /instruments/{id}/ to retrieve the symbol.

Solution: Add a second API call in your Robinhood API group: GET /instruments/[instrument_id]/ where instrument_id is the UUID from the instrument URL. Extract the UUID with a Bubble expression: 'Do a search for… extract the part between the last two slashes' or use Bubble's 'Truncate…' expression chain. Cache the result in an Instruments database table to avoid repeated calls. Each row in your positions Repeating Group needs one instrument call to resolve — budget the WU cost accordingly.

typescript
1// Extract instrument ID from URL:
2// Full URL: https://api.robinhood.com/instruments/450dfc6d-5510-4d40-abfb-f633b7d9be3e/
3// In Bubble: use ":extract with regex" → regex: [a-f0-9-]{36}
4// Result: 450dfc6d-5510-4d40-abfb-f633b7d9be3e
5// Pass this as [instrument_id] parameter to the GET /instruments/ call

API calls return 401 Unauthorized unexpectedly

Cause: The unofficial Robinhood access token has a short lifespan (approximately 24 hours). If the stored token has passed this window, all calls will return 401 until a new token is obtained.

Solution: Check your stored `robinhood_token_issued_at` date against the current time. If older than 23 hours, the token has expired. You need to re-authenticate manually (or via Backend Workflow if you have stored credentials — not recommended for security reasons). Show a re-authentication prompt to the user in your Bubble app when token expiry is detected.

'Workflow API is not enabled' error when trying to use Backend Workflows

Cause: Backend Workflows (API Workflows) are a paid Bubble feature. They are not available on the Free plan. The error appears in Bubble's Settings → API section when your app is on the Free plan.

Solution: Upgrade to the Bubble Starter plan (minimum $32/month) to enable Backend Workflows. This is required for MFA handling, token refresh automation, and the 'Schedule API Workflow on a list' action used for batch instrument URL resolution. The Free plan cannot complete this integration at all.

There was an issue setting up your call — Initialize Call fails for the positions or portfolios endpoint

Cause: The Initialize Call requires a live successful API response to detect the response schema. If your bearer token is expired or the endpoint URL has a typo, Bubble shows this generic error because it received a non-200 response.

Solution: Verify the endpoint URL exactly: GET https://api.robinhood.com/positions/ (with trailing slash). Verify your bearer token is fresh (less than 24 hours old). Try calling the URL directly in Postman with the same token to confirm it returns 200 before attempting the Initialize Call in Bubble. Once you get a successful response, Bubble will auto-detect the fields.

Best practices

  • Use Plaid Investment Holdings API instead of unofficial Robinhood endpoints for any app serving real users — it provides the same portfolio data through an official, ToS-compliant integration with documented rate limits and enterprise-grade support.
  • Always mark the Authorization header as Private in Bubble's API Connector — without the Private checkbox, bearer tokens appear in browser network requests and can be stolen by anyone with DevTools access to your app.
  • Cache instrument details (ticker symbols and company names) in a Bubble database table after the first resolution call — instrument IDs never change, so re-fetching them on every page load wastes WU with no benefit.
  • Add Privacy Rules immediately when storing any financial tokens — go to Data tab → Privacy → User type and restrict robinhood_token to 'This User is Current User' read access only, preventing data leakage between users.
  • Implement a token age check before every API call: compare robinhood_token_issued_at to current time, and show a re-authentication prompt when the token is older than 23 hours — before it actually expires mid-workflow.
  • Use Bubble's 'Repeating Group fixed row count' and load-more pagination pattern rather than fetching all positions at once — this keeps initial page load fast and prevents WU spikes on portfolios with many positions.
  • For personal-use tools, consider whether a simpler read-only Plaid integration in sandbox mode is sufficient for your needs — sandbox data mirrors real portfolio structures and lets you build and test the full UI without using unofficial endpoints.

Alternatives

Frequently asked questions

Is it legal to use the unofficial Robinhood API?

Using it does not violate any law, but it does violate Robinhood's Terms of Service (Section 3, which prohibits automated access). Robinhood can suspend your account without notice for ToS violations. They have banned third-party client_ids in the past. If you are building an app for users beyond yourself, those users' accounts are also at risk. This is why Plaid is the recommended path — it provides the same investment data legally and officially.

Why does the positions endpoint return URLs instead of ticker symbols?

The unofficial Robinhood API was designed for Robinhood's mobile app, not for third-party integration. The positions endpoint returns instrument resource URLs because Robinhood's app lazy-loads instrument details. For a Bubble integration, you need a secondary GET call to /instruments/{id}/ for each position to retrieve the ticker symbol. Cache the results in Bubble's database after the first lookup — instrument IDs are permanent.

Can I handle Robinhood's MFA requirement inside Bubble?

Only partially. A fully automated, unattended login with MFA is not possible in Bubble. You can build a multi-step user-facing flow: first call triggers the MFA challenge, show a Bubble popup asking the user for their SMS/authenticator code, then submit the code in a second API call to complete authentication. This requires a paid Bubble plan for Backend Workflows and is significantly more complex than a standard API integration.

Will Robinhood shut down unofficial API access?

They have done it before — Robinhood has blocked specific third-party client_ids and changed authentication flows without notice. There is no way to predict future availability. Any app built on unofficial Robinhood endpoints could break overnight without any warning or recourse. This unpredictability is one of the strongest reasons to use Plaid instead.

Do I need a paid Bubble plan for this integration?

Yes. The free Bubble plan cannot complete this integration. Backend Workflows (paid feature, minimum Starter plan at ~$32/month) are required for MFA handling, automated token refresh, and the 'Schedule API Workflow on a list' action used for batch instrument resolution. Even on a paid plan, the MFA flow requires significant workflow complexity.

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.