Sandbox vs. Prod: Data, Credentials, and Migration

Hey Dev Team,

I’ve got a few questions about sandbox vs. production environments:

  1. Differences: Is it just the login credentials (API key/token) that change between sandbox and prod, or are there other differences (endpoints, rate limits, behavior)?
  2. Market Data: Can I use real market data from production in the sandbox for testing orders, or is sandbox data simulated/outdated?
  3. Migration: If I test a full setup in sandbox, can I switch to production by only replacing the sandbox credentials with live ones? Or are there extra steps (configs, endpoint tweaks, etc.)?

Just trying to avoid “works in sandbox, breaks in prod” surprises. Thanks for the help!

Hey! Here’s what I’ve learned about working with Upstox sandbox vs. production environments:

Configuration Differences:
It’s not just about changing the API token — you also need to set the correct configuration values when switching between sandbox and live. This includes flags like sandbox=True and the appropriate host settings, depending on how you structure your integration.

Market Data:
Sandbox doesn’t provide live market data or LTP. Order placement returns a success status, but there’s no price feedback. So if you want realistic testing, you’ll need to fetch live data separately (e.g., from production) and manually simulate slippage and execution logic.

Switching to Production:
Switching is simple if you’ve modularized your setup. The config object needs to be set properly with its working in mind. With that in place, moving from sandbox to production is just a matter of passing the correct values — no major rewrites needed.

Things to Watch:
You’ll need to create a separate app for sandbox access, and the sandbox token expires every 30 days. Also, not all APIs support sandbox mode yet, so you might run into gaps if your strategy uses more than basic order placement.

1 Like

Thanks a lot for the detailed informations, in another thread it was shared


Does that mean I cant use Prod Token for market Data & Sandbox Token for orders in teh same setup. I am quite confused.

Yeah, its a bit tricky. When you make a sandbox API call (like placing an order), you need to set sandbox=True in the configuration. But here’s the catch: once your configuration is initialized in sandbox mode, it stays in that mode for the session — even if you later switch the token or try to call a non-sandbox-supported API.

So if you try to use a production token for market data and a sandbox token for order placement in the same setup, it gets tricky. The configuration object still thinks it’s in sandbox mode, and it can cause issues or failed calls when you hit endpoints that don’t support sandbox.

1 Like

Ah, that sandbox/production toggle is a real pain. How’ve you been handling it so far? Like, did you find a way to juggle both tokens cleanly, or did you work around the config sticking in sandbox mode?

What I’ve done is build a custom configuration function around a single SANDBOX_MODE flag. This function handles everything — sets the correct token, toggles sandbox mode, and resets the relevant hosts and flags each time a config is created. That way, even if the client was previously in sandbox, switching to production (or vice versa) works cleanly without leftovers from the previous mode.

So with just one flag (SANDBOX_MODE = True/False), I can toggle between environments seamlessly across my app. It avoids all the silent bugs from leftover config states, and I don’t have to manually reset anything elsewhere in the code.

Yeah, happy to share a snippet if it helps — just a heads-up though: I’d be cautious about posting the full workaround publicly.

Since it technically works around how the SDK handles configuration persistence, sharing it openly might draw unnecessary scrutiny or be seen as circumventing intended design choices. The sandbox setup isn’t fully modular yet, so mixing modes needs careful handling.

But if you’re running into issues toggling between tokens or modes, feel free to DM me — I can point you in the right direction without posting the full implementation here.

Thanks for sharing your approach—it’s really insightful! I like how you’ve streamlined the config switching with a single SANDBOX_MODE flag. I’m going to try implementing something similar based on your suggestion; it looks much cleaner than manual toggling.

If I hit any roadblocks, I’ll definitely reach out—really appreciate you offering to point me in the right direction. You’ve gone above and beyond to help, thanks again!

Could you please confirm if it’s possible to check order placements directly in Sandbox, or should we verify them only via API? Additionally, could you provide details on the virtual funds available for testing? Any guidance would be greatly appreciated.

Thanks in advance for your help!

I tried implementing and it’s partially working , but I came across these


what are these

