Skip to main content
This article is for educational purposes only and does not constitute financial advice. Trading involves risk of loss. Past performance does not guarantee future results. Consult a licensed financial advisor before making investment decisions.
AI & Automation16 min readUpdated October 3, 2026
TW

Coinbase Advanced Trade API Bot in Python: A Beginner's Guide

Build a Coinbase Python bot preview: validate product limits with Decimal, persist decision IDs, and inspect SDK authentication and sandbox limitations.

Put this into practice with a watchlist

Build a watchlist, then review each signal’s entry, stop, target, and reasoning. Broker access is optional.

Build a Watchlist

What a Coinbase API Trading Bot Needs

A Coinbase API trading bot combines market data, written decision rules, request validation and order-state handling. Use the official coinbase-advanced-py SDK to handle Coinbase Advanced Trade REST authentication and request shapes. Start with product and preview checks, not an order-submitting loop. This guide's runnable example defaults to an invented offline fixture; its optional authenticated mode reads product information and requests an order preview. It never creates or cancels an order. This is educational material, not financial advice, and it does not establish profitability or readiness for funded trading.

Coinbase Pro keys and passphrases belong to the retired Pro API. For Advanced Trade, follow the current CDP key authentication guide. This example supports only BTC-USD spot limit previews; do not assume every product ID is a spot crypto pair.

Create CDP API Keys With Least Privilege

Create the key through the Coinbase Developer Platform portal. Select the relevant Coinbase App/Advanced Trade restrictions, permitted portfolio and optional IP allowlist. Copy the key name and secret securely when issued; downloading the key JSON is optional. Never put a real secret in source code, screenshots or a public message.

For the optional authenticated preview in this guide, grant view only. The Advanced Trade scope table assigns view to Get Product and POST /orders/preview; Create Order and Cancel Orders require trade. This program needs neither trade nor transfer. Public product/candle methods require no key, but this example uses the authenticated product endpoint to request tradability information.

The key name has the form organizations/{org_id}/apiKeys/{key_id}. Preserve the exact secret format and newlines. There is a current documentation distinction: the Coinbase App authentication page specifies ECDSA, while the official SDK README recommends Ed25519 and documents support for both signing algorithms. Signing support does not prove that a particular endpoint accepts that key. Follow the endpoint's current requirements and verify a permitted read request; do not diagnose every 401 as a key-type problem.

The SDK reads COINBASE_API_KEY and COINBASE_API_SECRET when constructing RESTClient() without explicit keys. Use a secrets manager or protected environment configuration. No credentials are needed for the default offline mode.

How JWT Authentication Works

Private REST requests use a signed Bearer JWT scoped to the request method and path. Coinbase documents a two-minute JWT lifetime and a new token for each unique request. The SDK signs requests as it sends them. Public data methods and the static sandbox do not prove private JWT authentication.

Keep the signing machine's clock synchronized. Clock skew, the wrong endpoint, insufficient permissions, mismatched key restrictions or a malformed secret can all cause authentication errors. Inspect the documented response and requested method/path without logging a private key or token.

The Sandbox Is Static

The Advanced Trade sandbox at https://api-sandbox.coinbase.com/api/v3/brokerage/{resource} needs no authentication and returns predefined mocked responses. It documents Accounts and Orders workflows and selected additional endpoints. It does not simulate fills from a live order book.

CheckWhat it establishesWhat it does not establish
Offline fixtureDecimal math, request shape and local decision recordCurrent product rules, authentication or fees
Static sandboxParsing documented mocked responsesJWT validity, actual liquidity or strategy results
Authenticated product readProduct fields visible to that key at that momentAccount buying power or order acceptance
Authenticated order previewCurrent preview response, estimates and validation messagesA created order, reserved liquidity, a fill or guaranteed costs

Public products and candles can support research. Cache rules, historical aggregation and product prices differ from an executable bid/ask quote. A local simulator must state its own fill and cost assumptions; it is not a Coinbase paper account. Treat any separately submitted production order as funded execution.

Validate Products and Preview a Limit Request

Use Python 3.11+ and install the tested SDK version:

python -m pip install coinbase-advanced-py==1.8.4

Append the following three blocks in order to coinbase_preview.py. Run python coinbase_preview.py first. It makes no network call: all product values, the $25 budget and $50,000 limit price are invented teaching inputs, not current Coinbase limits, market data or a recommendation.

The Get Product reference supplies base, quote and price increments; base and quote minimum/maximum sizes; and product status/type flags. This example rejects missing/ambiguous flags, unsupported product types and nonfinite numbers. It rounds down to actual increment multiples rather than merely counting decimal places, then checks the result against the sample budget and both size ranges. Its positive input ceiling and precision limit are arbitrary defensive teaching bounds, not Coinbase rules. The budget excludes fees; it is not an account balance or maximum-loss cap.

import argparse
import json
import os
import uuid
from decimal import Decimal, InvalidOperation, ROUND_CEILING
from pathlib import Path

from coinbase.rest import RESTClient

# Invented fixture, not a current product response or market quote.
PRODUCT = {
    "product_id": "BTC-USD",
    "product_type": "SPOT",
    "status": "online",
    "base_increment": "0.00001",
    "price_increment": "0.05",
    "quote_increment": "0.01",
    "base_min_size": "0.00001",
    "base_max_size": "1",
    "quote_min_size": "1",
    "quote_max_size": "1000",
    "is_disabled": False,
    "trading_disabled": False,
    "cancel_only": False,
    "view_only": False,
    "auction_mode": False,
    "limit_only": False,
    "post_only": False,
}


def positive(value):
    if not isinstance(value, (str, Decimal)):
        raise ValueError("Use decimal strings, not floats or booleans")
    try:
        value = Decimal(value)
    except InvalidOperation as exc:
        raise ValueError("Invalid decimal input") from exc
    if not value.is_finite() or not Decimal("0") < value <= Decimal("1000000000"):
        raise ValueError("Expected a finite positive teaching input")
    if value.as_tuple().exponent < -18:
        raise ValueError("Input exceeds this example's decimal precision")
    return value


def preview_args(product, budget, desired_price):
    if (product.get("product_id"), product.get("product_type"), product.get("status")) != ("BTC-USD", "SPOT", "online"):
        raise ValueError("This example supports only an online BTC-USD spot product")
    flags = ("is_disabled", "trading_disabled", "cancel_only", "view_only", "auction_mode", "limit_only", "post_only")
    if any(type(product.get(flag)) is not bool for flag in flags):
        raise ValueError("Missing or ambiguous product flags")
    if any(product[flag] for flag in flags[:5]):
        raise ValueError("Product is not supported for this preview")
    # Always a post-only limit request, including limit-only/post-only products.
    base_step = positive(product["base_increment"])
    price_step = positive(product["price_increment"])
    quote_step = positive(product["quote_increment"])
    base_min, base_max = (positive(product[key]) for key in ("base_min_size", "base_max_size"))
    quote_min, quote_max = (positive(product[key]) for key in ("quote_min_size", "quote_max_size"))
    if base_min > base_max or quote_min > quote_max:
        raise ValueError("Inconsistent product bounds")
    budget = positive(budget)
    price = (positive(desired_price) // price_step) * price_step
    if price <= 0:
        raise ValueError("Rounded price is zero")
    size = ((budget / price) // base_step) * base_step
    notional = size * price
    rounded_cost = (notional / quote_step).to_integral_value(rounding=ROUND_CEILING) * quote_step
    if not base_min <= size <= base_max:
        raise ValueError("Rounded size is outside product limits")
    if not quote_min <= notional or rounded_cost > min(budget, quote_max):
        raise ValueError("Notional is outside the sample budget/product limits")
    return {
        "product_id": "BTC-USD",
        "base_size": format(size, "f"),
        "limit_price": format(price, "f"),
        "post_only": True,
    }

limit_only is compatible with this limit request; post_only is compatible because the request always sets it to true. Auction, disabled, cancel-only and view-only products are rejected by this deliberately narrow example. The estimated notional is rounded up to the quote increment for a conservative budget check, but no quote_size is sent for this base-sized limit preview. Validate actual account constraints separately before any future order.

Persist One ID for Each Intended Decision

The Create Order reference says a reused client_order_id does not create another order: Coinbase returns the order corresponding to that ID. Generate an ID once for an intended decision and retain it across restarts or ambiguous results. Creating a new UUID whenever a request is rerun removes this duplicate safeguard.

The next block stores a UUID and the exact preview arguments in a local file. Reruns with the same file and arguments reuse the ID. Changed arguments, malformed records or an interrupted write stop the example instead of silently replacing the record. It is a persistent local teaching record, not a distributed lock, crash-proof journal or broker reconciliation service. Use one operator/process and preserve the file; deleting or moving it loses the local identity.

def unique_keys(pairs):
    result = {}
    for key, value in pairs:
        if key in result:
            raise ValueError("Duplicate decision-record key")
        result[key] = value
    return result


def decision_record(path, args):
    path = Path(path)
    path.parent.mkdir(parents=True, exist_ok=True)
    try:
        with path.open("x") as file:
            record = {"version": 1, "client_order_id": str(uuid.uuid4()), "preview_args": args}
            json.dump(record, file, sort_keys=True)
            file.flush()
            os.fsync(file.fileno())
    except FileExistsError:
        record = json.loads(path.read_text(), object_pairs_hook=unique_keys)
    if (
        set(record) != {"version", "client_order_id", "preview_args"}
        or type(record["version"]) is not int
        or record["version"] != 1
        or record["preview_args"] != args
        or str(uuid.UUID(record["client_order_id"])) != record["client_order_id"]
    ):
        raise ValueError("Existing decision changed or is malformed; inspect it instead of replacing it")
    return record

The UUID is not sent to the Preview Order endpoint, which does not create an order. It records the identity that a separately reviewed future Create Order request would use. This program implements no order submission or automatic retry. Before treating a future submission as absent, inspect broker order history and reconcile the original ID, request, order status and fills. A timeout is not proof of rejection. Do not issue a replacement under a new ID while the original outcome is unresolved.

Put the setup on a watchlist first

Use the rules in this guide to evaluate a signal’s entry, stop, target, and reasoning before deciding what, if anything, to do.

Build a Watchlist

Run Offline, Then Optionally Request an API Preview

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--preview-api", action="store_true")
    parser.add_argument("--budget", default="25")
    parser.add_argument("--price", default="50000")
    parser.add_argument("--decision-file", default=".coinbase-guide/btc-example.json")
    options = parser.parse_args()
    client = RESTClient(timeout=5) if options.preview_api else None
    product = client.get_product("BTC-USD", get_tradability_status=True).to_dict() if client else PRODUCT
    args = preview_args(product, options.budget, options.price)
    record = decision_record(options.decision_file, args)
    print(json.dumps(record, indent=2))
    if client:
        result = client.preview_limit_order_gtc_buy(**args)
        print(json.dumps(result.to_dict(), indent=2))
    else:
        print("OFFLINE: fixture/request checks only; no Coinbase API call or order")


if __name__ == "__main__":
    main()

Default mode creates or reuses .coinbase-guide/btc-example.json, prints the local record and exits. The repeated default decision is not a second intended trade. If the arguments change, review the existing record and any corresponding broker state; choose another decision file only for a deliberately new decision, never to bypass an unresolved result.

To inspect an authenticated preview only, first configure a restricted view key as described above. Then explicitly run python coinbase_preview.py --preview-api --budget 25 --price 50000. The numeric values remain arbitrary examples that you must choose and review; the program does not infer a trading price from product.price. This mode performs Get Product with get_tradability_status=True, validates that response and calls the SDK's preview_limit_order_gtc_buy. Inspect the full returned errors, warnings, estimates and preview identifier. Do not interpret a preview identifier as an order ID or fill.

The default offline checks prove local behavior only. API preview results can change before a future order, and this program does not inspect balances, outstanding orders, holdings, a current order book or fill history. It does not reserve funds or liquidity. No mode in this program calls Create Order, Cancel Orders or a live execution loop.

Acceptance, Fills, Fees and Cancellation Are Separate

An accepted request is not a completed fill. Coinbase order management documents partial fills and remaining open quantities. Market orders take available liquidity, can encounter protection limits, and do not guarantee a complete fill or the displayed price. Limit orders bound the requested price but may remain unfilled. A cancellation request can race with execution; inspect the actual final status and fills instead of assuming immediate cancellation prevented a purchase.

Coinbase Advanced uses maker/taker pricing. Review the current fee rules, the signed-in schedule for the exact account/product and the actual preview. This guide does not publish a headline rate or subscription rebate as a universal cost. A post-only request is designed to avoid taking liquidity, but its broker response and eventual fill/fee records still need inspection. It is not a zero-fee or execution guarantee.

Rate Limits and Failure Handling

Use the current rate-limit documentation for the exact API rather than importing Coinbase Exchange, CDP Platform or another product's limits. Cache appropriate research data, bound polling, and back off read/preview requests on rate limits. A new key is not evidence of an independent rate-limit allowance. For order creation, distinguish throttling from an ambiguous acceptance and reconcile the persisted decision before retrying; this example submits no orders.

FailureInspect before continuing
401/authentication errorClock, method/path, secret format, key algorithm and restrictions
Missing or ambiguous product fieldsCurrent endpoint/schema and access; do not assume tradability
Increment/minimum/maximum violationRound to product increments, recheck budget and both size ranges
Preview errors or warningsFull preview response; do not turn a rejected preview into an order
Existing decision changedPreserve ID/record and inspect the intended decision; do not overwrite it
Ambiguous future submissionOriginal ID, broker history, actual order state and fills
Partial fill or cancel raceRemaining open quantity and actual holdings

The CCXT Alternative

CCXT provides a common interface for multiple exchanges. If you choose its Coinbase adapter, verify the current adapter's authentication, symbols, precision, capabilities and sandbox behavior in the CCXT documentation. A unified interface does not make every venue support the same order types or a paper environment. CCXT's create_order is an execution method, so this guide does not include a default call that sends a funded order. Use the native Coinbase SDK when its documented endpoint-specific preview and response handling fit your workflow.

How Tradewink Handles Coinbase

Tradewink's current Coinbase adapter is marked as not supporting a paper account. Public-plan users cannot connect Coinbase for funded execution; they can build a watchlist, review signals and inspect simulation without a Coinbase connection. Public subscriptions are paper-only; separately approved private beta accounts may submit live broker orders. This disclosure is not a public live-access application path. Review the broker directory for the currently supported paper/sandbox connections.

This guide's standalone SDK example does not test or certify Tradewink's broker adapter, authorization gates, fills or production controls. No provider or local guard eliminates execution errors or loss.

What to Review Before Adding Any Execution

  1. Current account eligibility, endpoint permissions and key restrictions; least privilege for the action.
  2. Actual product flags, increment/minimum/maximum constraints, balances, pending orders and holdings.
  3. One persisted identity per intended decision, plus reconciliation of ambiguous broker outcomes.
  4. Accepted versus filled quantities, costs, partial fills, cancellation races and remaining exposure.
  5. Separate actions for stopping new submissions, canceling outstanding orders and closing positions; verify each result.

The paper trading guide explains simulation limitations; how to build a trading bot covers the broader engineering workflow. No fixed number of days, trades or profitable paper observations establishes live readiness. Automation can repeat flawed decisions and can lose all capital it controls.

Frequently Asked Questions

Which Python library should I use for a Coinbase API trading bot?

Coinbase publishes an official SDK called coinbase-advanced-py on PyPI, installed with pip3 install coinbase-advanced-py. It handles JWT generation, REST calls, and WebSocket streaming. CCXT's coinbase class is the main alternative when you want the same code to work across many exchanges.

How do Coinbase CDP API keys work?

You create a Secret API Key in the Coinbase Developer Platform portal, set permissions (view, trade, transfer) and an optional IP allowlist, and securely copy the key name and private key; a JSON download is optional. Your code signs a JSON Web Token with that private key for each request. Coinbase's docs state the JWT expires after 2 minutes and that a different JWT is needed per request.

Does Coinbase Advanced Trade have a sandbox for paper trading?

Only a static one. The sandbox at api-sandbox.coinbase.com needs no authentication, returns pre-defined mocked workflow responses rather than a matching engine. It is useful for checking response formats, not for simulating fills. Fill simulation must be implemented locally or in a framework, with its assumptions disclosed.

Why does my Coinbase API bot get 401 errors with a valid key?

Inspect clock synchronization, secret format, request method/path, endpoint, key permissions and restrictions. The Coinbase App auth page specifies ECDSA while the SDK README documents both key types; SDK signing support is not endpoint acceptance proof. A successful public or sandbox call does not verify private JWT authentication.

What permissions should a Coinbase trading bot key have?

Use the permissions required by the exact endpoint. This guide's optional product read and order preview require API-key view only; they need neither trade nor transfer. Create and cancel order endpoints require trade. Check the current scope table and restrict portfolio/IP access as applicable.

Should I use CCXT or the native Coinbase SDK?

The native SDK exposes Coinbase-specific requests and preview responses. CCXT provides a common interface across exchanges, but authentication, symbols, precision, order capabilities and sandbox support vary by adapter. Check current documentation and implement your own validation, state reconciliation and monitoring.

Is running a Coinbase trading bot profitable?

No example, preview or fixed test duration establishes profitability. Outcomes depend on strategy, data, implementation, costs and execution. The static sandbox does not simulate market fills. Record rejected and unresolved requests, partial fills and actual costs; automation can repeat flawed decisions and lose capital. This is educational content, not financial advice.

Keep learning with a related guide before putting an idea on your watchlist.

Ready to evaluate a signal?

Start free with a watchlist and inspect the context before you consider a broker connection.

Try AI signals on your watchlist

Send yourself a signal preview, then add tickers to see ranked entries, exits, and risk notes in Tradewink.

Enter the email address where you want to receive a Tradewink AI signal preview.

TW

Tradewink builds explainable market research for self-directed traders. Build a watchlist, inspect signal reasoning and risk context, and paper-track ideas before you decide. Public subscriptions are paper-only; separately approved private beta accounts may submit live broker orders.

How this guide is reviewed

Tradewink reviews educational content against its documented market-data sources, risk controls, and product methodology. See our data sources and evaluation methodology for the evidence and limitations behind the platform.

Important disclosures

Informational purposes only

Tradewink is published by Tradewink LLC, which is not a registered investment adviser, broker-dealer, commodity trading advisor, or financial planner. All data, signals, and analytics on this page are general, impersonal, and for informational purposes only. They do not constitute investment advice, financial advice, or a recommendation to buy or sell any security or other instrument.

Trading risk

Past performance does not guarantee future results. Trading involves substantial risk of loss, including the possibility of losing more than your initial investment. You are solely responsible for your own trading decisions.