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.
| Fact | Value |
|---|---|
| Tool | Robinhood API |
| Category | Finance & Accounting |
| Method | Bubble API Connector |
| Difficulty | Advanced |
| Time required | 3–5 hours |
| Last updated | July 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
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
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.
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 Connector5 "PLAID-SECRET": "<your_sandbox_secret>", // Private in Bubble API Connector6 "Content-Type": "application/json"7}89// POST /investments/holdings10// 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.
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.
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": true10 }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.
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.
1// Initialize Call configuration2{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 header9 }10}1112// 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.
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.
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}1415// GET /instruments/{id}/ — response shape16{17 "symbol": "AAPL",18 "simple_name": "Apple",19 "name": "Apple Inc. Common Stock",20 "tradeable": true,21 "type": "stock"22}2324// GET /portfolios/ — response shape25{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.
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.
1// Bubble Data type: User (additional fields)2{3 "robinhood_token": "text",4 "robinhood_token_issued_at": "date"5}67// Workflow logic for token age check:8// Condition: Current User's robinhood_token_issued_at < Current date/time - 23 hours9// Action: Show "Please re-authenticate" popup1011// Privacy Rule (Data tab → Privacy → User):12// Rule: When This User is Current User13// 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.
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.
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 User45// OR live API (higher WU cost):6// Data source: Get data from external API → Robinhood - Get Positions → results78// 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 currency12// Total value: Current cell's quantity * Current cell's current_price (if stored)1314// Portfolio total text:15// Get data from external API → Robinhood - Get Portfolios → equity16// formatted as currencyPro 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.
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.
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.
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.
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-f633b7d9be3e5// Pass this as [instrument_id] parameter to the GET /instruments/ callAPI 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
Plaid is the strongly recommended alternative — it provides official OAuth access to Robinhood investment holdings (and 100+ other brokerages) through a sanctioned API with documented rate limits, enterprise security, and no ToS risk. The trade-off is production pricing and a business approval process.
If your goal is payment processing rather than portfolio tracking, Stripe is the leading choice. Stripe has an official API, a generous developer sandbox, and native Bubble plugin support. It is not a substitute for investment data, but clarifies that the choice of financial API depends entirely on your use case.
For crypto portfolio tracking specifically, Coinbase's official API provides authenticated account balance and holdings data through a supported developer program — with the same server-side HMAC signature requirement for private endpoints as Binance. A legal alternative to the unofficial Robinhood path for crypto holdings.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation