@dodopayments/opencode-plugin 0.5.1 → 0.6.1

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: credit-based-billing
3
- description: Complete guide for giving customers included, free, prepaid, promotional, or top-up credits using grants, balances, ledger deductions, rollover, expiry, alerts, and overage.
3
+ description: Dodo Payments credit-based billing with credit entitlements, grants, balances, ledger deductions, rollover, expiry, low-balance alerts, and overage. Use when selling prepaid or top-up credit packs, including credits in a plan, deducting AI token or API credits via meters, or granting promo credits; use usage-based-billing for direct per-unit charges.
4
4
  ---
5
5
 
6
6
  # Dodo Payments Credit-Based Billing
@@ -203,7 +203,7 @@ Attach the credit entitlement to the same usage-based product as the meter. The
203
203
  | Field | Purpose |
204
204
  |---|---|
205
205
  | `meter_id` | Meter to aggregate usage. |
206
- | `free_threshold` | Optional usage excluded before rating. |
206
+ | `free_threshold` | Free usage excluded before rating - applies **only** when the meter bills in money. It is ignored for credit-billed meters: every unit is deducted from the credit balance. |
207
207
  | `credit_entitlement_id` | Credit pool consumed by this meter. |
208
208
  | `meter_units_per_credit` | Usage units required to deduct one credit; required when `credit_entitlement_id` is set. |
209
209
  | `price_per_unit` | Direct per-unit monetary price when using pure usage pricing; decimal string. |
@@ -297,7 +297,7 @@ If the same `idempotency_key` already exists, the API can return `409`. Treat th
297
297
 
298
298
  ## Deduct credits manually
299
299
 
300
- Use the same method with `entry_type: 'debit'`. Debits consume the oldest grants first (FIFO) and can return `400` when the balance is insufficient.
300
+ Use the same method with `entry_type: 'debit'`. Debits consume the earliest-expiring grants first and can return `400` when the balance is insufficient.
301
301
 
302
302
  ```typescript
303
303
  async function deductJobCredits(
@@ -356,10 +356,9 @@ Persist the Dodo ledger entry ID with internal order, support-case, or job IDs.
356
356
  2. Add the meter to the same product.
357
357
  3. Enable **Bill usage in Credits** for that meter.
358
358
  4. Set `credit_entitlement_id` and `meter_units_per_credit`.
359
- 5. Optionally set `free_threshold`; usage under it is excluded.
360
- 6. Ingest events whose case-sensitive `event_name` matches the meter.
359
+ 5. Ingest events whose case-sensitive `event_name` matches the meter.
361
360
 
362
- A background worker processes new usage approximately every minute, aggregates it according to the meter, converts meter units using `meter_units_per_credit`, and consumes the oldest non-expired grants first (FIFO). Multiple meters can consume one shared credit pool at different conversion rates.
361
+ A background worker processes new usage approximately every minute, aggregates it according to the meter, converts meter units using `meter_units_per_credit`, and deducts from non-expired grants with the earliest-expiring grants consumed first. There is no free threshold on a credit-billed meter. Multiple meters can consume one shared credit pool at different conversion rates.
363
362
 
364
363
  This delay is material: an accepted usage event does not imply that a balance read immediately afterward includes its deduction.
365
364
 
@@ -467,7 +466,7 @@ The lifecycle is:
467
466
  1. The model returns actual prompt and completion token counts.
468
467
  2. The app ingests one uniquely identified `ai.tokens` event.
469
468
  3. The meter aggregates the `tokens` metadata value.
470
- 4. The worker converts units into credits and deducts FIFO from non-expired grants.
469
+ 4. The worker converts units into credits and deducts from non-expired grants, earliest-expiring first.
471
470
  5. Dodo emits `credit.deducted`; when configured threshold conditions are met, it emits `credit.balance_low`.
472
471
  6. The app notifies the customer and offers checkout for the attached top-up product.
473
472
 
@@ -588,7 +587,7 @@ Return an application-level payment/credit-required response with a top-up or up
588
587
  16. **Sending duplicate IDs within one batch.** Duplicate `event_id` values in the same ingestion request reject the entire request. Previously ingested IDs are ignored on retry.
589
588
  17. **Inventing a deduplication window.** Current docs require unique event IDs but do not publish a retention duration.
590
589
  18. **Getting conversion backward.** `meter_units_per_credit` is usage units required for one credit, and is required when `credit_entitlement_id` is set.
591
- 19. **Ignoring the free threshold.** Usage under `free_threshold` is excluded before credit deduction.
590
+ 19. **Expecting a free threshold on credit-billed meters.** `free_threshold` applies only to meters billed in money. When **Bill usage in Credits** is on, every unit deducts credits; model included usage as granted credits instead.
592
591
  20. **Supplying one rollover timeframe field.** `rollover_timeframe_count` and `rollover_timeframe_interval` must be supplied together.
593
592
  21. **Using the wrong rollover enum case.** Confirmed values are `Day`, `Week`, `Month`, and `Year`.
594
593
  22. **Assuming expiry happens before rollover.** Rollover is applied first; only the remainder expires.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: customer-management
3
- description: Guide for managing customer records, saved payment methods, monetary wallets, and hosted customer-portal sessions; subscription lifecycle and custom billing UI are covered separately.
3
+ description: Dodo Payments customer management, covering customer CRUD, saved payment methods, real-money customer wallets and ledger entries, entitlements, and hosted customer portal sessions. Use when linking app users to cus_ IDs, opening a Manage billing portal, listing or deleting payment methods, or crediting wallets; use credit-based-billing for usage credits.
4
4
  ---
5
5
 
6
6
  # Customer Management
@@ -68,19 +68,28 @@ await client.customers.update('cus_abc123', {
68
68
 
69
69
  ## Customer Portal
70
70
 
71
- Create a time-bound session that redirects customers to a hosted portal. The portal is read-only for most actions; customers can view invoices, cancel subscriptions, change plans, update payment methods, recover on-hold subscriptions, and download license keys.
71
+ Create a time-bound session that redirects customers to a hosted portal. In the portal, customers can view invoices, cancel subscriptions, change plans, update payment methods, recover on-hold subscriptions, and download license keys.
72
+
73
+ Never call the SDK from the browser: it uses your secret API key. Create the session in a server route and return only the link.
72
74
 
73
75
  ```typescript
74
- // Create a portal session
76
+ // Server route: create a portal session
75
77
  const session = await client.customers.customerPortal.create('cus_abc123', {
76
78
  return_url: 'https://yourapp.com/dashboard',
79
+ // send_email: true, // optionally also email the link to the customer
77
80
  });
78
81
 
79
- // Redirect the customer
80
- window.location.href = session.link;
82
+ return Response.json({ link: session.link });
81
83
  ```
82
84
 
83
- The portal session link expires after a short time. Customers cannot:
85
+ ```typescript
86
+ // Browser: redirect the customer
87
+ const res = await fetch('/api/portal', { method: 'POST' });
88
+ const { link } = await res.json();
89
+ window.location.href = link;
90
+ ```
91
+
92
+ The portal session link is valid for 24 hours. Pass `send_email: true` to have Dodo also email the link to the customer. Customers cannot:
84
93
  - Create new subscriptions (use checkout for that)
85
94
  - Refund themselves
86
95
  - Change their email or name (contact support)
@@ -225,23 +234,22 @@ const found = customers.items.find(c => c.email === user.email);
225
234
 
226
235
  ## Common mistakes
227
236
 
228
- **Creating duplicate customers per checkout.** Each checkout should reuse an existing `customer_id` or create the customer once. Creating a new customer for every transaction fragments your customer data.
237
+ **Creating duplicate customers per checkout.** Passing `customer: { email }` does not create a new customer each time: by default, checkout uses the email to find an existing customer and attaches the session to it. Duplicates come from setting `feature_flags.always_create_new_customer: true`, or from calling `customers.create` before every checkout. Create the customer once (or let the first checkout create it), store its `customer_id`, and pass that on later checkouts.
229
238
 
230
239
  ```typescript
