@farazirfan/costar-server-executor 1.7.19 → 1.7.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/index.html CHANGED
@@ -1142,6 +1142,30 @@
1142
1142
 
1143
1143
  <!-- ═══ Settings Page ═══ -->
1144
1144
  <div class="page" id="page-settings">
1145
+ <div class="card">
1146
+ <div class="card-header">
1147
+ <h3>Environment Variables</h3>
1148
+ <div style="display:flex;gap:8px;">
1149
+ <button class="btn btn-sm" onclick="loadEnvVariables()">Refresh</button>
1150
+ <button class="btn btn-sm btn-primary" onclick="openEnvModal()">+ Add Variable</button>
1151
+ </div>
1152
+ </div>
1153
+ <div class="table-wrap">
1154
+ <table>
1155
+ <thead>
1156
+ <tr>
1157
+ <th>Key</th>
1158
+ <th>Value</th>
1159
+ <th>Actions</th>
1160
+ </tr>
1161
+ </thead>
1162
+ <tbody id="env-table-body">
1163
+ <tr><td colspan="3"><div class="loading-center"><div class="spinner"></div></div></td></tr>
1164
+ </tbody>
1165
+ </table>
1166
+ </div>
1167
+ </div>
1168
+
1145
1169
  <div class="card">
1146
1170
  <div class="card-header">
1147
1171
  <h3>Configuration</h3>
@@ -1211,6 +1235,25 @@
1211
1235
  </div>
1212
1236
  </div>
1213
1237
 
1238
+ <!-- Environment Variable Modal -->
1239
+ <div class="modal-overlay" id="env-modal">
1240
+ <div class="modal">
1241
+ <h3 id="env-modal-title">Add Environment Variable</h3>
1242
+ <div class="form-group">
1243
+ <label>Key (UPPERCASE_WITH_UNDERSCORES)</label>
1244
+ <input type="text" id="env-key" placeholder="e.g. MY_API_KEY" style="text-transform:uppercase;" />
1245
+ </div>
1246
+ <div class="form-group">
1247
+ <label>Value</label>
1248
+ <input type="text" id="env-value" placeholder="Enter value..." />
1249
+ </div>
1250
+ <div class="modal-actions">
1251
+ <button class="btn" onclick="closeEnvModal()">Cancel</button>
1252
+ <button class="btn btn-primary" onclick="saveEnvVariable()">Save</button>
1253
+ </div>
1254
+ </div>
1255
+ </div>
1256
+
1214
1257
  <script>
