Coinbase
Coinbase was founded in 2012 and is one of the largest US-regulated cryptocurrency exchanges. Its Advanced Trade API offers spot, perpetual swap, and dated futures trading. This adapter supports live market data ingest and order execution on both spot (Cash) and CFM derivatives (Margin) accounts through a shared execution client, with the account type selected by the factory (see Execution scope).
Overview
The Coinbase adapter is implemented in Rust and exposed to Python through configurations, factories, enums, and constants.
Components:
CoinbaseRawHttpClient: Low-level REST client owning transport, JWT signing, and rate limits.CoinbaseHttpClient: Domain REST client parsing venue responses into Nautilus types.CoinbaseWebSocketClient: Low-level WebSocket connectivity with JWT subscribe auth.CoinbaseInstrumentProvider: Instrument parsing and loading.CoinbaseDataClient: Market data feed manager.CoinbaseDataClientFactory: Data client factory.CoinbaseExecutionClient: Execution client (spot or CFM derivatives; REST orders + WS streams).CoinbaseExecutionClientFactory: Execution client factory. Spot vs CFM derivatives is selected byaccount_typeon the config.
Python surface available from nautilus_trader.adapters.coinbase:
CoinbaseDataClientConfig,CoinbaseExecutionClientConfigCoinbaseDataClientFactory,CoinbaseExecutionClientFactoryCoinbaseEnvironment,CoinbaseMarginTypeCOINBASE,COINBASE_CLIENT_ID, andCOINBASE_VENUE
Examples
Coinbase documentation
Coinbase provides documentation for the Advanced Trade API:
Read the Coinbase documentation with this NautilusTrader integration guide.
This adapter targets the Coinbase Advanced Trade API. The separate Coinbase International Exchange (INTX) venue is not covered. Its adapter was removed in NautilusTrader 1.224.0.
Products
A product is an umbrella term for a group of related instrument types.
The following product types are supported:
| Product Type | Supported | Notes |
|---|---|---|
| Spot | ✓ | USD, USDC, and USDT-quoted spot pairs. |
| Perpetual contracts | ✓ | USD-margined perpetual swaps on the FCM venue. |
| Futures contracts | ✓ | Dated delivery futures (nano BTC, nano ETH, etc). |
Symbology
Coinbase uses the venue's native product_id field directly as the Nautilus
symbol. The instrument ID is {product_id}.COINBASE.
| Product | Format | Examples |
|---|---|---|
| Spot | {base}-{quote} | BTC-USD, ETH-USDC, SOL-USDT. |
| Perpetual | {contract_code}-{ddMMMyy}-CDE | BIP-20DEC30-CDE (BTC PERP). |
| Dated future | {contract_code}-{ddMMMyy}-CDE | BIT-24APR26-CDE (BTC Apr 2026). |
The -CDE suffix denotes the Coinbase Derivatives Exchange (FCM venue).
Perpetuals carry an exchange-assigned far-future expiry (e.g. 20DEC30) but
are classified as CryptoPerpetual based on the presence of an ongoing
funding rate. Dated futures are classified as CryptoFuture.
The adapter resolves the product type structurally from API metadata
(future_product_details.contract_expiry_type and, when that is EXPIRING, the presence of a non-empty
future_product_details.funding_rate as a perpetual-only structural signal). The fallback heuristic checks
display_name for PERP or Perpetual substrings.
Examples of full Nautilus instrument IDs:
BTC-USD.COINBASE(spot Bitcoin/USD).ETH-USDC.COINBASE(spot Ether/USDC).BIP-20DEC30-CDE.COINBASE(BTC perpetual swap).BIT-24APR26-CDE.COINBASE(BTC dated future, Apr 2026).
Aliased products (USDC and USD)
Coinbase consolidates USDC- and USD-quoted versions of the same pair into one matching-engine book.
GET /products exposes this relationship through alias and alias_to:
BTC-USD : alias="" alias_to=["BTC-USDC"] # canonical
BTC-USDC: alias="BTC-USD" alias_to=[] # alias of BTC-USDWhen a caller subscribes or submits using the alias side, the venue rewrites
the request to the canonical id on the wire. The adapter records the
product_id -> alias map at instrument bootstrap and handles the rewrite
transparently on both sides:
- Data subscriptions go out on the canonical id. The data WebSocket client holds the reverse mapping and re-keys inbound messages back to the caller-supplied id before parsing.
- Orders are submitted on the caller's
product_id. Unfilled working updates restore that ID from submit-time metadata. Updates with fills and terminal reports use the product IDs from REST order details and fill history. Fill commissions use that instrument's quote currency. An alias-side order remains associated with its submitted instrument.
A strategy holding only USDC can therefore trade BTC-USDC.COINBASE end to
end without referencing the canonical BTC-USD. Settlement currency is
determined by the submitted product_id, so an order placed on
BTC-USDC.COINBASE always debits or credits the USDC wallet.
Environments
Coinbase provides two trading environments. Configure the appropriate
environment using the environment field in your client configuration.
| Environment | environment value | REST base URL |
|---|---|---|
| Live | CoinbaseEnvironment.LIVE | https://api.coinbase.com |
| Sandbox | CoinbaseEnvironment.SANDBOX | https://api-sandbox.coinbase.com |
Live (production)
The default environment for live trading with real funds.
config = CoinbaseExecutionClientConfig(
api_key="YOUR_API_KEY",
api_secret="YOUR_API_SECRET",
# environment=CoinbaseEnvironment.LIVE (default)
)Environment variables: COINBASE_API_KEY, COINBASE_API_SECRET.
Sandbox
A static-mock test environment for integration plumbing, per the Sandbox docs.
config = CoinbaseExecutionClientConfig(
api_key="ANY_NON_EMPTY_STRING", # required by the adapter constructor
api_secret="ANY_NON_EMPTY_STRING",
environment=CoinbaseEnvironment.SANDBOX,
)The sandbox venue does not enforce authentication. But CoinbaseExecutionClient::new requires both fields (or
the matching environment variables) for construction.
Sandbox is not a parallel trading venue:
- All responses are static and pre-defined. There is no live market or dynamic pricing.
- Only Accounts and Orders endpoints are available. Other resources are not.
- Authentication is not required (and not enforced).
- A custom
X-Sandboxrequest header can trigger predefined error scenarios.
Use sandbox to wire up your client and verify request/response shape. Use production (with real funds and care) for any realistic behavior testing.
Authentication
Coinbase Advanced Trade uses ES256 JWT authentication. Each REST request and each WebSocket subscription generates a short-lived JWT signed with your EC private key. The adapter resolves credentials from environment variables or from the config fields.
Creating an API key
Coinbase has several key types. The adapter requires a Coinbase App Secret API key with the ECDSA signature algorithm (not Ed25519).
Coinbase no longer auto-downloads the key file. Copy the values from the creation modal or click the download button before closing it. You cannot retrieve the private key afterward.
Do not use expired legacy Coinbase App API keys. Create a CDP API key and select the ECDSA algorithm. The adapter signs requests with ES256. See Coinbase's legacy key migration guide.
For full details see the Coinbase API key authentication guide.
Environment variables
| Variable | Description |
|---|---|
COINBASE_API_KEY | Key name (organizations/{org_id}/apiKeys/{key_id}). |
COINBASE_API_SECRET | PEM-encoded EC private key (full multi-line string). |
Example:
export COINBASE_API_KEY="organizations/abc-123/apiKeys/def-456"
export COINBASE_API_SECRET="$(cat ~/path/to/cdp_api_key.pem)"We recommend using environment variables to manage your credentials.
JWT lifetime
Coinbase JWTs expire after 120 seconds. Per the WebSocket overview, a different JWT must be generated for each authenticated WebSocket message (i.e. for each subscribe). The adapter regenerates a fresh JWT for every signed REST request and for every authenticated subscribe message. No manual rotation is required.
Portfolios
A Coinbase account holds one or more portfolios. Each portfolio has its own wallets (USD, USDC, BTC,
etc.), balances, and order scope. Every account has a DEFAULT portfolio. Users can create additional
CONSUMER portfolios to segregate strategies, risk, or tax lots.
A CDP API key is bound to a single portfolio at creation time. Every authenticated request (account lookup, order submission, cancel) operates against that portfolio unless a different one is explicitly specified.
Finding your portfolio UUIDs
Run the adapter's authenticated probe binary:
cargo run --bin coinbase-http-private --package nautilus-coinbaseIt prints:
- The portfolios visible to your CDP key.
- The account balances in the bound portfolio.
- A few reference REST calls.
Sample output:
Found 1 portfolio(s)
name=Default type=DEFAULT uuid=ca7244bc-21d1-5e4c-bfe5-80f208ac5723 deleted=false
Account has 3 balance(s)
USDC total=100.00000000 USDC free=100.00000000 USDC locked=0.00000000 USDC
AUD total=0.00 AUD free=0.00 AUD locked=0.00 AUD
BTC total=0.00000000 BTC free=0.00000000 BTC locked=0.00000000 BTCEquivalent curl (you have to sign your own ES256 JWT with your CDP PEM key first):
curl -H "Authorization: Bearer $JWT" \
https://api.coinbase.com/api/v3/brokerage/portfoliosWhen retail_portfolio_id is required
Coinbase's POST /orders endpoint routes to the key's bound portfolio by
default, so a single-portfolio account does not need to set this field.
Set it on CoinbaseExecutionClientConfig
when either is true:
- The account holds multiple portfolios and you want to trade against one that is not the key's default.
- The venue rejects orders with
account is not availableand the wallet diagnosis below has been ruled out.
Coinbase's Create Order reference
marks retail_portfolio_id as deprecated and applicable only to legacy keys,
stating that CDP keys default to the key's permissioned portfolio. The adapter
still sends the field when configured, so it remains available if the venue's
default routing does not match your account layout.
Creating a new portfolio
Most users do not need to create a portfolio. The account's default works without additional setup. Create one on coinbase.com/portfolios only if you want to:
- Segregate API-driven trading from manual retail activity.
- Isolate risk or P&L between strategies.
- Work around a restricted default (e.g. a Vault).
After creating a portfolio, fund it (transfer from the default portfolio's
wallet on coinbase.com) before sending any orders, otherwise the venue
returns account is not available for the quote currency.
Troubleshooting account is not available
The venue returns this error for several distinct reasons. Diagnose by running the probe binary above and inspecting the portfolio wallet list.
| Symptom | Likely cause | Fix |
|---|---|---|
Rejected only for a specific product (e.g. BTC-USD with only USDC) | Portfolio is missing a wallet for the product's quote currency. USD and USDC are separate on Coinbase, and the venue routes orders by the submitted product_id, not by the canonical alias. | Submit against the product whose quote currency you hold (e.g. BTC-USDC for USDC wallets). The adapter resolves the data-side alias internally; no config change needed. Funding the missing wallet via coinbase.com is also an option but unnecessary when only one currency is held. |
| Every order rejected across all products | Key is bound to a non-default portfolio and retail_portfolio_id is unset. | Set retail_portfolio_id on CoinbaseExecutionClientConfig to the target portfolio UUID. |
Rejected for *-USD products on a non-US account | Jurisdictional restriction (e.g. AU accounts cannot trade USD-quoted pairs). | Use locally-available quotes (USDC, AUD, EUR, etc.) instead of USD. |
| Rejected right after key rotation | New key was created in a different portfolio than the previous one. | Update retail_portfolio_id to match the new key's portfolio, or move funds. |
Market data
The data client serves everything except derivatives index and funding data from WebSocket channels. Coinbase Advanced Trade does not publish index prices or funding rates on any WebSocket channel, so those two streams are sourced from REST polling instead.
| Nautilus subscription | Source | Notes |
|---|---|---|
| Book deltas | level2 channel | L2_MBP only; other book types are rejected. |
| Quotes | ticker channel | Top-of-book from the venue's ticker payload. |
| Trades | market_trades channel | Also available as a REST request. |
| Bars | candles channel | Fixed five-minute buckets; the venue accepts no granularity parameter. |
| Instrument status | status channel | See Instrument status. |
| Index prices | REST /products/{id} poll | Derivatives only, at derivatives_poll_interval_secs. |
| Funding rates | REST /products/{id} poll | Perpetuals only; see Funding rates. |
| Mark prices | Not supported by Coinbase | The subscription is rejected rather than synthesized from settlement price. |
A heartbeats subscription is always sent on connect and replayed on every
reconnect. It satisfies the venue's five-second subscribe deadline and keeps
the connection alive when the subscribed product topics are quiet.
Bars
Historical bar requests accept EXTERNAL aggregation at the granularities the
adapter maps: 1m, 5m, 15m, 30m, 1h, 2h, 6h, and 1d. Any other step or
aggregation is rejected. Coinbase's /products/{id}/candles endpoint also
accepts FOUR_HOUR, which the adapter does not currently map.
Live bar subscriptions are different: the WebSocket candles channel takes no granularity parameter and
publishes five-minute buckets only. The adapter stamps each received candle with the BarType registered for
that product. Thus, other subscription specifications yield five-minute bars labeled with the requested type.
Request a 5-MINUTE-LAST-EXTERNAL bar type for live subscriptions, and use historical requests for the other
granularities.
Funding rates
The adapter polls the REST /products/{id} endpoint at
derivatives_poll_interval_secs (default 15 s) and emits a
FundingRateUpdate from the FCM future_product_details payload when
funding_rate is present. The funding interval is parsed from the
funding_interval field (typically "3600s", hourly funding) and the next
funding timestamp from funding_time. Coinbase Advanced Trade does not
publish funding_rate on the WebSocket ticker channel, so REST polling
is the only live source.
Historical funding rate requests are not implemented.
Instrument status
subscribe_instrument_status joins the Coinbase WebSocket status channel on first subscription (the venue
publishes one status feed for all products). It filters incoming events to the subscribed instruments. It
emits InstrumentStatus events with these actions:
MarketStatusAction::Tradingforonline.Haltforoffline.Closefordelisted.
Products reporting an unset status (futures) or a status the adapter does not model carry no information for the data engine and are skipped. The channel subscription is dropped when the last instrument unsubscribes.
Orders capability
The tables below describe the Coinbase venue order surface. The shipped
CoinbaseExecutionClient handles spot or CFM derivatives
based on the configured account_type. Coinbase order capabilities differ
between Spot and Derivatives (perpetuals and dated futures share the same
FCM order surface).
Execution scope
CoinbaseExecutionClientFactory produces a single CoinbaseExecutionClient
type. The product family is selected by the account_type field on
CoinbaseExecutionClientConfig:
account_type | Bootstrap instruments | Account state source |
|---|---|---|
AccountType::Cash | CoinbaseProductType::Spot only. | /accounts REST endpoint. |
AccountType::Margin | CoinbaseProductType::Future (perp + dated). | CFM balance_summary REST + futures_balance_summary WS, plus position reports from cfm/positions. |
Other account types are rejected at factory creation. OMS is always
Netting because the venue does not expose hedge mode.
To prevent cross-account bleed-through:
- Connect-time instrument bootstrap is limited to the configured product family. The other family's products never enter the in-process cache.
submit_orderdenies any order whose instrument is outside that cache.generate_order_status_report(s)andgenerate_fill_reportspost-filter their output through the same cache. Thus, a single client never exposes the other scope's reports, even for an account with both spot and derivative activity.
Run one execution client per scope. For both spot and CFM activity on the same trader, create two clients with
distinct account_type values and account_ids. Configure an explicit COINBASE venue route or a default
client (see Execution client routing). When submitting orders,
pass the intended client_id.
Order types
The matrix lists order types as exposed through the Nautilus model. The
right column shows the corresponding order_configuration keys the adapter
emits. Coinbase order types not in this table (TWAP, Bracket, Scaled, SOR
LIMIT IOC) are documented under Advanced order features
and noted there as Not currently implemented by the adapter.
| Order Type | Spot | Perpetual | Future | Wire shape |
|---|---|---|---|---|
MARKET | ✓ | ✓ | ✓ | market_market_ioc (all products); market_market_fok (perps) |
LIMIT | ✓ | ✓ | ✓ | limit_limit_gtc / limit_limit_gtd / limit_limit_fok |
STOP_LIMIT | ✓ | ✓ | ✓ | stop_limit_stop_limit_gtc / stop_limit_stop_limit_gtd |
STOP_MARKET | - | - | - | Not supported by Coinbase. |
MARKET_IF_TOUCHED | - | - | - | Not supported by Coinbase. |
LIMIT_IF_TOUCHED | - | - | - | Not supported by Coinbase. |
TRAILING_STOP_MARKET | - | - | - | Not supported by Coinbase. |
Execution instructions
| Instruction | Spot | Perpetual | Future | Notes |
|---|---|---|---|---|
post_only | ✓ | ✓ | ✓ | LIMIT GTC and LIMIT GTD only. |
reduce_only | - | - | - | Not supported by Coinbase; see Derivatives trading. |
Time in force
The adapter accepts the values in this matrix. Combinations not listed are rejected at submit time with
"Unsupported TIF {tif} for {order_type}".
| Order type | GTC | GTD | IOC | FOK | Notes |
|---|---|---|---|---|---|
MARKET | ✓ | - | ✓ | (✓) | GTC is mapped to IOC; explicit IOC is honored. FOK builds market_market_fok, which Coinbase documents as perpetuals-only and rejects on spot. |
LIMIT | ✓ | ✓ | - | ✓ | GTD requires expire_time. LIMIT IOC not currently implemented (see SOR LIMIT IOC). |
STOP_LIMIT | ✓ | ✓ | - | - | Requires trigger_price. |
Advanced order features
| Feature | Spot | Perpetual | Future | Notes |
|---|---|---|---|---|
| Order Modification | ✓ | - | - | Open GTC variants only; Coinbase rejects futures-venue edits with CANNOT_EDIT_FUTURES_ORDER. |
| Bracket Orders | - | - | - | Not currently implemented. Venue exposes trigger_bracket_gtc / trigger_bracket_gtd. |
| OCO Orders | - | - | - | Not supported by Coinbase as a distinct order type. |
| Iceberg Orders | - | - | - | Not supported by Coinbase. |
| TWAP Orders | - | - | - | Not currently implemented. Venue exposes twap_limit_gtd. |
| Scaled Orders | - | - | - | Not currently implemented. Venue exposes scaled_limit_gtc. |
| SOR LIMIT IOC | - | - | - | Not currently implemented. Venue exposes sor_limit_ioc for smart-order-routed LIMIT IOC. |
See the Create Order reference and Edit Order reference for the underlying venue specification.
Position controls (derivatives)
| Control | Notes |
|---|---|
| Leverage | Set per order; default 1.0. |
| Margin type | Set per order: cross (default) or isolated. |
| Position mode | One-way only; hedge mode is not exposed. |
Batch operations
| Operation | Notes |
|---|---|
| Batch Submit | Not supported. Each order is one Create Order request. |
| Batch Modify | Not supported. Each edit is one Edit Order request. |
| Batch Cancel | POST /api/v3/brokerage/orders/batch_cancel accepts an order_ids array. No documented max size; per-order success/failure in the response. |
Order querying
| Feature | Spot | Perpetual | Future | Notes |
|---|---|---|---|---|
| Query open orders | ✓ | ✓ | ✓ | List all active orders. |
| Query order history | ✓ | ✓ | ✓ | Historical order data with cursor paging. |
| Order status updates | ✓ | ✓ | ✓ | Real-time state changes via user channel. |
| Trade history | ✓ | ✓ | ✓ | Execution and fill reports. |
Spot trading limitations
- MARKET FOK is not accepted on spot. Coinbase documents
market_market_fokas perpetuals-only and rejects it withUNSUPPORTED_ORDER_CONFIGURATION. - Quote-denominated MARKET orders are supported. LIMIT orders are sized in base units.
Derivatives trading
Coinbase derivatives trade through the FCM (Futures Commission Merchant) venue. The exec client submits orders
through the same POST /orders endpoint used for spot. Per-order leverage and margin_type (CROSS or
ISOLATED) defaults come from CoinbaseExecutionClientConfig.default_leverage and default_margin_type.
Margin balances update from both the REST cfm/balance_summary endpoint (connect-time snapshot,
query_account, and on WebSocket reconnect) and the authenticated futures_balance_summary WebSocket
channel. Position reports come from the REST cfm/positions endpoints.
Coinbase's Advanced Trade API does not document a reduce_only field on the create-order schema.
The execution client rejects reduce-only orders before transport instead of submitting them without
the instruction.
The adapter logs a warning for these conditions:
- A REST order status report describes a forced-close order.
- The CFM balance summary reports a liquidation buffer below 20% of the liquidation threshold.
Coinbase does not flag auto-deleveraging separately from liquidation, so both surface through the same warning. The user channel carries no equivalent warning, so forced closes are visible from reconciliation rather than from the live stream.
Execution client behavior
This section documents how CoinbaseExecutionClient translates Nautilus
order commands and Coinbase venue events into Nautilus execution events.
Order submission
submit_order builds the Coinbase order_configuration shape directly from
Nautilus order fields:
MARKETIOC and GTC (the Nautilus default) ->market_market_ioc. FOK ->market_market_fok.DayandGtdare rejected before the HTTP call so callers do not silently receive IOC semantics. AMARKETorder built withGtcexecutes as IOC at the venue. Strategies that require strict backtest/live parity should constructMarketOrderwithIocexplicitly.LIMITGTC ->limit_limit_gtc, GTD ->limit_limit_gtd(requiresexpire_time), FOK ->limit_limit_fok.STOP_LIMITGTC ->stop_limit_stop_limit_gtc, GTD ->stop_limit_stop_limit_gtd. Stop direction is derived from the order side (Buy->STOP_DIRECTION_STOP_UP,Sell->STOP_DIRECTION_STOP_DOWN).STOP_MARKET,MARKET_IF_TOUCHED,LIMIT_IF_TOUCHED, and trailing-stop variants are not supported by Coinbase. They surface asOrderRejectedcarrying thebuild_order_configurationerror from the spawned submit task (the order is emitted asOrderSubmittedfirst).
On a successful HTTP create, an OrderAccepted is emitted carrying the
venue order ID returned in success_response.order_id. On a success=false
response, OrderRejected is emitted with the formatted venue failure reason.
Because any submit attempt may have reached Coinbase, a transport error,
timeout, rate-limit response, decode failure, or HTTP 5xx does not prove
rejection. The adapter leaves the order in flight and retains its submit
metadata until the user channel or reconciliation resolves it.
Order modification
modify_order posts to /orders/edit with the typed EditOrderRequest.
Coinbase supports edits on open GTC variants only, and rejects edits on
futures-venue orders with CANNOT_EDIT_FUTURES_ORDER, so modification is
effectively spot-only. Other order types must use cancel-replace.
Coinbase's /orders/edit requires both price and size even when only one changes. It reads an omitted
size as 0 and rejects it with INVALID_EDITED_SIZE or CANNOT_EDIT_TO_BELOW_FILLED_SIZE. The exec client
auto-fills missing fields from the cached order, so strategies can call modify_order(price=X) without
repeating the current quantity. Values from the ModifyOrder command win. Otherwise the cached order's
current price and quantity are used.
Venue edit failures emit OrderModifyRejected with the typed EditOrderResponse
reason (preferring edit_failure_reason, falling back to preview_failure_reason).
HTTP failures with unknown venue outcome leave the order in PENDING_UPDATE until
an update, query result, or reconciliation resolves it.
Cancellation
cancel_orderposts a single-idbatch_cancel. An explicit per-order venue failure surfaces asOrderCancelRejected. A whole-request transport failure with unknown venue outcome leaves the order inPENDING_CANCELfor reconciliation.cancel_all_orderslists open orders via REST without theOPEN-only filter (because Coinbase'sOPENfilter excludesPENDINGandQUEUEDorders that are still cancelable), filters locally to{Accepted, Triggered, PendingUpdate, PartiallyFilled}and the requested side, then chunksbatch_cancelcalls in groups of 100. Per-order venue failures emitOrderCancelRejected. Whole-request failures with unknown venue outcome leave affected orders pending reconciliation.batch_cancel_orderssplits repeated venue order IDs into separate requests so each reply identifies one command UUID. Other chunks contain up to 100 orders. It surfaces explicit per-order venue failures asOrderCancelRejected. Transport failures with unknown venue outcome leave affected orders pending reconciliation.
User WebSocket channel
CoinbaseExecutionClient subscribes to the user channel with no product_ids filter and a fresh JWT.
For updates with nonzero cumulative filled quantity and for terminal updates, it fetches REST order details.
For orders with reported or discovered fills, it also fetches the complete per-order fill history.
Recovery retains discovered trade IDs across failed reads and empty scans. It waits until REST reaches
every pending user-channel target, includes every discovered trade, and each order's unique fill
quantities sum exactly to its REST filled quantity. For an order not yet tracked from the Nautilus
cache, recovery publishes an accepted (or rejected) order status with zero filled quantity before
the real fill reports, then its final REST status after those fills. The preceding status establishes
order context without inferring fills. Recovery publishes individual fills across orders in trade-time order. Known orders use native
cancellation, expiry, and rejection events once the cache contains the recovered filled quantity.
Known submitted orders receive native acceptance from user-channel or REST order evidence without
waiting for fill history.
Fills retain Coinbase's trade_id, per-trade price, commission, liquidity side, and trade timestamp.
Quote-sized fills are converted to base quantity using the fill price. Terminal reports use the original
REST order quantity even when the user channel zeroes leaves_quantity.
Live fill delivery waits for REST visibility. Incomplete or failed reads hold the whole recovery batch
pending for retry while connected. Polling continues even after an empty scan, so delayed fills from closed orders
and pending queries do not require another WebSocket update. Polls have no attempt limit and run at
intervals of retry_delay_max_ms (at least 1 ms); each HTTP request uses the existing retry policy.
Recovery publication is serialized across queries and user updates. A changed target or a REST state
older than a previously published recovery leaves the batch pending. A slow, failing, or incomplete
read delays all processing on the user channel, including acceptances, amendments, reconnect account
refreshes, and futures balance updates. Those messages queue until the read completes.
The user channel omits price, stop_price, trigger_type, and post_only. The client fills these
fields from submit-time metadata for its own orders and from REST enrichment for external orders.
For orders submitted by this client, non-snapshot user updates emit native amendments using the
reported quantity and captured submission or modification metadata. User-channel snapshots and fill
recovery preserve cached limit and trigger prices. Recovery updates cached base quantity before
fills when complete fill coverage exceeds it or a terminal Filled report establishes the final total.
Quote-sized market orders wait for terminal REST totals, then normalize their quantity to base units
before publishing fills. Order statuses PENDING, QUEUED, and OPEN map to OrderStatus::Accepted.
A submit_order rejection carrying INVALID_LIMIT_PRICE_POST_ONLY (or the
preview/new-order equivalent) is emitted with due_post_only = true so
strategies can react to post-only crossings (typically by re-quoting against
the new TOB).
On reconnect, the client refreshes account state. Fill discovery runs throughout the connection from the venue timestamp recorded before its first connection, with no fixed lookback window. Each scan also pages through the account's full REST order history, then filters for filled orders whose last fill is at or after that timestamp. A closed order can therefore hold recovery pending before its fills appear in the trade history. Each poll reprocesses matching orders, including already reconciled orders and orders that fill and close while disconnected. Each target requires a REST order read and its complete fill history. REST usage depends on the account's full order history and grows with activity during the client instance; see Rate limiting. Pending targets and published recovery state survive disconnects of the same client instance.
Fill deduplication
Live updates, startup reconciliation, and reconnect recovery use the same venue trade IDs. The execution engine uses those identities for fill deduplication. Recovery deduplicates each order's response and sorts fills across the complete batch by trade timestamp before publication. This preserves individual partial fills when a user-channel update combines executions or several orders fill during a disconnect. Live recovery publishes each fill separately after order context is established. Known-order recovery uses fill reports and native lifecycle events without replaying REST price or trigger terms or treating a lower cumulative fill total as a fill correction.
Position reconciliation
For Cash (spot) accounts the client returns no position reports. Periodic position checks retain
cached spot positions when reports omit them. For Margin accounts position reports come
from the REST cfm/positions (list) and cfm/positions/{product_id} (single) endpoints.
Periodic position checks treat an absent report as flat only for instruments in the Margin client's
bootstrapped futures scope.
Open orders and historical fills are reconciled from REST via
generate_order_status_report(s) and generate_fill_reports on connect
and on the standard reconciliation interval set by LiveExecutionEngineConfig.
Rate limiting
Coinbase publishes the following limits for the Advanced Trade APIs:
| Surface | Limit | Source |
|---|---|---|
| WebSocket connections | 8 per second per IP address | Advanced Trade WebSocket Rate Limits |
| WebSocket unauthenticated msgs | 8 per second per IP address | Advanced Trade WebSocket Rate Limits |
| WebSocket subscribe deadline | First subscribe message must arrive within 5 s of connect or the server disconnects | Advanced Trade WebSocket Overview |
| Authenticated WebSocket JWT | 120 s; a fresh JWT must be generated for every authenticated subscribe message | Advanced Trade WebSocket Overview |
| REST per-key quota | 10,000 requests per hour per API key (Coinbase App general policy) | Coinbase App Rate Limiting |
When the REST limit is exceeded, Coinbase returns HTTP 429 with this body:
{
"errors": [
{
"id": "rate_limit_exceeded",
"message": "Too many requests"
}
]
}Each recovery poll pages through fills since the first connection and the account's full order
history. It also reads each matching filled order and its complete fill history. This costs at least
two discovery requests plus two requests per filled order, before pagination and HTTP retries.
The default five-second interval corresponds to 720 polls per hour before delays. Raise retry_delay_max_ms
to reduce this REST usage; targeted order queries and user updates can trigger additional scans.
Against the general Coinbase App quota above, six filled orders yield at least 10,080 requests per hour at the default interval, before delays or other traffic. The set of scanned orders grows for the lifetime of the client instance. A longer interval reduces the rate but does not bound that history, and it also increases the maximum HTTP retry delay.
The adapter also throttles all REST traffic client-side at 30 requests per second. It limits subscribe and unsubscribe messages to 8 per second. Thus, it shapes bursts before they reach the venue.
Coinbase's current Advanced Trade documentation publishes WebSocket limits but no Advanced Trade-specific REST quota (per-second ceilings, per-portfolio limits), so the Coinbase App per-hour quota above is the most specific documented value. References: WebSocket rate limits, WebSocket overview, Coinbase App rate limiting.
Reconnect and resubscribe
The WebSocket client uses exponential backoff with a base of 250ms and a cap of 30s on reconnect. Retained
subscriptions are replayed automatically after the handshake completes. Coinbase disconnects a client that
sends no subscribe message within 5 seconds of connecting. Thus, replay always includes heartbeats. The
client marks this topic before the first replay and keeps it for its lifetime.
For authenticated channels (user, and futures_balance_summary on Margin clients), the adapter generates a
fresh JWT for every subscribe message, as Coinbase requires a different JWT for each authenticated WebSocket
message. If credentials are absent or JWT construction fails, the adapter skips topics that require
authentication instead of sending them unsigned. The failure appears as an error on the client's message
stream. Once a subscription is accepted the data flow continues for the lifetime of the WebSocket connection
without further authentication.
If a prior user WebSocket is still active or reconnecting when the execution client connects again, it closes
that connection. It rebuilds the inner client instead of reusing the existing state machine. This guarantees a
fresh command channel, output channel, and shutdown signal even when the previous session's Disconnect
command lost a race with the shutdown signal.
Configuration
Data client configuration options
| Option | Default | Description |
|---|---|---|
api_key | None | Falls back to COINBASE_API_KEY env var. |
api_secret | None | Falls back to COINBASE_API_SECRET env var. |
base_url_rest | None | Override for the REST base URL. |
base_url_ws | None | Override for the WebSocket market data URL. |
proxy_url | None | Optional proxy URL for HTTP and WebSocket transports. |
environment | Live | Live or Sandbox. |
http_timeout_secs | 10 | HTTP request timeout (seconds). |
ws_timeout_secs | 30 | WebSocket timeout (seconds). |
update_instruments_interval_mins | 60 | Interval between instrument catalog refreshes. |
derivatives_poll_interval_secs | 15 | Interval between REST polls that emit IndexPriceUpdate and FundingRateUpdate. |
transport_backend | Sockudo | WebSocket transport backend. |
Execution client configuration options
| Option | Default | Description |
|---|---|---|
account_id | Venue | Nautilus account identifier; defaults to COINBASE-001. |
api_key | None | Falls back to COINBASE_API_KEY env var. |
api_secret | None | Falls back to COINBASE_API_SECRET env var. |
base_url_rest | None | Override for the REST base URL. |
base_url_ws | None | Override for the user data WebSocket URL. |
proxy_url | None | Optional proxy URL for HTTP and WebSocket transports. |
environment | Live | Live or Sandbox. |
http_timeout_secs | 10 | HTTP request timeout (seconds). |
max_retries | 3 | Maximum retry attempts for HTTP requests. |
retry_delay_initial_ms | 100 | Initial retry delay (milliseconds). |
retry_delay_max_ms | 5,000 | Maximum HTTP retry delay (milliseconds), also used for recovery polls with a 1 ms minimum. |
account_type | Cash | Cash for spot or Margin for CFM derivatives. See Execution scope. |
default_margin_type | None | Default CoinbaseMarginType (Cross or Isolated) applied to derivatives orders. Ignored on Cash. |
default_leverage | None | Default leverage applied to derivatives orders. Ignored on Cash. |
retail_portfolio_id | None | CDP retail portfolio UUID, sent on create-order when set. Coinbase marks the field deprecated for CDP keys. See Portfolios. |
transport_backend | Sockudo | WebSocket transport backend. |
Configurations are constructed from the adapter's public Python module:
from nautilus_trader.adapters.coinbase import CoinbaseDataClientConfig
from nautilus_trader.adapters.coinbase import CoinbaseEnvironment
from nautilus_trader.adapters.coinbase import CoinbaseExecutionClientConfig
from nautilus_trader.model import AccountId
data_config = CoinbaseDataClientConfig(
api_key="YOUR_COINBASE_API_KEY",
api_secret="YOUR_COINBASE_API_SECRET",
environment=CoinbaseEnvironment.LIVE,
)
exec_config = CoinbaseExecutionClientConfig(
account_id=AccountId("COINBASE-001"),
api_key="YOUR_COINBASE_API_KEY",
api_secret="YOUR_COINBASE_API_SECRET",
environment=CoinbaseEnvironment.LIVE,
)The current Python examples show how to pair these configs with
CoinbaseDataClientFactory and CoinbaseExecutionClientFactory in LiveNode.builder(...).
Known limitations
Venue-side
- Order modification is restricted to open GTC orders and is rejected on futures-venue orders with
CANNOT_EDIT_FUTURES_ORDER. Everything else must use cancel-replace. - OCO orders are not exposed as a distinct order type.
- Trailing stop, MARKET_IF_TOUCHED, LIMIT_IF_TOUCHED, and iceberg orders are not supported by Coinbase.
- Mark prices are not published on REST or WebSocket, so mark price subscriptions are rejected.
- Batch submit and batch modify are not available. Only batch cancel is.
- Sandbox is a static-mock environment (Accounts and Orders endpoints only, pre-defined responses, no real market data).
- The user-channel WebSocket reports cumulative per-order state without per-trade IDs. Live fill delivery therefore requires REST order and fill reads.
Adapter-side
- One product family per client. Submission, modification, cancellation,
and report generation are filtered to the configured product family (spot
under
AccountType::Cash; perp + dated futures underAccountType::Margin). Orders whose instrument falls outside the bootstrapped cache are denied. See Execution scope. - Position reports are always empty for Cash accounts. Coinbase spot has
no positions. Derivatives (CFM) position reports come from
cfm/positionsand appear only on Margin clients. - User-channel updates omit
price,stop_price,trigger_type, andpost_only. The client retains submission and modification metadata for its own orders. It enriches external orders with/orders/historical/{venue_order_id}when an unfilled, non-terminal update needs a missing price. It retains that enrichment until a closed REST status is published. The lookup adds latency. The metadata supplies external order status reports; known orders use native lifecycle events. See User WebSocket channel for amendments and fill recovery. - Cancel-all and batch-cancel REST list failures are logged only. If the list-open-orders REST call fails,
no per-order
OrderCancelRejectedis emitted. Orders remain inPendingCanceluntil the next reconciliation recovers them. Mirrors the Bybit adapter pattern. - Newly listed products require a reconnect to be tradeable. The instrument cache is populated on connect.
Products listed after that are not in the cache and
submit_orderwill deny them. - MARKET orders default to IOC. A
MarketOrderconstructed with the Nautilus defaultTimeInForce::Gtcis mapped tomarket_market_iocat the venue. ExplicitTimeInForce::Iocis honored.TimeInForce::Fokroutes tomarket_market_fok, which Coinbase documents as perpetuals-only and rejects at runtime on spot withUNSUPPORTED_ORDER_CONFIGURATION.DayandGtdare rejected at submit time. - Historical funding rate requests are not implemented. Funding rates are available only as live updates from the derivatives REST poll.
- Live bar subscriptions ignore the requested granularity. The venue's
candleschannel publishes five-minute buckets and accepts no granularity parameter, andsubscribe_barsdoes not reject other bar specifications. A subscription at any other step receives five-minute bars stamped with the requestedBarType. See Bars.
Diagnostic binaries
Three binaries assist with connectivity checks, live verification, and account hygiene:
coinbase-http-publicrequests spot instruments, a product book, and recent trades without credentials. Use it to confirm connectivity before configuring an API key.coinbase-http-privatelists portfolios, prints wallet balances, runs/orders/previewforBTC-USDandBTC-USDC, and surfaces per-product gating flags. Recommended first stop when bringing a new account online.coinbase-cancel-all-opencancels every open order on the authenticated CDP key. Useful between test runs to clear resting orders.
The two authenticated binaries read COINBASE_API_KEY and
COINBASE_API_SECRET from the environment.
Contributing
For additional features or to contribute to the Coinbase adapter, please see our contributing guide.
CoinAPI
CoinAPI by API Bricks provides real-time and historical cryptocurrency market data across multiple exchanges. Its REST and WebSocket APIs expose trades...
Databento
NautilusTrader includes an adapter for the Databento API and for data in Databento Binary Encoding (DBN). Databento is a market data provider only, so the...