231
- // WRONG
240
+ // WRONG: forces a new customer record on every checkout
232
241
  const session = await client.checkoutSessions.create({
233
- product_cart: [...],
234
- customer: { email: user.email }, // Creates a new customer each time
242
+ product_cart: [{ product_id: 'pdt_abc', quantity: 1 }],
243
+ customer: { email: user.email },
244
+ feature_flags: { always_create_new_customer: true },
235
245
  });
236
246
 
237
- // RIGHT
238
- const customer = await client.customers.create({
239
- email: user.email,
240
- name: user.name,
241
- });
247
+ // RIGHT: reuse the stored customer ID, or fall back to email (matched to an existing customer by default)
242
248
  const session = await client.checkoutSessions.create({
243
- product_cart: [...],
244
- customer: { customer_id: customer.customer_id }, // Reuse the customer
249
+ product_cart: [{ product_id: 'pdt_abc', quantity: 1 }],
250
+ customer: user.dodo_customer_id
251
+ ? { customer_id: user.dodo_customer_id }
252
+ : { email: user.email },
245
253
  });
246
254
  ```
247
255
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: discounts-and-promotions
3
- description: Guide for implementing discount codes and promotional pricing with Dodo Payments, including CRUD operations, eligibility rules, stacking, subscription-cycle limits, and plan-change preservation.
3
+ description: Dodo Payments discount codes and promotional pricing, covering discount CRUD, percentage and flat amounts, eligibility rules, usage limits, stacking via discount_codes, subscription-cycle limits, and preservation on plan changes. Use when creating coupons or promo codes, applying or validating a code at checkout, or debugging a discount that won't apply.
4
4
  ---
5
5
 
6
6
  # Discounts and Promotions
@@ -20,7 +20,7 @@ This skill covers discount codes, coupons, and promotional pricing in Dodo Payme
20
20
 
21
21
  **Discount types:** Dodo supports both percentage and flat-amount discounts. Percentage amounts are expressed as integers where 1500 means 15% (100 = 1%). Flat amounts are in the smallest currency unit (cents for USD).
22
22
 
23
- **Discount code:** A human-readable string (e.g., `SUMMER2025`) that customers enter at checkout. Codes are case-sensitive.
23
+ **Discount code:** A human-readable string (e.g., `SUMMER2025`) that customers enter at checkout. The API uppercases codes on create (`summer2025` is stored as `SUMMER2025`); codes must be at least 3 characters, and omitting `code` generates a random 16-character uppercase code.
24
24
 
25
25
  **Eligibility:** Discounts can be restricted to specific products, customers, or date ranges. A discount without restrictions applies to any product and any customer.
26
26
 
@@ -119,7 +119,7 @@ await client.discounts.delete('discount_id');
119
119
 
120
120
  ## Discount types and configuration
121
121
 
122
- **Important:** The feature documentation states that only percentage discounts are currently supported. However, the current OpenAPI schema defines both `flat` and `percentage` types with currency options. Follow the API reference when implementing. If you encounter unexpected behavior with flat discounts, verify with Dodo support whether flat discounts are fully enabled in your account.
122
+ Two types are supported: `percentage` and `flat` (shown as **Amount** in the dashboard). A `flat` discount requires `currency_options` with at least one resolvable default currency; for a `percentage` discount, `currency_options` is optional and caps the discount per currency. A flat deduction is pooled across the whole cart rather than applied per line item.
123
123
 
124
124
  ### Percentage discounts
125
125
 
@@ -177,19 +177,12 @@ const discount = await client.discounts.create({
177
177
  customer_eligibility: 'specific',
178
178
  });
179
179
 
180
- await fetch(
181
- `https://test.dodopayments.com/discounts/${discount.discount_id}/customers`,
182
- {
183
- method: 'POST',
184
- headers: {
185
- Authorization: `Bearer ${process.env.DODO_PAYMENTS_API_KEY}`,
186
- 'Content-Type': 'application/json',
187
- },
188
- body: JSON.stringify({
189
- customer_ids: ['cus_vip_001', 'cus_vip_002'],
190
- }),
191
- },
192
- );
180
+ // The SDK has no named method for this endpoint yet. `client.post` reuses the
181
+ // client's auth and base URL (test.dodopayments.com for `test_mode`,
182
+ // live.dodopayments.com for `live_mode`), so nothing is hardcoded.
183
+ await client.post(`/discounts/${discount.discount_id}/customers`, {
184
+ body: { customer_ids: ['cus_vip_001', 'cus_vip_002'] },
185
+ });
193
186
  ```
194
187
 
195
188
  ### Date range
@@ -236,7 +229,10 @@ const discount = await client.discounts.create({
236
229
 
237
230
  ### Single code
238
231
 
232
+ Never call the SDK from the browser: it uses your secret API key. Create the session in a server route and return only the URL.
233
+
239
234
  ```typescript
