# Schwab API Python: OAuth, token expiry and first requests

URL: https://fincodelabs.com/en/blog/schwab-api-python · Publisher: Fincode (Fincode Yazılım ve Teknoloji A.Ş.), Istanbul, Turkey · Category: Stocks, forex and broker APIs · Published: 2026-11-06 · Updated: 2026-11-06

Schwab API in Python: Trader API registration, callback rules, token expiry, schwab-py tested with fake tokens, and a re-login monitor for unattended programs.

**Short answer:** The **Schwab API in Python** means Charles Schwab's Trader API for individual developers, reached in this guide through the unofficial `schwab-py` wrapper (1.5.1, 30 June 2025, MIT). You need a Schwab brokerage account and one approved app with an exact callback URL. The design constraint is the token: community documentation says access tokens last about 30 minutes and refresh tokens about seven days, so an unattended program needs a human login roughly once a week.

Schwab's portal could only be partly read for this guide, on 5 November 2026. Its public user guides and app form text were readable; the Trader API product documentation, API specification and terms were not. So each statement below says whose it is: Schwab's, or the community wrapper's. No Schwab account, app or key was used, and the tests ran on fake tokens and local mocks.

This is not a recommendation of Schwab, and automated trading can lose money.

## Does Charles Schwab have an API?

Yes, Schwab's portal lists Trader API products for individuals and for commercial use, and a Schwab article dated 14 August 2026 describes what traders do with them.

**What Schwab's own pages say, read 5 November 2026**

| Item | Schwab's description |
| --- | --- |
| Purpose | Connects thinkorswim's market data, account information and functionality to third-party AI applications; listed uses include order entry for stocks, options and ETFs |
| Individual role | Access to your own Schwab brokerage accounts, which you must already hold, and a limit of one app |
| Commercial offering | Traders can request that an external application be integrated; not all requests are allowed |
| Login | OAuth 2, with `authorization_code` as the only available flow |

`schwab-py` is not Schwab's: it labels itself unofficial and says it is not endorsed by or affiliated with Schwab. Calling `api.schwabapi.com` without a token returned HTTP 401 on 5 November 2026, which shows only that the host answers.

## Schwab API key: register, create one app, wait for approval

Registration is a manual sequence whose last step, approval, is outside your control, so start it before you need the API.

