Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
coinbase-advanced-py
Advanced tools
Welcome to the official Coinbase Advanced API Python SDK. This python project was created to allow coders to easily plug into the Coinbase Advanced API. This SDK also supports easy connection to the Coinbase Advanced Trade WebSocket API.
Coinbase Advanced Trade offers a comprehensive API for traders, providing access to real-time market data, order management, and execution. Elevate your trading strategies and develop sophisticated solutions using our powerful tools and features.
For thorough documentation of all available functions, refer to the following link: https://coinbase.github.io/coinbase-advanced-py
pip3 install coinbase-advanced-py
This SDK uses Cloud Developer Platform (CDP) API keys. To use this SDK, you will need to create a CDP API key and secret by following the instructions here. Make sure to save your API key and secret in a safe place. You will not be able to retrieve your secret again.
WARNING: We do not recommend that you save your API secrets directly in your code outside of testing purposes. Best practice is to use a secrets manager and access your secrets that way. You should be careful about exposing your secrets publicly if posting code that leverages this library.
Optional: Set your API key and secret in your environment (make sure to put these in quotation marks). For example:
export COINBASE_API_KEY="organizations/{org_id}/apiKeys/{key_id}"
export COINBASE_API_SECRET="-----BEGIN EC PRIVATE KEY-----\nYOUR PRIVATE KEY\n-----END EC PRIVATE KEY-----\n"
In your code, import the RESTClient class and instantiate it:
from coinbase.rest import RESTClient
client = RESTClient() # Uses environment variables for API key and secret
If you did not set your API key and secret in your environment, you can pass them in as arguments:
from coinbase.rest import RESTClient
api_key = "organizations/{org_id}/apiKeys/{key_id}"
api_secret = "-----BEGIN EC PRIVATE KEY-----\nYOUR PRIVATE KEY\n-----END EC PRIVATE KEY-----\n"
client = RESTClient(api_key=api_key, api_secret=api_secret)
After creating your API key, a json file will be downloaded to your computer. It's possible to pass in the path to this file as an argument:
client = RESTClient(key_file="path/to/cdp_api_key.json")
We also support passing a file-like object as the key_file
argument:
from io import StringIO
client = RESTClient(key_file=StringIO('{"name": "key-name", "privateKey": "private-key"}'))
You can also set a timeout in seconds for your REST requests like so:
client = RESTClient(api_key=api_key, api_secret=api_secret, timeout=5)
You are able to use any of the API hooks to make calls to the Coinbase API. For example:
from json import dumps
accounts = client.get_accounts()
print(dumps(accounts.to_dict(), indent=2))
order = client.market_order_buy(client_order_id="clientOrderId", product_id="BTC-USD", quote_size="1")
print(dumps(order.to_dict(), indent=2))
This code calls the get_accounts
and market_order_buy
endpoints.
TIP: Setting client_order_id
to the empty string will auto generate a unique client_order_id per call.
However, this will remove the intended safeguard of accidentally placing duplicate orders.
Refer to the Advanced API Reference for detailed information on each exposed endpoint.
Look in the coinbase.rest
module to see the API hooks that are exposed.
Endpoints will return corresponding, custom class objects. This allows you to retrieve response object fields using dot-notation. Here is an example of how you can access a product's price via the Get Product endpoint:
product = client.get_product("BTC-USD")
print(product.price)
Dot-notation is only available for fields that are defined. Although all higher-level fields have been defined, not every nested field has. Fields that are not defined are still accessible using standard bracket notation.
For example, we make a call to List Accounts. We take the first account from the defined accounts
field and access the defined available_balance
field. Despite its nested fields not being explicitly defined and inaccessible via dot-notation, we can still access them manually using bracket notation, like:
accounts = client.get_accounts()
print(accounts.accounts[0].available_balance['value'])
Use kwargs
to pass in any additional parameters. For example:
kwargs = {
"param1": 10,
"param2": "mock_param"
}
product = client.get_product(product_id="BTC-USD", **kwargs)
You can make generic REST calls using the get
, post
, put
, and delete
methods. For example:
market_trades = client.get("/api/v3/brokerage/products/BTC-USD/ticker", params={"limit": 5})
portfolio = client.post("/api/v3/brokerage/portfolios", data={"name": "TestPortfolio"})
Here we are calling the GetMarketTrades and CreatePortfolio endpoints through the generic REST functions. Once again, the built-in way to query these through the SDK would be:
market_trades = client.get_market_trades(product_id="BTC-USD", limit=5)
portfolio = client.create_portfolio(name="TestPortfolio")
The Advanced API returns useful rate limit information in the response headers as detailed in our documentation. By initializing the RESTClient with the rate_limit_headers
field set to True, as shown below, these headers will be appended as fields to the API response body:
client = RESTClient(api_key=api_key, api_secret=api_secret, rate_limit_headers=True)
We offer a WebSocket API client that allows you to connect to the Coinbase Advanced Trade WebSocket API. Refer to the Advanced Trade WebSocket Channels page for detailed information on each offered channel.
In your code, import the WSClient class and instantiate it. The WSClient requires an API key and secret to be passed in as arguments. You can also use a key file or environment variables as described in the RESTClient instructions above.
You must specify an on_message
function that will be called when a message is received from the WebSocket API. This function must take in a single argument, which will be the raw message received from the WebSocket API. For example:
from coinbase.websocket import WSClient
api_key = "organizations/{org_id}/apiKeys/{key_id}"
api_secret = "-----BEGIN EC PRIVATE KEY-----\nYOUR PRIVATE KEY\n-----END EC PRIVATE KEY-----\n"
def on_message(msg):
print(msg)
client = WSClient(api_key=api_key, api_secret=api_secret, on_message=on_message)
In this example, the on_message
function simply prints the message received from the WebSocket API.
You can also set a timeout
in seconds for your WebSocket connection, as well as a max_size
in bytes for the messages received from the WebSocket API.
client = WSClient(api_key=api_key, api_secret=api_secret, on_message=on_message, timeout=5, max_size=65536) # 64 KB max_size
Other configurable fields are the on_open
and on_close
functions. If provided, these are called when the WebSocket connection is opened or closed, respectively. For example:
def on_open():
print("Connection opened!")
client = WSClient(api_key=api_key, api_secret=api_secret, on_message=on_message, on_open=on_open)
We offer a WebSocket User API client that allows you to connect to the Coinbase Advanced Trade WebSocket user channel and futures_balance_summary channel.
In your code, import the WSUserClient class instead of WSClient.
from coinbase.websocket import WSUserClient
api_key = "organizations/{org_id}/apiKeys/{key_id}"
api_secret = "-----BEGIN EC PRIVATE KEY-----\nYOUR PRIVATE KEY\n-----END EC PRIVATE KEY-----\n"
def on_message(msg):
print(msg)
client = WSUserClient(api_key=api_key, api_secret=api_secret, on_message=on_message)
Once you have instantiated the client, you can connect to the WebSocket API by calling the open
method, and disconnect by calling the close
method.
The subscribe
method allows you to subscribe to specific channels, for specific products. Similarly, the unsubscribe
method allows you to unsubscribe from specific channels, for specific products. For example:
# open the connection and subscribe to the ticker and heartbeat channels for BTC-USD and ETH-USD
client.open()
client.subscribe(product_ids=["BTC-USD", "ETH-USD"], channels=["ticker", "heartbeats"])
# wait 10 seconds
time.sleep(10)
# unsubscribe from the ticker channel and heartbeat channels for BTC-USD and ETH-USD, and close the connection
client.unsubscribe(product_ids=["BTC-USD", "ETH-USD"], channels=["ticker", "heartbeats"])
client.close()
We also provide channel specific methods for subscribing and unsubscribing. For example, the below code is equivalent to the example from above:
client.open()
client.ticker(product_ids=["BTC-USD", "ETH-USD"])
client.heartbeats(product_ids=["BTC-USD", "ETH-USD"])
# wait 10 seconds
time.sleep(10)
client.ticker_unsubscribe(product_ids=["BTC-USD", "ETH-USD"])
client.heartbeats_unsubscribe(product_ids=["BTC-USD", "ETH-USD"])
client.close()
The WebSocket client will automatically attempt to reconnect the WebSocket API if the connection is lost, and will resubscribe to any channels that were previously subscribed to.
The client uses an exponential backoff algorithm to determine how long to wait before attempting to reconnect, with a maximum number of retries of 5.
If you do not want to automatically reconnect, you can set the retry
argument to False
when instantiating the client.
client = WSClient(api_key=api_key, api_secret=api_secret, on_message=on_message, retry=False)
The WebSocket API client will raise exceptions if it encounters an error. On forced disconnects it will raise a WSClientConnectionClosedException
, otherwise it will raise a WSClientException
.
NOTE: Errors on forced disconnects, or within logic in the message handler, will not be automatically raised since this will be running on its own thread.
We provide the sleep_with_exception_check
and run_forever_with_exception_check
methods to allow you to catch these exceptions. sleep_with_exception_check
will sleep for the specified number of seconds, and will check for any exception raised during that time. run_forever_with_exception_check
will run forever, checking for exceptions every second. For example:
from coinbase.websocket import (WSClient, WSClientConnectionClosedException,
WSClientException)
client = WSClient(api_key=api_key, api_secret=api_secret, on_message=on_message)
try:
client.open()
client.subscribe(product_ids=["BTC-USD", "ETH-USD"], channels=["ticker", "heartbeats"])
client.run_forever_with_exception_check()
except WSClientConnectionClosedException as e:
print("Connection closed! Retry attempts exhausted.")
except WSClientException as e:
print("Error encountered!")
This code will open the connection, subscribe to the ticker and heartbeat channels for BTC-USD and ETH-USD, and will sleep forever, checking for exceptions every second. If an exception is raised, it will be caught and handled appropriately.
If you only want to run for 5 seconds, you can use sleep_with_exception_check
:
client.sleep_with_exception_check(sleep=5)
Note that if the automatic reconnection fails after the retry limit is reached, a WSClientConnectionClosedException
will be raised.
If you wish to implement your own reconnection logic, you can catch the WSClientConnectionClosedException
and handle it appropriately. For example:
client = WSClient(api_key=api_key, api_secret=api_secret, on_message=on_message, retry=False)
def connect_and_subscribe():
try:
client.open()
client.subscribe(product_ids=["BTC-USD", "ETH-USD"], channels=["ticker", "heartbeats"])
client.run_forever_with_exception_check()
except WSClientConnectionClosedException as e:
print("Connection closed! Sleeping for 20 seconds before reconnecting...")
time.sleep(20)
connect_and_subscribe()
The functions described above handle the asynchronous nature of WebSocket connections for you. However, if you wish to handle this yourself, you can use the async_open
, async_subscribe
, async_unsubscribe
, and async_close
methods.
We similarly provide async channel specific methods for subscribing and unsubscribing such as ticker_async
, ticker_unsubscribe_async
, etc.
For your convenience, we have provided a custom, built-in WebSocket response type object to help interact with our WebSocket feeds more easily.
Simply import it from the same module as you do the WSClient
:
from coinbase.websocket import WSClient, WebsocketResponse
Assume we simply want the price feed for BTC-USD and ETH-USD.
Like we did in previous steps, we subscribe to the ticker
channel and include 'BTC-USD' and 'ETH-USD' in the product_ids
list.
As the data comes through, it is passed into the on_message
function. From there, we use it to build the WebsocketResponse
object.
Using said object, we can now extract only the desired parts. In our case, we retrieve and print only the product_id
and price
fields, resulting in a cleaner feed.
from coinbase.websocket import WSClient, WebsocketResponse
def on_message(msg):
ws_object = WebsocketResponse(json.loads(msg))
if ws_object.channel == "ticker" :
for event in ws_object.events:
for ticker in event.tickers:
print(ticker.product_id + ": " + ticker.price)
client.open()
client.subscribe(product_ids=["BTC-USD", "ETH-USD"], channels=["ticker"])
time.sleep(10)
client.unsubscribe(product_ids=["BTC-USD", "ETH-USD"], channels=["ticker"])
client.close()
In the example, note how we first checked if ws_object.channel == "ticker"
.
Since each channel's event field has a unique structure and set of fields, it's important to ensure that the fields we access are actually present in the object.
For example, if we were to subscribe to the user
channel and try to access a field that does not exist in it, such as the tickers
field, we would be met with an error.
Therefore, we urge users to reference our documentation, which outlines the JSON object that each channel will return.
You can enable debug logging for the REST and WebSocket clients by setting the verbose
variable to True
when initializing the clients. This will log useful information throughout the lifecycle of the REST request or WebSocket connection, and is highly recommended for debugging purposes.
rest_client = RESTClient(api_key=api_key, api_secret=api_secret, verbose=True)
ws_client = WSClient(api_key=api_key, api_secret=api_secret, on_message=on_message, verbose=True)
Authentication of CDP API Keys is handled automatically by the SDK when making a REST request or sending a WebSocket message.
However, if you wish to handle this yourself, you must create a JWT token and attach it to your request as detailed in the API docs here. Use the built in jwt_generator
to create your JWT token. For example:
from coinbase import jwt_generator
api_key = "organizations/{org_id}/apiKeys/{key_id}"
api_secret = "-----BEGIN EC PRIVATE KEY-----\nYOUR PRIVATE KEY\n-----END EC PRIVATE KEY-----\n"
uri = "/api/v3/brokerage/orders"
jwt_uri = jwt_generator.format_jwt_uri("POST", uri)
jwt = jwt_generator.build_rest_jwt(jwt_uri, api_key, api_secret)
This will create a JWT token for the POST /api/v3/brokerage/orders
endpoint. Pass this JWT token in the Authorization
header of your request as:
"Authorization": "Bearer " + jwt
You can also generate JWTs to use with the Websocket API. These do not require passing a specific URI. For example:
from coinbase import jwt_generator
api_key = "organizations/{org_id}/apiKeys/{key_id}"
api_secret = "-----BEGIN EC PRIVATE KEY-----\nYOUR PRIVATE KEY\n-----END EC PRIVATE KEY-----\n"
jwt = jwt_generator.build_ws_jwt(api_key, api_secret)
Use this JWT to connect to the Websocket API by setting it in the "jwt" field of your subscription requests. See the docs here for more details.
Both clients contain public endpoints which can be accessed without authentication.
To do so, simply initialize the clients without providing any API keys as arguments.
Notes:
In the REST client, here is an example calling Get Public Products. It does not require authentication and is the public counterpart to Get Products, which does require authentication.
Both endpoints return the same data.
from coinbase.rest import RESTClient
client = RESTClient()
public_products = client.get_public_products()
print(json.dumps(public_products.to_dict(), indent=2))
Full list of all public REST endpoints here
Rate limit details for REST endpoints here
In the Websocket client, here is an example subscribing to the ticker channel. Unlike the REST client, Websocket channels handle both authenticated and unauthenticated requests. At the moment, most channels in the Websocket client are public and can be used without keys.
import time
from coinbase.websocket import WSClient
def on_message(msg):
print(msg)
client = WSClient(on_message=on_message)
client.open()
client.ticker(product_ids=["BTC-USD"])
time.sleep(10)
client.ticker_unsubscribe(product_ids=["BTC-USD"])
client.close()
Full list of all public Websocket channels here
Rate limit details for Websocket channels here
For a detailed list of changes, see the Changelog.
If you've found a bug within this project, open an issue on this repo and add the "bug" label to it. If you would like to request a new feature, open an issue on this repo and add the "enhancement" label to it. Direct concerns or questions on the API to the Advanced API Discord (use this invite link if it's your first time accessing the Discord).
FAQs
Coinbase Advanced API Python SDK
We found that coinbase-advanced-py demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.