The Upstox sandbox is a request-response simulation environment. The sandbox is purely a simulation layer. That means when you place an order in sandbox, it doesn’t actually create a virtual position or reflect in any holdings or order book. There are no virtual funds or portfolio tracking — it simply returns a mock success response to help you test if your API requests are formatted correctly and working as expected.

To verify order placement in sandbox, you should rely entirely on the API response, as nothing gets processed on the backend. There’s no live order book, no trade fills, and no execution logic involved.

Also, Upstox offers a really useful tool called the “Same Endpoint Tester” in their developer documentation. It lets you try out the exact same request formats (like placing an order or modifying it) against either the live or sandbox environment directly from the browser. This is great for checking how your payloads behave before coding them into your app.

So, if you’re just testing integration or learning the request structure, sandbox + the endpoint tester is the way to go. For any real data testing like P&L, slippage, or holdings — that has to happen in production.

2 Likes

Thanks for the detailed explanation. I ’ll definitely use it to validate my order placement requests and experiment with the payload structure. The “Same Endpoint Tester” you mentioned seems handy. Just to confirm, since the sandbox doesn’t simulate actual orders or positions, I’ll rely solely on the mock responses for debugging.

1 Like

Hi Raghav, I am facing the same issue, upstox sandbox api with simple success return is of no use for paper trading simulation. I would like to know more about the custom config function.

Hi! I faced the same issue where the Upstox sandbox just returns a basic success response and doesn’t track positions, holdings, or funds. So using it directly for real paper trading simulation was not effective. But I came up with a workaround that lets me simulate a full live strategy while still validating order API calls.

My Workaround Process

What I did was treat the app as if it’s running entirely live, but only route the order placement API through sandbox. This way, I still get to test my request formats and order flow without risking capital, while using live LTP data for accurate simulation.

For example, after placing an order via the sandbox, I immediately call the LTP API from live to fetch the price and use it as the entry. I use this same logic for partial exits or trailing SL — fetch LTP again, update the position object, and continue. This gives me a very close-to-reality paper trading system without relying on any fake sandbox fills.

Class Design

I have a UpstoxHelper class that wraps all Upstox API calls. Each strategy instantiates its own helper and passes a SANDBOX_MODE flag. This makes each strategy completely independent — some can run in live mode, others in sandbox — even simultaneously.

Here’s how I store the tokens at initialization:

if sandbox_mode:
    logger.notice("SANDBOX MODE IS ACTIVE. Setting sandbox_mode_live_token")
    self.sandbox_mode_live_token = access_token
    self.sandbox_mode = True
else:
    logger.notice("CAUTION!!! LIVE MODE. Setting LIVE TOKEN FOR USE")
    self.access_token = access_token
    self.sandbox_mode = False

Tokens are stored once: The SANDBOX_TOKEN is loaded earlier in the file from .env. That one’s stable and doesn’t change often (usually once a month), making it easy to use for multiple test runs without constantly refreshing. And live from wherever I set it every day.

Configuration Switcher

The key insight I had was: even if you switch tokens, if the configuration is stuck in sandbox mode (sandbox=True), it’ll keep using the sandbox environment. I dug into the SDK and noticed that the main difference is just two endpoint URLs — so I manually set them in the config object to be safe.

Every time an API is called, my helper uses a shared method: get_upstox_configuration(sandbox_mode, sandbox_available). This method returns a fresh config object depending on the use case.

Example:

# For LTP, sandbox isn't supported, so force live config
configuration = self.get_upstox_configuration(self.sandbox_mode, sandbox_available=False)

With this setup, I can run multiple strategies in parallel — some live, some sandbox — and each has full control over its config and execution path.

This setup ensures:

  • Orders get routed through sandbox only when I want to test structure, not real execution.

  • LTP and market data always come from live, so strategy simulation is accurate.

  • Each strategy can independently toggle sandbox or live, thanks to the SANDBOX_MODE flag at init time.

Since this involves working around some SDK design limitations (like how it sticks in sandbox mode), I’d recommend not sharing the full implementation publicly.

That said, this method has let me simulate and test real trading flows without risking real funds — so happy to share more details privately if anyone’s stuck.

hey hi,

At last I have finalised in building my trading system using Upstox, which offers two environments:

  • Sandbox (testing with mock data)

  • Production (live trading)