235
+ // Server route
240
236
  const session = await client.checkoutSessions.create({
241
237
  product_cart: [{ product_id: 'pdt_abc', quantity: 1 }],
242
238
  discount_codes: ['SUMMER2025'],
@@ -244,7 +240,14 @@ const session = await client.checkoutSessions.create({
244
240
  return_url: 'https://yoursite.com/return'
245
241
  });
246
242
 
247
- window.location.href = session.checkout_url;
243
+ return Response.json({ checkout_url: session.checkout_url });
244
+ ```
245
+
246
+ ```typescript
247
+ // Browser
248
+ const res = await fetch('/api/checkout', { method: 'POST' });
249
+ const { checkout_url } = await res.json();
250
+ window.location.href = checkout_url;
248
251
  ```
249
252
 
250
253
  ### Multiple codes (stacking)
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dodo-best-practices
3
- description: Guide for initial Dodo Payments setup, including SDK installation, test and live environments, API keys, and the canonical checkout-to-webhook architecture.
3
+ description: Starting point for any Dodo Payments integration, covering Merchant of Record basics, SDK installation and client setup, test and live environments, API keys, and the canonical checkout-to-webhook flow. Use when starting a new integration, initializing the dodopayments client, choosing an architecture, or unsure which Dodo Payments skill applies.
4
4
  ---
5
5
 
6
6
  # Dodo Payments Integration Guide
@@ -125,8 +125,13 @@ composer require "dodopayments/client:6.19.0"
125
125
  ```php
126
126
  use Dodopayments\Client;
127
127
 
128
+ $apiKey = getenv('DODO_PAYMENTS_API_KEY');
129
+ if ($apiKey === false || $apiKey === '') {
130
+ throw new RuntimeException('DODO_PAYMENTS_API_KEY is not set');
131
+ }
132
+
128
133
  $client = new Client(
129
- bearerToken: getenv('DODO_PAYMENTS_API_KEY') ?: 'My Bearer Token',
134
+ bearerToken: $apiKey,
130
135
  environment: 'test_mode',
131
136
  );
132
137
  ```
@@ -155,8 +160,12 @@ dodo_payments = Dodopayments::Client.new(
155
160
  ```
156
161
 
157
162
  ```java
158
- DodoPaymentsClient client = DodoPaymentsOkHttpClient.fromEnv();
159
- // Reads DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_WEBHOOK_KEY, DODO_PAYMENTS_BASE_URL
163
+ // fromEnv() reads DODO_PAYMENTS_API_KEY, DODO_PAYMENTS_WEBHOOK_KEY, DODO_PAYMENTS_BASE_URL.
164
+ // Without testMode() (or a test DODO_PAYMENTS_BASE_URL) the client targets live mode.
165
+ DodoPaymentsClient client = DodoPaymentsOkHttpClient.builder()
166
+ .fromEnv()
167
+ .testMode()
168
+ .build();
160
169
  ```
161
170
 
162
171
  ### Kotlin
@@ -170,7 +179,11 @@ DodoPaymentsClient client = DodoPaymentsOkHttpClient.fromEnv();
170
179
  ```
171
180
 
172
181
  ```kotlin
173
- val client: DodoPaymentsClient = DodoPaymentsOkHttpClient.fromEnv()
182
+ // Without testMode() (or a test DODO_PAYMENTS_BASE_URL) the client targets live mode.
183
+ val client: DodoPaymentsClient = DodoPaymentsOkHttpClient.builder()
184
+ .fromEnv()
185
+ .testMode()
186
+ .build()
174
187
  ```
175
188
 
176
189
  ---
@@ -0,0 +1,152 @@
1
+ ---
2
+ name: dodo-mcp-usage
3
+ description: How and when to use the Dodo Payments MCP servers (dodo-knowledge for docs search, dodopayments-api for the live API via Code Mode) safely. Use when looking up current Dodo payloads or event names, or inspecting or changing real products, checkouts, customers, subscriptions, webhooks or refunds.
4
+ ---
5
+
6
+ # Using the Dodo Payments MCP servers
7
+
8
+ The [Dodo Payments agent plugin](https://github.com/dodopayments/dodo-agent-plugin) ships two MCP servers. They do different jobs, so pick the right one before you call anything.
9
+
10
+ **Prerequisite:** this skill is useful only when the servers are connected. Installing these skills on their own (for example with `npx skills add`) does **not** connect them. Check your tool list for the Dodo tools; if they are missing, install the agent plugin, which registers both servers, or add them by hand as described under [Authentication](#authentication). Without them, fall back to the other skills and the docs at docs.dodopayments.com.
11
+
12
+ | Server | URL | Auth | Use it for |
13
+ |--------|-----|------|------------|
14
+ | `dodo-knowledge` | `https://knowledge.dodopayments.com/mcp` | None | Semantic search over Dodo Payments docs, API reference and SDK docs |
15
+ | `dodopayments-api` | `https://mcp.dodopayments.com/mcp` | OAuth in the browser, or a local API key | Reading and changing real resources in your Dodo account |
16
+
17
+ ## When to call which
18
+
19
+ ### Call `dodo-knowledge` first, before writing integration code
20
+
21
+ Your memory of the Dodo API can be stale. Search the docs whenever you need:
22
+
23
+ - Request and response shapes (for example checkout session fields, subscription plan change params).
24
+ - Webhook event names and payload fields.
25
+ - SDK method names and which endpoints are deprecated.
26
+ - Base URLs (`https://test.dodopayments.com` for test mode, `https://live.dodopayments.com` for live mode).
27
+
28
+ Prefer a docs result over what you remember. If the docs and your memory disagree, the docs win. Put the framework or language in the query for sharper results, for example "Next.js webhook handler verify signature".
29
+
30
+ ### Call `dodopayments-api` to act on real data
31
+
32
+ Use it when the task needs the user's actual account:
33
+
34
+ - List or create products and prices.
35
+ - Create a checkout session to test a flow end to end.
36
+ - Look up a customer, payment or subscription by ID.
37
+ - Inspect or register webhook endpoints.
38
+ - Issue a refund or cancel a subscription (only after confirmation, see Safety).
39
+
40
+ Don't use the API server to answer "how does X work" questions. That's what `dodo-knowledge` is for.
41
+
42
+ ## How the API server works: Code Mode
43
+
44
+ The Dodo Payments MCP server uses **Code Mode**. Instead of one tool per endpoint, it exposes two tools:
45
+
46
+ 1. **Docs search**: query the API and SDK documentation.
47
+ 2. **Code execution** (`execute`): write TypeScript against the `dodopayments` SDK. It runs in an isolated sandbox with no network or filesystem access, and whatever the code returns or prints comes back as the tool result.
48
+
49
+ What this means for you:
50
+
51
+ - Look at the tool list your client exposes for the exact tool names. Don't invent per-endpoint tool names like `create_product`; they don't exist in Code Mode.
52
+ - The API key is injected server-side. You never pass a key in tool parameters, and you shouldn't try to read one from inside the sandbox.
53
+ - Batch related calls into one script and return only what you need (IDs, statuses, counts), not whole objects. One script with 20 calls beats 20 round trips.
54
+ - Search docs (either server) for the SDK method shape before writing the script, then execute.
55
+
56
+ Typical read-only flow:
57
+
58
+ 1. Search docs for the method, for example "list subscriptions for a customer".
59
+ 2. Run a short `execute` script that calls it and returns a trimmed summary.
60
+ 3. Report the result to the user.
61
+
62
+ ## Safety rules
63
+
64
+ These are non-negotiable when the API server is connected.
65
+
66
+ 1. **Default to test mode.** For the local server, `DODO_PAYMENTS_ENVIRONMENT` defaults to `live_mode` when unset, so set `test_mode` explicitly during development. Use `dodo_test_` keys with test mode and `dodo_live_` keys with live mode.
67
+ 2. **Know which mode you're in before any write.** With the local server, it is `DODO_PAYMENTS_ENVIRONMENT`. With the remote OAuth server the mode follows the account the user signed in to and is not visible in the tool configuration, so **always ask the user to confirm test or live mode before the first write in a session**. A read can help you check: for example, if the user gives an ID, retrieving it succeeds only in the mode it was created in.
68
+ 3. **Read before write.** Retrieve the resource first, show the user what will change, then act.
69
+ 4. **Confirm every live mode write with the user**, naming the exact resource ID and the effect. This is mandatory for:
70
+ - refunds (full or partial),
71
+ - subscription cancellations and plan changes,
72
+ - anything that moves money or affects payouts,
73
+ - deleting or archiving products and discounts,
74
+ - changing or deleting webhook endpoints.
75
+ 5. **Never print secrets.** Don't echo API keys, webhook signing keys, or full card or bank details into chat, logs, or files. Don't return them from `execute` scripts.
76
+ 6. **Don't retry writes blindly.** If a create or refund call errors or times out, read the current state first (list refunds for the payment, fetch the subscription) before trying again, so you don't double-refund or create duplicates.
77
+ 7. **Least privilege.** Dodo API keys can be created with a read-only access level. For inspection-only work with the local server, suggest a read-only key.
78
+
79
+ ## Authentication
80
+
81
+ ### Remote server (recommended)
82
+
83
+ The plugin points at `https://mcp.dodopayments.com/mcp`. The first time a tool is called, the client opens a browser window to sign in to Dodo Payments. Finish sign-in there, then retry the call.
84
+
85
+ Both servers speak Streamable HTTP, so clients connect to the URL directly (no bridge process). Client setup, if the user is wiring it up by hand:
86
+
87
+ - **Claude Code**: `claude mcp add --transport http dodopayments-api https://mcp.dodopayments.com/mcp`
88
+ - **Cursor and other `.mcp.json` clients**: `{"mcpServers": {"dodopayments-api": {"type": "http", "url": "https://mcp.dodopayments.com/mcp"}}}`
89
+ - **VS Code** (`.vscode/mcp.json` uses a top-level `servers` key): `{"servers": {"dodopayments-api": {"type": "http", "url": "https://mcp.dodopayments.com/mcp"}}}`
90
+ - **Codex CLI**: `codex mcp add dodopayments-api --url https://mcp.dodopayments.com/mcp`, then `codex mcp login dodopayments-api`
91
+ - **Claude.ai**: Settings, Connectors, Add Custom Connector, paste the URL.
92
+
93
+ `dodo-knowledge` is configured the same way with `https://knowledge.dodopayments.com/mcp` and needs no sign-in.
94
+
95
+ For a client that cannot dial Streamable HTTP or run MCP OAuth itself, the `mcp-remote` bridge still works as a fallback: run `npx -y mcp-remote@latest https://mcp.dodopayments.com/mcp` as a stdio server.
96
+
97
+ ### Local stdio server
98
+
99
+ Run the server on the user's machine with an API key:
100
+
101
+ ```json
102
+ {
103
+ "mcpServers": {
104
+ "dodopayments_api": {
105
+ "command": "npx",
106
+ "args": ["-y", "dodopayments-mcp@latest"],
107
+ "env": {
108
+ "DODO_PAYMENTS_API_KEY": "dodo_test_...",
109
+ "DODO_PAYMENTS_WEBHOOK_KEY": "your_webhook_key",
110
+ "DODO_PAYMENTS_ENVIRONMENT": "test_mode"
111
+ }
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ | Variable | Required | Notes |
118
+ |----------|----------|-------|
119
+ | `DODO_PAYMENTS_API_KEY` | Yes | From the dashboard, Developer, API Keys |
120
+ | `DODO_PAYMENTS_WEBHOOK_KEY` | No | Webhook signing key |
121
+ | `DODO_PAYMENTS_ENVIRONMENT` | No | `test_mode` or `live_mode`; defaults to `live_mode` |
122
+
123
+ The local `execute` tool needs Deno on `PATH` (Deno 2.8 or earlier for now) and works on macOS and Linux only. On Windows, use WSL2 or the remote server. Docs search still works without Deno.
124
+
125
+ Tell the user to keep the key in their client config or environment, never in a committed file.
126
+
127
+ ## Troubleshooting
128
+
129
+ **"Needs authentication" or 401 from the remote server**
130
+ The OAuth sign-in hasn't happened or has expired. Ask the user to complete the browser sign-in and retry. If it keeps failing, sign out and back in from the client's MCP menu (for example `/mcp` in Claude Code, `codex mcp login`), then restart the client. Clients that still use the older `mcp-remote` bridge cache tokens in `~/.mcp-auth`; clearing that folder forces a fresh sign-in.
131
+
132
+ **Authentication errors with the local server**
133
+ The key and environment don't match. A `dodo_test_` key needs `DODO_PAYMENTS_ENVIRONMENT=test_mode`; a `dodo_live_` key needs `live_mode`. If it still fails, have the user generate a new key in the dashboard.
134
+
135
+ **Empty lists or "not found" for an ID the user just gave you**
136
+ You're probably in the wrong mode. Test mode and live mode data are fully separate. Confirm which mode the ID came from before concluding it doesn't exist.
137
+
138
+ **`execute` errors locally but docs search works**
139
+ Deno is missing, too new, or you're on Windows. See the local server notes above.
140
+
141
+ **Tool execution failures**
142
+ Read the API error in the result; it usually names the bad field. Re-check the method shape with `dodo-knowledge`, then fix the script. You can also have the user reproduce the call with curl against `https://test.dodopayments.com` to rule out the MCP layer.
143
+
144
+ **Rate limiting (429)**
145
+ Slow down, batch work into fewer scripts, and back off before retrying. Don't loop on writes.
146
+
147
+ ## Related skills
148
+
149
+ - `checkout-integration`: building checkout sessions in app code.
150
+ - `webhook-integration`: verifying and handling webhook events.
151
+ - `testing-and-go-live`: moving from test mode to live mode safely.
152
+ - `dodo-best-practices`: overall integration guidance.