Introduction

Interactive Brokers offers an OAuth 1.0a authentication procedure for licensed Financial Advisors, Organizations, IBrokers, and third party services. Beyond the initial authentication procedure, the OAuth implementation will behave the same as the standard Client Portal API and it’s endpoints.

OAuth and the Client Portal API

Interactive Brokers offers the Client Portal API alongside OAuth. It is important to note that all underlying functionality regarding trades, market data, and account information is done using the Client Portal API. The only unique separation between standard OAuth and the Client Portal API as documented is the method of authentication.

This will also trigger a difference in the base url of requests. Instead of sending your requests to localhost:5000 as with the Client Portal API, requests should instead be directed to api.ibkr.com.

First Party Oauth

Interactive Brokers classifies first party entities as institutions that will be trading on behalf of themselves or their institution. The same entity developing with the API platform will be the same entity that will be using it for trading.

Examples of first party entities include financial advisors, hedge funds, and organizations looking to trade their own capital.

For interested first party candidates, please create a web ticket with the API team with the following questions answered.

  1. What do you intend to do with OAuth access?
  2. Please list all accounts that will use the developed OAuth program.
  3. Will the client application be developed in-house or by a third-party developer?

The Self Service Portal

Approved groups using First Party OAuth will need to use the Self Service Portal in order to generate their consumer key, encryption keys, and access tokens. This link will be provided directly to approved entities during the onboarding process. This is an essential step in creating your program and following the steps listed below.

Important:

Registering a consumer key requires a midnight reset in order to function on your account. After saving your consumer key, please wait until the following day to test your application.

Third Party OAuth

Interactive Brokers classifies third party entities as any organization offering a platform or medium of trading to individuals outside of the organization. Please note that interested groups that would like to register as a third party with Interactive Brokers must have an established platform with other brokerage firms, or a full proof of concept with an integration using the Client Portal API.

Examples of this would include auto-traders or robo-advisors, public mobile applications, and groups forwarding market data.

For organizations that meet the criteria above and have an interest in integrating with Interactive Brokers, we would ask that you please fill out this form and email it to [email protected].

Onboarding Questionaire

Third Party Approval Process

To give you a sense for the process ahead:

  1. Our onboarding team conducts the initial vetting process. Once we have collected a sufficient amount of information, we will complete a preliminary review. Estimated time to complete this step is 2-3 weeks. Assuming we are able to proceed, we will send your application to our Compliance team for review.
  2. IBKR Compliance conducts an enhanced due diligence review on all third party applicants, followed by a three tier approval process. Estimated time to complete Compliance related reviews and tasks is 3-6 weeks.
  3. If Compliance approval is reached, our Legal team will generate the WebAPI agreement which we will send to you for review and signature. In parallel, we will ask you to provide public keys and a CallbackURL which will be required in to generate your consumer key and finalize the setup. Detailed instructions for this process will be provided once we reach this stage. Estimated time for our side to complete the aforementioned work and processes is 3-5 weeks.

The above timelines are all estimations and can vary. We recommend providing as much information as possible up front. Not doing so can exaggerate timelines.

During the enhanced due diligence reviews conducted by our Compliance teams, they will expect to see that 3rd Party Vendors looking to offer their services to our clients have a completed website with details on all of the features/services that will be provided and finalized details on their offering. This typically includes a clear user work flow for all components and details on their functionality, capability.

As mentioned previously, should Compliance approval be reached we would be able to generate a live consumer key for you and any significant changes to the offering after it has been approved (like addition of trading functionality) would require additional review and approval from our Compliance Teams before being offered to IBKR clients.

Please be aware we expect 3rd Parties offering automated trading solutions would hold applicable registration with financial authority in all regions they plan offer the service, unless you are able to provide support (i.e. a legal opinion) as to why the business provided would not require registration that location. Additionally, the offering will need to be reviewed and approved by Compliance teams in all regions you intend to support IBKR clients.

The above timelines are all estimations and can vary. We recommend providing as much information as possible up front. Not doing so can exaggerate timelines.

Sessions with OAuth

Interactive Brokers permits a single username to be signed in once at any given time. However, the Client Portal API permits users to log in without connecting to their brokerage session. This allows brokerage sessions to continue trading while non-brokerage sessions can perform certain actions such as requesting portfolio information without breaking existing trading sessions.

Background regarding CP Web API sessions:

  1. An IB username can only have one brokerage session (trading-enabled) session open at a time, i.e., it can only trade and use similarly restricted functionality on one platform (TWS, Client Portal, etc.) at any given moment, and switching to trading on a different platform entails closing the existing brokerage session and opening a new one from the new platform.
  2. Web API sessions in general are two-tiered:
    1. An “outer” prerequisite “read-only session” that is required to be active/valid in order to make any CP Web API request, though by itself it only permits access to non-/iserver endpoints.
    2. The “brokerage session”, established after the read-only, that permits access to trading, consumption of market data, and all other functionality behind /iserver endpoints.
  3. This two-tiered arrangement reflects the way our Client Portal website permits you to log into a read-only session for account management when that username is already logged into a brokerage session elsewhere (e.g., TWS), leaving the TWS brokerage session undisturbed.
  4. The CPAPI iframe actually behaves like the CP Gateway mentioned frequently in our CP Web API documentation (the Java-based reverse proxy tool for retail clients). The CPAPI iframe will attempt to establish a brokerage session automatically, as soon as a login occurs and the read-only session is created via SSO.

About those parameters:

  1. Sending compete=true will have this new attempt to open a brokerage session kick out a previous session for that username. You can omit the parameter entirely, in which case your attempt to open a brokerage session will fail if one already exists for that username. (Given what you described below, you would likely want to include compete=true in all requests – it’s only really useful if e.g. you believe the username may be in use elsewhere and do not want to disturb that session.)
  2. Sending publish=true is required for this request. It was added to this endpoint to streamline the process of opening brokerage sessions via CP Web API overall, as this previously required two separate requests to be made: first to /publish and the second to /init – now both actions are handled in this single request to /ssodh/init by way of the publish=true parameter.

You can read more about initializing sessions in the documentation for /iserver/auth/ssodh/init

Understanding Brokerage Sessions

All resources behind /iserver are accessible only with an active “brokerage” session. Some additional info:

  1. TWS being a trading platform requires that a username has trading permissions in order for it to be used to access TWS – there is no purely “read-only” TWS access
  2. The Client Portal website, on the other hand, contains all of the reporting and account management functionality, and consequently it is possible to log in to Client Portal with a read-only/no-trading-permissions username and access reports, portfolio info, etc.
  3. Iserver is effectively TWS running on IB infrastructure, and it serves all of the trading-permissions-required resources, hence the need for a brokerage session to access those endpoints
  4. As a result of #2 and #3, the CP Web API also has this two-tiered session arrangement: The first tier is the read-only Portal session, and the second tier is the brokerage session through which you can talk to /iserver and actually trade the account(s), etc.
  5. Once you have a live session token, you have a Portal session and can visit non-iserver resources. The additional /ssodh/init endpoint is used to subsequently open a brokerage session with our backend, through which you can access the protected /iserver endpoints.
  6. The brokerage session is associated with the credentials in use – your username – so you don’t need to select an account here. Rather, once you have access to a brokerage session, you can manipulate all accounts visible/accessible to the username in use.
  7. Non-iserver endpoints like /portfolio are served by different backend processes that do not require trading permissions and are accessible without a brokerage session

Understanding Interactive Brokers terminology

A brokerage session (or trading session) is established by a username (your credentials), which in turn has trading permissions for one or more accounts (the actual pools of equity). A single username can only have one trading (or “brokerage”) session active at a time. Permissions for trading in general/ for specific asset classes, market data subscriptions (and thus access to the subscribed feeds), etc. are carried by usernames, not the underlying accounts. Hence references to brokerage sessions refer to a logged-in username that is in contact with IBKR’s backend trading infrastructure.

OAuth Base URL

Interactive Brokers maintains multiple base URLs depending on the environment of interest. In our Client Portal API documentation, we use localhost:5000/v1/api as the base url. However, OAuth 1.0A allows for multiple.

Note: Please be aware that the Standard URL is recommended and is the intended use case for the OAuth 1.0A.

Title Description Primary URL Secondary URL
Standard The intended base url to be used for most OAuth 1.0A implementations using load balancing to
provide the optimal server connection for our clients.
api.ibkr.com/v1/api
New York Direct Routing Forego load balancing, and have traffic routed directly to the New York Primary/Secondary server. 1.api.ibkr.com/v1/api 2.api.ibkr.com/v1/api
Chicago Direct Routing Forego load balancing, and have traffic routed directly to the Chicago Primary/Secondary server. 3.api.ibkr.com/v1/api 4.api.ibkr.com/v1/api
Hong Kong Direct Routing Forego load balancing, and have traffic routed directly to the Hong Kong Primary/Secondary server. 5.api.ibkr.com/v1/api 6.api.ibkr.com/v1/api
Zug Direct Routing Forego load balancing, and have traffic routed directly to the Zug Primary/Secondary server. 7.api.ibkr.com/v1/api 8.api.ibkr.com/v1/api
Alpha Environment An alpha environment used to test new features. This should never be used in a production setting
unless specifically instructed to do so.
api.ibkr.com/alpha/api

The Authorization Header

OAuth requests require a header with the name “Authorization”. The authorization header must start with the string “OAuth”. Following “OAuth”, it has to contain the following key/value pairs separated by comma. All values should be percent-encoded.

realm
oauth_callback (only when getting request token)
oauth_consumer_key
oauth_nonce
oauth_signature
oauth_signature_method
oauth_timestamp
oauth_verifier (only when getting access token)
oauth_token (all requests except getting request token)
diffie_hellman_challenge (only when getting live session token)

 

Realm

The realm is set to “limited_poa” (when using a custom 9-character Consumer Key)
The realm is set to “test_realm” (when using TESTCONS, as the Consumer Key)

oauth_callback

This is only required when getting a request token, and should just be set to “oob”

oauth_nonce

The nonce needs to be a randomly generated, unique value. Each oauth request needs to have a different and unique nonce.