Both environments share identical Data API endpoints and structures, but differ in:

  • Base URLs (eg:sandbox.upstox.com vs api.upstox.com)

  • Separate Client ID and Access Token credentials

I’m deciding between three approaches for migrating from Sandbox to Live and would love your input:

Option 1: Single Codebase with URL Replacement

  • Develop/test entirely in Sandbox.

  • For production, swap the base URL and credentials (manually or via config).

  • Pros: Simple, minimal maintenance.

  • Cons: Risk of accidental sandbox config leaking into live trades.

Option 2: Dual Environment System

  • Use a dynamic config (e.g., environment variables) to toggle between Sandbox/Live.

  • Single codebase, but environment-aware (e.g., ENV=production flag).

  • Pros: No code duplication, easier testing.

  • Cons: Must rigorously validate configs to avoid live mistakes.

Option 3: Separate Codebases

  • Develop in Sandbox, then clone the codebase for Production with hardcoded live settings.

  • Pros: Complete isolation, no runtime switching risks.

  • Cons: Double maintenance effort (bug fixes, feature updates).

Kindly assist,

  1. Which approach is safest for Upstox’s API? (Especially given its rate limits/auth flow.)

  2. Are there any better approaches?

  3. Any tips for the upstox-python-sdk or handling WebSocket connections across envs?

Please share your war stories or lessons learned!

You’ve outlined three migration strategies. Based on my experience (and what you’ve probably already sensed), Option 2 is the most maintainable and safest — and it’s actually how I’ve structured my own system.

Option 1 vs. Option 2 – They’re Closer Than You Think

At first glance, Option 1 (manual URL/token replacement) seems different from Option 2 (env-based switching). But at the core, it addresses the same thing — the difference between token/URL replacement and proper config-level sandbox toggling is just where and when you flip the switch..

It is what I have done with my get_upstox_configuration() function — they’re essentially the same in logic. In my case I do it by properly setting the host endpoints in the configuration object created by the SDK.

When I drilled into the code of the Upstox SDK, I noticed that the only real difference between sandbox and live environments was how the hosts (base URLs) were set. So, I made a simple toggle to handle that change dynamically. Here’s the actual code from my get_upstox_configuration() function:

def get_upstox_configuration(self, sandbox_mode, sandbox_available=True):
    """
    Returns the correct Upstox configuration based on sandbox mode and availability.
        
    Args:
        SANDBOX_MODE (bool): User preference to use sandbox.
        sandbox_available (bool): Whether sandbox setup is available/functional.
        self: Object containing `access_token` for live usage.
        
    Returns:
        upstox_client.Configuration: Proper configuration object.
    """
    if sandbox_mode and sandbox_available:
        config = upstox_client.Configuration(sandbox=True)
        config.access_token = SANDBOX_ACCESS_TOKEN

        # Double check fallback in case sandbox isn't properly set
        if config.sandbox == False:
                        
            config.sandbox = True    #Set to True
            config.host = "https://api-sandbox.upstox.com"   #Set Sandbox Host
            config.order_host = "https://api-sandbox.upstox.com"     #Set Sandbox Order Host
            
            logger.info(f"Using Sandbox Access Token as SANDBOX MODE: {sandbox_mode} & SANDBOX AVAILABLE: {sandbox_available}")
        
    elif sandbox_mode and sandbox_available == False:
                
        config = upstox_client.Configuration()
        config.access_token = self.sandbox_mode_live_token

        if config.sandbox == True:
                        
            config.sandbox = False    #Set to False
            config.host = "https://api.upstox.com"   #Set Live Host
            config.order_host = "https://api-hft.upstox.com"     #Set Sandbox Order Host
            
            logger.info(f"Using Live Access Token in SANDBOX as SANDBOX MODE: {sandbox_mode} & SANDBOX AVAILABLE: {sandbox_available}")

    else:
        config = upstox_client.Configuration()
        config.access_token = self.access_token

        if config.sandbox == True:
                        
            config.sandbox = False   #Set to False
            config.host = "https://api.upstox.com"   #Set Live Host
            config.order_host = "https://api-hft.upstox.com"     #Set Sandbox Order Host
            
            logger.notice(f"!!! Using Live Access Token as SANDBOX MODE: {sandbox_mode} !!!")

    return config

