Skip to content
Browse the documentation
Connectors

Coinbase

What the Coinbase connector reads, with which permissions, what it never touches, how it syncs, and the facts it produces.

Last changed Markdown versionllms.txt

Source module: app/connectors/coinbase.py. Kind: facts only. Display name: "Coinbase".

Purpose

Reads the orders and fills of a Coinbase account through the Advanced Trade API, so that a trade an agent places (through the Coinbase MCP server of Coinbase for Agents, orders_create, or the API directly) can be verified under the coinbase_agent_trade library contract: did an order on the product the agent was told to trade exist, once, for the notional asked, did it fill within the window, and was it not cancelled, rejected, busted or corrected afterwards.

Coinbase's own controls run before the order: the portfolio the agent's key is scoped to, the permissions on the key, the spending cap on x402 payments. This connector adds nothing there. It reads Coinbase's record after the order, so that the agent's report of what it traded is checked against what Coinbase says it traded.

Auth and scopes

Item Value
Method CDP API key (Coinbase Developer Platform, "Secret API Keys"); every request carries a two-minute ES256 JWT signed with the key's EC private key, as Coinbase's reference signs it (sub and kid the key name, iss cdp, nbf, exp now + 120 s, uri GET api.coinbase.com/<path>, a nonce header)
Credentials stored (encrypted) key_name, private_key (PEM); optional portfolio_id; optional base_url
Least-privilege permissions for the key View only. No Trade, no Transfer. Create the key with the signature algorithm set to ECDSA (Advanced Trade does not accept Ed25519 keys; the connector refuses one with the reason) and, when the portal offers it, restricted to the portfolio the agent trades in
Connection test GET /api/v3/brokerage/orders/historical/batch?limit=1
Non-secret config portfolio_id: when set, only orders and fills whose retail_portfolio_id matches are read. Coinbase for Agents keeps each agent in its own portfolio; naming it keeps a person's own trades out of the agent's evidence

The private key is stored only as ciphertext and is used only to sign tokens for read requests. It is never logged and never leaves the API process.

Fields pulled and why

Endpoint Fields Used for
GET /api/v3/brokerage/orders/historical/batch?start_date=since-7d&limit=100 (pages by cursor while has_next) order_id, client_order_id, product_id, side, status, order_type, order_configuration (quote_size, base_size, limit_price), filled_size, filled_value, average_filled_price, total_fees, number_of_fills, created_time, last_fill_time, last_update_time, retail_portfolio_id, order_placement_source, reject_reason Order facts, subject id, watermark
GET /api/v3/brokerage/orders/historical/fills?start_sequence_timestamp=since-7d&limit=100 (pages by cursor) trade_id, order_id, product_id, side, trade_type, price, size, commission, trade_time, retail_portfolio_id Reversal facts

Orders are re-read seven days behind the watermark on every sync: an order placed days ago can still fill, be cancelled or have a fill reversed, and the facts for those are keyed on the order, so the re-read is idempotent. A fills list the key cannot read (403) is noted on the sync and does not fail it; reversals are then not observed, and the connection's notes say so.

The order's notional

Agents are asked to trade an amount in the quote currency ("buy $50 of TSLA"). The connector derives one number, quote_value, from the order: the quote_size it was placed with when it was; otherwise filled_value; otherwise, for a limit order placed by base size, base_size × limit_price. The contract compares it with the action's amount.

What is hashed / never stored

  • Nothing in these two lists identifies a person; no hashing is needed.
  • Account balances, deposit and withdrawal history, payment methods, addresses, names, tax forms and anything outside the two endpoints above are never requested.

Incremental sync and watermark

  • Orders: start_date = watermark − 7 days (2020-01-01 on first sync), page size 100, cursor while has_next.
  • Fills: start_sequence_timestamp = watermark − 7 days, page size 100, cursor while pages are non-empty.
  • Watermark = maximum order created_time.
  • Upserts are idempotent (by raw_ref).

Rate-limit handling

Shared HttpClient (Retry-After, exponential backoff on 429/5xx, 6 attempts, immediate reauth failure on 401/403 for the orders list).

Fact types produced

Subject id is Coinbase's product id (BTC-USD, TSLA-USD), subject kind product: the thing the agent was told to trade, which is what its recorded action names. Every fact carries order_id, client_order_id, side, order_type, quote_value and portfolio_id.

fact_type When Extra attributes raw_ref
order_created Every order, at created_time; attributes refresh on every sync status (current), placement_source coinbase:order:{order_id}
order_filled Status FILLED, at last_fill_time filled_size, filled_value, average_filled_price, total_fees, number_of_fills coinbase:order:{order_id}:filled
order_failed Status CANCELLED, EXPIRED or FAILED, at last_update_time status, reject_reason coinbase:order:{order_id}:{status}
order_reversed A fill whose trade_type is REVERSAL or CORRECTION, at trade_time trade_id, trade_type, price, size, commission coinbase:fill:{trade_id}

Ordinary fills (trade_type FILL) do not become facts of their own: the order's fill is already order_filled.

Claims produced

None.

Subject id format

The product id as Coinbase spells it. An agent that records a coinbase_agent_trade action uses the same subject (the product_id it passes to orders_create); the recorder mapping in recorder/python/examples/coinbase.json does this.

Known limitations

  • A product the account has never traded has no record at all, so an agent's claim to have traded it, when nothing was created, answers UNKNOWN (identity_unmatched, "subject not found") rather than FAIL; once any order on the product exists, a missing one is a FAIL. The sandbox's broker seeds a product_status record per product for this reason; a production workspace can push one through the facts API for the products its agents are allowed to trade.
  • The side (BUY or SELL) is limited on the action's parameter and shown in the evidence; the engine's value comparisons are numeric, so the order's side is not compared with the request.
  • order_placement_source is read and shown but its values for orders placed through the Coinbase MCP server are not documented (listed under unknown_fields on the contract); nothing is keyed on it.
  • Balances and positions are not read: whether the account could afford the order is Coinbase's check before the order, not evidence of it afterwards.
  • The connector has been tested against recorded responses in the shape of the reference, not yet against a live Coinbase account; the first live sync is on the backlog.