1. Register on the portal and add the Individual Developer role to your profile.
2. Request access to the Trader API product; Schwab's general guide says most requests are reviewed within two business days, sometimes longer.
3. Create the app with its name, callback URL, API products and order limit.
4. Wait for the status `Ready For Use`. The wrapper's docs warn that `Approved - Pending` is not ready yet and that approval can take a few days.
5. Copy the app key and secret, which Schwab calls highly confidential, into a secret store: see [API key security](https://fincodelabs.com/en/blog/api-key-security-trading-bots).

**App form fields**

| Field | Schwab's portal | schwab-py docs |
| --- | --- | --- |
| Callback URL | HTTPS per the OAuth guide, while the callback guide says the scheme varies by line of business; 255 characters at most (one guide says 256); several separated by a comma without a space. The URL sent in the flow must be identical to a registered one | `https://127.0.0.1:8182` with no trailing slash; its login flow accepts only that host |
| Order limit | 0 to 120 order requests per account per minute, covering new orders, replacements and cancels; applied automatically | Suggests 120 |
| API product | Not readable | Suggests Accounts and Trading Production; also lists Market Data |

The portal's status list also includes `App Registration Error`; the wrapper's docs tie that to a console bug reported on 21 July 2024 and say to email Schwab's API team.

## Schwab access token and refresh token: what is documented where

The 30-minute and seven-day figures could not be confirmed on a Schwab page read for this guide, so treat them as community-documented and read `expires_in` at run time.

**Token facts by source, read 5 November 2026**

| Question | Schwab's public guides | schwab-py docs and source |
| --- | --- | --- |
| Access token life | Returned in seconds as `expires_in`; no figure given | About 30 minutes |
| Refresh token life | No figure in the guides read | Seven days, with no way to extend it; may end sooner or later, since Schwab enforces it |
| Refresh early? | Common and fine if not excessive; it avoids 401 errors near expiry | Automatic, with a 300-second leeway in the source |
| Full login again when | Refresh token compromised, different accounts needed, user revokes access or changes credentials or two-factor setup, policy changes | Token expired: delete the file and create a new one |
| Proactive age limit | Not mentioned | `easy_client` defaults to `max_token_age` of 561,600 seconds, 6.5 days |

A person must complete Schwab's consent page to start a new token, so no unattended renewal path appears in these documents. Do not script that login with stored credentials; check Schwab's terms first.

## Tested: how schwab-py behaves as tokens age

A stale token fails late and as an exception rather than an HTTP status, so code that only checks `status_code` never sees it.

**schwab-py 1.5.1, Python 3.11.3, fake tokens and a local mock, 5 November 2026**

| Test | Result |
| --- | --- |
| Token file 9 days old, `client_from_token_file` | No error; `token_age()` reports 9.0 days. Only `easy_client` checks age |
| Access token valid | One GET with the existing token |
| Access token expired, refresh accepted | POST with `grant_type=refresh_token`, then the GET with the new token; file rewritten, `creation_timestamp` unchanged |
| 200 seconds left on the access token | Refreshed anyway, because of the leeway |
| Refresh refused by the mock | `authlib.integrations.base_client.errors.OAuthError` raised from the GET; file unchanged |
| Token file in an older format | `ValueError` asking you to delete it |

The refusal was synthetic, because Schwab's real error body needs an expired real token. Importing schwab-py also printed an Authlib deprecation warning about httpx (seen on 1.9.4 and on 1.9.5, released that day), a sign to pin versions for a wrapper last released in June 2025.

## A token-age monitor for unattended Schwab programs

Run a small check outside the trading process, so a token problem reaches a person before it stops the program. `creation_timestamp` marks the login and does not move when the access token refreshes.

```python
import json, time

REFRESH_DAYS = 7                  # community-documented, Schwab can end it sooner
WARN_DAYS = 5.5                   # your choice: leave time for a human

def token_status(path, now=None):
    """Classify a schwab-py token file. Reads timestamps only, never prints secrets."""
    now = now or time.time()
    try:
        with open(path, encoding="utf-8") as f:
            wrapped = json.load(f)
        days = (now - wrapped["creation_timestamp"]) / 86400
        access_left = wrapped["token"]["expires_at"] - now
    except (OSError, ValueError, KeyError, TypeError) as err:
        return "UNREADABLE", type(err).__name__
    if days >= REFRESH_DAYS:
        return "RELOGIN_REQUIRED", f"{days:.1f} days old"
    if days >= WARN_DAYS:
        return "RELOGIN_SOON", f"{REFRESH_DAYS - days:.1f} days left"
    return "OK", f"{days:.1f} days old, access token {access_left / 60:.0f} min"
```

On synthetic files with a fixed clock:

```
new      OK 0.0 days old, access token 30 min
midweek  OK 3.0 days old, access token -5 min
late     RELOGIN_SOON 1.2 days left
day7     RELOGIN_REQUIRED 7.0 days old
old      RELOGIN_REQUIRED 9.0 days old
missing  UNREADABLE FileNotFoundError
legacy   UNREADABLE KeyError
```

> **Hypothetical schedule.** Log in every Sunday before the token turns seven days old, as the wrapper's docs suggest for weekday trading. Last Sunday's token is 5 days old on Friday, so a 5.5-day warning stays quiet through the trading week and fires by Saturday as the reminder. The thresholds are examples, not Schwab figures.

- Run it daily from a scheduler and send `RELOGIN_SOON` to a person.
- On `RELOGIN_REQUIRED` or `OAuthError`, block new orders and alert. Decide in advance how open orders get handled while you are locked out.
- Never share a token file between machines or programs: the wrapper's docs warn that one program then locks the other out.

## Schwab API Python tutorial: first requests

Account calls take an account hash rather than the number, so fetch the hashes first.

```python
import json, time, httpx
from schwab import auth
from schwab.orders.equities import equity_buy_limit

now = int(time.time())
with open("token.json", "w", encoding="utf-8") as f:     # synthetic token
    json.dump({"creation_timestamp": now - 3600,
               "token": {"access_token": "FAKE", "refresh_token": "FAKE",
                         "token_type": "Bearer", "expires_in": 1800,
                         "expires_at": now + 1500, "scope": "api"}}, f)

client = auth.client_from_token_file("token.json", "FAKE_KEY", "FAKE_SECRET")

def fake_schwab(request):                # test harness: nothing leaves this machine
    print(request.method, request.url.path)
    return httpx.Response(200, json={})

client.session._transport = httpx.MockTransport(fake_schwab)  # delete this line for real use
client.get_quote("TEST")
client.get_price_history_every_day("TEST")
client.get_account_numbers()                                  # gives the account hashes
client.place_order("ACCOUNT_HASH", equity_buy_limit("TEST", 1, "10.00").build())
print(client.token_age() // 60, "minutes since login")
```

Output on 5 November 2026, with nothing sent to Schwab:

```http
GET /marketdata/v1/TEST/quotes
GET /marketdata/v1/pricehistory
GET /trader/v1/accounts/accountNumbers
POST /trader/v1/accounts/ACCOUNT_HASH/orders
60 minutes since login
```

Real tokens come from the login flow, `schwab-generate-token.py` or `easy_client`, none of which could be run here. The wrapper's docs say a successful `place_order` usually returns no JSON, with the order ID in the response for `Utils.extract_order_id` to read, and its client sets a 30-second timeout by default. Check `status_code` yourself, since each method returns an HTTPX response. The wrapper also covers option chains, movers, market hours and instruments, plus a streaming client for quotes, charts, order book and account activity.

## Schwab paper trading API and thinkorswim API

No public Schwab page read here confirms a paper endpoint for the individual Trader API, and schwab-py's [README](https://pypi.org/project/schwab-py/) says "Paper trading is not supported", so plan for none unless your own portal account shows one.

- **Sandbox.** The portal's general guide describes Sandbox apps with engineered test data and says each line of business sets its own rules. It does not say which applies to the Trader API. The wrapper's docs suggest a production product and do not mention a sandbox.
- **paperMoney.** Schwab calls it thinkorswim's virtual trading environment. Nothing read says API orders can reach it.
- **thinkorswim API.** No separate one was found; Schwab describes the Trader API as the connection to thinkorswim's data and functionality, while the wrapper's README calls the API unaffiliated with thinkorswim, trading the same accounts with only part of its functionality. Per the wrapper's docs, old TD Ameritrade apps are not valid, and saved orders and watchlists are gone.

Until your own portal account shows a test environment, exercise order code on a mock as above, and treat the first real order as a live action you approve by hand. How to structure those tests is in [how to test trading bots](https://fincodelabs.com/en/blog/how-to-test-trading-bots).

## Personal account use against software for others

The individual product covers your own accounts, while serving other people is a different product and a legal question.

The portal's sitemap lists [Trader API terms](https://developer.schwab.com/products/trader-api--individual/terms-and-conditions/trader-api-terms-and-conditions), which could not be read for this guide, so nothing here paraphrases them; read them before building, especially on showing market data to others, covered in [stock market data API](https://fincodelabs.com/en/blog/stock-market-data-api). schwab-py's own guide says its login flows are not for apps you distribute or run on a server for other users. Use the API rather than scraping Schwab's site. Order state and reconciliation are in [broker API integration](https://fincodelabs.com/en/blog/broker-api-integration-fix-rest).

Rules depend on your country, and account types and API access vary; no broker is recommended or ranked here. For readers in Turkey, the Capital Markets Board (SPK) said on 23 February 2023 that residents can trade leveraged forex only through institutions it has authorized, and Law No. 6362 provides access blocks for online offers from abroad (Article 99(4)) and criminal penalties for unauthorized activity (Article 109(2)).

Fincode quotes integration work through [exchange API integration](https://fincodelabs.com/en/services/exchange-api-integration) and claims no prior Schwab work.

## Frequently asked questions

### Does Charles Schwab have an API?

Yes. Its developer portal lists Trader API products for individuals and for commercial use. Individuals need a Schwab brokerage account and may create one app.

### Is schwab-py an official Schwab library?

No. It describes itself as an unofficial wrapper, not endorsed by or affiliated with Charles Schwab. Version 1.5.1 was released on 30 June 2025 under the MIT license.

### How long does a Schwab refresh token last?

schwab-py's documentation says seven days and that Schwab enforces it, so it may end sooner or later. The Schwab guides readable for this page give no figure.

### Can I avoid the weekly Schwab login?

schwab-py's documentation says there is currently no way to make a refresh token last longer than seven days. Schedule the login and alert on token age.

### Does Schwab have a paper trading API?

No Schwab page read here confirms one, and schwab-py's README says paper trading is not supported. The portal's general guide describes Sandbox apps set per line of business, and nothing public read here ties one to the individual Trader API.

### Is there a thinkorswim API?

No separate one was found. A Schwab article dated 14 August 2026 describes the Trader API as connecting thinkorswim's market data, account information and functionality to third-party AI applications; schwab-py's README calls the API unaffiliated with thinkorswim.

## Sources

- [Charles Schwab: 4 Ways AI Trading Tools Help Increase Efficiency (14 August 2026)](https://www.schwab.com/learn/story/ways-ai-trading-tools-help-increase-efficiency)
- [Schwab Developer Portal: Authenticate with OAuth](https://developer.schwab.com/user-guides/get-started/authenticate-with-oauth)
- [Schwab Developer Portal: OAuth Restart vs. Refresh Token](https://developer.schwab.com/user-guides/apis-and-apps/oauth-restart-vs-refresh-token)
- [Schwab Developer Portal: App Callback URL Requirements and Limitations](https://developer.schwab.com/user-guides/apis-and-apps/app-callback-url-requirements)
- [Schwab Developer Portal: About the Individual Developer Role](https://developer.schwab.com/user-guides/individual-developer/about-individual-developer-role)
- [Schwab Developer Portal: Requesting Product Access](https://developer.schwab.com/user-guides/get-started/requesting-product-access)
- [Schwab Developer Portal: Test in Sandbox](https://developer.schwab.com/user-guides/apis-and-apps/test-in-sandbox)
- [schwab-py documentation: Getting Started (community)](https://schwab-py.readthedocs.io/en/latest/getting-started.html)
- [schwab-py documentation: Authentication and Client Creation (community)](https://schwab-py.readthedocs.io/en/latest/auth.html)
- [schwab-py documentation: HTTP Client (community)](https://schwab-py.readthedocs.io/en/latest/client.html)
- [schwab-py documentation: Transitioning from TDAmeritrade (community)](https://schwab-py.readthedocs.io/en/latest/tda-transition.html)
- [PyPI: schwab-py 1.5.1 (unofficial, MIT)](https://pypi.org/project/schwab-py/)
- [SPK (Capital Markets Board): Notice on unauthorized leveraged transactions, 23 February 2023 (Turkish)](https://spk.gov.tr/duyurular/basin-duyurulari/2023/izinsiz-kaldiracli-islemlere-iliskin-basin-duyurusu)
- [Capital Markets Law No. 6362, consolidated text, Articles 99 and 109 (Turkish)](https://www.mevzuat.gov.tr/mevzuatmetin/1.5.6362.pdf)

> This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the [editorial policy](https://fincodelabs.com/en/editorial-policy).

---

Published by Fincode (Fincode Yazılım ve Teknoloji A.Ş.), a software company in Istanbul, Turkey: https://fincodelabs.com/en/blog/schwab-api-python

Cite as: Fincode, "Schwab API Python: OAuth, token expiry and first requests", updated November 6, 2026, https://fincodelabs.com/en/blog/schwab-api-python