1215
1258
  /* ═══════════════════════════════════════════════
1216
1259
  * CoStar Dashboard - Client-side JS
@@ -2072,6 +2115,166 @@
2072
2115
  } catch (err) {
2073
2116
  document.getElementById('settings-config').innerHTML = `<div style="color:var(--red);padding:12px;">Error: ${esc(err.message)}</div>`;
2074
2117
  }
2118
+
2119
+ // Also load env variables
2120
+ loadEnvVariables();
2121
+ }
2122
+
2123
+ // ─── Environment Variables ───────────────────────
2124
+ let envVariables = [];
2125
+
2126
+ async function loadEnvVariables() {
2127
+ try {
2128
+ const data = await api('/api/env');
2129
+ envVariables = data.variables || [];
2130
+ const tbody = document.getElementById('env-table-body');
2131
+
2132
+ if (envVariables.length === 0) {
2133
+ tbody.innerHTML = `<tr><td colspan="3"><div class="empty-state"><div class="empty-icon">&#128295;</div><p>No environment variables configured.</p></div></td></tr>`;
2134
+ return;
2135
+ }
2136
+
2137
+ tbody.innerHTML = envVariables.map((env, idx) => {
2138
+ const valueDisplay = env.masked
2139
+ ? `<span style="font-family:var(--mono);color:var(--text-muted);" id="env-value-${idx}">${esc(env.value)}</span>
2140
+ <button class="btn btn-sm" onclick="revealEnvValue('${esc(env.key)}', ${idx})" id="env-reveal-${idx}" style="margin-left:8px;">&#128065; Reveal</button>`
2141
+ : `<span style="font-family:var(--mono);">${esc(env.value)}</span>`;
2142
+
2143
+ return `<tr>
2144
+ <td><code style="font-size:13px;color:var(--accent);">${esc(env.key)}</code></td>
2145
+ <td>${valueDisplay}</td>
2146
+ <td>
2147
+ <button class="btn btn-sm" onclick="editEnvVariable('${esc(env.key)}')">Edit</button>
2148
+ <button class="btn btn-sm btn-danger" onclick="deleteEnvVariable('${esc(env.key)}')">Delete</button>
2149
+ </td>
2150
+ </tr>`;
2151
+ }).join('');
2152
+ } catch (err) {
2153
+ document.getElementById('env-table-body').innerHTML = `<tr><td colspan="3" style="color:var(--red);padding:16px;">Error: ${esc(err.message)}</td></tr>`;
2154
+ }
2155
+ }
2156
+
2157
+ async function revealEnvValue(key, idx) {
2158
+ try {
2159
+ const btn = document.getElementById(`env-reveal-${idx}`);
2160
+ const valueEl = document.getElementById(`env-value-${idx}`);
2161
+
2162
+ if (!btn || !valueEl) return;
2163
+
2164
+ // If already revealed, hide it again
2165
+ if (btn.textContent.includes('Hide')) {
2166
+ const maskedVar = envVariables[idx];
2167
+ valueEl.textContent = maskedVar.value;
2168
+ btn.innerHTML = '&#128065; Reveal';
2169
+ return;
2170
+ }
2171
+
2172
+ btn.disabled = true;
2173
+ btn.textContent = 'Loading...';
2174
+
2175
+ const data = await api(`/api/env/${encodeURIComponent(key)}`);
2176
+ valueEl.textContent = data.value;
2177
+ btn.disabled = false;
2178
+ btn.innerHTML = '&#128065; Hide';
2179
+
2180
+ // Auto-hide after 10 seconds
2181
+ setTimeout(() => {
2182
+ if (btn.textContent.includes('Hide')) {
2183
+ const maskedVar = envVariables[idx];
2184
+ valueEl.textContent = maskedVar.value;
2185
+ btn.innerHTML = '&#128065; Reveal';
2186
+ }
2187
+ }, 10000);
2188
+ } catch (err) {
2189
+ alert('Failed to reveal value: ' + err.message);
2190
+ const btn = document.getElementById(`env-reveal-${idx}`);
2191
+ if (btn) {
2192
+ btn.disabled = false;
2193
+ btn.innerHTML = '&#128065; Reveal';
2194
+ }
2195
+ }
2196
+ }
2197
+
2198
+ function openEnvModal() {
2199
+ document.getElementById('env-modal').classList.add('open');
2200
+ document.getElementById('env-modal-title').textContent = 'Add Environment Variable';
2201
+ document.getElementById('env-key').value = '';
2202
+ document.getElementById('env-key').disabled = false;
2203
+ document.getElementById('env-value').value = '';
2204
+ document.getElementById('env-key').dataset.editMode = 'false';
2205
+ }
2206
+
2207
+ function closeEnvModal() {
2208
+ document.getElementById('env-modal').classList.remove('open');
2209
+ }
2210
+
2211
+ function editEnvVariable(key) {
2212
+ const envVar = envVariables.find(e => e.key === key);
2213
+ if (!envVar) return;
2214
+
2215
+ document.getElementById('env-modal').classList.add('open');
2216
+ document.getElementById('env-modal-title').textContent = 'Edit Environment Variable';
2217
+ document.getElementById('env-key').value = key;
2218
+ document.getElementById('env-key').disabled = true; // Can't change key when editing
2219
+ document.getElementById('env-value').value = ''; // Don't pre-fill for security
2220
+ document.getElementById('env-value').placeholder = 'Enter new value...';
2221
+ document.getElementById('env-key').dataset.editMode = 'true';
2222
+ }
2223
+
2224
+ async function saveEnvVariable() {
2225
+ const keyInput = document.getElementById('env-key');
2226
+ const valueInput = document.getElementById('env-value');
2227
+ const key = keyInput.value.trim().toUpperCase();
2228
+ const value = valueInput.value.trim();
2229
+ const isEdit = keyInput.dataset.editMode === 'true';
2230
+
2231
+ if (!key) {
2232
+ alert('Key is required.');
2233
+ return;
2234
+ }
2235
+
2236
+ if (!value) {
2237
+ alert('Value is required.');
2238
+ return;
2239
+ }
2240
+
2241
+ // Validate key format
2242
+ if (!/^[A-Z0-9_]+$/.test(key)) {
2243
+ alert('Key must contain only uppercase letters, numbers, and underscores.');
2244
+ return;
2245
+ }
2246
+
2247
+ try {
2248
+ if (isEdit) {
2249
+ await api(`/api/env/${encodeURIComponent(key)}`, {
2250
+ method: 'PUT',
2251
+ body: JSON.stringify({ value }),
2252
+ });
2253
+ } else {
2254
+ await api('/api/env', {
2255
+ method: 'POST',
2256
+ body: JSON.stringify({ key, value }),
2257
+ });
2258
+ }
2259
+
2260
+ closeEnvModal();
2261
+ loadEnvVariables();
2262
+ } catch (err) {
2263
+ alert('Failed to save: ' + err.message);
2264
+ }
2265
+ }
2266
+
2267
+ async function deleteEnvVariable(key) {
2268
+ if (!confirm(`Delete environment variable "${key}"?\n\nThis will remove it from ~/.costar/.env and the current process.`)) {
2269
+ return;
2270
+ }
2271
+
2272
+ try {
2273
+ await api(`/api/env/${encodeURIComponent(key)}`, { method: 'DELETE' });
2274
+ loadEnvVariables();
2275
+ } catch (err) {
2276
+ alert('Failed to delete: ' + err.message);
2277
+ }
2075
2278
  }
2076
2279
 
2077
2280
  // ─── Utilities ───────────────────────────────────
@@ -0,0 +1,117 @@
1
+ # Coinbase Trading Journal
2
+
3
+ > **Read this before every Coinbase operation.** Update after every trade.
4
+ > Never delete entries — mark outdated ones as `[OUTDATED]` with date and reason.
5
+
6
+ ---
7
+
8
+ ## Trade Log
9
+
10
+ | Date | Pair | Side | Type | Size | Fill Price | Fees | P&L | Notes |
11
+ |------|------|------|------|------|------------|------|-----|-------|
12
+ | *(No trades yet)* | | | | | | | | |
13
+
14
+ ---
15
+
16
+ ## API Quirks & Gotchas
17
+
18
+ > Things that tripped you up. Save others (your future self) the pain.
19
+
20
+ 1. All numeric values must be strings — `"100.00"` not `100`
21
+ 2. HTTP 200 doesn't mean success — always check `order.success`
22
+ 3. Duplicate `client_order_id` returns existing order, not error
23
+
24
+ *(Add more as you discover them)*
25
+
26
+ ---
27
+
28
+ ## Fee Insights
29
+
30
+ > Track fee rates and patterns to optimize execution.
31
+
32
+ - **Current fee tier:** *Unknown — check with `getTransactionSummary()`*
33
+ - **Maker rate:** *TBD*
34
+ - **Taker rate:** *TBD*
35
+
36
+ <!--
37
+ - [DATE] Fee observation. Example: post_only limit orders save 0.2% in fees vs market orders
38
+ -->
39
+
40
+ ---
41
+
42
+ ## Order Patterns That Work
43
+
44
+ > Reusable patterns you've validated with real trades.
45
+
46
+ <!--
47
+ - [DATE] PATTERN: description. PAIR: which pair. RESULT: outcome.
48
+ Example:
49
+ - [2026-02-14] PATTERN: Limit buy 0.5% below current price with GTD 1-hour expiry. PAIR: BTC-USD. RESULT: Filled 70% of the time, saved avg $15 in slippage vs market orders.
50
+ -->
51
+
52
+ ---
53
+
54
+ ## Order Patterns That Failed
55
+
56
+ > Equally important — what NOT to do.
57
+
58
+ <!--
59
+ - [DATE] PATTERN: description. PAIR: which pair. PROBLEM: what went wrong.
60
+ Example:
61
+ - [2026-02-14] PATTERN: Market sell large SOL position ($5000). PAIR: SOL-USD. PROBLEM: 1.2% slippage due to thin order book. Should have used limit or split into smaller orders.
62
+ -->
63
+
64
+ ---
65
+
66
+ ## Product-Specific Notes
67
+
68
+ > Observations about specific trading pairs.
69
+
70
+ <!--
71
+ - [DATE] PRODUCT: observation
72
+ Example:
73
+ - [2026-02-14] BTC-USD: min order $1, base_increment 0.00000001, quote_increment 0.01
74
+ - [2026-02-14] SOL-USD: wider spreads after hours, better to trade during US market hours
75
+ -->
76
+
77
+ ---
78
+
79
+ ## Scripts I've Created
80
+
81
+ | Script | Purpose | Works Well? | Last Modified |
82
+ |--------|---------|-------------|---------------|
83
+ | *(None yet)* | | | |
84
+
85
+ ---
86
+
87
+ ## Mistakes to Never Repeat
88
+
89
+ 1. *(Will accumulate from real trading)*
90
+
91
+ ---
92
+
93
+ ## Performance Summary
94
+
95
+ ### All Time
96
+ - **Total Trades:** 0
97
+ - **Total Fees Paid:** $0
98
+ - **Net P&L:** $0
99
+ - **Avg Slippage:** N/A
100
+
101
+ ### Current Week
102
+ - **Trades:** 0
103
+ - **Fees:** $0
104
+ - **P&L:** $0
105
+
106
+ ---
107
+
108
+ ## Open Questions
109
+
110
+ 1. What are the actual minimum order sizes for each product?
111
+ 2. How much slippage to expect on market orders for different pair sizes?
112
+ 3. What's the optimal order type for different trade sizes?
113
+ 4. When does `post_only` get rejected vs filled?
114
+
115
+ ---
116
+
117
+ > Every entry makes your next trade better. Record everything.
@@ -0,0 +1,217 @@
1
+ ---
2
+ name: coinbase
3
+ description: "Execute real trades on Coinbase Advanced Trade. Supports market, limit, stop-limit, and bracket orders for crypto. Handles account balances, order management, price data, portfolio tracking, and fee estimation. Use when the user wants to buy/sell crypto, check balances, place orders, manage positions, or do anything on Coinbase."
4
+ metadata:
5
+ emoji: "🪙"
6
+ requires:
7
+ env: ["COINBASE_API_KEY", "COINBASE_API_SECRET"]
8
+ ---
9
+
10
+ # Coinbase Trading Executor
11
+
12
+ You can execute real trades on Coinbase using the Advanced Trade API. This is real money — be precise, confirm with the user, and never skip risk checks.
13
+
14
+ ## CRITICAL: Always Read LEARNING.md First
15
+
16
+ **Before ANY Coinbase action, read [LEARNING.md](LEARNING.md).** It contains API quirks you've discovered, order patterns that work, mistakes to avoid, and fee insights from real trades.
17
+
18
+ ## Authentication
19
+
20
+ The user's Coinbase API credentials are available as environment variables:
21
+ - `COINBASE_API_KEY` — format: `organizations/{org_id}/apiKeys/{key_id}`
22
+ - `COINBASE_API_SECRET` — EC private key in PEM format
23
+
24
+ These use **CDP (Coinbase Developer Platform) keys** with **ES256 JWT** authentication. The `coinbase-api` npm package handles JWT generation internally.
25
+
26
+ ## Setup
27
+
28
+ Before first use, ensure the SDK is installed in the workspace:
29
+
30
+ ```bash
31
+ npm list coinbase-api 2>/dev/null || npm install coinbase-api
32
+ ```
33
+
34
+ ## How to Execute Trades
35
+
36
+ All Coinbase operations use TypeScript/JavaScript scripts via `execute_code` or shell commands. Use the `coinbase-api` package:
37
+
38
+ ```typescript
39
+ import { CBAdvancedTradeClient } from 'coinbase-api';
40
+
41
+ const client = new CBAdvancedTradeClient({
42
+ apiKey: process.env.COINBASE_API_KEY,
43
+ apiSecret: process.env.COINBASE_API_SECRET,
44
+ });
45
+ ```
46
+
47
+ ## Execution Workflow
48
+
49
+ ```
50
+ - [ ] Step 1: Read LEARNING.md
51
+ - [ ] Step 2: Verify balances and account status
52
+ - [ ] Step 3: Get current price and market data
53
+ - [ ] Step 4: Preview the order (estimate fees/slippage)
54
+ - [ ] Step 5: Execute the order
55
+ - [ ] Step 6: Verify order fill and record result
56
+ - [ ] Step 7: Update LEARNING.md
57
+ ```
58
+
59
+ ### Step 2: Check Balances
60
+
61
+ ```typescript
62
+ const accounts = await client.getAccounts();
63
+ for (const acct of accounts.accounts) {
64
+ if (parseFloat(acct.available_balance.value) > 0) {
65
+ console.log(`${acct.currency}: ${acct.available_balance.value}`);
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### Step 3: Get Price Data
71
+
72
+ ```typescript
73
+ // Current price
74
+ const product = await client.getProduct({ product_id: 'BTC-USD' });
75
+ console.log(`Price: $${product.price}, 24h change: ${product.price_percentage_change_24h}%`);
76
+
77
+ // Best bid/ask spread
78
+ const spread = await client.getBestBidAsk({ product_ids: ['BTC-USD'] });
79
+
80
+ // OHLCV candles (last 24h, 1-hour granularity)
81
+ const candles = await client.getProductCandles({
82
+ product_id: 'BTC-USD',
83
+ start: String(Math.floor(Date.now() / 1000) - 86400),
84
+ end: String(Math.floor(Date.now() / 1000)),
85
+ granularity: 'ONE_HOUR',
86
+ });
87
+ ```
88
+
89
+ ### Step 4: Preview Order
90
+
91
+ **Always preview before executing.** This shows estimated fees and slippage:
92
+
93
+ ```typescript
94
+ const preview = await client.previewOrder({
95
+ product_id: 'BTC-USD',
96
+ side: 'BUY',
97
+ order_configuration: {
98
+ market_market_ioc: { quote_size: '100.00' },
99
+ },
100
+ });
101
+ // Shows: quote_size, base_size, best_bid, best_ask, total_fees, slippage
102
+ ```
103
+
104
+ ### Step 5: Place Orders
105
+
106
+ See [references/order-types.md](references/order-types.md) for all order types and configurations.
107
+
108
+ **Market Buy** (spend $X of quote currency):
109
+ ```typescript
110
+ const order = await client.submitOrder({
111
+ client_order_id: crypto.randomUUID(),
112
+ product_id: 'BTC-USD',
113
+ side: 'BUY',
114
+ order_configuration: {
115
+ market_market_ioc: { quote_size: '100.00' }, // spend $100
116
+ },
117
+ });
118
+ ```
119
+
120
+ **Market Sell** (sell X units of base asset):
121
+ ```typescript
122
+ const order = await client.submitOrder({
123
+ client_order_id: crypto.randomUUID(),
124
+ product_id: 'BTC-USD',
125
+ side: 'SELL',
126
+ order_configuration: {
127
+ market_market_ioc: { base_size: '0.001' }, // sell 0.001 BTC
128
+ },
129
+ });
130
+ ```
131
+
132
+ **Limit Buy** (Good Till Cancel):
133
+ ```typescript
134
+ const order = await client.submitOrder({
135
+ client_order_id: crypto.randomUUID(),
136
+ product_id: 'BTC-USD',
137
+ side: 'BUY',
138
+ order_configuration: {
139
+ limit_limit_gtc: {
140
+ base_size: '0.001',
141
+ limit_price: '95000.00',
142
+ post_only: false,
143
+ },
144
+ },
145
+ });
146
+ ```
147
+
148
+ ### Step 6: Verify Order
149
+
150
+ ```typescript
151
+ // Check response immediately
152
+ if (order.success) {
153
+ const orderId = order.success_response.order_id;
154
+ // Get fill details
155
+ const status = await client.getOrder({ order_id: orderId });
156
+ console.log(`Status: ${status.status}, Filled: ${status.filled_size} @ ${status.average_filled_price}`);
157
+ } else {
158
+ console.error(`Order failed: ${order.error_response.message}`);
159
+ }
160
+ ```
161
+
162
+ **IMPORTANT:** Coinbase returns HTTP 200 even on order failure. Always check `order.success === true`.
163
+
164
+ ### Step 7: Update LEARNING.md
165
+
166
+ After every trade, record: asset, side, type, size, fill price, fees, outcome, and any API quirks encountered.
167
+
168
+ ## Order Management
169
+
170
+ ```typescript
171
+ // List open orders
172
+ const orders = await client.getOrders({ order_status: ['OPEN', 'PENDING'] });
173
+
174
+ // Cancel specific orders
175
+ const cancel = await client.cancelOrders({ order_ids: ['order-id-here'] });
176
+
177
+ // Get fills (trade executions)
178
+ const fills = await client.getFills({ order_id: 'order-id-here' });
179
+ ```
180
+
181
+ ## Portfolio & Fees
182
+
183
+ ```typescript
184
+ // Portfolio breakdown
185
+ const portfolios = await client.getPortfolios({});
186
+ const breakdown = await client.getPortfolioBreakdown({ portfolio_id: portfolios.portfolios[0].uuid });
187
+
188
+ // Fee tier info
189
+ const fees = await client.getTransactionSummary({});
190
+ // Shows: fee_tier, total_volume, maker_fee_rate, taker_fee_rate
191
+ ```
192
+
193
+ ## Critical Rules
194
+
195
+ 1. **ALWAYS preview orders before executing** — know the fees and slippage
196
+ 2. **All values are STRINGS** — `"100.00"` not `100`. The API rejects numbers.
197
+ 3. **client_order_id must be unique** — always use `crypto.randomUUID()`
198
+ 4. **Check `order.success`** — HTTP 200 doesn't mean order succeeded
199
+ 5. **Product ID format** — always `BASE-QUOTE` e.g. `BTC-USD`, `ETH-USD`, `SOL-USD`
200
+ 6. **Minimum order sizes vary** — check product details before ordering
201
+ 7. **Rate limits** — ~10 requests/second for private endpoints
202
+ 8. **Never place orders in a loop without delays** — respect rate limits
203
+ 9. **Log everything in LEARNING.md** — every trade, every error, every quirk
204
+
205
+ ## Reference Files
206
+
207
+ - [references/order-types.md](references/order-types.md) — All order configurations with examples
208
+ - [references/api-reference.md](references/api-reference.md) — Complete API endpoint list
209
+ - [references/products.md](references/products.md) — Common trading pairs and their details
210
+
211
+ ## Self-Evolution
212
+
213
+ You can modify any file in this skill:
214
+ - Found an API quirk? Update LEARNING.md and references
215
+ - Built a reusable script? Save it in `scripts/`
216
+ - Discovered a better workflow? Update this SKILL.md
217
+ - New order type pattern? Add to `references/order-types.md`
@@ -0,0 +1,124 @@
1
+ # Coinbase Advanced Trade API Reference
2
+
3
+ > Agent-editable. Add notes and quirks as you discover them.
4
+ > Last updated: Initial version
5
+
6
+ ## Base URL
7
+
8
+ `https://api.coinbase.com/api/v3/brokerage/`
9
+
10
+ ## SDK Methods (coinbase-api package)
11
+
12
+ ### Accounts & Balances
13
+
14
+ | Method | Description |
15
+ |--------|-------------|
16
+ | `client.getAccounts()` | List all accounts with balances |
17
+ | `client.getAccount({ account_id })` | Get specific account |
18
+
19
+ ### Market Data
20
+
21
+ | Method | Description |
22
+ |--------|-------------|
23
+ | `client.getProducts({})` | List all trading pairs |
24
+ | `client.getProduct({ product_id })` | Get product details + current price |
25
+ | `client.getProductCandles({ product_id, start, end, granularity })` | OHLCV candle data |
26
+ | `client.getProductBook({ product_id, limit })` | Order book depth |
27
+ | `client.getBestBidAsk({ product_ids: [...] })` | Best bid/ask spread |
28
+ | `client.getMarketTrades({ product_id, limit })` | Recent market trades |
29
+
30
+ ### Orders
31
+
32
+ | Method | Description |
33
+ |--------|-------------|
34
+ | `client.submitOrder({ ... })` | Place an order |
35
+ | `client.previewOrder({ ... })` | Preview order (estimate fees/slippage) |
36
+ | `client.getOrder({ order_id })` | Get order details |
37
+ | `client.getOrders({ order_status, product_id, ... })` | List orders with filters |
38
+ | `client.cancelOrders({ order_ids: [...] })` | Cancel one or more orders |
39
+ | `client.getFills({ order_id, product_id, ... })` | Get trade fills |
40
+
41
+ ### Portfolios
42
+
43
+ | Method | Description |
44
+ |--------|-------------|
45
+ | `client.getPortfolios({})` | List portfolios |
46
+ | `client.getPortfolioBreakdown({ portfolio_id })` | Detailed breakdown with positions |
47
+
48
+ ### Fees
49
+
50
+ | Method | Description |
51
+ |--------|-------------|
52
+ | `client.getTransactionSummary({})` | Fee tier, volume, maker/taker rates |
53
+
54
+ ## Candle Granularity Options
55
+
56
+ | Value | Period |
57
+ |-------|--------|
58
+ | `ONE_MINUTE` | 1 min |
59
+ | `FIVE_MINUTE` | 5 min |
60
+ | `FIFTEEN_MINUTE` | 15 min |
61
+ | `THIRTY_MINUTE` | 30 min |
62
+ | `ONE_HOUR` | 1 hr |
63
+ | `TWO_HOUR` | 2 hr |
64
+ | `SIX_HOUR` | 6 hr |
65
+ | `ONE_DAY` | 1 day |
66
+
67
+ ## Candle Timestamps
68
+
69
+ - `start` and `end` are **Unix timestamps in seconds** (as strings)
70
+ - Example: last 24 hours
71
+ ```typescript
72
+ start: String(Math.floor(Date.now() / 1000) - 86400),
73
+ end: String(Math.floor(Date.now() / 1000)),
74
+ ```
75
+
76
+ ## Rate Limits
77
+
78
+ | Type | Limit |
79
+ |------|-------|
80
+ | Private endpoints | ~10 requests/second |
81
+ | Private endpoints | ~5,000 requests/hour |
82
+ | Public endpoints | ~10,000 requests/hour |
83
+
84
+ Public endpoints have 1-second cache. Use WebSocket for real-time data.
85
+
86
+ ## Error Handling
87
+
88
+ The API returns HTTP 200 for most responses, including failures. Always check:
89
+
90
+ ```typescript
91
+ if (order.success) {
92
+ // order.success_response.order_id
93
+ } else {
94
+ // order.error_response.error, order.error_response.message
95
+ // Common: INSUFFICIENT_FUND, INVALID_LIMIT_PRICE_POST_ONLY, UNKNOWN_FAILURE_REASON
96
+ }
97
+ ```
98
+
99
+ ## Common Error Codes
100
+
101
+ | Error | Meaning | Fix |
102
+ |-------|---------|-----|
103
+ | `INSUFFICIENT_FUND` | Not enough balance | Check available balance first |
104
+ | `INVALID_LIMIT_PRICE_POST_ONLY` | Post-only order would immediately fill | Adjust price or set `post_only: false` |
105
+ | `UNKNOWN_FAILURE_REASON` | Generic failure | Check order params, product status |
106
+ | `UNSUPPORTED_ORDER_CONFIGURATION` | Invalid order config | Check order type and required fields |
107
+ | `INVALID_PRODUCT_ID` | Product doesn't exist | Verify product_id with `getProducts()` |
108
+
109
+ ## WebSocket Channels (Real-Time)
110
+
111
+ | Channel | Auth | Description |
112
+ |---------|------|-------------|
113
+ | `ticker` | Public | Real-time price per trade |
114
+ | `ticker_batch` | Public | Batched price updates |
115
+ | `level2` | Public | Order book updates |
116
+ | `market_trades` | Public | Trade executions |
117
+ | `candles` | Public | OHLCV updates |
118
+ | `status` | Public | Product status changes |
119
+ | `user` | Private | Your order fills and updates |
120
+ | `heartbeats` | Public | Connection keepalive |
121
+
122
+ WebSocket endpoints:
123
+ - Market data: `wss://advanced-trade-ws.coinbase.com`
124
+ - User data: `wss://advanced-trade-ws-user.coinbase.com`