The second and third conditions are logically the same — they both result in using live mode — but I’ve kept them separate intentionally for clarity and future-proofing. For my own sanity, I prefer explicitly separating:

  • Live usage intended via sandbox_mode = False
  • Sandbox mode fallback (i.e., sandbox_mode = True but no sandbox setup available)

This structure makes the branching clearer when debugging or adding new behavior. It’s essentially just toggling the host, order_host, and access_token depending on the context.

Also, I read the SANDBOX_ACCESS_TOKEN from .env near the top of the file at initialization, so this config function is clean and stateless — each strategy calls it independently and gets the appropriate config without worrying about shared state.

This is exactly what prevents those weird bugs where your client still thinks it’s in sandbox, even after swapping tokens.

WebSocket Handling

For WebSocket, my pattern is very similar. I run a single WebSocket connection in a dedicated thread that pushes market data to different strategy instances. Each strategy then filters the ticks for the instruments it cares about.

This separation of concerns lets your core feed run once (efficient and lightweight) and all strategies act independently — just like microservices subscribing to a common pub-sub.

Final Advice

  • Avoid Option 3 unless your environments are regulated, audited, or require hard separation (e.g., in algo funds). It’s just double the work.
  • The config function is golden — as long as you’re passing mode flags properly and isolating tokens, you’re safe.
  • With the current sandbox limitations (mocked order response, no real LTP), your hybrid live-sandbox strategy (live data + sandbox order API) is honestly the best simulation you can get.
  • Document clearly which APIs are sandbox-supported — not all are, and sandbox=True won’t magically work for everything.

This is super helpful—thanks for breaking it down! I’ll definitely implement your get_upstox_configuration() approach. A few quick things:

  1. Just to double-check: When you change sandbox_mode but sandbox isn’t available, you fall back to live mode but still log it as a sandbox attempt (with sandbox_mode_live_token). Is that token just a backup live token, or something else?

  2. For the WebSocket—do you maintain separate connections for live/sandbox, or just route everything through one and let strategies filter?

  3. Any edge cases where the SDK’s sandbox=True gets weird? (I’ll document unsupported APIs either way.)

Your hybrid setup (live data + sandbox orders) is exactly what I’m leaning toward too. Appreciate the sanity check!

Let me break it down:

:white_check_mark: sandbox_mode_live_token – What Is It?

You’re absolutely right in your observation — that token is not different from my regular access_token, it’s just a naming convenience. I store it as sandbox_mode_live_token only because it’s used when a strategy runs in “sandbox mode”, but the specific API call (like LTP or option chain) isn’t supported on the sandbox endpoint, so I fall back to production.

In that case, I still want to log the context as “sandbox mode active” even if I’m hitting live APIs — just to make debugging consistent and reflect intent. So it’s not a backup token or a different scope — it’s the same live token reused with context.

WebSocket Connections

For WebSockets, I maintain a single shared connection (via one Upstox WebSocket instance), and then each strategy — via its own UpstoxHelper or handler — decides whether it’s running in sandbox mode or not.

This works well since the streaming layer is generally read-only (market data), and I let each strategy decide how to interpret/react to the feed. Keeps things simple and efficient.

:warning: Sandbox Quirks & Gotchas

Yes, the sandbox=True flag works great as long as you’re placing orders or testing execution logic. But here’s where you can run into trouble:

  • If your strategy or code calls APIs like get_order_details, get_positions, or any that require order state tracking, the sandbox may fail silently or return misleading data.
  • That’s because sandbox doesn’t maintain a real order book or order history, so if you try to query details of an order just placed in sandbox, it won’t show up.
  • And importantly — if you use the live token in sandbox mode to check those order details (thinking you’re just reusing a working token), it won’t find those sandbox-placed orders in production either.

So if your app or bot relies on syncing order states or reconciling PnL, just be cautious — that’s the weak link in otherwise very usable sandbox setups.

I think a hybrid approach makes total sense — just with a couple toggles and token-context awareness, I can run production-grade logic with sandbox isolation

Kind of clear now! Your breakdown helped—especially the sandbox quirks part. I think I’ll roll with the hybrid approach and shout if I hit road blocks. Thanks!"