An example of how to generate a nonce is here: https://github.com/ddo/oauth-1.0a/blob/master/oauth-1.0a.js
Refer to the getNonce() method, found by searching for “OAuth.prototype.getNonce = function() {“

oauth_signature

The signature is the base string signed using a private signing key or live session token. More details about this value can be found in The OAuth Signature

oauth_signature_method

The method which we sign the base string. It is “RSA-SHA256” for the steps leading up to and including getting the live session token, and “HMAC-SHA256” when accessing protected resources.

oauth_timestamp

The timestamp in milliseconds of when the request is made.

oauth_verifier

After logging in with a user during the /authorize step, you will be redirected to a page with the verifier token in the URL as a query parameter. The
oauth_verifier value is the verifier token. This is only used for getting the access token and not required for any other request.

oauth_token

When getting the access token, this value is the same as the request token value. For all other requests (except request token requests where this is not required) this value is the access token.

diffie_hellman_challenge

This value is only required for the live session token step. Explanation on how to get it is below.

The OAuth Signature

The signature is acquired by signing the base string of the OAuth request using your private signing key and the OAuth signature method.

The Base String

The base string is a string generated based on the parameters of the OAuth request. The method the demo uses to build the base string can be found
here: https://github.com/ddo/oauth-1.0a/blob/master/oauth-1.0a.js. Search for “OAuth.prototype.getBaseString = function(request, oauth_data) {“.

The base string is in the following format:

[HTTP_METHOD]&[BASE_URL]&[PARAMETER_LIST]

 

There is a special case where there is a prepend string added to the front of the base string, only for live session token requests. In this case, the base string would be:

[PREPEND][HTTP_METHOD]&[BASE_URL]&[PARAMETER_LIST]

 

[HTTP_METHOD] = the HTTP method, such as GET, POST, DELETE etc.

[BASE_URL] = The percent encoded URL of the request, without any query parameters

[PARAMETER_LIST] = A lexicographically sorted list of key/value pairs including the authorization header pairs, query parameters and if the request
contains a body of type x-www-form-urlencoded, the body parameters. The list values are separated using the character ‘&’, then the list is percent
encoded. See section 1.2b.

[PREPEND] = A string needed to be included at the front of the base string. Unlike other parts of the base string, this value is not separated by &

Normal Base String Example

[HTTP_METHOD] = POST

[BASE_URL] = https%3A%2F%2Fapi.ibkr.com%2Fv1%2Fapi%2Foauth%2Fsession_token

[PARAMETER_LIST] = device_id%3DCCCCCC95%7C48-DF-37-57-33-80%26oauth_consumer_key%3DTESTCONS%26oauth_nonce%
3DmQfUqcZD3TjC5RNguaYVQwOXfFyCgt0m%26oauth_signature_method%3DRSA-SHA256%26oauth_timestamp%3D1605211475%
26oauth_token%3Deb31c080cc0bd45b2f55%26username%3D

POST&https%3A%2F%2Fapi.ibkr.com%2Fv1%2Fapi%2Foauth%2Fsession_token&device_id%3DCCCCCC95%7C48-DF-37-57-33-80%26oauth_consumer_key%3DTESTCONS%26oauth_nonce%3DmQfUqcZD3TjC5RNguaYVQwOXfFyCgt0m%26oauth_signature_method%3DRSA-SHA256%26oauth_timestamp%3D1605211475%26oauth_token%3Deb31c080cc0bd45b2f55%26username%3D

 

Live Session Token Base String

[PREPEND] = 901c5e47fc1abec4ae9b4747024ff4d3ba186f16522eaf823238f4cadbef9cdc

[HTTP_METHOD] = POST

[BASE_URL] = https%3A%2F%2Fapi.ibkr.com%2Fv1%2Fapi%2Foauth%2Flive_session_token

[PARAMETER_LIST] = device_id%3DCCCCCC95%7C48-DF-37-57-33-80%26diffie_hellman_challenge%
3D76ebad1411bb88283c3195498402ac238459ecfef83ab11c466a04e6cf7fb93c77a35e63f25055eae1720e702a0c218286d8dd00f04ae
40e42da57daa7f3a3d1560d2eada9cbb5b3d6f4e76d4d651c86bec396afeabdcb34fcc676bdcff017fa3ee5712198725b86a1a72e854d5da
17f8fc7f60801cecc6eeaa4892fae21effec8dc7d0514d0b7667d5c33ba32c502a908ddb7518a80f7221f76bde8b6e182d42c6ee30f925cd
c4e752c5907e0e9d3eeffc2e0ab314acb721fd83c45ca7ceba01e98c8e14623aeb19cad5a20093d937f29609f3336fa8214ba997f41c
c3a611d56128423229af7f01f8a21c6c9953c52f15c99d61cc472f05c4caf76183%26oauth_consumer_key%3DTESTCONS%
26oauth_nonce%3DHqx0Q3UxBdyEvo4I71bmAZ1lIj7LRRz7%26oauth_signature_method%3DRSA-SHA256%26oauth_timestamp%
3D1605211318%26oauth_token%3Deb31c080cc0bd45b2f

901c5e47fc1abec4ae9b4747024ff4d3ba186f16522eaf823238f4cadbef9cdcPOST&https%3A%2F%2Fapi.ibkr.com%2Fv1%2Fapi%2Foauth%2Flive_session_token&device_id%3DCCCCCC95%7C48-DF-37-57-33-80%26diffie_hellman_challenge%3D76ebad1411bb88283c3195498402ac238459ecfef83ab11c466a04e6cf7fb93c77a35e63f25055eae1720e702a0c218286d8dd00f04ae40e42da57daa7f3a3d1560d2eada9cbb5b3d6f4e76d4d651c86bec396afeabdcb34fcc676bdcff017fa3ee5712198725b86a1a72e854d5da17f8fc7f60801cecc6eeaa4892fae21effec8dc7d0514d0b7667d5c33ba32c502a908ddb7518a80f7221f76bde8b6e182d42c6ee30f925cdc4e752c5907e0e9d3eeffc2e0ab314acb721fd83c45ca7ceba01e98c8e14623aeb19cad5a20093d937f29609f3336fa8214ba997f41cc3a611d56128423229af7f01f8a21c6c9953c52f15c99d61cc472f05c4caf76183%26oauth_consumer_key%3DTESTCONS%26oauth_nonce%3DHqx0Q3UxBdyEvo4I71bmAZ1lIj7LRRz7%26oauth_signature_method%3DRSA-SHA256%26oauth_timestamp%3D1605211318%26oauth_token%3Deb31c080cc0bd45b2f

 

The Parameter List

The parameter list is a list of lexicographically sorted parameters in the following format: key=value&key=value&key=value

The list is then url encoded, so it would look something like this: key%3Dvalue%26key%3Dvalue%26key%3Dvalue

The method the demo uses to build the parameter list can be found here: https://github.com/ddo/oauth-1.0a/blob/master/oauth-1.0a.js. Search for “OAuth.prototype.getParameterString = function(request, oauth_data) {”

Parameters consist of the following:

  • They key/value pairs in the authorization header, such as oauth_signature_method, oauth_nonce, but NOT the oauth_signature
  • Any key/value pairs in the body of the request if the content-type of the request is x-www-urlencoded (so the body is excluded if it is of type JSON)

The Prepend

When we successfully request an access token, we are given an oauth_token_secret in the response. This secret must be decrypted using your private encryption key, and then converted to a hex value. The hex value of the decrypted secret is the prepend string we add in front of the base string when requesting a live session token.’

Generating The Signature

Once the base string has been successfully created, it must then be signed. When getting the request, access or live session token, the key used for signing is the private signing key, and the signature method is RSA-SHA256.

When accessing any other endpoint, which means any protected resource, the key used is the live session token as a byte array and the signature method is HMAC-SHA256.

The OAuth Process

It is important to note that the OAuth Process will begin and end differently depending on whether you are implementing first or third party access.

First Party OAuth users should skip to the Live Session Token

Request Token

For Third Party OAuth users to start the OAuth process, we must first get a request token.

To get a request token, an OAuth request to https://api.ibkr.com/v1/api/oauth/request_token must be made.

The request should be a POST request but with no body. Remember that an authorization header we mentioned has to be added. Refer to the example there.

This step is a good indicator of whether or not something is wrong with your OAuth request. If you are missing any portion of the authorization header, the response will tell you so. If something is wrong with either the base string or signature creation, then you will be met with a 401 response.

Important: This step is not necessary for First Party OAuth users. Please proceed to Requesting the Live Session Token.

/authorization step

After getting the request token, you must then redirect the user to https://www.interactivebrokers.com/authorize?oauth_token=REQUEST_TOKEN

Replace REQUEST_TOKEN with the request token you generated

After the user logs in, they will be redirected to a URL specified during consumer key creation, and there will be two query parameters in the URL:
oauth_token and oauth_verifier

oauth_token is the request token, and oauth_verifier is the verifier token required for the next step.

An example of url after the user logs in http://localhost:20000/?oauth_token=dc75fcf43e3752c1a1ce&oauth_verifier=f11e2c5d9b6d0624e

Important: This step is not necessary for First Party OAuth users. Please proceed to Requesting the Live Session Token.

Access Token

A POST OAuth request to https://api.ibkr.com/v1/api/oauth/access_token must now be made.

This time, oauth_verifier must be added to the authorization header, with the value being the verifier token retrieved from the previous step.

oauth_token must also be added to the authorization header, the value being the request token.

If the request succeeds, the response will contain two values: oauth_token and oauth_token_secret.

The oauth_token in the response is the access token, and the oauth_token_secret will be used for the next step.

The method for getting the access token in the demo can be found in src/ib/ib-oauth-settings.vue. Search for “accessTokenRequest: function”.

Important: This step is not necessary for First Party OAuth users. Please proceed to Requesting the Live Session Token.

Live Session Token

The final step in the OAuth authorization process is the live session token. A POST OAuth request to https://api.ibkr.com/v1/api/oauth/live_session_token must be made.

If you are an IB customer who registered using the Self-Service OAuth page then on that same page you should have completed the Access Token step. You would now proceed to this final step to complete the OAuth authorization process.

In this step we must calculate a Diffie-Hellman challenge using the prime and generator in the Diffie-Hellman spec provided when registering your consumer key.

Diffie-Hellman Challenge

To calculate the Diffie-Hellman challenge, we must use the following formula

A is the Diffie-Hellman challenge

g is the Diffie-Hellman generator

p is the Diffie-Hellman prime

a is the Diffie-Hellman random value, randomly generated

The calculation can be see in the Live Session Token request

Prepend

For live session token requests, we have to add a prepend to the base string.

The prepend in this case is the decrypted oauth_token_secret received from the access token step.

oauth_token_secret needs to be decrypted using your private encryption key with scheme pkcs1. The result needs to be recorded as a hex value.

The prepend is therefore the hex value of oauth_token_secret decrypted.

The Response

If the live session token request is successful, the response will contain a value “diffie_hellman”response”. This diffie_hellman_response is used to calculate the live session token. diffie_hellman_response will be in hex form.

Calculation of the Live Session Token

To calculate a live session token, we must first calculate to value of K.

  • B is the diffie_hellman_response
  • a is the Diffie-Hellman random value from step 2.4a
  • p is the Diffie-Hellman prime

To calculate the live session token, we must use K as the key to signing oauth_token_secret (as a byte array) from the access token response. The
method of signing is HMAC-SHA1. The result is the live session token.

Remember that when using the live session token to sign requests, it must be a byte array.

Important:

When K is a byte array, it MUST have a leading zero bit denoting the sign. This is because Java's BigInteger's toByteArray() method always includes a sign bit. In our case, the sign bit will always be 0 because the value of K is always positive.

If your conversion of K to a byte array does not include this sign bit, please make sure to add it before using K to calculate the live session
token. Otherwise, it will be wrong. So if you are using any sort of library that includes this function, such as Java’s BigInteger, then you don’t need to
worry about this.

An easy way to ensure your K byte array includes a sign bit, convert K to bits. If there is no remainder after dividing the length of bits by 8, then you need
to manually add a 0 byte at the beginning of the K byte array.
This is because when the number of bits is divisible by 8, then adding an extra sign bit would result in one extra byte in the array.

LST Calculation Example 1

In this example, we produce K with 8 total digits. Given this value is divisible by 8, we must prepend 0 to the beginning to the byte array in order to guarantee our sign bit.

K = 0xff
K in bytes = [255]
K in bits = 11111111
K in bits length = 8
8 % 8 = 0
K byte array = [0, 255]

 

LST Calculation Example 2

Meanwhile, this example shows K with 7 total digits. No longer divisible by 8, we must do not need to add the sign bit as this will be produced automatically.

K = 0x7f
K in bytes = [127]
K in bits = 1111111
K in bits length = 7
7 % 8 = 7
Result K byte array = [127]

 

Verification of the Live Session Token

In the live session token response, there is a value called “live_session_token_signature”. The purpose of this is to verify whether or not the live session
token you calculated was correct.

To verify, create an HMAC-SHA1 hash of your consumer key in bytes, with the live session token you calculated in bytes as the signing key. Then convert
the result to hex format. If hex result is the same as live_session_token_signature, the live session token you calculated is correct. Now that you have a live session token, non-brokerage endpoints (non-/iserver endpoints) are now accessible.

In order to access /iserver endpoints, users will need to initialize their brokerage session.

SSODH Init Request

An OAuth protected-resource POST request must be made to https://api.ibkr.com/v1/api/iserver/auth/ssodh/init

There are two required parameters: compete and publish.

“compete” is whether or not the session should compete, usually set to false, but can be set to true if you want to disconnect other sessions.

“publish” must be set to true.

The parameters can be sent as request parameters or in the POST body. If sent in the body, the body must be of type x-www-form-urlencoded.

If the request is successful, the response will contain a JSON object telling you if you are authenticated and connected.

OAuth-Specific Endpoints

The endpoints included below require some structure to implement. As such, we have included a list of variables and packages referenced throughout the code samples. These requests are structured using the TESTCONS consumer keys. These can be adjusted or modified with the First Party OAuth implementation or a consumer key of your own.

Please note these are sample references not intended for production use. The underlying structure for these requests are listed above in The OAuth Process section.

key_dir = "C:\\web.demo\\keys\"

with open(key_dir+"private_encryption.pem", "r") as f:
    encryption_key = RSA.import_key(f.read())

with open(key_dir+"private_signature.pem", "r") as f:
    signature_key = RSA.import_key(f.read())

dh_prime = "f51d7ab737a452668fd8b5eec12fcdc3c01a0744d93db2e9b1dc335bd2551ec67e11becc60c33a73497a0f7c086d87e45781ada35b7af72708f31ae221347a1c6517575a347df83a321d05450547ee13a8182280ed81423002aa6337b48a251d840bfdabe8d41b8109284933a6c33bc6652ea9c7a5fd6b4945b7b39f1d951ae19b9192061e2f9de84768b67c425258724cdb96975917cabdea87e7e0bc72b01a331d06f2f34229a5ec742b399fcffa510bf6b8f9b5bf9858f058371a49aa4f950f7fbfb3f47710af34baa83fff1b467d38d0e6b1b0a2d117f178cf930d7dfdcc8f6755a2229d48492a967f493041121e382b9e87ca1368c09f54e6352d909f2b"
dh_generator = 2
consumer_key = "TESTCONS"
realm = "test_realm"
callback = "oob"

 

# Python Imports Used
import json
import requests
import random
import base64
from datetime import datetime
from urllib.parse import quote, quote_plus
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5 as PKCS1_v1_5_Signature
from Crypto.Cipher import PKCS1_v1_5 as PKCS1_v1_5_Cipher
from Crypto.Hash import SHA256, HMAC, SHA1
import websocket
import time

 

Request Token

Obtain a request token. See section 6.1 of the OAuth v1.0a specification for more information.

Endpoint

Note we do not return an oauth_token_secret in the response as we are using RSA signatures rather than PLAINTEXT authentication.

Important: This step is not necessary for First Party OAuth users. Please proceed to Requesting the Live Session Token.

POST /oauth/request_token

 

OAuth Params

oauth_consumer_key: String. Required
The 25-character hexadecimal string that was obtained from Interactive Brokers during the OAuth consumer registration process.

oauth_signature_method: String. Required
The signature method used to sign the request. Currently only ‘RSA-SHA256’ is supported.

oauth_signature: String. Required
The signature for the request generated using the method specified in the oauth_signature_method parameter. See section 9 of the OAuth v1.0a specification for more details on signing requests.

oauth_timestamp: String. Required
Timestamp expressed in seconds since 1/1/1970 00:00:00 GMT. Must be a positive integer and greater than or equal to any timestamp used in previous requests.

oauth_nonce: String. Required
A random string uniquely generated for each request.

oauth_callback: String. Required
An absolute URL to which IB will redirect the user. This URL is provided by the consumer during registration. This parameter must be set to ‘oob’.

{
  "oauth_consumer_key": "string",
  "oauth_signature_method": "string",
  "oauth_signature": "string",
  "oauth_timestamp": "string",
  "oauth_nonce": "string",
  "oauth_callback": "string"
}

 

Request Structure

url = f'https://api.ibkr.com/v1/api/oauth/request_token'
oauth_params = {
  "oauth_callback": {{oauth_callback }},
  "oauth_consumer_key": {{consumer_key}},
  "oauth_nonce": hex(random.getrandbits(128))[2:],
  "oauth_signature_method": "RSA-SHA256",
  "oauth_timestamp": str(int(datetime.now().timestamp())),
  }
params_string = "&".join([f"{k}={v}" for k, v in sorted(oauth_params.items())])

# Base string successfully created
base_string = f"POST&{quote_plus(url)}&{quote(params_string)}"

# Base string should then signed with the private key in RSA-SHA256
encoded_base_string = base_string.encode("utf-8")
sha256_hash = SHA256.new(data=encoded_base_string)
bytes_pkcs115_signature = PKCS1_v1_5_Signature.new(
  rsa_key=signature_key
  ).sign(msg_hash=sha256_hash)
b64_str_pkcs115_signature = base64.b64encode(bytes_pkcs115_signature).decode("utf-8")

# Establish the authorization header
oauth_params["oauth_signature"] = quote_plus(b64_str_pkcs115_signature)
oauth_params["realm"] = realm
oauth_header = "OAuth " + ", ".join([f'{k}="{v}"' for k, v in sorted(oauth_params.items())])
headers = {"authorization": oauth_header}
headers["User-Agent"] = "python/3.11"

rToken_request = requests.post(url=url, headers=headers)
rToken = rToken_request.json()["oauth_token"]

 

Response Object

oauth_token: String.
Resulting oauth or access token used as an encoded authentication value.

{
  "oauth_token": "b9082d68cfef06b030de"
}

 

Authorization

After retrieving our request token, we need to authorize the value against the Interactive Brokers server. This is done by directing users to https://interactivebrokers.com/authorize?oauth_token={{ Request Token }} where they will log in with their Interactive Brokers credentials. Because we are using “oob” as our callback url, users will be presented with an error page.

Our verifier token is returned in the url; however, this value is returned directly for fully implemented third parties who have established their redirect URL with the Interactive Brokers Onboarding team.

Authorization URL

Direct the client to the Interactive Brokers authorize uri where they can then log in to establish the request token to their user.

https://interactivebrokers.com/authorize?oauth_token={{ Request Token }}

 

Request

For our programmatic implementation without an official callback url, we can introduce a simple line to direct the user to the login page and save the verifier token for later.

url = f'https://interactivebrokers.com/authorize?oauth_token={rToken}'
vToken = input(f"Please log in to {url} and paste the 'oauth_verifier' value here: ")

 

Response

A successful login will direct them to the callback url, which displays both the oauth_token (request token) and the oauth_verifier (verifier token). This can be pasted back to our original request, and saved as a variable for the Access Token request.

The /authorize response

Access Token

Obtain an access token using the request token and the verification code you received after the user provided authorization. See section 6.1 of the OAuth v1.0a specification for more information.

Endpoint

Note we do not return an oauth_token_secret in the response as we are using RSA signatures rather than PLAINTEXT authentication.

Important: This step is not necessary for First Party OAuth users. Please proceed to Requesting the Live Session Token.

POST /oauth/access_token

 

OAuth Params

oauth_consumer_key: String. Required
The 25-character hexadecimal string that was obtained from Interactive Brokers during the OAuth consumer registration process.

oauth_token: String. Required
The request token obtained from IB via /request_token.

oauth_signature_method: String. Required
The signature method used to sign the request. Currently only ‘RSA-SHA256’ is supported.

oauth_signature: String. Required
The signature for the request generated using the method specified in the oauth_signature_method parameter. See section 9 of the OAuth v1.0a specification for more details on signing requests.

oauth_timestamp: String. Required
Timestamp expressed in seconds since 1/1/1970 00:00:00 GMT. Must be a positive integer and greater than or equal to any timestamp used in previous requests.

oauth_nonce: String. Required
A random string uniquely generated for each request.

oauth_verifier: String. Required
The verification code received from IB after the user has granted authorization.

{
  "oauth_consumer_key": "string",
  "oauth_token": "string",
  "oauth_signature_method": "string",
  "oauth_signature": "string",
  "oauth_timestamp": "string",
  "oauth_nonce": "string",
  "oauth_verifier": "string"
}

 

Request Structure

url = f'https://api.ibkr.com/v1/api/oauth/access_token'
oauth_params = {
  "oauth_callback":callback,
  "oauth_consumer_key": consumer_key,
  "oauth_nonce": hex(random.getrandbits(128))[2:],
  "oauth_signature_method": "RSA-SHA256",
  "oauth_timestamp": str(int(datetime.now().timestamp())),
  "oauth_token": rToken,
  "oauth_verifier": vToken,
  }
params_string = "&".join([f"{k}={v}" for k, v in sorted(oauth_params.items())])

# Base string successfully created
base_string = f"POST&{quote_plus(url)}&{quote(params_string)}"

# Base string should then signed with the private key in RSA-SHA256
encoded_base_string = base_string.encode("utf-8")
sha256_hash = SHA256.new(data=encoded_base_string)
bytes_pkcs115_signature = PKCS1_v1_5_Signature.new(
  rsa_key=signature_key
  ).sign(msg_hash=sha256_hash)
b64_str_pkcs115_signature = base64.b64encode(bytes_pkcs115_signature).decode("utf-8")

# Establish the authorization header
oauth_params["oauth_signature"] = quote_plus(b64_str_pkcs115_signature)
oauth_params["realm"] = realm
oauth_header = "OAuth " + ", ".join([f'{k}="{v}"' for k, v in sorted(oauth_params.items())])
headers = {"authorization": oauth_header}
headers["User-Agent"] = "python/3.11"

# Send the request and save the tokens to variables
atoken_request = requests.post(url=url, headers=headers)
aToken = atoken_request.json()["oauth_token"]
aToken_secret = atoken_request.json()["oauth_token_secret"]

 

Response Object

Response Object

oauth_token: String.
Resulting oauth or access token used as an encoded authentication value.

oauth_token_secret: String.
Resulting access token secret used as an encoded authentication value.

{
  "oauth_token": "string",
  "oauth_token_secret": "string"
}

 

Live Session Token

In order to access protected IB resources, a live session token is required. This endpoint allows consumers to obtain a live session token to access these resources using an OAuth access token and the Diffie-Hellman prime and generator supplied during the registration process. Note this is an additional IB-specific step, and not part of the OAuth v1.0a specification. Please refer to the “OAuth at Interactive Brokers” document for more details. https://www.interactivebrokers.com/webtradingapi/oauth.pdf

Endpoint

The live session token will allow the user to access their API, for trading or for portfolio access, over a 24 hour period. The creation of the Live Session Token does not establish a complete trading session, as that would be handled by Initializing the Brokerage Session.

POST /oauth/live_session_token

 

OAuth Params

oauth_consumer_key: String. Required
The 25-character hexadecimal string that was obtained from Interactive Brokers during the OAuth consumer registration process.

oauth_token: String. Required
The request token obtained from IB via /request_token.

oauth_signature_method: String. Required
The signature method used to sign the request. Currently only ‘RSA-SHA256’ is supported.

oauth_signature: String. Required
The signature for the request generated using the method specified in the oauth_signature_method parameter. See section 9 of the OAuth v1.0a specification for more details on signing requests.

oauth_timestamp: String. Required
Timestamp expressed in seconds since 1/1/1970 00:00:00 GMT. Must be a positive integer and greater than or equal to any timestamp used in previous requests.

oauth_nonce: String. Required
A random string uniquely generated for each request.

diffie_hellman_challenge: String. Required
Challenge value calculated using the Diffie-Hellman prime and generated provided during the registration process. See the “OAuth at Interactive Brokers” document for more details.

{
  "oauth_consumer_key": "string",
  "oauth_token": "string",
  "oauth_signature_method": "string",
  "oauth_signature": "string",
  "oauth_timestamp": "string",
  "oauth_nonce": "string",
  "diffie_hellman_challenge": "string"
}

 

Request

dh_random = str(random.getrandbits(256))
dh_unchallenged = pow(dh_generator, int(dh_random, 16), int(dh_prime, 16))
dh_challenge = hex(dh_unchallenged)[2:]
bytes_decrypted_secret = PKCS1_v1_5_Cipher.new(
  key=encryption_key
  ).decrypt(
    ciphertext=base64.b64decode(access_token_secret), 
    sentinel=None,
    )
prepend = bytes_decrypted_secret.hex()
base_string = prepend
method = 'POST'
url = f'https://api.ibkr.com/v1/api/oauth/live_session_token'
oauth_params = {
  "oauth_consumer_key": consumer_key,
  "oauth_nonce": hex(random.getrandbits(128))[2:],
  "oauth_timestamp": str(int(datetime.now().timestamp())),
  "oauth_token": access_token,
  "oauth_signature_method": "RSA-SHA256",
  "diffie_hellman_challenge": dh_challenge,
}
params_string = "&".join([f"{k}={v}" for k, v in sorted(oauth_params.items())])
base_string += f"{method}&{quote_plus(url)}&{quote(params_string)}"
encoded_base_string = base_string.encode("utf-8")
sha256_hash = SHA256.new(data=encoded_base_string)
bytes_pkcs115_signature = PKCS1_v1_5_Signature.new(
  rsa_key=signature_key
  ).sign(msg_hash=sha256_hash)
b64_str_pkcs115_signature = base64.b64encode(bytes_pkcs115_signature).decode("utf-8")
oauth_params['oauth_signature'] = quote_plus(b64_str_pkcs115_signature)
oauth_params["realm"] = realm
oauth_header = "OAuth " + ", ".join([f'{k}="{v}"' for k, v in sorted(oauth_params.items())])
headers = {"Authorization": oauth_header}
headers["User-Agent"] = "python/3.11"

'''
Make the request, and manage any potential fails
If your request fails, it may be possible the OAuth verifier was copied incorrectly.
It is also important to confirm the signature and encryption keys are valid and correspond to the dh_prime.
'''
lst_request = requests.post(url=url, headers=headers)
lst_response = json.dumps(lst_request.json())
if lst_request.status_code != 200:
  print(f"ERROR: Request to /live_session_token failed. Exiting...")
  raise SystemExit(0)

response_data = lst_request.json()
dh_response = response_data["diffie_hellman_response"]
lst_signature = response_data["live_session_token_signature"]
lst_expiration = response_data["live_session_token_expiration"]

 

Response

diffie_hellman_response: String.
Response based on the calculated Diffie Hellman challenge.
The full value should be 512 characters long.

live_session_token_signature: String.
Signature value used to prove authenticated status for subsequent requests.

live_session_token_expiration: Number.
Returns the epoch timestamp of the live session token’s expiration.
The live session token is valid for approximately 24 hours after creation.
 

{
  "diffie_hellman_response": "62933e{...}d64d6db34d",
"live_session_token_signature": "9bd5922b2b79effef23c6fb03cc715dcdc8d6219",
 "live_session_token_signature": "9bd5922b2b79effef23c6fb03cc715dcdc8d6219",
"live_session_token_expiration": 1700691802316
}

 

Compute Live Session Token

prepend_bytes = bytes.fromhex(prepend)

# To calculate a live session token, we must first calculate to value of K.
INT_BASE = 16
B = int(dh_response, INT_BASE)
a = int(dh_random, INT_BASE)
p = int(dh_prime, INT_BASE)
K = pow(B, a, p)
hex_str_K = hex(K)[2:]

# To calculate the live session token, we must use K as the key to signing oauth_token_secret (as a byte array) from the access token response.
if len(hex_str_K) % 2:
  print("adding leading 0 for even number of chars")
  hex_str_K = "0" + hex_str_K
hex_bytes_K = bytes.fromhex(hex_str_K)
if len(bin(K)[2:]) % 8 == 0:
  hex_bytes_K = bytes(1) + hex_bytes_K

# The method of signing is HMAC-SHA1. The result is the live session token.
bytes_hmac_hash_K = HMAC.new(
  key=hex_bytes_K,
  msg=prepend_bytes,
  digestmod=SHA1,
  ).digest()

computed_lst = base64.b64encode(bytes_hmac_hash_K).decode("utf-8")

hex_str_hmac_hash_lst = HMAC.new(
  key=base64.b64decode(computed_lst),
  msg=consumer_key.encode("utf-8"),
  digestmod=SHA1,
).hexdigest()

# Remember that when using the live session token to sign requests, it must be a byte array.
if hex_str_hmac_hash_lst == lst_signature:
  live_session_token = computed_lst
  lst_expiration = lst_expiration
  print("Live session token computation and validation successful.")
  print(f"LST: {live_session_token}; expires: {datetime.fromtimestamp(lst_expiration/1000)}\n")
else:
  print(f"ERROR: LST validation failed. Exiting...")
  raise SystemExit(0)

 

Initialize sesion

Endpoint

After retrieving the access token and subsequent Live Session Token, customers can initialize their brokerage session with the ssodh/init endpoint.

NOTE: This is essential for using all /iserver endpoints, including access to trading and market data,

POST /iserver/auth/ssodh/init

 

OAuth Params

oauth_consumer_key: String. Required
The 25-character hexadecimal string that was obtained from Interactive Brokers during the OAuth consumer registration process.

oauth_token: String. Required
The request token obtained from IB via /request_token.

oauth_signature_method: String. Required
The signature method used to sign the request. Currently only ‘RSA-SHA256’ is supported.

oauth_signature: String. Required
The signature for the request generated using the method specified in the oauth_signature_method parameter. See section 9 of the OAuth v1.0a specification for more details on signing requests.

oauth_timestamp: String. Required
Timestamp expressed in seconds since 1/1/1970 00:00:00 GMT. Must be a positive integer and greater than or equal to any timestamp used in previous requests.

oauth_nonce: String. Required
A random string uniquely generated for each request.

{
  "oauth_consumer_key": consumer_key,
  "oauth_token": access_token,
  "oauth_signature_method": "HMAC-SHA256",
  "oauth_timestamp": "string",
  "oauth_nonce": "string"
}

 

Request

method = "POST"
url = f'https://api.ibkr.com/v1/api/iserver/auth/ssodh/init?publish=true&compete=true'
oauth_params = {
    "oauth_consumer_key": consumer_key,
    "oauth_nonce": hex(random.getrandbits(128))[2:],
    "oauth_signature_method": "HMAC-SHA256",
    "oauth_timestamp": str(int(datetime.now().timestamp())),
    "oauth_token": access_token
  }
params_string = "&".join([f"{k}={v}" for k, v in sorted(oauth_params.items())])
base_string = f"{method}&{quote_plus(url)}&{quote(params_string)}"
bytes_hmac_hash = HMAC.new(
  key=base64.b64decode(live_session_token), 
  msg=base_string.encode("utf-8"),
  digestmod=SHA256
  ).digest()
b64_str_hmac_hash = base64.b64encode(bytes_hmac_hash).decode("utf-8")
oauth_params["oauth_signature"] = quote_plus(b64_str_hmac_hash)
oauth_params["realm"] = realm
oauth_header = "OAuth " + ", ".join([f'{k}="{v}"' for k, v in sorted(oauth_params.items())])
headers = {"Authorization": oauth_header}
headers["User-Agent"] = "python/3.11"
init_request = requests.post(url=url, headers=headers)
init_response = json.dumps(init_request.json(), indent=2)
print(init_response)

 

Response

Response Object

authenticated: bool.
Returns whether your brokerage session is authenticated or not.

competing: bool.
Returns whether you have a competing brokerage session in another connection.

connected: bool.
Returns whether you are connected to the gateway, authenticated or not.

message: String.
If there is a message about your authenticate status, it will be returned here.
Authenticated sessions return an empty string.

MAC: String.
IBKR MAC information. Internal use only.

serverInfo: Object.

serverName: String.
IBKR server information. Internal use only.

serverVersion: String.
IBKR version information. Internal use only.

{
  "authenticated":authenticated,
  "competing":competing,
  "connected":connected,
  "message":"message",
  "MAC":"MAC",
  "serverInfo":{
    "serverName":"serverName",
    "serverVersion":"serverVersion"
  },
}

 

Websockets Over OAuth

In some ways, we see that websockets will behave differently from how they function for the conventional Client Portal Gateway connection. This would be in addition to the standard connection process, including sending the session token as a cookie or through the websocket upon opening. To read more about this behavior, please see the Client Portal Websocket Connection Guide.

Please keep in mind that the CPAPI Websocket Topics themselves will remain consistent with OAuth.

OAuth users, both First and Third party, must include the additional parameters of “oauth_token” which should be set to the user’s access token.

wss://api.ibkr.com/v1/api/ws?oauth_token={{access_token}}