#
tokens: 49966/50000 141/259 files (page 1/8)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 8. Use http://codebase.md/dodopayments/dodopayments-node?page={x} to view the full context.

# Directory Structure

```
├── .devcontainer
│   └── devcontainer.json
├── .github
│   └── workflows
│       ├── ci.yml
│       ├── docker-mcp.yml
│       ├── publish-npm.yml
│       └── release-doctor.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── .release-please-manifest.json
├── .stats.yml
├── api.md
├── bin
│   ├── check-release-environment
│   ├── cli
│   ├── docker-tags
│   ├── migration-config.json
│   └── publish-npm
├── Brewfile
├── CHANGELOG.md
├── CONTRIBUTING.md
├── eslint.config.mjs
├── examples
│   └── .keep
├── jest.config.ts
├── LICENSE
├── MIGRATION.md
├── package.json
├── packages
│   └── mcp-server
│       ├── .dockerignore
│       ├── build
│       ├── cloudflare-worker
│       │   ├── .gitignore
│       │   ├── biome.json
│       │   ├── package.json
│       │   ├── README.md
│       │   ├── src
│       │   │   ├── app.ts
│       │   │   ├── index.ts
│       │   │   └── utils.ts
│       │   ├── static
│       │   │   └── home.md
│       │   ├── tsconfig.json
│       │   ├── worker-configuration.d.ts
│       │   └── wrangler.jsonc
│       ├── Dockerfile
│       ├── jest.config.ts
│       ├── manifest.json
│       ├── package.json
│       ├── README.md
│       ├── scripts
│       │   ├── copy-bundle-files.cjs
│       │   └── postprocess-dist-package-json.cjs
│       ├── src
│       │   ├── code-tool-paths.cts
│       │   ├── code-tool-types.ts
│       │   ├── code-tool-worker.ts
│       │   ├── code-tool.ts
│       │   ├── compat.ts
│       │   ├── docs-search-tool.ts
│       │   ├── dynamic-tools.ts
│       │   ├── filtering.ts
│       │   ├── headers.ts
│       │   ├── http.ts
│       │   ├── index.ts
│       │   ├── options.ts
│       │   ├── server.ts
│       │   ├── stdio.ts
│       │   ├── tools
│       │   │   ├── addons
│       │   │   │   ├── create-addons.ts
│       │   │   │   ├── list-addons.ts
│       │   │   │   ├── retrieve-addons.ts
│       │   │   │   ├── update-addons.ts
│       │   │   │   └── update-images-addons.ts
│       │   │   ├── brands
│       │   │   │   ├── create-brands.ts
│       │   │   │   ├── list-brands.ts
│       │   │   │   ├── retrieve-brands.ts
│       │   │   │   ├── update-brands.ts
│       │   │   │   └── update-images-brands.ts
│       │   │   ├── checkout-sessions
│       │   │   │   └── create-checkout-sessions.ts
│       │   │   ├── customers
│       │   │   │   ├── create-customers.ts
│       │   │   │   ├── customer-portal
│       │   │   │   │   └── create-customers-customer-portal.ts
│       │   │   │   ├── list-customers.ts
│       │   │   │   ├── retrieve-customers.ts
│       │   │   │   ├── update-customers.ts
│       │   │   │   └── wallets
│       │   │   │       ├── ledger-entries
│       │   │   │       │   ├── create-wallets-customers-ledger-entries.ts
│       │   │   │       │   └── list-wallets-customers-ledger-entries.ts
│       │   │   │       └── list-customers-wallets.ts
│       │   │   ├── discounts
│       │   │   │   ├── create-discounts.ts
│       │   │   │   ├── delete-discounts.ts
│       │   │   │   ├── list-discounts.ts
│       │   │   │   ├── retrieve-discounts.ts
│       │   │   │   └── update-discounts.ts
│       │   │   ├── disputes
│       │   │   │   ├── list-disputes.ts
│       │   │   │   └── retrieve-disputes.ts
│       │   │   ├── index.ts
│       │   │   ├── invoices
│       │   │   │   └── payments
│       │   │   │       ├── retrieve-invoices-payments.ts
│       │   │   │       └── retrieve-refund-invoices-payments.ts
│       │   │   ├── license-key-instances
│       │   │   │   ├── list-license-key-instances.ts
│       │   │   │   ├── retrieve-license-key-instances.ts
│       │   │   │   └── update-license-key-instances.ts
│       │   │   ├── license-keys
│       │   │   │   ├── list-license-keys.ts
│       │   │   │   ├── retrieve-license-keys.ts
│       │   │   │   └── update-license-keys.ts
│       │   │   ├── licenses
│       │   │   │   ├── activate-licenses.ts
│       │   │   │   ├── deactivate-licenses.ts
│       │   │   │   └── validate-licenses.ts
│       │   │   ├── meters
│       │   │   │   ├── archive-meters.ts
│       │   │   │   ├── create-meters.ts
│       │   │   │   ├── list-meters.ts
│       │   │   │   ├── retrieve-meters.ts
│       │   │   │   └── unarchive-meters.ts
│       │   │   ├── misc
│       │   │   │   └── list-supported-countries-misc.ts
│       │   │   ├── payments
│       │   │   │   ├── create-payments.ts
│       │   │   │   ├── list-payments.ts
│       │   │   │   ├── retrieve-line-items-payments.ts
│       │   │   │   └── retrieve-payments.ts
│       │   │   ├── payouts
│       │   │   │   └── list-payouts.ts
│       │   │   ├── products
│       │   │   │   ├── archive-products.ts
│       │   │   │   ├── create-products.ts
│       │   │   │   ├── images
│       │   │   │   │   └── update-products-images.ts
│       │   │   │   ├── list-products.ts
│       │   │   │   ├── retrieve-products.ts
│       │   │   │   ├── unarchive-products.ts
│       │   │   │   ├── update-files-products.ts
│       │   │   │   └── update-products.ts
│       │   │   ├── refunds
│       │   │   │   ├── create-refunds.ts
│       │   │   │   ├── list-refunds.ts
│       │   │   │   └── retrieve-refunds.ts
│       │   │   ├── subscriptions
│       │   │   │   ├── change-plan-subscriptions.ts
│       │   │   │   ├── charge-subscriptions.ts
│       │   │   │   ├── create-subscriptions.ts
│       │   │   │   ├── list-subscriptions.ts
│       │   │   │   ├── retrieve-subscriptions.ts
│       │   │   │   ├── retrieve-usage-history-subscriptions.ts
│       │   │   │   └── update-subscriptions.ts
│       │   │   ├── types.ts
│       │   │   ├── usage-events
│       │   │   │   ├── ingest-usage-events.ts
│       │   │   │   ├── list-usage-events.ts
│       │   │   │   └── retrieve-usage-events.ts
│       │   │   └── webhooks
│       │   │       ├── create-webhooks.ts
│       │   │       ├── delete-webhooks.ts
│       │   │       ├── headers
│       │   │       │   ├── retrieve-webhooks-headers.ts
│       │   │       │   └── update-webhooks-headers.ts
│       │   │       ├── list-webhooks.ts
│       │   │       ├── retrieve-secret-webhooks.ts
│       │   │       ├── retrieve-webhooks.ts
│       │   │       └── update-webhooks.ts
│       │   └── tools.ts
│       ├── tests
│       │   ├── compat.test.ts
│       │   ├── dynamic-tools.test.ts
│       │   ├── options.test.ts
│       │   └── tools.test.ts
│       ├── tsc-multi.json
│       ├── tsconfig.build.json
│       ├── tsconfig.dist-src.json
│       ├── tsconfig.json
│       └── yarn.lock
├── README.md
├── release-please-config.json
├── scripts
│   ├── bootstrap
│   ├── build
│   ├── build-all
│   ├── fast-format
│   ├── format
│   ├── lint
│   ├── mock
│   ├── publish-packages.ts
│   ├── test
│   └── utils
│       ├── attw-report.cjs
│       ├── check-is-in-git-install.sh
│       ├── check-version.cjs
│       ├── fix-index-exports.cjs
│       ├── git-swap.sh
│       ├── make-dist-package-json.cjs
│       ├── postprocess-files.cjs
│       └── upload-artifact.sh
├── SECURITY.md
├── src
│   ├── api-promise.ts
│   ├── client.ts
│   ├── core
│   │   ├── api-promise.ts
│   │   ├── error.ts
│   │   ├── pagination.ts
│   │   ├── README.md
│   │   ├── resource.ts
│   │   └── uploads.ts
│   ├── error.ts
│   ├── index.ts
│   ├── internal
│   │   ├── builtin-types.ts
│   │   ├── detect-platform.ts
│   │   ├── errors.ts
│   │   ├── headers.ts
│   │   ├── parse.ts
│   │   ├── README.md
│   │   ├── request-options.ts
│   │   ├── shim-types.ts
│   │   ├── shims.ts
│   │   ├── to-file.ts
│   │   ├── types.ts
│   │   ├── uploads.ts
│   │   ├── utils
│   │   │   ├── base64.ts
│   │   │   ├── bytes.ts
│   │   │   ├── env.ts
│   │   │   ├── log.ts
│   │   │   ├── path.ts
│   │   │   ├── sleep.ts
│   │   │   ├── uuid.ts
│   │   │   └── values.ts
│   │   └── utils.ts
│   ├── lib
│   │   └── .keep
│   ├── pagination.ts
│   ├── resource.ts
│   ├── resources
│   │   ├── addons.ts
│   │   ├── brands.ts
│   │   ├── checkout-sessions.ts
│   │   ├── customers
│   │   │   ├── customer-portal.ts
│   │   │   ├── customers.ts
│   │   │   ├── index.ts
│   │   │   ├── wallets
│   │   │   │   ├── index.ts
│   │   │   │   ├── ledger-entries.ts
│   │   │   │   └── wallets.ts
│   │   │   └── wallets.ts
│   │   ├── customers.ts
│   │   ├── discounts.ts
│   │   ├── disputes.ts
│   │   ├── index.ts
│   │   ├── invoices
│   │   │   ├── index.ts
│   │   │   ├── invoices.ts
│   │   │   └── payments.ts
│   │   ├── invoices.ts
│   │   ├── license-key-instances.ts
│   │   ├── license-keys.ts
│   │   ├── licenses.ts
│   │   ├── meters.ts
│   │   ├── misc.ts
│   │   ├── payments.ts
│   │   ├── payouts.ts
│   │   ├── products
│   │   │   ├── images.ts
│   │   │   ├── index.ts
│   │   │   └── products.ts
│   │   ├── products.ts
│   │   ├── refunds.ts
│   │   ├── subscriptions.ts
│   │   ├── usage-events.ts
│   │   ├── webhook-events.ts
│   │   ├── webhooks
│   │   │   ├── headers.ts
│   │   │   ├── index.ts
│   │   │   └── webhooks.ts
│   │   └── webhooks.ts
│   ├── resources.ts
│   ├── uploads.ts
│   └── version.ts
├── tests
│   ├── api-resources
│   │   ├── addons.test.ts
│   │   ├── brands.test.ts
│   │   ├── checkout-sessions.test.ts
│   │   ├── customers
│   │   │   ├── customer-portal.test.ts
│   │   │   ├── customers.test.ts
│   │   │   └── wallets
│   │   │       ├── ledger-entries.test.ts
│   │   │       └── wallets.test.ts
│   │   ├── discounts.test.ts
│   │   ├── disputes.test.ts
│   │   ├── license-key-instances.test.ts
│   │   ├── license-keys.test.ts
│   │   ├── licenses.test.ts
│   │   ├── meters.test.ts
│   │   ├── misc.test.ts
│   │   ├── payments.test.ts
│   │   ├── payouts.test.ts
│   │   ├── products
│   │   │   ├── images.test.ts
│   │   │   └── products.test.ts
│   │   ├── refunds.test.ts
│   │   ├── subscriptions.test.ts
│   │   ├── usage-events.test.ts
│   │   └── webhooks
│   │       ├── headers.test.ts
│   │       └── webhooks.test.ts
│   ├── base64.test.ts
│   ├── buildHeaders.test.ts
│   ├── form.test.ts
│   ├── index.test.ts
│   ├── path.test.ts
│   ├── stringifyQuery.test.ts
│   └── uploads.test.ts
├── tsc-multi.json
├── tsconfig.build.json
├── tsconfig.deno.json
├── tsconfig.dist-src.json
├── tsconfig.json
└── yarn.lock
```

# Files

--------------------------------------------------------------------------------
/.release-please-manifest.json:
--------------------------------------------------------------------------------

```json
{
  ".": "2.3.0"
}

```

--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------

```
CHANGELOG.md
/ecosystem-tests/*/**
/node_modules
/deno

# don't format tsc output, will break source maps
dist

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
.prism.log
node_modules
yarn-error.log
codegen.log
Brewfile.lock.json
dist
dist-deno
/*.tgz
.idea/
.eslintcache
dist-bundle
*.mcpb

```

--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------

```json
{
  "arrowParens": "always",
  "experimentalTernaries": true,
  "printWidth": 110,
  "singleQuote": true,
  "trailingComma": "all"
}

```

--------------------------------------------------------------------------------
/src/lib/.keep:
--------------------------------------------------------------------------------

```
File generated from our OpenAPI spec by Stainless.

This directory can be used to store custom files to expand the SDK.
It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.

```

--------------------------------------------------------------------------------
/examples/.keep:
--------------------------------------------------------------------------------

```
File generated from our OpenAPI spec by Stainless.

This directory can be used to store example files demonstrating usage of this SDK.
It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.

```

--------------------------------------------------------------------------------
/.stats.yml:
--------------------------------------------------------------------------------

```yaml
configured_endpoints: 77
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/dodo-payments%2Fdodo-payments-233d6735a99febbd67b26d1682829a9958a94aeb99afb13d9a0f7ed2bbf760e7.yml
openapi_spec_hash: 15ae965d2fa82590457ccf0ed7d83a30
config_hash: 4e66056c26dbceea9d8c234d58c45669

```

--------------------------------------------------------------------------------
/packages/mcp-server/.dockerignore:
--------------------------------------------------------------------------------

```
# Dependencies
node_modules/
**/node_modules/

# Build outputs
dist/
**/dist/
build/
**/build/

# Git
.git/
.gitignore

# CI/CD
.github/
.gitlab-ci.yml
.travis.yml

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Documentation
*.md
docs/
LICENSE

# Testing
test/
tests/
__tests__/
*.test.js
*.spec.js
coverage/
.nyc_output/

# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Environment
.env
.env.*

# Temporary files
*.tmp
*.temp
.cache/

# Examples and scripts
examples/
bin/

# Other packages (we only need mcp-server)
packages/*/
!packages/mcp-server/

```

--------------------------------------------------------------------------------
/packages/mcp-server/cloudflare-worker/.gitignore:
--------------------------------------------------------------------------------

```
node_modules

.nx
.idea
.vscode
.zed
# Logs

logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)

report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# Runtime data

pids
_.pid
_.seed
\*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover

lib-cov

# Coverage directory used by tools like istanbul

coverage
\*.lcov

# nyc test coverage

.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)

.grunt

# Bower dependency directory (https://bower.io/)

bower_components

# node-waf configuration

.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)

build/Release

# Dependency directories

node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)

web_modules/

# TypeScript cache

\*.tsbuildinfo

# Optional npm cache directory

.npm

# Optional eslint cache

.eslintcache

# Optional stylelint cache

.stylelintcache

# Microbundle cache

.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history

.node_repl_history

# Output of 'npm pack'

\*.tgz

# Yarn Integrity file

.yarn-integrity

# dotenv environment variable files

.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)

.cache
.parcel-cache

# Next.js build output

.next
out

# Nuxt.js build / generate output

.nuxt
dist

# Gatsby files

.cache/

# Comment in the public line in if your project uses Gatsby and not Next.js

# https://nextjs.org/blog/next-9-1#public-directory-support

# public

# vuepress build output

.vuepress/dist

# vuepress v2.x temp and cache directory

.temp
.cache

# Docusaurus cache and generated files

.docusaurus

# Serverless directories

.serverless/

# FuseBox cache

.fusebox/

# DynamoDB Local files

.dynamodb/

# TernJS port file

.tern-port

# Stores VSCode versions used for testing VSCode extensions

.vscode-test

# yarn v2

.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*

# wrangler project

.dev.vars
.wrangler/

```

--------------------------------------------------------------------------------
/src/core/README.md:
--------------------------------------------------------------------------------

```markdown
# `core`

This directory holds public modules implementing non-resource-specific SDK functionality.

```

--------------------------------------------------------------------------------
/src/internal/README.md:
--------------------------------------------------------------------------------

```markdown
# `internal`

The modules in this directory are not importable outside this package and will change between releases.

```

--------------------------------------------------------------------------------
/packages/mcp-server/cloudflare-worker/README.md:
--------------------------------------------------------------------------------

```markdown
# Remote MCP Server on Cloudflare with Stainless

Remote MCP servers require OAuth, so this flow implements a local version of the OAuth redirects, but instead accepts the
API token and any other client configuration options that you'd need to instantiate your TypeScript client.

## Usage

The recommended way to use this project is to use the below "deploy to cloudflare" button to use this repo as a template for generating a server.

[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/dodopayments/dodopayments-typescript/tree/main/packages/mcp-server/cloudflare-worker)

## Develop locally

```bash
# install dependencies
npm install

# run locally
npm run dev
```

You should be able to open [`http://localhost:8787/`](http://localhost:8787/) in your browser

## Connect the MCP inspector to your server

To explore your new MCP api, you can use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector).

- Start it with `npx @modelcontextprotocol/inspector`
- [Within the inspector](http://localhost:5173), switch the Transport Type to `SSE` and enter `http://localhost:8787/sse` as the URL of the MCP server to connect to, and click "Connect"
- You will navigate to a (mock) user/password login screen. Input any email and pass to login.
- You should be redirected back to the MCP Inspector and you can now list and call any defined tools!

## Connect Claude Desktop to your local MCP server

The MCP inspector is great, but we really want to connect this to Claude! Follow [Anthropic's Quickstart](https://modelcontextprotocol.io/quickstart/user) and within Claude Desktop go to Settings > Developer > Edit Config to find your configuration file.

Open the file in your text editor and replace it with this configuration:

```json
{
  "mcpServers": {
    "dodopayments_api": {
      "command": "npx",
      "args": ["mcp-remote", "http://localhost:8787/sse"]
    }
  }
}
```

This will run a local proxy and let Claude talk to your MCP server over HTTP

When you open Claude a browser window should open and allow you to login. You should see the tools available in the bottom right. Given the right prompt Claude should ask to call the tool.

## Deploy to Cloudflare

If you want to manually deploy this server (e.g. without the "deploy to cloudflare" button)

1. `npx wrangler@latest kv namespace create remote-mcp-server-oauth-kv`
2. Follow the guidance to add the kv namespace ID to `wrangler.jsonc`
3. `npm run deploy`

## Call your newly deployed remote MCP server from a remote MCP client

Just like you did above in "Develop locally", run the MCP inspector:

`npx @modelcontextprotocol/inspector@latest`

Then enter the `workers.dev` URL (ex: `worker-name.account-name.workers.dev/sse`) of your Worker in the inspector as the URL of the MCP server to connect to, and click "Connect".

You've now connected to your MCP server from a remote MCP client.

## Connect Claude Desktop to your remote MCP server

Update the Claude configuration file to point to your `workers.dev` URL (ex: `worker-name.account-name.workers.dev/sse`) and restart Claude

```json
{
  "mcpServers": {
    "dodopayments_api": {
      "command": "npx",
      "args": ["mcp-remote", "https://worker-name.account-name.workers.dev/sse"]
    }
  }
}
```

## Debugging

Should anything go wrong it can be helpful to restart Claude, or to try connecting directly to your
MCP server on the command line with the following command.

```bash
npx mcp-remote http://localhost:8787/sse
```

In some rare cases it may help to clear the files added to `~/.mcp-auth`

```bash
rm -rf ~/.mcp-auth
```

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Dodo Payments TypeScript API Library

[![NPM version](<https://img.shields.io/npm/v/dodopayments.svg?label=npm%20(stable)>)](https://npmjs.org/package/dodopayments) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/dodopayments)

This library provides convenient access to the [Dodo Payments](https://dodopayments.com) REST API from server-side TypeScript or JavaScript.

The REST API documentation can be found on [docs.dodopayments.com](https://docs.dodopayments.com/api-reference/introduction). The full API of this library can be found in [api.md](api.md).

It is generated with [Stainless](https://www.stainless.com/).

## Installation

```sh
npm install dodopayments
```

## Usage

The full API of this library can be found in [api.md](api.md).

<!-- prettier-ignore -->
```js
import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env['DODO_PAYMENTS_API_KEY'], // This is the default and can be omitted
  environment: 'test_mode', // defaults to 'live_mode'
});

const checkoutSessionResponse = await client.checkoutSessions.create({
  product_cart: [{ product_id: 'product_id', quantity: 0 }],
});

console.log(checkoutSessionResponse.session_id);
```

### Request & Response types

This library includes TypeScript definitions for all request params and response fields. You may import and use them like so:

<!-- prettier-ignore -->
```ts
import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env['DODO_PAYMENTS_API_KEY'], // This is the default and can be omitted
  environment: 'test_mode', // defaults to 'live_mode'
});

const params: DodoPayments.CheckoutSessionCreateParams = {
  product_cart: [{ product_id: 'product_id', quantity: 0 }],
};
const checkoutSessionResponse: DodoPayments.CheckoutSessionResponse = await client.checkoutSessions.create(
  params,
);
```

Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors.

## Handling errors

When the library is unable to connect to the API,
or if the API returns a non-success status code (i.e., 4xx or 5xx response),
a subclass of `APIError` will be thrown:

<!-- prettier-ignore -->
```ts
const checkoutSessionResponse = await client.checkoutSessions
  .create({ product_cart: [{ product_id: 'product_id', quantity: 0 }] })
  .catch(async (err) => {
    if (err instanceof DodoPayments.APIError) {
      console.log(err.status); // 400
      console.log(err.name); // BadRequestError
      console.log(err.headers); // {server: 'nginx', ...}
    } else {
      throw err;
    }
  });
```

Error codes are as follows:

| Status Code | Error Type                 |
| ----------- | -------------------------- |
| 400         | `BadRequestError`          |
| 401         | `AuthenticationError`      |
| 403         | `PermissionDeniedError`    |
| 404         | `NotFoundError`            |
| 422         | `UnprocessableEntityError` |
| 429         | `RateLimitError`           |
| >=500       | `InternalServerError`      |
| N/A         | `APIConnectionError`       |

### Retries

Certain errors will be automatically retried 2 times by default, with a short exponential backoff.
Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict,
429 Rate Limit, and >=500 Internal errors will all be retried by default.

You can use the `maxRetries` option to configure or disable this:

<!-- prettier-ignore -->
```js
// Configure the default for all requests:
const client = new DodoPayments({
  maxRetries: 0, // default is 2
});

// Or, configure per-request:
await client.checkoutSessions.create({ product_cart: [{ product_id: 'product_id', quantity: 0 }] }, {
  maxRetries: 5,
});
```

### Timeouts

Requests time out after 1 minute by default. You can configure this with a `timeout` option:

<!-- prettier-ignore -->
```ts
// Configure the default for all requests:
const client = new DodoPayments({
  timeout: 20 * 1000, // 20 seconds (default is 1 minute)
});

// Override per-request:
await client.checkoutSessions.create({ product_cart: [{ product_id: 'product_id', quantity: 0 }] }, {
  timeout: 5 * 1000,
});
```

On timeout, an `APIConnectionTimeoutError` is thrown.

Note that requests which time out will be [retried twice by default](#retries).

## Auto-pagination

List methods in the DodoPayments API are paginated.
You can use the `for await … of` syntax to iterate through items across all pages:

```ts
async function fetchAllPaymentListResponses(params) {
  const allPaymentListResponses = [];
  // Automatically fetches more pages as needed.
  for await (const paymentListResponse of client.payments.list()) {
    allPaymentListResponses.push(paymentListResponse);
  }
  return allPaymentListResponses;
}
```

Alternatively, you can request a single page at a time:

```ts
let page = await client.payments.list();
for (const paymentListResponse of page.items) {
  console.log(paymentListResponse);
}

// Convenience methods are provided for manually paginating:
while (page.hasNextPage()) {
  page = await page.getNextPage();
  // ...
}
```

## Advanced Usage

### Accessing raw Response data (e.g., headers)

The "raw" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return.
This method returns as soon as the headers for a successful response are received and does not consume the response body, so you are free to write custom parsing or streaming logic.

You can also use the `.withResponse()` method to get the raw `Response` along with the parsed data.
Unlike `.asResponse()` this method consumes the body, returning once it is parsed.

<!-- prettier-ignore -->
```ts
const client = new DodoPayments();

const response = await client.checkoutSessions
  .create({ product_cart: [{ product_id: 'product_id', quantity: 0 }] })
  .asResponse();
console.log(response.headers.get('X-My-Header'));
console.log(response.statusText); // access the underlying Response object

const { data: checkoutSessionResponse, response: raw } = await client.checkoutSessions
  .create({ product_cart: [{ product_id: 'product_id', quantity: 0 }] })
  .withResponse();
console.log(raw.headers.get('X-My-Header'));
console.log(checkoutSessionResponse.session_id);
```

### Logging

> [!IMPORTANT]
> All log messages are intended for debugging only. The format and content of log messages
> may change between releases.

#### Log levels

The log level can be configured in two ways:

1. Via the `DODO_PAYMENTS_LOG` environment variable
2. Using the `logLevel` client option (overrides the environment variable if set)

```ts
import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  logLevel: 'debug', // Show all log messages
});
```

Available log levels, from most to least verbose:

- `'debug'` - Show debug messages, info, warnings, and errors
- `'info'` - Show info messages, warnings, and errors
- `'warn'` - Show warnings and errors (default)
- `'error'` - Show only errors
- `'off'` - Disable all logging

At the `'debug'` level, all HTTP requests and responses are logged, including headers and bodies.
Some authentication-related headers are redacted, but sensitive data in request and response bodies
may still be visible.

#### Custom logger

By default, this library logs to `globalThis.console`. You can also provide a custom logger.
Most logging libraries are supported, including [pino](https://www.npmjs.com/package/pino), [winston](https://www.npmjs.com/package/winston), [bunyan](https://www.npmjs.com/package/bunyan), [consola](https://www.npmjs.com/package/consola), [signale](https://www.npmjs.com/package/signale), and [@std/log](https://jsr.io/@std/log). If your logger doesn't work, please open an issue.

When providing a custom logger, the `logLevel` option still controls which messages are emitted, messages
below the configured level will not be sent to your logger.

```ts
import DodoPayments from 'dodopayments';
import pino from 'pino';

const logger = pino();

const client = new DodoPayments({
  logger: logger.child({ name: 'DodoPayments' }),
  logLevel: 'debug', // Send all messages to pino, allowing it to filter
});
```

### Making custom/undocumented requests

This library is typed for convenient access to the documented API. If you need to access undocumented
endpoints, params, or response properties, the library can still be used.

#### Undocumented endpoints

To make requests to undocumented endpoints, you can use `client.get`, `client.post`, and other HTTP verbs.
Options on the client, such as retries, will be respected when making these requests.

```ts
await client.post('/some/path', {
  body: { some_prop: 'foo' },
  query: { some_query_arg: 'bar' },
});
```

#### Undocumented request params

To make requests using undocumented parameters, you may use `// @ts-expect-error` on the undocumented
parameter. This library doesn't validate at runtime that the request matches the type, so any extra values you
send will be sent as-is.

```ts
client.checkoutSessions.create({
  // ...
  // @ts-expect-error baz is not yet public
  baz: 'undocumented option',
});
```

For requests with the `GET` verb, any extra params will be in the query, all other requests will send the
extra param in the body.

If you want to explicitly send an extra argument, you can do so with the `query`, `body`, and `headers` request
options.

#### Undocumented response properties

To access undocumented response properties, you may access the response object with `// @ts-expect-error` on
the response object, or cast the response object to the requisite type. Like the request params, we do not
validate or strip extra properties from the response from the API.

### Customizing the fetch client

By default, this library expects a global `fetch` function is defined.

If you want to use a different `fetch` function, you can either polyfill the global:

```ts
import fetch from 'my-fetch';

globalThis.fetch = fetch;
```

Or pass it to the client:

```ts
import DodoPayments from 'dodopayments';
import fetch from 'my-fetch';

const client = new DodoPayments({ fetch });
```

### Fetch options

If you want to set custom `fetch` options without overriding the `fetch` function, you can provide a `fetchOptions` object when instantiating the client or making a request. (Request-specific options override client options.)

```ts
import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  fetchOptions: {
    // `RequestInit` options
  },
});
```

#### Configuring proxies

To modify proxy behavior, you can provide custom `fetchOptions` that add runtime-specific proxy
options to requests:

<img src="https://raw.githubusercontent.com/stainless-api/sdk-assets/refs/heads/main/node.svg" align="top" width="18" height="21"> **Node** <sup>[[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)]</sup>

```ts
import DodoPayments from 'dodopayments';
import * as undici from 'undici';

const proxyAgent = new undici.ProxyAgent('http://localhost:8888');
const client = new DodoPayments({
  fetchOptions: {
    dispatcher: proxyAgent,
  },
});
```

<img src="https://raw.githubusercontent.com/stainless-api/sdk-assets/refs/heads/main/bun.svg" align="top" width="18" height="21"> **Bun** <sup>[[docs](https://bun.sh/guides/http/proxy)]</sup>

```ts
import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  fetchOptions: {
    proxy: 'http://localhost:8888',
  },
});
```

<img src="https://raw.githubusercontent.com/stainless-api/sdk-assets/refs/heads/main/deno.svg" align="top" width="18" height="21"> **Deno** <sup>[[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)]</sup>

```ts
import DodoPayments from 'npm:dodopayments';

const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } });
const client = new DodoPayments({
  fetchOptions: {
    client: httpClient,
  },
});
```

## Frequently Asked Questions

## Semantic versioning

This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:

1. Changes that only affect static types, without breaking runtime behavior.
2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
3. Changes that we do not expect to impact the vast majority of users in practice.

We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.

We are keen for your feedback; please open an [issue](https://www.github.com/dodopayments/dodopayments-typescript/issues) with questions, bugs, or suggestions.

## Requirements

TypeScript >= 4.9 is supported.

The following runtimes are supported:

- Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more)
- Node.js 20 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions.
- Deno v1.28.0 or higher.
- Bun 1.0 or later.
- Cloudflare Workers.
- Vercel Edge Runtime.
- Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time).
- Nitro v2.6 or greater.

Note that React Native is not supported at this time.

If you are interested in other runtime environments, please open or upvote an issue on GitHub.

## Contributing

See [the contributing documentation](./CONTRIBUTING.md).

```

--------------------------------------------------------------------------------
/packages/mcp-server/README.md:
--------------------------------------------------------------------------------

```markdown
# Dodo Payments TypeScript MCP Server

It is generated with [Stainless](https://www.stainless.com/).

## Installation

### Direct invocation

You can run the MCP Server directly via `npx`:

```sh
export DODO_PAYMENTS_API_KEY="My Bearer Token"
export DODO_PAYMENTS_WEBHOOK_KEY="My Webhook Key"
export DODO_PAYMENTS_ENVIRONMENT="live_mode"
npx -y dodopayments-mcp@latest
```

### Via MCP Client

There is a partial list of existing clients at [modelcontextprotocol.io](https://modelcontextprotocol.io/clients). If you already
have a client, consult their documentation to install the MCP server.

For clients with a configuration JSON, it might look something like this:

```json
{
  "mcpServers": {
    "dodopayments_api": {
      "command": "npx",
      "args": ["-y", "dodopayments-mcp", "--client=claude", "--tools=dynamic"],
      "env": {
        "DODO_PAYMENTS_API_KEY": "My Bearer Token",
        "DODO_PAYMENTS_WEBHOOK_KEY": "My Webhook Key",
        "DODO_PAYMENTS_ENVIRONMENT": "live_mode"
      }
    }
  }
}
```

## Exposing endpoints to your MCP Client

There are two ways to expose endpoints as tools in the MCP server:

1. Exposing one tool per endpoint, and filtering as necessary
2. Exposing a set of tools to dynamically discover and invoke endpoints from the API

### Filtering endpoints and tools

You can run the package on the command line to discover and filter the set of tools that are exposed by the
MCP Server. This can be helpful for large APIs where including all endpoints at once is too much for your AI's
context window.

You can filter by multiple aspects:

- `--tool` includes a specific tool by name
- `--resource` includes all tools under a specific resource, and can have wildcards, e.g. `my.resource*`
- `--operation` includes just read (get/list) or just write operations

### Dynamic tools

If you specify `--tools=dynamic` to the MCP server, instead of exposing one tool per endpoint in the API, it will
expose the following tools:

1. `list_api_endpoints` - Discovers available endpoints, with optional filtering by search query
2. `get_api_endpoint_schema` - Gets detailed schema information for a specific endpoint
3. `invoke_api_endpoint` - Executes any endpoint with the appropriate parameters

This allows you to have the full set of API endpoints available to your MCP Client, while not requiring that all
of their schemas be loaded into context at once. Instead, the LLM will automatically use these tools together to
search for, look up, and invoke endpoints dynamically. However, due to the indirect nature of the schemas, it
can struggle to provide the correct properties a bit more than when tools are imported explicitly. Therefore,
you can opt-in to explicit tools, the dynamic tools, or both.

See more information with `--help`.

All of these command-line options can be repeated, combined together, and have corresponding exclusion versions (e.g. `--no-tool`).

Use `--list` to see the list of available tools, or see below.

### Specifying the MCP Client

Different clients have varying abilities to handle arbitrary tools and schemas.

You can specify the client you are using with the `--client` argument, and the MCP server will automatically
serve tools and schemas that are more compatible with that client.

- `--client=<type>`: Set all capabilities based on a known MCP client

  - Valid values: `openai-agents`, `claude`, `claude-code`, `cursor`
  - Example: `--client=cursor`

Additionally, if you have a client not on the above list, or the client has gotten better
over time, you can manually enable or disable certain capabilities:

- `--capability=<name>`: Specify individual client capabilities
  - Available capabilities:
    - `top-level-unions`: Enable support for top-level unions in tool schemas
    - `valid-json`: Enable JSON string parsing for arguments
    - `refs`: Enable support for $ref pointers in schemas
    - `unions`: Enable support for union types (anyOf) in schemas
    - `formats`: Enable support for format validations in schemas (e.g. date-time, email)
    - `tool-name-length=N`: Set maximum tool name length to N characters
  - Example: `--capability=top-level-unions --capability=tool-name-length=40`
  - Example: `--capability=top-level-unions,tool-name-length=40`

### Examples

1. Filter for read operations on cards:

```bash
--resource=cards --operation=read
```

2. Exclude specific tools while including others:

```bash
--resource=cards --no-tool=create_cards
```

3. Configure for Cursor client with custom max tool name length:

```bash
--client=cursor --capability=tool-name-length=40
```

4. Complex filtering with multiple criteria:

```bash
--resource=cards,accounts --operation=read --tag=kyc --no-tool=create_cards
```

## Running remotely

Launching the client with `--transport=http` launches the server as a remote server using Streamable HTTP transport. The `--port` setting can choose the port it will run on, and the `--socket` setting allows it to run on a Unix socket.

Authorization can be provided via the `Authorization` header using the Bearer scheme.

Additionally, authorization can be provided via the following headers:
| Header | Equivalent client option | Security scheme |
| ------------------------- | ------------------------ | --------------- |
| `x-dodo-payments-api-key` | `bearerToken` | API_KEY |

A configuration JSON for this server might look like this, assuming the server is hosted at `http://localhost:3000`:

```json
{
  "mcpServers": {
    "dodopayments_api": {
      "url": "http://localhost:3000",
      "headers": {
        "Authorization": "Bearer <auth value>"
      }
    }
  }
}
```

The command-line arguments for filtering tools and specifying clients can also be used as query parameters in the URL.
For example, to exclude specific tools while including others, use the URL:

```
http://localhost:3000?resource=cards&resource=accounts&no_tool=create_cards
```

Or, to configure for the Cursor client, with a custom max tool name length, use the URL:

```
http://localhost:3000?client=cursor&capability=tool-name-length%3D40
```

## Importing the tools and server individually

```js
// Import the server, generated endpoints, or the init function
import { server, endpoints, init } from "dodopayments-mcp/server";

// import a specific tool
import createCheckoutSessions from "dodopayments-mcp/tools/checkout-sessions/create-checkout-sessions";

// initialize the server and all endpoints
init({ server, endpoints });

// manually start server
const transport = new StdioServerTransport();
await server.connect(transport);

// or initialize your own server with specific tools
const myServer = new McpServer(...);

// define your own endpoint
const myCustomEndpoint = {
  tool: {
    name: 'my_custom_tool',
    description: 'My custom tool',
    inputSchema: zodToJsonSchema(z.object({ a_property: z.string() })),
  },
  handler: async (client: client, args: any) => {
    return { myResponse: 'Hello world!' };
  })
};

// initialize the server with your custom endpoints
init({ server: myServer, endpoints: [createCheckoutSessions, myCustomEndpoint] });
```

## Available Tools

The following tools are available in this MCP server.

### Resource `checkout_sessions`:

- `create_checkout_sessions` (`write`):

### Resource `payments`:

- `create_payments` (`write`):
- `retrieve_payments` (`read`):
- `list_payments` (`read`):
- `retrieve_line_items_payments` (`read`):

### Resource `subscriptions`:

- `create_subscriptions` (`write`):
- `retrieve_subscriptions` (`read`):
- `update_subscriptions` (`write`):
- `list_subscriptions` (`read`):
- `change_plan_subscriptions` (`write`):
- `charge_subscriptions` (`write`):
- `retrieve_usage_history_subscriptions` (`read`): Get detailed usage history for a subscription that includes usage-based billing (metered components).
  This endpoint provides insights into customer usage patterns and billing calculations over time.

  ## What You'll Get:

  - **Billing periods**: Each item represents a billing cycle with start and end dates
  - **Meter usage**: Detailed breakdown of usage for each meter configured on the subscription
  - **Usage calculations**: Total units consumed, free threshold units, and chargeable units
  - **Historical tracking**: Complete audit trail of usage-based charges

  ## Use Cases:

  - **Customer support**: Investigate billing questions and usage discrepancies
  - **Usage analytics**: Analyze customer consumption patterns over time
  - **Billing transparency**: Provide customers with detailed usage breakdowns
  - **Revenue optimization**: Identify usage trends to optimize pricing strategies

  ## Filtering Options:

  - **Date range filtering**: Get usage history for specific time periods
  - **Meter-specific filtering**: Focus on usage for a particular meter
  - **Pagination**: Navigate through large usage histories efficiently

  ## Important Notes:

  - Only returns data for subscriptions with usage-based (metered) components
  - Usage history is organized by billing periods (subscription cycles)
  - Free threshold units are calculated and displayed separately from chargeable units
  - Historical data is preserved even if meter configurations change

  ## Example Query Patterns:

  - Get last 3 months: `?start_date=2024-01-01T00:00:00Z&end_date=2024-03-31T23:59:59Z`
  - Filter by meter: `?meter_id=mtr_api_requests`
  - Paginate results: `?page_size=20&page_number=1`
  - Recent usage: `?start_date=2024-03-01T00:00:00Z` (from March 1st to now)

### Resource `invoices.payments`:

- `retrieve_invoices_payments` (`read`):
- `retrieve_refund_invoices_payments` (`read`):

### Resource `licenses`:

- `activate_licenses` (`write`):
- `deactivate_licenses` (`write`):
- `validate_licenses` (`write`):

### Resource `license_keys`:

- `retrieve_license_keys` (`read`):
- `update_license_keys` (`write`):
- `list_license_keys` (`read`):

### Resource `license_key_instances`:

- `retrieve_license_key_instances` (`read`):
- `update_license_key_instances` (`write`):
- `list_license_key_instances` (`read`):

### Resource `customers`:

- `create_customers` (`write`):
- `retrieve_customers` (`read`):
- `update_customers` (`write`):
- `list_customers` (`read`):

### Resource `customers.customer_portal`:

- `create_customers_customer_portal` (`write`):

### Resource `customers.wallets`:

- `list_customers_wallets` (`read`):

### Resource `customers.wallets.ledger_entries`:

- `create_wallets_customers_ledger_entries` (`write`):
- `list_wallets_customers_ledger_entries` (`read`):

### Resource `refunds`:

- `create_refunds` (`write`):
- `retrieve_refunds` (`read`):
- `list_refunds` (`read`):

### Resource `disputes`:

- `retrieve_disputes` (`read`):
- `list_disputes` (`read`):

### Resource `payouts`:

- `list_payouts` (`read`):

### Resource `products`:

- `create_products` (`write`):
- `retrieve_products` (`read`):
- `update_products` (`write`):
- `list_products` (`read`):
- `archive_products` (`write`):
- `unarchive_products` (`write`):
- `update_files_products` (`write`):

### Resource `products.images`:

- `update_products_images` (`write`):

### Resource `misc`:

- `list_supported_countries_misc` (`read`):

### Resource `discounts`:

- `create_discounts` (`write`): POST /discounts
  If `code` is omitted or empty, a random 16-char uppercase code is generated.
- `retrieve_discounts` (`read`): GET /discounts/{discount_id}
- `update_discounts` (`write`): PATCH /discounts/{discount_id}
- `list_discounts` (`read`): GET /discounts
- `delete_discounts` (`write`): DELETE /discounts/{discount_id}

### Resource `addons`:

- `create_addons` (`write`):
- `retrieve_addons` (`read`):
- `update_addons` (`write`):
- `list_addons` (`read`):
- `update_images_addons` (`write`):

### Resource `brands`:

- `create_brands` (`write`):
- `retrieve_brands` (`read`): Thin handler just calls `get_brand` and wraps in `Json(...)`
- `update_brands` (`write`):
- `list_brands` (`read`):
- `update_images_brands` (`write`):

### Resource `webhooks`:

- `create_webhooks` (`write`): Create a new webhook
- `retrieve_webhooks` (`read`): Get a webhook by id
- `update_webhooks` (`write`): Patch a webhook by id
- `list_webhooks` (`read`): List all webhooks
- `delete_webhooks` (`write`): Delete a webhook by id
- `retrieve_secret_webhooks` (`read`): Get webhook secret by id

### Resource `webhooks.headers`:

- `retrieve_webhooks_headers` (`read`): Get a webhook by id
- `update_webhooks_headers` (`write`): Patch a webhook by id

### Resource `usage_events`:

- `retrieve_usage_events` (`read`): Fetch detailed information about a single event using its unique event ID. This endpoint is useful for:

  - Debugging specific event ingestion issues
  - Retrieving event details for customer support
  - Validating that events were processed correctly
  - Getting the complete metadata for an event

  ## Event ID Format:

  The event ID should be the same value that was provided during event ingestion via the `/events/ingest` endpoint.
  Event IDs are case-sensitive and must match exactly.

  ## Response Details:

  The response includes all event data including:

  - Complete metadata key-value pairs
  - Original timestamp (preserved from ingestion)
  - Customer and business association
  - Event name and processing information

  ## Example Usage:

  ```text
  GET /events/api_call_12345
  ```

- `list_usage_events` (`read`): Fetch events from your account with powerful filtering capabilities. This endpoint is ideal for:

  - Debugging event ingestion issues
  - Analyzing customer usage patterns
  - Building custom analytics dashboards
  - Auditing billing-related events

  ## Filtering Options:

  - **Customer filtering**: Filter by specific customer ID
  - **Event name filtering**: Filter by event type/name
  - **Meter-based filtering**: Use a meter ID to apply the meter's event name and filter criteria automatically
  - **Time range filtering**: Filter events within a specific date range
  - **Pagination**: Navigate through large result sets

  ## Meter Integration:

  When using `meter_id`, the endpoint automatically applies:

  - The meter's configured `event_name` filter
  - The meter's custom filter criteria (if any)
  - If you also provide `event_name`, it must match the meter's event name

  ## Example Queries:

  - Get all events for a customer: `?customer_id=cus_abc123`
  - Get API request events: `?event_name=api_request`
  - Get events from last 24 hours: `?start=2024-01-14T10:30:00Z&end=2024-01-15T10:30:00Z`
  - Get events with meter filtering: `?meter_id=mtr_xyz789`
  - Paginate results: `?page_size=50&page_number=2`

- `ingest_usage_events` (`write`): This endpoint allows you to ingest custom events that can be used for:

  - Usage-based billing and metering
  - Analytics and reporting
  - Customer behavior tracking

  ## Important Notes:

  - **Duplicate Prevention**:
    - Duplicate `event_id` values within the same request are rejected (entire request fails)
    - Subsequent requests with existing `event_id` values are ignored (idempotent behavior)
  - **Rate Limiting**: Maximum 1000 events per request
  - **Time Validation**: Events with timestamps older than 1 hour or more than 5 minutes in the future will be rejected
  - **Metadata Limits**: Maximum 50 key-value pairs per event, keys max 100 chars, values max 500 chars

  ## Example Usage:

  ```json
  {
    "events": [
      {
        "event_id": "api_call_12345",
        "customer_id": "cus_abc123",
        "event_name": "api_request",
        "timestamp": "2024-01-15T10:30:00Z",
        "metadata": {
          "endpoint": "/api/v1/users",
          "method": "GET",
          "tokens_used": "150"
        }
      }
    ]
  }
  ```

### Resource `meters`:

- `create_meters` (`write`):
- `retrieve_meters` (`read`):
- `list_meters` (`read`):
- `archive_meters` (`write`):
- `unarchive_meters` (`write`):

```

--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------

```markdown
# Security Policy

## Reporting Security Issues

This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.

To report a security issue, please contact the Stainless team at [email protected].

## Responsible Disclosure

We appreciate the efforts of security researchers and individuals who help us maintain the security of
SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible
disclosure practices by allowing us a reasonable amount of time to investigate and address the issue
before making any information public.

## Reporting Non-SDK Related Security Issues

If you encounter security issues that are not directly related to SDKs but pertain to the services
or products provided by Dodo Payments, please follow the respective company's security reporting guidelines.

### Dodo Payments Terms and Policies

Please contact [email protected] for any questions or concerns regarding the security of our services.

---

Thank you for helping us keep the SDKs and systems they interact with secure.

```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
## Setting up the environment

This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install).
Other package managers may work but are not officially supported for development.

To set up the repository, run:

```sh
$ yarn
$ yarn build
```

This will install all the required dependencies and build output files to `dist/`.

## Modifying/Adding code

Most of the SDK is generated code. Modifications to code will be persisted between generations, but may
result in merge conflicts between manual patches and changes from the generator. The generator will never
modify the contents of the `src/lib/` and `examples/` directories.

## Adding and running examples

All files in the `examples/` directory are not modified by the generator and can be freely edited or added to.

```ts
// add an example to examples/<your-example>.ts

#!/usr/bin/env -S npm run tsn -T
…
```

```sh
$ chmod +x examples/<your-example>.ts
# run the example against your api
$ yarn tsn -T examples/<your-example>.ts
```

## Using the repository from source

If you’d like to use the repository from source, you can either install from git or link to a cloned repository:

To install via git:

```sh
$ npm install git+ssh://[email protected]:dodopayments/dodopayments-typescript.git
```

Alternatively, to link a local copy of the repo:

```sh
# Clone
$ git clone https://www.github.com/dodopayments/dodopayments-typescript
$ cd dodopayments-typescript

# With yarn
$ yarn link
$ cd ../my-package
$ yarn link dodopayments

# With pnpm
$ pnpm link --global
$ cd ../my-package
$ pnpm link -—global dodopayments
```

## Running tests

Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests.

```sh
$ npx prism mock path/to/your/openapi.yml
```

```sh
$ yarn run test
```

## Linting and formatting

This repository uses [prettier](https://www.npmjs.com/package/prettier) and
[eslint](https://www.npmjs.com/package/eslint) to format the code in the repository.

To lint:

```sh
$ yarn lint
```

To format and fix all lint issues automatically:

```sh
$ yarn fix
```

## Publishing and releases

Changes made to this repository via the automated release PR pipeline should publish to npm automatically. If
the changes aren't made through the automated pipeline, you may want to make releases manually.

### Publish with a GitHub workflow

You can release to package managers by using [the `Publish NPM` GitHub action](https://www.github.com/dodopayments/dodopayments-typescript/actions/workflows/publish-npm.yml). This requires a setup organization or repository secret to be set up.

### Publish manually

If you need to manually release a package, you can run the `bin/publish-npm` script with an `NPM_TOKEN` set on
the environment.

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools.ts:
--------------------------------------------------------------------------------

```typescript
export * from './tools/index';

```

--------------------------------------------------------------------------------
/src/resources.ts:
--------------------------------------------------------------------------------

```typescript
export * from './resources/index';

```

--------------------------------------------------------------------------------
/src/version.ts:
--------------------------------------------------------------------------------

```typescript
export const VERSION = '2.3.0'; // x-release-please-version

```

--------------------------------------------------------------------------------
/src/error.ts:
--------------------------------------------------------------------------------

```typescript
/** @deprecated Import from ./core/error instead */
export * from './core/error';

```

--------------------------------------------------------------------------------
/src/uploads.ts:
--------------------------------------------------------------------------------

```typescript
/** @deprecated Import from ./core/uploads instead */
export * from './core/uploads';

```

--------------------------------------------------------------------------------
/src/resource.ts:
--------------------------------------------------------------------------------

```typescript
/** @deprecated Import from ./core/resource instead */
export * from './core/resource';

```

--------------------------------------------------------------------------------
/src/pagination.ts:
--------------------------------------------------------------------------------

```typescript
/** @deprecated Import from ./core/pagination instead */
export * from './core/pagination';

```

--------------------------------------------------------------------------------
/src/api-promise.ts:
--------------------------------------------------------------------------------

```typescript
/** @deprecated Import from ./core/api-promise instead */
export * from './core/api-promise';

```

--------------------------------------------------------------------------------
/src/core/uploads.ts:
--------------------------------------------------------------------------------

```typescript
export { type Uploadable } from '../internal/uploads';
export { toFile, type ToFileInput } from '../internal/to-file';

```

--------------------------------------------------------------------------------
/src/resources/customers/wallets.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export * from './wallets/index';

```

--------------------------------------------------------------------------------
/src/resources/invoices.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export * from './invoices/index';

```

--------------------------------------------------------------------------------
/src/resources/products.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export * from './products/index';

```

--------------------------------------------------------------------------------
/src/resources/webhooks.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export * from './webhooks/index';

```

--------------------------------------------------------------------------------
/src/resources/customers.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export * from './customers/index';

```

--------------------------------------------------------------------------------
/bin/migration-config.json:
--------------------------------------------------------------------------------

```json
{
  "pkg": "dodopayments",
  "githubRepo": "https://github.com/dodopayments/dodopayments-typescript",
  "clientClass": "DodoPayments",
  "methods": []
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/tsc-multi.json:
--------------------------------------------------------------------------------

```json
{
  "targets": [
    { "extname": ".js", "module": "commonjs" },
    { "extname": ".mjs", "module": "esnext" }
  ],
  "projects": ["tsconfig.build.json"]
}

```

--------------------------------------------------------------------------------
/src/resources/invoices/index.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export { Invoices } from './invoices';
export { Payments } from './payments';

```

--------------------------------------------------------------------------------
/src/internal/utils/sleep.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));

```

--------------------------------------------------------------------------------
/src/internal/utils.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export * from './utils/values';
export * from './utils/base64';
export * from './utils/env';
export * from './utils/log';
export * from './utils/uuid';
export * from './utils/sleep';

```

--------------------------------------------------------------------------------
/tsc-multi.json:
--------------------------------------------------------------------------------

```json
{
  "targets": [
    {
      "extname": ".js",
      "module": "commonjs",
      "shareHelpers": "internal/tslib.js"
    },
    {
      "extname": ".mjs",
      "module": "esnext",
      "shareHelpers": "internal/tslib.mjs"
    }
  ],
  "projects": ["tsconfig.build.json"]
}

```

--------------------------------------------------------------------------------
/src/core/resource.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import type { DodoPayments } from '../client';

export abstract class APIResource {
  protected _client: DodoPayments;

  constructor(client: DodoPayments) {
    this._client = client;
  }
}

```

--------------------------------------------------------------------------------
/tsconfig.deno.json:
--------------------------------------------------------------------------------

```json
{
  "extends": "./tsconfig.json",
  "include": ["dist-deno"],
  "exclude": [],
  "compilerOptions": {
    "rootDir": "./dist-deno",
    "lib": ["es2020", "DOM"],
    "noEmit": true,
    "declaration": true,
    "declarationMap": true,
    "outDir": "dist-deno",
    "pretty": true,
    "sourceMap": true
  }
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/tsconfig.dist-src.json:
--------------------------------------------------------------------------------

```json
{
  // this config is included in the published src directory to prevent TS errors
  // from appearing when users go to source, and VSCode opens the source .ts file
  // via declaration maps
  "include": ["index.ts"],
  "compilerOptions": {
    "target": "es2015",
    "lib": ["DOM"],
    "moduleResolution": "node"
  }
}

```

--------------------------------------------------------------------------------
/tsconfig.dist-src.json:
--------------------------------------------------------------------------------

```json
{
  // this config is included in the published src directory to prevent TS errors
  // from appearing when users go to source, and VSCode opens the source .ts file
  // via declaration maps
  "include": ["index.ts"],
  "compilerOptions": {
    "target": "ES2015",
    "lib": ["DOM", "DOM.Iterable", "ES2018"],
    "moduleResolution": "node"
  }
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/code-tool-types.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { ClientOptions } from 'dodopayments';

export type WorkerInput = {
  opts: ClientOptions;
  code: string;
};
export type WorkerSuccess = {
  result: unknown | null;
  logLines: string[];
  errLines: string[];
};
export type WorkerError = { message: string | undefined };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/filtering.ts:
--------------------------------------------------------------------------------

```typescript
// @ts-nocheck
import initJq from 'jq-web';

export async function maybeFilter(jqFilter: unknown | undefined, response: any): Promise<any> {
  if (jqFilter && typeof jqFilter === 'string') {
    return await jq(response, jqFilter);
  } else {
    return response;
  }
}

async function jq(json: any, jqFilter: string) {
  return (await initJq).json(json, jqFilter);
}

```

--------------------------------------------------------------------------------
/src/resources/customers/wallets/index.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export {
  LedgerEntries,
  type CustomerWalletTransaction,
  type LedgerEntryCreateParams,
  type LedgerEntryListParams,
  type CustomerWalletTransactionsDefaultPageNumberPagination,
} from './ledger-entries';
export { Wallets, type CustomerWallet, type WalletListResponse } from './wallets';

```

--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------

```json
{
  "extends": "./tsconfig.json",
  "include": ["dist/src"],
  "exclude": [],
  "compilerOptions": {
    "rootDir": "./dist/src",
    "paths": {
      "dodopayments/*": ["./dist/src/*"],
      "dodopayments": ["./dist/src/index.ts"]
    },
    "noEmit": false,
    "declaration": true,
    "declarationMap": true,
    "outDir": "dist",
    "pretty": true,
    "sourceMap": true
  }
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/tsconfig.build.json:
--------------------------------------------------------------------------------

```json
{
  "extends": "./tsconfig.json",
  "include": ["dist/src"],
  "exclude": [],
  "compilerOptions": {
    "rootDir": "./dist/src",
    "paths": {
      "dodopayments-mcp/*": ["./dist/src/*"],
      "dodopayments-mcp": ["./dist/src/index.ts"]
    },
    "noEmit": false,
    "declaration": true,
    "declarationMap": true,
    "outDir": "dist",
    "pretty": true,
    "sourceMap": true
  }
}

```

--------------------------------------------------------------------------------
/scripts/utils/git-swap.sh:
--------------------------------------------------------------------------------

```bash
#!/usr/bin/env bash
set -exuo pipefail
# the package is published to NPM from ./dist
# we want the final file structure for git installs to match the npm installs, so we

# delete everything except ./dist and ./node_modules
find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' -exec rm -rf '{}' +

# move everything from ./dist to .
mv dist/* .

# delete the now-empty ./dist
rmdir dist

```

--------------------------------------------------------------------------------
/packages/mcp-server/scripts/postprocess-dist-package-json.cjs:
--------------------------------------------------------------------------------

```
const fs = require('fs');
const pkgJson = require('../dist/package.json');
const parentPkgJson = require('../../../package.json');

for (const dep in pkgJson.dependencies) {
  // ensure we point to NPM instead of a local directory
  if (dep === 'dodopayments') {
    pkgJson.dependencies[dep] = '^' + parentPkgJson.version;
  }
}

fs.writeFileSync('dist/package.json', JSON.stringify(pkgJson, null, 2));

```

--------------------------------------------------------------------------------
/src/resources/invoices/invoices.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../core/resource';
import * as PaymentsAPI from './payments';
import { Payments } from './payments';

export class Invoices extends APIResource {
  payments: PaymentsAPI.Payments = new PaymentsAPI.Payments(this._client);
}

Invoices.Payments = Payments;

export declare namespace Invoices {
  export { Payments as Payments };
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/stdio.ts:
--------------------------------------------------------------------------------

```typescript
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { initMcpServer, newMcpServer } from './server';
import { McpOptions } from './options';

export const launchStdioServer = async (options: McpOptions) => {
  const server = newMcpServer();

  initMcpServer({ server, mcpOptions: options });

  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('MCP Server running on stdio');
};

```

--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------

```json
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
{
  "name": "Development",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:latest",
  "features": {
    "ghcr.io/devcontainers/features/node:1": {}
  },
  "postCreateCommand": "yarn install",
  "customizations": {
    "vscode": {
      "extensions": ["esbenp.prettier-vscode"]
    }
  }
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/cloudflare-worker/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "es2021",
    "lib": ["es2021"],
    "jsx": "react-jsx",
    "module": "es2022",
    "moduleResolution": "Bundler",
    "resolveJsonModule": true,
    "allowJs": true,
    "checkJs": false,
    "noEmit": true,
    "isolatedModules": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["worker-configuration.d.ts", "src/**/*.ts"]
}

```

--------------------------------------------------------------------------------
/scripts/utils/check-is-in-git-install.sh:
--------------------------------------------------------------------------------

```bash
#!/usr/bin/env bash
# Check if you happen to call prepare for a repository that's already in node_modules.
[ "$(basename "$(dirname "$PWD")")" = 'node_modules' ] ||
# The name of the containing directory that 'npm` uses, which looks like
# $HOME/.npm/_cacache/git-cloneXXXXXX
[ "$(basename "$(dirname "$PWD")")" = 'tmp' ] ||
# The name of the containing directory that 'yarn` uses, which looks like
# $(yarn cache dir)/.tmp/XXXXX
[ "$(basename "$(dirname "$PWD")")" = '.tmp' ]

```

--------------------------------------------------------------------------------
/src/resources/customers/index.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export { CustomerPortal, type CustomerPortalCreateParams } from './customer-portal';
export {
  Customers,
  type Customer,
  type CustomerPortalSession,
  type CustomerCreateParams,
  type CustomerUpdateParams,
  type CustomerListParams,
  type CustomersDefaultPageNumberPagination,
} from './customers';
export { Wallets, type CustomerWallet, type WalletListResponse } from './wallets/index';

```

--------------------------------------------------------------------------------
/packages/mcp-server/jest.config.ts:
--------------------------------------------------------------------------------

```typescript
import type { JestConfigWithTsJest } from 'ts-jest';

const config: JestConfigWithTsJest = {
  preset: 'ts-jest/presets/default-esm',
  testEnvironment: 'node',
  transform: {
    '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }],
  },
  moduleNameMapper: {
    '^dodopayments-mcp$': '<rootDir>/src/index.ts',
    '^dodopayments-mcp/(.*)$': '<rootDir>/src/$1',
  },
  modulePathIgnorePatterns: ['<rootDir>/dist/'],
  testPathIgnorePatterns: ['scripts'],
};

export default config;

```

--------------------------------------------------------------------------------
/src/resources/products/index.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export { Images, type ImageUpdateResponse, type ImageUpdateParams } from './images';
export {
  Products,
  type AddMeterToPrice,
  type LicenseKeyDuration,
  type Price,
  type Product,
  type ProductListResponse,
  type ProductUpdateFilesResponse,
  type ProductCreateParams,
  type ProductUpdateParams,
  type ProductListParams,
  type ProductUpdateFilesParams,
  type ProductListResponsesDefaultPageNumberPagination,
} from './products';

```

--------------------------------------------------------------------------------
/scripts/utils/fix-index-exports.cjs:
--------------------------------------------------------------------------------

```
const fs = require('fs');
const path = require('path');

const indexJs =
  process.env['DIST_PATH'] ?
    path.resolve(process.env['DIST_PATH'], 'index.js')
  : path.resolve(__dirname, '..', '..', 'dist', 'index.js');

let before = fs.readFileSync(indexJs, 'utf8');
let after = before.replace(
  /^(\s*Object\.defineProperty\s*\(exports,\s*["']__esModule["'].+)$/m,
  `exports = module.exports = function (...args) {
    return new exports.default(...args)
  }
  $1`.replace(/^  /gm, ''),
);
fs.writeFileSync(indexJs, after, 'utf8');

```

--------------------------------------------------------------------------------
/src/internal/utils/uuid.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

/**
 * https://stackoverflow.com/a/2117523
 */
export let uuid4 = function () {
  const { crypto } = globalThis as any;
  if (crypto?.randomUUID) {
    uuid4 = crypto.randomUUID.bind(crypto);
    return crypto.randomUUID();
  }
  const u8 = new Uint8Array(1);
  const randomByte = crypto ? () => crypto.getRandomValues(u8)[0]! : () => (Math.random() * 0xff) & 0xff;
  return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) =>
    (+c ^ (randomByte() & (15 >> (+c / 4)))).toString(16),
  );
};

```

--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------

```typescript
import type { JestConfigWithTsJest } from 'ts-jest';

const config: JestConfigWithTsJest = {
  preset: 'ts-jest/presets/default-esm',
  testEnvironment: 'node',
  transform: {
    '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }],
  },
  moduleNameMapper: {
    '^dodopayments$': '<rootDir>/src/index.ts',
    '^dodopayments/(.*)$': '<rootDir>/src/$1',
  },
  modulePathIgnorePatterns: [
    '<rootDir>/ecosystem-tests/',
    '<rootDir>/dist/',
    '<rootDir>/deno/',
    '<rootDir>/deno_tests/',
    '<rootDir>/packages/',
  ],
  testPathIgnorePatterns: ['scripts'],
};

export default config;

```

--------------------------------------------------------------------------------
/src/internal/utils/env.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

/**
 * Read an environment variable.
 *
 * Trims beginning and trailing whitespace.
 *
 * Will return undefined if the environment variable doesn't exist or cannot be accessed.
 */
export const readEnv = (env: string): string | undefined => {
  if (typeof (globalThis as any).process !== 'undefined') {
    return (globalThis as any).process.env?.[env]?.trim() ?? undefined;
  }
  if (typeof (globalThis as any).Deno !== 'undefined') {
    return (globalThis as any).Deno.env?.get?.(env)?.trim();
  }
  return undefined;
};

```

--------------------------------------------------------------------------------
/packages/mcp-server/cloudflare-worker/biome.json:
--------------------------------------------------------------------------------

```json
{
  "$schema": "https://biomejs.dev/schemas/1.6.2/schema.json",
  "organizeImports": {
    "enabled": true
  },
  "files": {
    "ignore": ["worker-configuration.d.ts"]
  },
  "vcs": {
    "enabled": true,
    "clientKind": "git",
    "useIgnoreFile": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "suspicious": {
        "noExplicitAny": "off",
        "noDebugger": "off",
        "noConsoleLog": "off",
        "noConfusingVoidType": "off"
      },
      "style": {
        "noNonNullAssertion": "off"
      }
    }
  },
  "formatter": {
    "enabled": true,
    "indentWidth": 4,
    "lineWidth": 100
  }
}

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export { DodoPayments as default } from './client';

export { type Uploadable, toFile } from './core/uploads';
export { APIPromise } from './core/api-promise';
export { DodoPayments, type ClientOptions } from './client';
export { PagePromise } from './core/pagination';
export {
  DodoPaymentsError,
  APIError,
  APIConnectionError,
  APIConnectionTimeoutError,
  APIUserAbortError,
  NotFoundError,
  ConflictError,
  RateLimitError,
  BadRequestError,
  AuthenticationError,
  InternalServerError,
  PermissionDeniedError,
  UnprocessableEntityError,
} from './core/error';

```

--------------------------------------------------------------------------------
/packages/mcp-server/cloudflare-worker/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "remote-mcp-server-with-stainless",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "deploy": "wrangler deploy",
    "dev": "wrangler dev",
    "format": "biome format --write",
    "lint:fix": "biome lint --fix",
    "start": "wrangler dev",
    "cf-typegen": "wrangler types"
  },
  "devDependencies": {
    "marked": "^15.0.11",
    "typescript": "^5.8.3",
    "workers-mcp": "0.1.0-3",
    "wrangler": "^4.15.2"
  },
  "dependencies": {
    "@cloudflare/workers-oauth-provider": "^0.0.5",
    "@modelcontextprotocol/sdk": "^1.11.4",
    "agents": "^0.0.88",
    "hono": "^4.7.9",
    "dodopayments-mcp": "latest",
    "zod": "^3.24.4"
  }
}

```

--------------------------------------------------------------------------------
/scripts/utils/check-version.cjs:
--------------------------------------------------------------------------------

```
const fs = require('fs');
const path = require('path');

const main = () => {
  const pkg = require('../../package.json');
  const version = pkg['version'];
  if (!version) throw 'The version property is not set in the package.json file';
  if (typeof version !== 'string') {
    throw `Unexpected type for the package.json version field; got ${typeof version}, expected string`;
  }

  const versionFile = path.resolve(__dirname, '..', '..', 'src', 'version.ts');
  const contents = fs.readFileSync(versionFile, 'utf8');
  const output = contents.replace(/(export const VERSION = ')(.*)(')/g, `$1${version}$3`);
  fs.writeFileSync(versionFile, output);
};

if (require.main === module) {
  main();
}

```

--------------------------------------------------------------------------------
/.github/workflows/release-doctor.yml:
--------------------------------------------------------------------------------

```yaml
name: Release Doctor
on:
  pull_request:
    branches:
      - main
  workflow_dispatch:

jobs:
  release_doctor:
    name: release doctor
    runs-on: ubuntu-latest
    if: github.repository == 'dodopayments/dodopayments-typescript' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')

    steps:
      - uses: actions/checkout@v4

      - name: Check release environment
        run: |
          bash ./bin/check-release-environment
        env:
          NPM_TOKEN: ${{ secrets.DODO_PAYMENTS_NPM_TOKEN || secrets.NPM_TOKEN }}
          DOCKERHUB_TOKEN: ${{ secrets.DODO_PAYMENTS_DOCKERHUB_TOKEN || secrets.DOCKERHUB_TOKEN }}


```

--------------------------------------------------------------------------------
/scripts/utils/upload-artifact.sh:
--------------------------------------------------------------------------------

```bash
#!/usr/bin/env bash
set -exuo pipefail

RESPONSE=$(curl -X POST "$URL" \
  -H "Authorization: Bearer $AUTH" \
  -H "Content-Type: application/json")

SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url')

if [[ "$SIGNED_URL" == "null" ]]; then
  echo -e "\033[31mFailed to get signed URL.\033[0m"
  exit 1
fi

TARBALL=$(cd dist && npm pack --silent)

UPLOAD_RESPONSE=$(curl -v -X PUT \
  -H "Content-Type: application/gzip" \
  --data-binary "@dist/$TARBALL" "$SIGNED_URL" 2>&1)

if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then
  echo -e "\033[32mUploaded build to Stainless storage.\033[0m"
  echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/dodo-payments-typescript/$SHA'\033[0m"
else
  echo -e "\033[31mFailed to upload artifact.\033[0m"
  exit 1
fi

```

--------------------------------------------------------------------------------
/tests/api-resources/customers/wallets/wallets.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource wallets', () => {
  test('list', async () => {
    const responsePromise = client.customers.wallets.list('customer_id');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });
});

```

--------------------------------------------------------------------------------
/tests/api-resources/misc.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource misc', () => {
  test('listSupportedCountries', async () => {
    const responsePromise = client.misc.listSupportedCountries();
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });
});

```

--------------------------------------------------------------------------------
/src/internal/utils/bytes.ts:
--------------------------------------------------------------------------------

```typescript
export function concatBytes(buffers: Uint8Array[]): Uint8Array {
  let length = 0;
  for (const buffer of buffers) {
    length += buffer.length;
  }
  const output = new Uint8Array(length);
  let index = 0;
  for (const buffer of buffers) {
    output.set(buffer, index);
    index += buffer.length;
  }

  return output;
}

let encodeUTF8_: (str: string) => Uint8Array;
export function encodeUTF8(str: string) {
  let encoder;
  return (
    encodeUTF8_ ??
    ((encoder = new (globalThis as any).TextEncoder()), (encodeUTF8_ = encoder.encode.bind(encoder)))
  )(str);
}

let decodeUTF8_: (bytes: Uint8Array) => string;
export function decodeUTF8(bytes: Uint8Array) {
  let decoder;
  return (
    decodeUTF8_ ??
    ((decoder = new (globalThis as any).TextDecoder()), (decodeUTF8_ = decoder.decode.bind(decoder)))
  )(bytes);
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/headers.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { IncomingMessage } from 'node:http';
import { ClientOptions } from 'dodopayments';

export const parseAuthHeaders = (req: IncomingMessage): Partial<ClientOptions> => {
  if (req.headers.authorization) {
    const scheme = req.headers.authorization.split(' ')[0]!;
    const value = req.headers.authorization.slice(scheme.length + 1);
    switch (scheme) {
      case 'Bearer':
        return { bearerToken: req.headers.authorization.slice('Bearer '.length) };
      default:
        throw new Error(`Unsupported authorization scheme`);
    }
  }

  const bearerToken =
    Array.isArray(req.headers['x-dodo-payments-api-key']) ?
      req.headers['x-dodo-payments-api-key'][0]
    : req.headers['x-dodo-payments-api-key'];
  return { bearerToken };
};

```

--------------------------------------------------------------------------------
/scripts/utils/make-dist-package-json.cjs:
--------------------------------------------------------------------------------

```
const pkgJson = require(process.env['PKG_JSON_PATH'] || '../../package.json');

function processExportMap(m) {
  for (const key in m) {
    const value = m[key];
    if (typeof value === 'string') m[key] = value.replace(/^\.\/dist\//, './');
    else processExportMap(value);
  }
}
processExportMap(pkgJson.exports);

for (const key of ['types', 'main', 'module']) {
  if (typeof pkgJson[key] === 'string') pkgJson[key] = pkgJson[key].replace(/^(\.\/)?dist\//, './');
}
// Fix bin paths if present
if (pkgJson.bin) {
  for (const key in pkgJson.bin) {
    if (typeof pkgJson.bin[key] === 'string') {
      pkgJson.bin[key] = pkgJson.bin[key].replace(/^(\.\/)?dist\//, './');
    }
  }
}

delete pkgJson.devDependencies;
delete pkgJson.scripts.prepack;
delete pkgJson.scripts.prepublishOnly;
delete pkgJson.scripts.prepare;

console.log(JSON.stringify(pkgJson, null, 2));

```

--------------------------------------------------------------------------------
/packages/mcp-server/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "include": ["src", "tests", "examples"],
  "exclude": [],
  "compilerOptions": {
    "target": "es2020",
    "lib": ["es2020"],
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "paths": {
      "dodopayments-mcp/*": ["./src/*"],
      "dodopayments-mcp": ["./src/index.ts"]
    },
    "noEmit": true,

    "resolveJsonModule": true,

    "forceConsistentCasingInFileNames": true,

    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "exactOptionalPropertyTypes": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,

    "skipLibCheck": true
  }
}

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "include": ["src", "tests", "examples"],
  "exclude": [],
  "compilerOptions": {
    "target": "es2020",
    "lib": ["es2020"],
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "paths": {
      "dodopayments/*": ["./src/*"],
      "dodopayments": ["./src/index.ts"]
    },
    "noEmit": true,

    "resolveJsonModule": true,

    "forceConsistentCasingInFileNames": true,

    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "exactOptionalPropertyTypes": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "isolatedModules": false,

    "skipLibCheck": true
  }
}

```

--------------------------------------------------------------------------------
/src/internal/shim-types.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

/**
 * Shims for types that we can't always rely on being available globally.
 *
 * Note: these only exist at the type-level, there is no corresponding runtime
 * version for any of these symbols.
 */

type NeverToAny<T> = T extends never ? any : T;

/** @ts-ignore */
type _DOMReadableStream<R = any> = globalThis.ReadableStream<R>;

/** @ts-ignore */
type _NodeReadableStream<R = any> = import('stream/web').ReadableStream<R>;

type _ConditionalNodeReadableStream<R = any> =
  typeof globalThis extends { ReadableStream: any } ? never : _NodeReadableStream<R>;

type _ReadableStream<R = any> = NeverToAny<
  | ([0] extends [1 & _DOMReadableStream<R>] ? never : _DOMReadableStream<R>)
  | ([0] extends [1 & _ConditionalNodeReadableStream<R>] ? never : _ConditionalNodeReadableStream<R>)
>;

export type { _ReadableStream as ReadableStream };

```

--------------------------------------------------------------------------------
/src/resources/products/images.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../core/resource';
import { APIPromise } from '../../core/api-promise';
import { RequestOptions } from '../../internal/request-options';
import { path } from '../../internal/utils/path';

export class Images extends APIResource {
  update(
    id: string,
    params: ImageUpdateParams | null | undefined = {},
    options?: RequestOptions,
  ): APIPromise<ImageUpdateResponse> {
    const { force_update } = params ?? {};
    return this._client.put(path`/products/${id}/images`, { query: { force_update }, ...options });
  }
}

export interface ImageUpdateResponse {
  url: string;

  image_id?: string | null;
}

export interface ImageUpdateParams {
  force_update?: boolean;
}

export declare namespace Images {
  export { type ImageUpdateResponse as ImageUpdateResponse, type ImageUpdateParams as ImageUpdateParams };
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/meters/retrieve-meters.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'meters',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/meters/{id}',
  operationId: 'get_meter_handler',
};

export const tool: Tool = {
  name: 'retrieve_meters',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
    },
    required: ['id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, ...body } = args as any;
  return asTextContentResult(await client.meters.retrieve(id));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/products/retrieve-products.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'products',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/products/{id}',
  operationId: 'get_product_handler',
};

export const tool: Tool = {
  name: 'retrieve_products',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
    },
    required: ['id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, ...body } = args as any;
  return asTextContentResult(await client.products.retrieve(id));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/tests/stringifyQuery.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { DodoPayments } from 'dodopayments';

const { stringifyQuery } = DodoPayments.prototype as any;

describe(stringifyQuery, () => {
  for (const [input, expected] of [
    [{ a: '1', b: 2, c: true }, 'a=1&b=2&c=true'],
    [{ a: null, b: false, c: undefined }, 'a=&b=false'],
    [{ 'a/b': 1.28341 }, `${encodeURIComponent('a/b')}=1.28341`],
    [
      { 'a/b': 'c/d', 'e=f': 'g&h' },
      `${encodeURIComponent('a/b')}=${encodeURIComponent('c/d')}&${encodeURIComponent(
        'e=f',
      )}=${encodeURIComponent('g&h')}`,
    ],
  ]) {
    it(`${JSON.stringify(input)} -> ${expected}`, () => {
      expect(stringifyQuery(input)).toEqual(expected);
    });
  }

  for (const value of [[], {}, new Date()]) {
    it(`${JSON.stringify(value)} -> <error>`, () => {
      expect(() => stringifyQuery({ value })).toThrow(`Cannot stringify type ${typeof value}`);
    });
  }
});

```

--------------------------------------------------------------------------------
/src/resources/invoices/payments.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../core/resource';
import { APIPromise } from '../../core/api-promise';
import { buildHeaders } from '../../internal/headers';
import { RequestOptions } from '../../internal/request-options';
import { path } from '../../internal/utils/path';

export class Payments extends APIResource {
  retrieve(paymentID: string, options?: RequestOptions): APIPromise<Response> {
    return this._client.get(path`/invoices/payments/${paymentID}`, {
      ...options,
      headers: buildHeaders([{ Accept: 'application/pdf' }, options?.headers]),
      __binaryResponse: true,
    });
  }

  retrieveRefund(refundID: string, options?: RequestOptions): APIPromise<Response> {
    return this._client.get(path`/invoices/refunds/${refundID}`, {
      ...options,
      headers: buildHeaders([{ Accept: 'application/pdf' }, options?.headers]),
      __binaryResponse: true,
    });
  }
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/meters/unarchive-meters.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'meters',
  operation: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/meters/{id}/unarchive',
  operationId: 'unarchive_meter',
};

export const tool: Tool = {
  name: 'unarchive_meters',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
    },
    required: ['id'],
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, ...body } = args as any;
  const response = await client.meters.unarchive(id).asResponse();
  return asTextContentResult(await response.text());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/products/unarchive-products.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'products',
  operation: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/products/{id}/unarchive',
  operationId: 'undelete_product',
};

export const tool: Tool = {
  name: 'unarchive_products',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
    },
    required: ['id'],
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, ...body } = args as any;
  const response = await client.products.unarchive(id).asResponse();
  return asTextContentResult(await response.text());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/payments/retrieve-payments.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'payments',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/payments/{payment_id}',
  operationId: 'get_payment_handler',
};

export const tool: Tool = {
  name: 'retrieve_payments',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      payment_id: {
        type: 'string',
      },
    },
    required: ['payment_id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { payment_id, ...body } = args as any;
  return asTextContentResult(await client.payments.retrieve(payment_id));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/meters/archive-meters.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'meters',
  operation: 'write',
  tags: [],
  httpMethod: 'delete',
  httpPath: '/meters/{id}',
  operationId: 'delete_meter',
};

export const tool: Tool = {
  name: 'archive_meters',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
    },
    required: ['id'],
  },
  annotations: {
    idempotentHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, ...body } = args as any;
  const response = await client.meters.archive(id).asResponse();
  return asTextContentResult(await response.text());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/resources/customers/customer-portal.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../core/resource';
import * as CustomersAPI from './customers';
import { APIPromise } from '../../core/api-promise';
import { RequestOptions } from '../../internal/request-options';
import { path } from '../../internal/utils/path';

export class CustomerPortal extends APIResource {
  create(
    customerID: string,
    params: CustomerPortalCreateParams | null | undefined = {},
    options?: RequestOptions,
  ): APIPromise<CustomersAPI.CustomerPortalSession> {
    const { send_email } = params ?? {};
    return this._client.post(path`/customers/${customerID}/customer-portal/session`, {
      query: { send_email },
      ...options,
    });
  }
}

export interface CustomerPortalCreateParams {
  /**
   * If true, will send link to user.
   */
  send_email?: boolean;
}

export declare namespace CustomerPortal {
  export { type CustomerPortalCreateParams as CustomerPortalCreateParams };
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/products/archive-products.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'products',
  operation: 'write',
  tags: [],
  httpMethod: 'delete',
  httpPath: '/products/{id}',
  operationId: 'delete_product',
};

export const tool: Tool = {
  name: 'archive_products',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
    },
    required: ['id'],
  },
  annotations: {
    idempotentHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, ...body } = args as any;
  const response = await client.products.archive(id).asResponse();
  return asTextContentResult(await response.text());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/invoices/payments/retrieve-invoices-payments.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asBinaryContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'invoices.payments',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/invoices/payments/{payment_id}',
  operationId: 'get_payment_invoice_no_auth',
};

export const tool: Tool = {
  name: 'retrieve_invoices_payments',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      payment_id: {
        type: 'string',
      },
    },
    required: ['payment_id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { payment_id, ...body } = args as any;
  return asBinaryContentResult(await client.invoices.payments.retrieve(payment_id));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/subscriptions/retrieve-subscriptions.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'subscriptions',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/subscriptions/{subscription_id}',
  operationId: 'get_subscription_handler',
};

export const tool: Tool = {
  name: 'retrieve_subscriptions',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      subscription_id: {
        type: 'string',
      },
    },
    required: ['subscription_id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { subscription_id, ...body } = args as any;
  return asTextContentResult(await client.subscriptions.retrieve(subscription_id));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------

```
// @ts-check
import tseslint from 'typescript-eslint';
import unusedImports from 'eslint-plugin-unused-imports';
import prettier from 'eslint-plugin-prettier';

export default tseslint.config(
  {
    languageOptions: {
      parser: tseslint.parser,
      parserOptions: { sourceType: 'module' },
    },
    files: ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.js', '**/*.mjs', '**/*.cjs'],
    ignores: ['dist/'],
    plugins: {
      '@typescript-eslint': tseslint.plugin,
      'unused-imports': unusedImports,
      prettier,
    },
    rules: {
      'no-unused-vars': 'off',
      'prettier/prettier': 'error',
      'unused-imports/no-unused-imports': 'error',
      'no-restricted-imports': [
        'error',
        {
          patterns: [
            {
              regex: '^dodopayments(/.*)?',
              message: 'Use a relative import, not a package import.',
            },
          ],
        },
      ],
    },
  },
  {
    files: ['tests/**', 'examples/**', 'packages/**'],
    rules: {
      'no-restricted-imports': 'off',
    },
  },
);

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/invoices/payments/retrieve-refund-invoices-payments.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asBinaryContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'invoices.payments',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/invoices/refunds/{refund_id}',
  operationId: 'get_refund_invoice_no_auth',
};

export const tool: Tool = {
  name: 'retrieve_refund_invoices_payments',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      refund_id: {
        type: 'string',
      },
    },
    required: ['refund_id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { refund_id, ...body } = args as any;
  return asBinaryContentResult(await client.invoices.payments.retrieveRefund(refund_id));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/webhooks/delete-webhooks.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'webhooks',
  operation: 'write',
  tags: [],
  httpMethod: 'delete',
  httpPath: '/webhooks/{webhook_id}',
  operationId: 'delete_webhook',
};

export const tool: Tool = {
  name: 'delete_webhooks',
  description: 'Delete a webhook by id',
  inputSchema: {
    type: 'object',
    properties: {
      webhook_id: {
        type: 'string',
      },
    },
    required: ['webhook_id'],
  },
  annotations: {
    idempotentHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { webhook_id, ...body } = args as any;
  const response = await client.webhooks.delete(webhook_id).asResponse();
  return asTextContentResult(await response.text());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/scripts/utils/attw-report.cjs:
--------------------------------------------------------------------------------

```
const fs = require('fs');
const problems = Object.values(JSON.parse(fs.readFileSync('.attw.json', 'utf-8')).problems)
  .flat()
  .filter(
    (problem) =>
      !(
        // This is intentional, if the user specifies .mjs they get ESM.
        (
          (problem.kind === 'CJSResolvesToESM' && problem.entrypoint.endsWith('.mjs')) ||
          // This is intentional for backwards compat reasons.
          (problem.kind === 'MissingExportEquals' && problem.implementationFileName.endsWith('/index.js')) ||
          // this is intentional, we deliberately attempt to import types that may not exist from parent node_modules
          // folders to better support various runtimes without triggering automatic type acquisition.
          (problem.kind === 'InternalResolutionError' && problem.moduleSpecifier.includes('node_modules'))
        )
      ),
  );
fs.unlinkSync('.attw.json');
if (problems.length) {
  process.stdout.write('The types are wrong!\n' + JSON.stringify(problems, null, 2) + '\n');
  process.exitCode = 1;
} else {
  process.stdout.write('Types ok!\n');
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/discounts/delete-discounts.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'discounts',
  operation: 'write',
  tags: [],
  httpMethod: 'delete',
  httpPath: '/discounts/{discount_id}',
  operationId: 'delete_discount_handler',
};

export const tool: Tool = {
  name: 'delete_discounts',
  description: 'DELETE /discounts/{discount_id}',
  inputSchema: {
    type: 'object',
    properties: {
      discount_id: {
        type: 'string',
      },
    },
    required: ['discount_id'],
  },
  annotations: {
    idempotentHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { discount_id, ...body } = args as any;
  const response = await client.discounts.delete(discount_id).asResponse();
  return asTextContentResult(await response.text());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/licenses/deactivate-licenses.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'licenses',
  operation: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/licenses/deactivate',
  operationId: 'deactivate_license_key',
};

export const tool: Tool = {
  name: 'deactivate_licenses',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      license_key: {
        type: 'string',
      },
      license_key_instance_id: {
        type: 'string',
      },
    },
    required: ['license_key', 'license_key_instance_id'],
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const body = args as any;
  const response = await client.licenses.deactivate(body).asResponse();
  return asTextContentResult(await response.text());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/scripts/copy-bundle-files.cjs:
--------------------------------------------------------------------------------

```
const fs = require('fs');
const path = require('path');
const pkgJson = require('../dist-bundle/package.json');

const distDir = path.resolve(__dirname, '..', 'dist');
const distBundleDir = path.resolve(__dirname, '..', 'dist-bundle');
const distBundlePkgJson = path.join(distBundleDir, 'package.json');

async function* walk(dir) {
  for await (const d of await fs.promises.opendir(dir)) {
    const entry = path.join(dir, d.name);
    if (d.isDirectory()) yield* walk(entry);
    else if (d.isFile()) yield entry;
  }
}

async function copyFiles() {
  // copy runtime files
  for await (const file of walk(distDir)) {
    if (!/[cm]?js$/.test(file)) continue;
    const dest = path.join(distBundleDir, path.relative(distDir, file));
    await fs.promises.mkdir(path.dirname(dest), { recursive: true });
    await fs.promises.copyFile(file, dest);
  }

  // replace package.json reference with local reference
  for (const dep in pkgJson.dependencies) {
    if (dep === 'dodopayments') {
      pkgJson.dependencies[dep] = 'file:../../../dist/';
    }
  }

  await fs.promises.writeFile(distBundlePkgJson, JSON.stringify(pkgJson, null, 2));
}

copyFiles();

```

--------------------------------------------------------------------------------
/tests/api-resources/products/images.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource images', () => {
  test('update', async () => {
    const responsePromise = client.products.images.update('id');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('update: request options and params are passed correctly', async () => {
    // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
    await expect(
      client.products.images.update('id', { force_update: true }, { path: '/_stainless_unknown_path' }),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });
});

```

--------------------------------------------------------------------------------
/src/internal/errors.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export function isAbortError(err: unknown) {
  return (
    typeof err === 'object' &&
    err !== null &&
    // Spec-compliant fetch implementations
    (('name' in err && (err as any).name === 'AbortError') ||
      // Expo fetch
      ('message' in err && String((err as any).message).includes('FetchRequestCanceledException')))
  );
}

export const castToError = (err: any): Error => {
  if (err instanceof Error) return err;
  if (typeof err === 'object' && err !== null) {
    try {
      if (Object.prototype.toString.call(err) === '[object Error]') {
        // @ts-ignore - not all envs have native support for cause yet
        const error = new Error(err.message, err.cause ? { cause: err.cause } : {});
        if (err.stack) error.stack = err.stack;
        // @ts-ignore - not all envs have native support for cause yet
        if (err.cause && !error.cause) error.cause = err.cause;
        if (err.name) error.name = err.name;
        return error;
      }
    } catch {}
    try {
      return new Error(JSON.stringify(err));
    } catch {}
  }
  return new Error(err);
};

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/code-tool-worker.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import util from 'node:util';
import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types';
import { DodoPayments } from 'dodopayments';

const fetch = async (req: Request): Promise<Response> => {
  const { opts, code } = (await req.json()) as WorkerInput;
  const client = new DodoPayments({
    ...opts,
  });

  const logLines: string[] = [];
  const errLines: string[] = [];
  const console = {
    log: (...args: unknown[]) => {
      logLines.push(util.format(...args));
    },
    error: (...args: unknown[]) => {
      errLines.push(util.format(...args));
    },
  };
  try {
    let run_ = async (client: any) => {};
    eval(`
      ${code}
      run_ = run;
    `);
    const result = await run_(client);
    return Response.json({
      result,
      logLines,
      errLines,
    } satisfies WorkerSuccess);
  } catch (e) {
    const message = e instanceof Error ? e.message : undefined;
    return Response.json(
      {
        message,
      } satisfies WorkerError,
      { status: 400, statusText: 'Code execution error' },
    );
  }
};

export default { fetch };

```

--------------------------------------------------------------------------------
/src/resources/webhooks/index.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export { Headers, type HeaderRetrieveResponse, type HeaderUpdateParams } from './headers';
export {
  Webhooks,
  type WebhookDetails,
  type WebhookRetrieveSecretResponse,
  type DisputeAcceptedWebhookEvent,
  type DisputeCancelledWebhookEvent,
  type DisputeChallengedWebhookEvent,
  type DisputeExpiredWebhookEvent,
  type DisputeLostWebhookEvent,
  type DisputeOpenedWebhookEvent,
  type DisputeWonWebhookEvent,
  type LicenseKeyCreatedWebhookEvent,
  type PaymentCancelledWebhookEvent,
  type PaymentFailedWebhookEvent,
  type PaymentProcessingWebhookEvent,
  type PaymentSucceededWebhookEvent,
  type RefundFailedWebhookEvent,
  type RefundSucceededWebhookEvent,
  type SubscriptionActiveWebhookEvent,
  type SubscriptionCancelledWebhookEvent,
  type SubscriptionExpiredWebhookEvent,
  type SubscriptionFailedWebhookEvent,
  type SubscriptionOnHoldWebhookEvent,
  type SubscriptionPlanChangedWebhookEvent,
  type SubscriptionRenewedWebhookEvent,
  type UnsafeUnwrapWebhookEvent,
  type UnwrapWebhookEvent,
  type WebhookCreateParams,
  type WebhookUpdateParams,
  type WebhookListParams,
  type WebhookDetailsCursorPagePagination,
} from './webhooks';

```

--------------------------------------------------------------------------------
/tests/api-resources/customers/customer-portal.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource customerPortal', () => {
  test('create', async () => {
    const responsePromise = client.customers.customerPortal.create('customer_id');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('create: request options and params are passed correctly', async () => {
    // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
    await expect(
      client.customers.customerPortal.create(
        'customer_id',
        { send_email: true },
        { path: '/_stainless_unknown_path' },
      ),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });
});

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/meters/list-meters.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'meters',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/meters',
  operationId: 'list_meters_handler',
};

export const tool: Tool = {
  name: 'list_meters',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      archived: {
        type: 'boolean',
        description: 'List archived meters',
      },
      page_number: {
        type: 'integer',
        description: 'Page number default is 0',
      },
      page_size: {
        type: 'integer',
        description: 'Page size default is 10 max is 100',
      },
    },
    required: [],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const body = args as any;
  const response = await client.meters.list(body).asResponse();
  return asTextContentResult(await response.json());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/webhooks/headers/update-webhooks-headers.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'webhooks.headers',
  operation: 'write',
  tags: [],
  httpMethod: 'patch',
  httpPath: '/webhooks/{webhook_id}/headers',
  operationId: 'patch_webhook_headers',
};

export const tool: Tool = {
  name: 'update_webhooks_headers',
  description: 'Patch a webhook by id',
  inputSchema: {
    type: 'object',
    properties: {
      webhook_id: {
        type: 'string',
      },
      headers: {
        type: 'object',
        description: 'Object of header-value pair to update or add',
        additionalProperties: true,
      },
    },
    required: ['webhook_id', 'headers'],
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { webhook_id, ...body } = args as any;
  const response = await client.webhooks.headers.update(webhook_id, body).asResponse();
  return asTextContentResult(await response.text());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/internal/utils/base64.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { DodoPaymentsError } from '../../core/error';
import { encodeUTF8 } from './bytes';

export const toBase64 = (data: string | Uint8Array | null | undefined): string => {
  if (!data) return '';

  if (typeof (globalThis as any).Buffer !== 'undefined') {
    return (globalThis as any).Buffer.from(data).toString('base64');
  }

  if (typeof data === 'string') {
    data = encodeUTF8(data);
  }

  if (typeof btoa !== 'undefined') {
    return btoa(String.fromCharCode.apply(null, data as any));
  }

  throw new DodoPaymentsError('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined');
};

export const fromBase64 = (str: string): Uint8Array => {
  if (typeof (globalThis as any).Buffer !== 'undefined') {
    const buf = (globalThis as any).Buffer.from(str, 'base64');
    return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
  }

  if (typeof atob !== 'undefined') {
    const bstr = atob(str);
    const buf = new Uint8Array(bstr.length);
    for (let i = 0; i < bstr.length; i++) {
      buf[i] = bstr.charCodeAt(i);
    }
    return buf;
  }

  throw new DodoPaymentsError('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined');
};

```

--------------------------------------------------------------------------------
/tests/api-resources/payouts.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource payouts', () => {
  test('list', async () => {
    const responsePromise = client.payouts.list();
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('list: request options and params are passed correctly', async () => {
    // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
    await expect(
      client.payouts.list(
        {
          created_at_gte: '2019-12-27T18:11:19.117Z',
          created_at_lte: '2019-12-27T18:11:19.117Z',
          page_number: 0,
          page_size: 0,
        },
        { path: '/_stainless_unknown_path' },
      ),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });
});

```

--------------------------------------------------------------------------------
/packages/mcp-server/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Build stage
FROM node:20-alpine AS builder

# Install bash for build script
RUN apk add --no-cache bash openssl

# Set working directory
WORKDIR /build

# Copy entire repository
COPY . .

# Install all dependencies and build everything
RUN yarn install --frozen-lockfile && \
    yarn build

# Production stage

        FROM denoland/deno:alpine
        RUN apk add --no-cache npm

# Add non-root user
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001

# Set working directory
WORKDIR /app

# Copy the built mcp-server dist directory
COPY --from=builder /build/packages/mcp-server/dist ./

# Copy node_modules from mcp-server (includes all production deps)
COPY --from=builder /build/packages/mcp-server/node_modules ./node_modules

# Copy the built dodopayments into node_modules
COPY --from=builder /build/dist ./node_modules/dodopayments

# Change ownership to nodejs user
RUN chown -R nodejs:nodejs /app

# Switch to non-root user
USER nodejs

# The MCP server uses stdio transport by default
# No exposed ports needed for stdio communication

    # This is needed for node to run on the deno:alpine image.
    # See <https://github.com/denoland/deno_docker/issues/373>.
    ENV LD_LIBRARY_PATH=/usr/lib:/usr/local/lib

# Set the entrypoint to the MCP server
ENTRYPOINT ["node", "index.js"]

# Allow passing arguments to the MCP server
CMD []

```

--------------------------------------------------------------------------------
/packages/mcp-server/manifest.json:
--------------------------------------------------------------------------------

```json
{
  "dxt_version": "0.2",
  "name": "dodopayments-mcp",
  "version": "1.52.5",
  "description": "The official MCP Server for the Dodo Payments API",
  "author": {
    "name": "Dodo Payments",
    "email": "[email protected]"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/dodopayments/dodopayments-typescript.git"
  },
  "homepage": "https://github.com/dodopayments/dodopayments-typescript/tree/main/packages/mcp-server#readme",
  "documentation": "https://docs.dodopayments.com/api-reference/introduction",
  "server": {
    "type": "node",
    "entry_point": "index.js",
    "mcp_config": {
      "command": "node",
      "args": ["${__dirname}/index.js"],
      "env": {
        "DODO_PAYMENTS_API_KEY": "${user_config.DODO_PAYMENTS_API_KEY}",
        "DODO_PAYMENTS_WEBHOOK_KEY": "${user_config.DODO_PAYMENTS_WEBHOOK_KEY}"
      }
    }
  },
  "user_config": {
    "DODO_PAYMENTS_API_KEY": {
      "title": "bearer_token",
      "description": "Bearer Token for API authentication",
      "required": true,
      "type": "string"
    },
    "DODO_PAYMENTS_WEBHOOK_KEY": {
      "title": "webhook_key",
      "description": "",
      "required": false,
      "type": "string"
    }
  },
  "tools": [],
  "tools_generated": true,
  "compatibility": {
    "runtimes": {
      "node": ">=18.0.0"
    }
  },
  "keywords": ["api"]
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/docs-search-tool.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from './tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';

export const metadata: Metadata = {
  resource: 'all',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
};

export const tool: Tool = {
  name: 'search_docs',
  description:
    'Search for documentation for how to use the client to interact with the API.\nThe tool will return an array of Markdown-formatted documentation pages.',
  inputSchema: {
    type: 'object',
    properties: {
      query: {
        type: 'string',
        description: 'The query to search for.',
      },
      language: {
        type: 'string',
        description: 'The language for the SDK to search for.',
        enum: ['http', 'python', 'go', 'typescript', 'terraform', 'ruby', 'java', 'kotlin'],
      },
    },
    required: ['query', 'language'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

const docsSearchURL =
  process.env['DOCS_SEARCH_URL'] || 'https://api.stainless.com/api/projects/dodo-payments/docs/search';

export const handler = async (_: unknown, args: Record<string, unknown> | undefined) => {
  const body = args as any;
  const query = new URLSearchParams(body).toString();
  const result = await fetch(`${docsSearchURL}?${query}`);
  return asTextContentResult(await result.json());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/release-please-config.json:
--------------------------------------------------------------------------------

```json
{
  "packages": {
    ".": {}
  },
  "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json",
  "include-v-in-tag": true,
  "include-component-in-tag": false,
  "versioning": "prerelease",
  "prerelease": true,
  "bump-minor-pre-major": true,
  "bump-patch-for-minor-pre-major": false,
  "pull-request-header": "Automated Release PR",
  "pull-request-title-pattern": "release: ${version}",
  "changelog-sections": [
    {
      "type": "feat",
      "section": "Features"
    },
    {
      "type": "fix",
      "section": "Bug Fixes"
    },
    {
      "type": "perf",
      "section": "Performance Improvements"
    },
    {
      "type": "revert",
      "section": "Reverts"
    },
    {
      "type": "chore",
      "section": "Chores"
    },
    {
      "type": "docs",
      "section": "Documentation"
    },
    {
      "type": "style",
      "section": "Styles"
    },
    {
      "type": "refactor",
      "section": "Refactors"
    },
    {
      "type": "test",
      "section": "Tests",
      "hidden": true
    },
    {
      "type": "build",
      "section": "Build System"
    },
    {
      "type": "ci",
      "section": "Continuous Integration",
      "hidden": true
    }
  ],
  "release-type": "node",
  "extra-files": [
    "src/version.ts",
    "README.md",
    "packages/mcp-server/yarn.lock",
    {
      "type": "json",
      "path": "packages/mcp-server/package.json",
      "jsonpath": "$.version"
    }
  ]
}

```

--------------------------------------------------------------------------------
/tests/api-resources/webhooks/headers.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource headers', () => {
  test('retrieve', async () => {
    const responsePromise = client.webhooks.headers.retrieve('webhook_id');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('update: only required params', async () => {
    const responsePromise = client.webhooks.headers.update('webhook_id', { headers: { foo: 'string' } });
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('update: required and optional params', async () => {
    const response = await client.webhooks.headers.update('webhook_id', { headers: { foo: 'string' } });
  });
});

```

--------------------------------------------------------------------------------
/.github/workflows/publish-npm.yml:
--------------------------------------------------------------------------------

```yaml
# This workflow is triggered when a GitHub release is created.
# It can also be run manually to re-publish to NPM in case it failed for some reason.
# You can run this workflow by navigating to https://www.github.com/dodopayments/dodopayments-typescript/actions/workflows/publish-npm.yml
name: Publish NPM
on:
  workflow_dispatch:
    inputs:
      path:
        description: The path to run the release in, e.g. '.' or 'packages/mcp-server'
        required: true

  release:
    types: [published]

jobs:
  publish:
    name: publish
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v3
        with:
          node-version: '20'

      - name: Install dependencies
        run: |
          yarn install

      - name: Publish to NPM
        run: |
          if [ -n "${{ github.event.inputs.path }}" ]; then
            PATHS_RELEASED='[\"${{ github.event.inputs.path }}\"]'
          else
            PATHS_RELEASED='[\".\", \"packages/mcp-server\"]'
          fi
          yarn tsn scripts/publish-packages.ts "{ \"paths_released\": \"$PATHS_RELEASED\" }"
        env:
          NPM_TOKEN: ${{ secrets.DODO_PAYMENTS_NPM_TOKEN || secrets.NPM_TOKEN }}

      - name: Upload MCP Server DXT GitHub release asset
        run: |
          gh release upload ${{ github.event.release.tag_name }} \
            packages/mcp-server/dodopayments_api.mcpb
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

```

--------------------------------------------------------------------------------
/src/internal/parse.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import type { FinalRequestOptions } from './request-options';
import { type DodoPayments } from '../client';
import { formatRequestDetails, loggerFor } from './utils/log';

export type APIResponseProps = {
  response: Response;
  options: FinalRequestOptions;
  controller: AbortController;
  requestLogID: string;
  retryOfRequestLogID: string | undefined;
  startTime: number;
};

export async function defaultParseResponse<T>(client: DodoPayments, props: APIResponseProps): Promise<T> {
  const { response, requestLogID, retryOfRequestLogID, startTime } = props;
  const body = await (async () => {
    // fetch refuses to read the body when the status code is 204.
    if (response.status === 204) {
      return null as T;
    }

    if (props.options.__binaryResponse) {
      return response as unknown as T;
    }

    const contentType = response.headers.get('content-type');
    const mediaType = contentType?.split(';')[0]?.trim();
    const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json');
    if (isJSON) {
      const json = await response.json();
      return json as T;
    }

    const text = await response.text();
    return text as unknown as T;
  })();
  loggerFor(client).debug(
    `[${requestLogID}] response parsed`,
    formatRequestDetails({
      retryOfRequestLogID,
      url: response.url,
      status: response.status,
      body,
      durationMs: Date.now() - startTime,
    }),
  );
  return body;
}

```

--------------------------------------------------------------------------------
/src/resources/webhooks/headers.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../core/resource';
import { APIPromise } from '../../core/api-promise';
import { buildHeaders } from '../../internal/headers';
import { RequestOptions } from '../../internal/request-options';
import { path } from '../../internal/utils/path';

export class Headers extends APIResource {
  /**
   * Get a webhook by id
   */
  retrieve(webhookID: string, options?: RequestOptions): APIPromise<HeaderRetrieveResponse> {
    return this._client.get(path`/webhooks/${webhookID}/headers`, options);
  }

  /**
   * Patch a webhook by id
   */
  update(webhookID: string, body: HeaderUpdateParams, options?: RequestOptions): APIPromise<void> {
    return this._client.patch(path`/webhooks/${webhookID}/headers`, {
      body,
      ...options,
      headers: buildHeaders([{ Accept: '*/*' }, options?.headers]),
    });
  }
}

/**
 * The value of the headers is returned in the `headers` field.
 *
 * Sensitive headers that have been redacted are returned in the sensitive field.
 */
export interface HeaderRetrieveResponse {
  /**
   * List of headers configured
   */
  headers: { [key: string]: string };

  /**
   * Sensitive headers without the value
   */
  sensitive: Array<string>;
}

export interface HeaderUpdateParams {
  /**
   * Object of header-value pair to update or add
   */
  headers: { [key: string]: string };
}

export declare namespace Headers {
  export {
    type HeaderRetrieveResponse as HeaderRetrieveResponse,
    type HeaderUpdateParams as HeaderUpdateParams,
  };
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/products/list-products.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'products',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/products',
  operationId: 'list_products_handler',
};

export const tool: Tool = {
  name: 'list_products',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      archived: {
        type: 'boolean',
        description: 'List archived products',
      },
      brand_id: {
        type: 'string',
        description: 'filter by Brand id',
      },
      page_number: {
        type: 'integer',
        description: 'Page number default is 0',
      },
      page_size: {
        type: 'integer',
        description: 'Page size default is 10 max is 100',
      },
      recurring: {
        type: 'boolean',
        description:
          'Filter products by pricing type:\n- `true`: Show only recurring pricing products (e.g. subscriptions)\n- `false`: Show only one-time price products\n- `null` or absent: Show both types of products',
      },
    },
    required: [],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const body = args as any;
  const response = await client.products.list(body).asResponse();
  return asTextContentResult(await response.json());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/resources/customers/wallets/wallets.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../../../core/resource';
import * as MiscAPI from '../../misc';
import * as LedgerEntriesAPI from './ledger-entries';
import {
  CustomerWalletTransaction,
  CustomerWalletTransactionsDefaultPageNumberPagination,
  LedgerEntries,
  LedgerEntryCreateParams,
  LedgerEntryListParams,
} from './ledger-entries';
import { APIPromise } from '../../../core/api-promise';
import { RequestOptions } from '../../../internal/request-options';
import { path } from '../../../internal/utils/path';

export class Wallets extends APIResource {
  ledgerEntries: LedgerEntriesAPI.LedgerEntries = new LedgerEntriesAPI.LedgerEntries(this._client);

  list(customerID: string, options?: RequestOptions): APIPromise<WalletListResponse> {
    return this._client.get(path`/customers/${customerID}/wallets`, options);
  }
}

export interface CustomerWallet {
  balance: number;

  created_at: string;

  currency: MiscAPI.Currency;

  customer_id: string;

  updated_at: string;
}

export interface WalletListResponse {
  items: Array<CustomerWallet>;

  /**
   * Sum of all wallet balances converted to USD (in smallest unit)
   */
  total_balance_usd: number;
}

Wallets.LedgerEntries = LedgerEntries;

export declare namespace Wallets {
  export { type CustomerWallet as CustomerWallet, type WalletListResponse as WalletListResponse };

  export {
    LedgerEntries as LedgerEntries,
    type CustomerWalletTransaction as CustomerWalletTransaction,
    type CustomerWalletTransactionsDefaultPageNumberPagination as CustomerWalletTransactionsDefaultPageNumberPagination,
    type LedgerEntryCreateParams as LedgerEntryCreateParams,
    type LedgerEntryListParams as LedgerEntryListParams,
  };
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/subscriptions/list-subscriptions.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'subscriptions',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/subscriptions',
  operationId: 'list_subscriptions_handler',
};

export const tool: Tool = {
  name: 'list_subscriptions',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      brand_id: {
        type: 'string',
        description: 'filter by Brand id',
      },
      created_at_gte: {
        type: 'string',
        description: 'Get events after this created time',
        format: 'date-time',
      },
      created_at_lte: {
        type: 'string',
        description: 'Get events created before this time',
        format: 'date-time',
      },
      customer_id: {
        type: 'string',
        description: 'Filter by customer id',
      },
      page_number: {
        type: 'integer',
        description: 'Page number default is 0',
      },
      page_size: {
        type: 'integer',
        description: 'Page size default is 10 max is 100',
      },
      status: {
        type: 'string',
        description: 'Filter by status',
        enum: ['pending', 'active', 'on_hold', 'cancelled', 'failed', 'expired'],
      },
    },
    required: [],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const body = args as any;
  const response = await client.subscriptions.list(body).asResponse();
  return asTextContentResult(await response.json());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/cloudflare-worker/static/home.md:
--------------------------------------------------------------------------------

```markdown
# DodoPayments Remote MCP Server

This MCP server is available at:

```
{{cloudflareWorkerUrl}}
```

### Claude.ai

To connect Claude Web to this MCP server:

1. Open Claude Web
2. Go to Settings -> Connectors
3. Click "+ Add Custom Connector"
4. Add the MCP server URL: `{{cloudflareWorkerUrl}}`

### Claude Desktop

Claude Desktop requires using the `mcp-remote` package to connect to remote MCP servers:

1. Edit your Claude Desktop configuration file:
   - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
   - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
2. Add the following configuration:

```json
{
  "mcpServers": {
    "dodopayments_api": {
      "command": "npx",
      "args": ["-y", "mcp-remote@latest", "{{cloudflareWorkerUrl}}"]
    }
  }
}
```

3. Restart Claude Desktop to see the MCP server connection (look for the hammer icon)

### Cursor

1. Edit your Cursor MCP configuration file at `~/.cursor/mcp.json`
2. Add the following configuration:

```json
{
  "mcpServers": {
    "dodopayments_api": {
      "command": "npx",
      "args": ["-y", "mcp-remote@latest", "{{cloudflareWorkerUrl}}"]
    }
  }
}
```

### Windsurf

1. Edit your Windsurf configuration file at `~/.codeium/windsurf/mcp_config.json`
2. Add the following configuration:

```json
{
  "mcpServers": {
    "dodopayments_api": {
      "command": "npx",
      "args": ["-y", "mcp-remote@latest", "{{cloudflareWorkerUrl}}"]
    }
  }
}
```

## Troubleshooting

If you encounter issues connecting:

1. Ensure you have Node.js 18 or higher installed
2. Try clearing MCP authentication cache: `rm -rf ~/.mcp-auth`
3. Restart your MCP client application
4. Check client logs for error messages

## Learn More

For more information about MCP:

- [MCP Introduction](https://modelcontextprotocol.io/introduction)
- [mcp-remote package](https://www.npmjs.com/package/mcp-remote)

```

--------------------------------------------------------------------------------
/tests/form.test.ts:
--------------------------------------------------------------------------------

```typescript
import { multipartFormRequestOptions, createForm } from 'dodopayments/internal/uploads';
import { toFile } from 'dodopayments/core/uploads';

describe('form data validation', () => {
  test('valid values do not error', async () => {
    await multipartFormRequestOptions(
      {
        body: {
          foo: 'foo',
          string: 1,
          bool: true,
          file: await toFile(Buffer.from('some-content')),
          blob: new Blob(['Some content'], { type: 'text/plain' }),
        },
      },
      fetch,
    );
  });

  test('null', async () => {
    await expect(() =>
      multipartFormRequestOptions(
        {
          body: {
            null: null,
          },
        },
        fetch,
      ),
    ).rejects.toThrow(TypeError);
  });

  test('undefined is stripped', async () => {
    const form = await createForm(
      {
        foo: undefined,
        bar: 'baz',
      },
      fetch,
    );
    expect(form.has('foo')).toBe(false);
    expect(form.get('bar')).toBe('baz');
  });

  test('nested undefined property is stripped', async () => {
    const form = await createForm(
      {
        bar: {
          baz: undefined,
        },
      },
      fetch,
    );
    expect(Array.from(form.entries())).toEqual([]);

    const form2 = await createForm(
      {
        bar: {
          foo: 'string',
          baz: undefined,
        },
      },
      fetch,
    );
    expect(Array.from(form2.entries())).toEqual([['bar[foo]', 'string']]);
  });

  test('nested undefined array item is stripped', async () => {
    const form = await createForm(
      {
        bar: [undefined, undefined],
      },
      fetch,
    );
    expect(Array.from(form.entries())).toEqual([]);

    const form2 = await createForm(
      {
        bar: [undefined, 'foo'],
      },
      fetch,
    );
    expect(Array.from(form2.entries())).toEqual([['bar[]', 'foo']]);
  });
});

```

--------------------------------------------------------------------------------
/tests/api-resources/disputes.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource disputes', () => {
  test('retrieve', async () => {
    const responsePromise = client.disputes.retrieve('dispute_id');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('list', async () => {
    const responsePromise = client.disputes.list();
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('list: request options and params are passed correctly', async () => {
    // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
    await expect(
      client.disputes.list(
        {
          created_at_gte: '2019-12-27T18:11:19.117Z',
          created_at_lte: '2019-12-27T18:11:19.117Z',
          customer_id: 'customer_id',
          dispute_stage: 'pre_dispute',
          dispute_status: 'dispute_opened',
          page_number: 0,
          page_size: 0,
        },
        { path: '/_stainless_unknown_path' },
      ),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });
});

```

--------------------------------------------------------------------------------
/tests/base64.test.ts:
--------------------------------------------------------------------------------

```typescript
import { fromBase64, toBase64 } from 'dodopayments/internal/utils/base64';

describe.each(['Buffer', 'atob'])('with %s', (mode) => {
  let originalBuffer: BufferConstructor;
  beforeAll(() => {
    if (mode === 'atob') {
      originalBuffer = globalThis.Buffer;
      // @ts-expect-error Can't assign undefined to BufferConstructor
      delete globalThis.Buffer;
    }
  });
  afterAll(() => {
    if (mode === 'atob') {
      globalThis.Buffer = originalBuffer;
    }
  });
  test('toBase64', () => {
    const testCases = [
      {
        input: 'hello world',
        expected: 'aGVsbG8gd29ybGQ=',
      },
      {
        input: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]),
        expected: 'aGVsbG8gd29ybGQ=',
      },
      {
        input: undefined,
        expected: '',
      },
      {
        input: new Uint8Array([
          229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123,
          193, 71,
        ]),
        expected: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH',
      },
      {
        input: '✓',
        expected: '4pyT',
      },
      {
        input: new Uint8Array([226, 156, 147]),
        expected: '4pyT',
      },
    ];

    testCases.forEach(({ input, expected }) => {
      expect(toBase64(input)).toBe(expected);
    });
  });

  test('fromBase64', () => {
    const testCases = [
      {
        input: 'aGVsbG8gd29ybGQ=',
        expected: new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]),
      },
      {
        input: '',
        expected: new Uint8Array([]),
      },
      {
        input: '5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH',
        expected: new Uint8Array([
          229, 102, 215, 230, 65, 22, 46, 87, 243, 176, 99, 99, 31, 174, 8, 242, 83, 142, 169, 64, 122, 123,
          193, 71,
        ]),
      },
      {
        input: '4pyT',
        expected: new Uint8Array([226, 156, 147]),
      },
    ];

    testCases.forEach(({ input, expected }) => {
      expect(fromBase64(input)).toEqual(expected);
    });
  });
});

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/licenses/validate-licenses.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'licenses',
  operation: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/licenses/validate',
  operationId: 'validate_license_key',
};

export const tool: Tool = {
  name: 'validate_licenses',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/license_validate_response',\n  $defs: {\n    license_validate_response: {\n      type: 'object',\n      properties: {\n        valid: {\n          type: 'boolean'\n        }\n      },\n      required: [        'valid'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      license_key: {
        type: 'string',
      },
      license_key_instance_id: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['license_key'],
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.licenses.validate(body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/addons/update-images-addons.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'addons',
  operation: 'write',
  tags: [],
  httpMethod: 'put',
  httpPath: '/addons/{id}/images',
  operationId: 'update_addon_image',
};

export const tool: Tool = {
  name: 'update_images_addons',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/addon_update_images_response',\n  $defs: {\n    addon_update_images_response: {\n      type: 'object',\n      properties: {\n        image_id: {\n          type: 'string'\n        },\n        url: {\n          type: 'string'\n        }\n      },\n      required: [        'image_id',\n        'url'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['id'],
  },
  annotations: {
    idempotentHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.addons.updateImages(id)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "dodopayments",
  "version": "2.3.0",
  "description": "The official TypeScript library for the Dodo Payments API",
  "author": "Dodo Payments <[email protected]>",
  "types": "dist/index.d.ts",
  "main": "dist/index.js",
  "type": "commonjs",
  "repository": "github:dodopayments/dodopayments-typescript",
  "license": "Apache-2.0",
  "packageManager": "[email protected]",
  "files": [
    "**/*"
  ],
  "private": false,
  "publishConfig": {
    "access": "public"
  },
  "scripts": {
    "test": "./scripts/test",
    "build": "./scripts/build",
    "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1",
    "format": "./scripts/format",
    "prepare": "if ./scripts/utils/check-is-in-git-install.sh; then ./scripts/build && ./scripts/utils/git-swap.sh; fi",
    "tsn": "ts-node -r tsconfig-paths/register",
    "lint": "./scripts/lint",
    "fix": "./scripts/format"
  },
  "dependencies": {
    "standardwebhooks": "^1.0.0"
  },
  "devDependencies": {
    "@arethetypeswrong/cli": "^0.17.0",
    "@swc/core": "^1.3.102",
    "@swc/jest": "^0.2.29",
    "@types/jest": "^29.4.0",
    "@types/node": "^20.17.6",
    "@typescript-eslint/eslint-plugin": "8.31.1",
    "@typescript-eslint/parser": "8.31.1",
    "eslint": "^9.20.1",
    "eslint-plugin-prettier": "^5.4.1",
    "eslint-plugin-unused-imports": "^4.1.4",
    "iconv-lite": "^0.6.3",
    "jest": "^29.4.0",
    "prettier": "^3.0.0",
    "publint": "^0.2.12",
    "ts-jest": "^29.1.0",
    "ts-node": "^10.5.0",
    "tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz",
    "tsconfig-paths": "^4.0.0",
    "tslib": "^2.8.1",
    "typescript": "5.8.3",
    "typescript-eslint": "8.31.1"
  },
  "bin": {
    "dodopayments": "bin/cli"
  },
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js"
    },
    "./*.mjs": {
      "default": "./dist/*.mjs"
    },
    "./*.js": {
      "default": "./dist/*.js"
    },
    "./*": {
      "import": "./dist/*.mjs",
      "require": "./dist/*.js"
    }
  }
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/webhooks/retrieve-secret-webhooks.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'webhooks',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/webhooks/{webhook_id}/secret',
  operationId: 'get_webhook_secret',
};

export const tool: Tool = {
  name: 'retrieve_secret_webhooks',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet webhook secret by id\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/webhook_retrieve_secret_response',\n  $defs: {\n    webhook_retrieve_secret_response: {\n      type: 'object',\n      properties: {\n        secret: {\n          type: 'string'\n        }\n      },\n      required: [        'secret'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      webhook_id: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['webhook_id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { webhook_id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.webhooks.retrieveSecret(webhook_id)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/products/images/update-products-images.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'products.images',
  operation: 'write',
  tags: [],
  httpMethod: 'put',
  httpPath: '/products/{id}/images',
  operationId: 'update_product_image',
};

export const tool: Tool = {
  name: 'update_products_images',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/image_update_response',\n  $defs: {\n    image_update_response: {\n      type: 'object',\n      properties: {\n        url: {\n          type: 'string'\n        },\n        image_id: {\n          type: 'string'\n        }\n      },\n      required: [        'url'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
      force_update: {
        type: 'boolean',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['id'],
  },
  annotations: {
    idempotentHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.products.images.update(id, body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/products/update-files-products.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'products',
  operation: 'write',
  tags: [],
  httpMethod: 'put',
  httpPath: '/products/{id}/files',
  operationId: 'upload_product_file',
};

export const tool: Tool = {
  name: 'update_files_products',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/product_update_files_response',\n  $defs: {\n    product_update_files_response: {\n      type: 'object',\n      properties: {\n        file_id: {\n          type: 'string'\n        },\n        url: {\n          type: 'string'\n        }\n      },\n      required: [        'file_id',\n        'url'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
      file_name: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['id', 'file_name'],
  },
  annotations: {
    idempotentHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.products.updateFiles(id, body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/tests/buildHeaders.test.ts:
--------------------------------------------------------------------------------

```typescript
import { inspect } from 'node:util';
import { buildHeaders, type HeadersLike, type NullableHeaders } from 'dodopayments/internal/headers';

function inspectNullableHeaders(headers: NullableHeaders) {
  return `NullableHeaders {${[
    ...[...headers.values.entries()].map(([name, value]) => ` ${inspect(name)}: ${inspect(value)}`),
    ...[...headers.nulls].map((name) => ` ${inspect(name)}: null`),
  ].join(', ')} }`;
}

describe('buildHeaders', () => {
  const cases: [HeadersLike[], string][] = [
    [[new Headers({ 'content-type': 'text/plain' })], `NullableHeaders { 'content-type': 'text/plain' }`],
    [
      [
        {
          'content-type': 'text/plain',
        },
        {
          'Content-Type': undefined,
        },
      ],
      `NullableHeaders { 'content-type': 'text/plain' }`,
    ],
    [
      [
        {
          'content-type': 'text/plain',
        },
        {
          'Content-Type': null,
        },
      ],
      `NullableHeaders { 'content-type': null }`,
    ],
    [
      [
        {
          cookie: 'name1=value1',
          Cookie: 'name2=value2',
        },
      ],
      `NullableHeaders { 'cookie': 'name2=value2' }`,
    ],
    [
      [
        {
          cookie: 'name1=value1',
          Cookie: undefined,
        },
      ],
      `NullableHeaders { 'cookie': 'name1=value1' }`,
    ],
    [
      [
        {
          cookie: ['name1=value1', 'name2=value2'],
        },
      ],
      `NullableHeaders { 'cookie': 'name1=value1; name2=value2' }`,
    ],
    [
      [
        {
          'x-foo': ['name1=value1', 'name2=value2'],
        },
      ],
      `NullableHeaders { 'x-foo': 'name1=value1, name2=value2' }`,
    ],
    [
      [
        [
          ['cookie', 'name1=value1'],
          ['cookie', 'name2=value2'],
          ['Cookie', 'name3=value3'],
        ],
      ],
      `NullableHeaders { 'cookie': 'name1=value1; name2=value2; name3=value3' }`,
    ],
    [[undefined], `NullableHeaders { }`],
    [[null], `NullableHeaders { }`],
  ];
  for (const [input, expected] of cases) {
    test(expected, () => {
      expect(inspectNullableHeaders(buildHeaders(input))).toEqual(expected);
    });
  }
});

```

--------------------------------------------------------------------------------
/tests/api-resources/customers/wallets/ledger-entries.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource ledgerEntries', () => {
  test('create: only required params', async () => {
    const responsePromise = client.customers.wallets.ledgerEntries.create('customer_id', {
      amount: 0,
      currency: 'AED',
      entry_type: 'credit',
    });
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('create: required and optional params', async () => {
    const response = await client.customers.wallets.ledgerEntries.create('customer_id', {
      amount: 0,
      currency: 'AED',
      entry_type: 'credit',
      idempotency_key: 'idempotency_key',
      reason: 'reason',
    });
  });

  test('list', async () => {
    const responsePromise = client.customers.wallets.ledgerEntries.list('customer_id');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('list: request options and params are passed correctly', async () => {
    // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
    await expect(
      client.customers.wallets.ledgerEntries.list(
        'customer_id',
        { currency: 'AED', page_number: 0, page_size: 0 },
        { path: '/_stainless_unknown_path' },
      ),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });
});

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/subscriptions/change-plan-subscriptions.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'subscriptions',
  operation: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/subscriptions/{subscription_id}/change-plan',
  operationId: 'update_subscription_plan_handler',
};

export const tool: Tool = {
  name: 'change_plan_subscriptions',
  description: '',
  inputSchema: {
    type: 'object',
    properties: {
      subscription_id: {
        type: 'string',
      },
      product_id: {
        type: 'string',
        description: 'Unique identifier of the product to subscribe to',
      },
      proration_billing_mode: {
        type: 'string',
        title: 'Proration Billing Mode',
        description: 'Proration Billing Mode',
        enum: ['prorated_immediately', 'full_immediately', 'difference_immediately'],
      },
      quantity: {
        type: 'integer',
        description: 'Number of units to subscribe for. Must be at least 1.',
      },
      addons: {
        type: 'array',
        description: 'Addons for the new plan.\nNote : Leaving this empty would remove any existing addons',
        items: {
          $ref: '#/$defs/attach_addon',
        },
      },
    },
    required: ['subscription_id', 'product_id', 'proration_billing_mode', 'quantity'],
    $defs: {
      attach_addon: {
        type: 'object',
        title: 'Attach Addon Request',
        properties: {
          addon_id: {
            type: 'string',
          },
          quantity: {
            type: 'integer',
          },
        },
        required: ['addon_id', 'quantity'],
      },
    },
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { subscription_id, ...body } = args as any;
  const response = await client.subscriptions.changePlan(subscription_id, body).asResponse();
  return asTextContentResult(await response.text());
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/types.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';
import { Tool } from '@modelcontextprotocol/sdk/types.js';

type TextContentBlock = {
  type: 'text';
  text: string;
};

type ImageContentBlock = {
  type: 'image';
  data: string;
  mimeType: string;
};

type AudioContentBlock = {
  type: 'audio';
  data: string;
  mimeType: string;
};

type ResourceContentBlock = {
  type: 'resource';
  resource:
    | {
        uri: string;
        mimeType: string;
        text: string;
      }
    | {
        uri: string;
        mimeType: string;
        blob: string;
      };
};

export type ContentBlock = TextContentBlock | ImageContentBlock | AudioContentBlock | ResourceContentBlock;

export type ToolCallResult = {
  content: ContentBlock[];
  isError?: boolean;
};

export type HandlerFunction = (
  client: DodoPayments,
  args: Record<string, unknown> | undefined,
) => Promise<ToolCallResult>;

export function asTextContentResult(result: unknown): ToolCallResult {
  return {
    content: [
      {
        type: 'text',
        text: JSON.stringify(result, null, 2),
      },
    ],
  };
}

export async function asBinaryContentResult(response: Response): Promise<ToolCallResult> {
  const blob = await response.blob();
  const mimeType = blob.type;
  const data = Buffer.from(await blob.arrayBuffer()).toString('base64');
  if (mimeType.startsWith('image/')) {
    return {
      content: [{ type: 'image', mimeType, data }],
    };
  } else if (mimeType.startsWith('audio/')) {
    return {
      content: [{ type: 'audio', mimeType, data }],
    };
  } else {
    return {
      content: [
        {
          type: 'resource',
          resource: {
            // We must give a URI, even though this isn't actually an MCP resource.
            uri: 'resource://tool-response',
            mimeType,
            blob: data,
          },
        },
      ],
    };
  }
}

export type Metadata = {
  resource: string;
  operation: 'read' | 'write';
  tags: string[];
  httpMethod?: string;
  httpPath?: string;
  operationId?: string;
};

export type Endpoint = {
  metadata: Metadata;
  tool: Tool;
  handler: HandlerFunction;
};

```

--------------------------------------------------------------------------------
/src/resources/webhook-events.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../core/resource';
import * as DisputesAPI from './disputes';
import * as LicenseKeysAPI from './license-keys';
import * as PaymentsAPI from './payments';
import * as RefundsAPI from './refunds';
import * as SubscriptionsAPI from './subscriptions';

export class WebhookEvents extends APIResource {}

/**
 * Event types for Dodo events
 */
export type WebhookEventType =
  | 'payment.succeeded'
  | 'payment.failed'
  | 'payment.processing'
  | 'payment.cancelled'
  | 'refund.succeeded'
  | 'refund.failed'
  | 'dispute.opened'
  | 'dispute.expired'
  | 'dispute.accepted'
  | 'dispute.cancelled'
  | 'dispute.challenged'
  | 'dispute.won'
  | 'dispute.lost'
  | 'subscription.active'
  | 'subscription.renewed'
  | 'subscription.on_hold'
  | 'subscription.cancelled'
  | 'subscription.failed'
  | 'subscription.expired'
  | 'subscription.plan_changed'
  | 'license_key.created';

export interface WebhookPayload {
  business_id: string;

  /**
   * The latest data at the time of delivery attempt
   */
  data:
    | WebhookPayload.Payment
    | WebhookPayload.Subscription
    | WebhookPayload.Refund
    | WebhookPayload.Dispute
    | WebhookPayload.LicenseKey;

  /**
   * The timestamp of when the event occurred (not necessarily the same of when it
   * was delivered)
   */
  timestamp: string;

  /**
   * Event types for Dodo events
   */
  type: WebhookEventType;
}

export namespace WebhookPayload {
  export interface Payment extends PaymentsAPI.Payment {
    payload_type: 'Payment';
  }

  /**
   * Response struct representing subscription details
   */
  export interface Subscription extends SubscriptionsAPI.Subscription {
    payload_type: 'Subscription';
  }

  export interface Refund extends RefundsAPI.Refund {
    payload_type: 'Refund';
  }

  export interface Dispute extends DisputesAPI.GetDispute {
    payload_type: 'Dispute';
  }

  export interface LicenseKey extends LicenseKeysAPI.LicenseKey {
    payload_type: 'LicenseKey';
  }
}

export declare namespace WebhookEvents {
  export { type WebhookEventType as WebhookEventType, type WebhookPayload as WebhookPayload };
}

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/customers/customer-portal/create-customers-customer-portal.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'customers.customer_portal',
  operation: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/customers/{customer_id}/customer-portal/session',
  operationId: 'create_customer_portal_session',
};

export const tool: Tool = {
  name: 'create_customers_customer_portal',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/customer_portal_session',\n  $defs: {\n    customer_portal_session: {\n      type: 'object',\n      properties: {\n        link: {\n          type: 'string'\n        }\n      },\n      required: [        'link'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      customer_id: {
        type: 'string',
      },
      send_email: {
        type: 'boolean',
        description: 'If true, will send link to user.',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['customer_id'],
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { customer_id, jq_filter, ...body } = args as any;
  return asTextContentResult(
    await maybeFilter(jq_filter, await client.customers.customerPortal.create(customer_id, body)),
  );
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/brands/update-images-brands.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'brands',
  operation: 'write',
  tags: [],
  httpMethod: 'put',
  httpPath: '/brands/{id}/images',
  operationId: 'update_brand_image',
};

export const tool: Tool = {
  name: 'update_images_brands',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/brand_update_images_response',\n  $defs: {\n    brand_update_images_response: {\n      type: 'object',\n      properties: {\n        image_id: {\n          type: 'string',\n          description: 'UUID that will be used as the image identifier/key suffix'\n        },\n        url: {\n          type: 'string',\n          description: 'Presigned URL to upload the image'\n        }\n      },\n      required: [        'image_id',\n        'url'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['id'],
  },
  annotations: {
    idempotentHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.brands.updateImages(id)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/tests/api-resources/checkout-sessions.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource checkoutSessions', () => {
  test('create: only required params', async () => {
    const responsePromise = client.checkoutSessions.create({
      product_cart: [{ product_id: 'product_id', quantity: 0 }],
    });
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('create: required and optional params', async () => {
    const response = await client.checkoutSessions.create({
      product_cart: [
        { product_id: 'product_id', quantity: 0, addons: [{ addon_id: 'addon_id', quantity: 0 }], amount: 0 },
      ],
      allowed_payment_method_types: ['credit'],
      billing_address: { country: 'AF', city: 'city', state: 'state', street: 'street', zipcode: 'zipcode' },
      billing_currency: 'AED',
      confirm: true,
      customer: { customer_id: 'customer_id' },
      customization: {
        force_language: 'force_language',
        show_on_demand_tag: true,
        show_order_details: true,
        theme: 'dark',
      },
      discount_code: 'discount_code',
      feature_flags: {
        allow_currency_selection: true,
        allow_discount_code: true,
        allow_phone_number_collection: true,
        allow_tax_id: true,
        always_create_new_customer: true,
      },
      force_3ds: true,
      metadata: { foo: 'string' },
      return_url: 'return_url',
      show_saved_payment_methods: true,
      subscription_data: {
        on_demand: {
          mandate_only: true,
          adaptive_currency_fees_inclusive: true,
          product_currency: 'AED',
          product_description: 'product_description',
          product_price: 0,
        },
        trial_period_days: 0,
      },
    });
  });
});

```

--------------------------------------------------------------------------------
/tests/api-resources/license-keys.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource licenseKeys', () => {
  test('retrieve', async () => {
    const responsePromise = client.licenseKeys.retrieve('lic_123');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('update', async () => {
    const responsePromise = client.licenseKeys.update('lic_123', {});
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('list', async () => {
    const responsePromise = client.licenseKeys.list();
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('list: request options and params are passed correctly', async () => {
    // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
    await expect(
      client.licenseKeys.list(
        {
          customer_id: 'customer_id',
          page_number: 0,
          page_size: 0,
          product_id: 'product_id',
          status: 'active',
        },
        { path: '/_stainless_unknown_path' },
      ),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });
});

```

--------------------------------------------------------------------------------
/src/internal/request-options.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { NullableHeaders } from './headers';

import type { BodyInit } from './builtin-types';
import type { HTTPMethod, MergedRequestInit } from './types';
import { type HeadersLike } from './headers';

export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string };

export type RequestOptions = {
  /**
   * The HTTP method for the request (e.g., 'get', 'post', 'put', 'delete').
   */
  method?: HTTPMethod;

  /**
   * The URL path for the request.
   *
   * @example "/v1/foo"
   */
  path?: string;

  /**
   * Query parameters to include in the request URL.
   */
  query?: object | undefined | null;

  /**
   * The request body. Can be a string, JSON object, FormData, or other supported types.
   */
  body?: unknown;

  /**
   * HTTP headers to include with the request. Can be a Headers object, plain object, or array of tuples.
   */
  headers?: HeadersLike;

  /**
   * The maximum number of times that the client will retry a request in case of a
   * temporary failure, like a network error or a 5XX error from the server.
   *
   * @default 2
   */
  maxRetries?: number;

  stream?: boolean | undefined;

  /**
   * The maximum amount of time (in milliseconds) that the client should wait for a response
   * from the server before timing out a single request.
   *
   * @unit milliseconds
   */
  timeout?: number;

  /**
   * Additional `RequestInit` options to be passed to the underlying `fetch` call.
   * These options will be merged with the client's default fetch options.
   */
  fetchOptions?: MergedRequestInit;

  /**
   * An AbortSignal that can be used to cancel the request.
   */
  signal?: AbortSignal | undefined | null;

  /**
   * A unique key for this request to enable idempotency.
   */
  idempotencyKey?: string;

  /**
   * Override the default base URL for this specific request.
   */
  defaultBaseURL?: string | undefined;

  __binaryResponse?: boolean | undefined;
};

export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit };
export type RequestEncoder = (request: { headers: NullableHeaders; body: unknown }) => EncodedContent;

export const FallbackEncoder: RequestEncoder = ({ headers, body }) => {
  return {
    bodyHeaders: {
      'content-type': 'application/json',
    },
    body: JSON.stringify(body),
  };
};

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/license-key-instances/retrieve-license-key-instances.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'license_key_instances',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/license_key_instances/{id}',
  operationId: 'get_license_key_instance',
};

export const tool: Tool = {
  name: 'retrieve_license_key_instances',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/license_key_instance',\n  $defs: {\n    license_key_instance: {\n      type: 'object',\n      properties: {\n        id: {\n          type: 'string'\n        },\n        business_id: {\n          type: 'string'\n        },\n        created_at: {\n          type: 'string',\n          format: 'date-time'\n        },\n        license_key_id: {\n          type: 'string'\n        },\n        name: {\n          type: 'string'\n        }\n      },\n      required: [        'id',\n        'business_id',\n        'created_at',\n        'license_key_id',\n        'name'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.licenseKeyInstances.retrieve(id)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/tests/api-resources/license-key-instances.test.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: 'My Bearer Token',
  baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
});

describe('resource licenseKeyInstances', () => {
  test('retrieve', async () => {
    const responsePromise = client.licenseKeyInstances.retrieve('lki_123');
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('update: only required params', async () => {
    const responsePromise = client.licenseKeyInstances.update('lki_123', { name: 'name' });
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('update: required and optional params', async () => {
    const response = await client.licenseKeyInstances.update('lki_123', { name: 'name' });
  });

  test('list', async () => {
    const responsePromise = client.licenseKeyInstances.list();
    const rawResponse = await responsePromise.asResponse();
    expect(rawResponse).toBeInstanceOf(Response);
    const response = await responsePromise;
    expect(response).not.toBeInstanceOf(Response);
    const dataAndResponse = await responsePromise.withResponse();
    expect(dataAndResponse.data).toBe(response);
    expect(dataAndResponse.response).toBe(rawResponse);
  });

  test('list: request options and params are passed correctly', async () => {
    // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
    await expect(
      client.licenseKeyInstances.list(
        { license_key_id: 'license_key_id', page_number: 0, page_size: 0 },
        { path: '/_stainless_unknown_path' },
      ),
    ).rejects.toThrow(DodoPayments.NotFoundError);
  });
});

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/customers/retrieve-customers.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'customers',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/customers/{customer_id}',
  operationId: 'get_customer_handler',
};

export const tool: Tool = {
  name: 'retrieve_customers',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/customer',\n  $defs: {\n    customer: {\n      type: 'object',\n      properties: {\n        business_id: {\n          type: 'string'\n        },\n        created_at: {\n          type: 'string',\n          format: 'date-time'\n        },\n        customer_id: {\n          type: 'string'\n        },\n        email: {\n          type: 'string'\n        },\n        name: {\n          type: 'string'\n        },\n        phone_number: {\n          type: 'string'\n        }\n      },\n      required: [        'business_id',\n        'created_at',\n        'customer_id',\n        'email',\n        'name'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      customer_id: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['customer_id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { customer_id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.customers.retrieve(customer_id)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/license-key-instances/update-license-key-instances.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'license_key_instances',
  operation: 'write',
  tags: [],
  httpMethod: 'patch',
  httpPath: '/license_key_instances/{id}',
  operationId: 'update_license_key_instance',
};

export const tool: Tool = {
  name: 'update_license_key_instances',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/license_key_instance',\n  $defs: {\n    license_key_instance: {\n      type: 'object',\n      properties: {\n        id: {\n          type: 'string'\n        },\n        business_id: {\n          type: 'string'\n        },\n        created_at: {\n          type: 'string',\n          format: 'date-time'\n        },\n        license_key_id: {\n          type: 'string'\n        },\n        name: {\n          type: 'string'\n        }\n      },\n      required: [        'id',\n        'business_id',\n        'created_at',\n        'license_key_id',\n        'name'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      id: {
        type: 'string',
      },
      name: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['id', 'name'],
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.licenseKeyInstances.update(id, body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/customers/create-customers.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'customers',
  operation: 'write',
  tags: [],
  httpMethod: 'post',
  httpPath: '/customers',
  operationId: 'create_customer',
};

export const tool: Tool = {
  name: 'create_customers',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/customer',\n  $defs: {\n    customer: {\n      type: 'object',\n      properties: {\n        business_id: {\n          type: 'string'\n        },\n        created_at: {\n          type: 'string',\n          format: 'date-time'\n        },\n        customer_id: {\n          type: 'string'\n        },\n        email: {\n          type: 'string'\n        },\n        name: {\n          type: 'string'\n        },\n        phone_number: {\n          type: 'string'\n        }\n      },\n      required: [        'business_id',\n        'created_at',\n        'customer_id',\n        'email',\n        'name'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      email: {
        type: 'string',
      },
      name: {
        type: 'string',
      },
      phone_number: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['email', 'name'],
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.customers.create(body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/customers/update-customers.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'customers',
  operation: 'write',
  tags: [],
  httpMethod: 'patch',
  httpPath: '/customers/{customer_id}',
  operationId: 'patch_customer',
};

export const tool: Tool = {
  name: 'update_customers',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\n\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/customer',\n  $defs: {\n    customer: {\n      type: 'object',\n      properties: {\n        business_id: {\n          type: 'string'\n        },\n        created_at: {\n          type: 'string',\n          format: 'date-time'\n        },\n        customer_id: {\n          type: 'string'\n        },\n        email: {\n          type: 'string'\n        },\n        name: {\n          type: 'string'\n        },\n        phone_number: {\n          type: 'string'\n        }\n      },\n      required: [        'business_id',\n        'created_at',\n        'customer_id',\n        'email',\n        'name'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      customer_id: {
        type: 'string',
      },
      name: {
        type: 'string',
      },
      phone_number: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['customer_id'],
  },
  annotations: {},
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { customer_id, jq_filter, ...body } = args as any;
  return asTextContentResult(await maybeFilter(jq_filter, await client.customers.update(customer_id, body)));
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/packages/mcp-server/src/tools/webhooks/headers/retrieve-webhooks-headers.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { maybeFilter } from 'dodopayments-mcp/filtering';
import { Metadata, asTextContentResult } from 'dodopayments-mcp/tools/types';

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import DodoPayments from 'dodopayments';

export const metadata: Metadata = {
  resource: 'webhooks.headers',
  operation: 'read',
  tags: [],
  httpMethod: 'get',
  httpPath: '/webhooks/{webhook_id}/headers',
  operationId: 'get_webhook_headers',
};

export const tool: Tool = {
  name: 'retrieve_webhooks_headers',
  description:
    "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nGet a webhook by id\n\n# Response Schema\n```json\n{\n  $ref: '#/$defs/header_retrieve_response',\n  $defs: {\n    header_retrieve_response: {\n      type: 'object',\n      description: 'The value of the headers is returned in the `headers` field.\\n\\nSensitive headers that have been redacted are returned in the sensitive\\nfield.',\n      properties: {\n        headers: {\n          type: 'object',\n          description: 'List of headers configured',\n          additionalProperties: true\n        },\n        sensitive: {\n          type: 'array',\n          description: 'Sensitive headers without the value',\n          items: {\n            type: 'string'\n          }\n        }\n      },\n      required: [        'headers',\n        'sensitive'\n      ]\n    }\n  }\n}\n```",
  inputSchema: {
    type: 'object',
    properties: {
      webhook_id: {
        type: 'string',
      },
      jq_filter: {
        type: 'string',
        title: 'jq Filter',
        description:
          'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
      },
    },
    required: ['webhook_id'],
  },
  annotations: {
    readOnlyHint: true,
  },
};

export const handler = async (client: DodoPayments, args: Record<string, unknown> | undefined) => {
  const { webhook_id, jq_filter, ...body } = args as any;
  return asTextContentResult(
    await maybeFilter(jq_filter, await client.webhooks.headers.retrieve(webhook_id)),
  );
};

export default { metadata, tool, handler };

```

--------------------------------------------------------------------------------
/src/resources/license-key-instances.ts:
--------------------------------------------------------------------------------

```typescript
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../core/resource';
import { APIPromise } from '../core/api-promise';
import {
  DefaultPageNumberPagination,
  type DefaultPageNumberPaginationParams,
  PagePromise,
} from '../core/pagination';
import { RequestOptions } from '../internal/request-options';
import { path } from '../internal/utils/path';

export class LicenseKeyInstances extends APIResource {
  /**
   * @example
   * ```ts
   * const licenseKeyInstance =
   *   await client.licenseKeyInstances.retrieve('lki_123');
   * ```
   */
  retrieve(id: string, options?: RequestOptions): APIPromise<LicenseKeyInstance> {
    return this._client.get(path`/license_key_instances/${id}`, options);
  }

  /**
   * @example
   * ```ts
   * const licenseKeyInstance =
   *   await client.licenseKeyInstances.update('lki_123', {
   *     name: 'name',
   *   });
   * ```
   */
  update(
    id: string,
    body: LicenseKeyInstanceUpdateParams,
    options?: RequestOptions,
  ): APIPromise<LicenseKeyInstance> {
    return this._client.patch(path`/license_key_instances/${id}`, { body, ...options });
  }

  /**
   * @example
   * ```ts
   * // Automatically fetches more pages as needed.
   * for await (const licenseKeyInstance of client.licenseKeyInstances.list()) {
   *   // ...
   * }
   * ```
   */
  list(
    query: LicenseKeyInstanceListParams | null | undefined = {},
    options?: RequestOptions,
  ): PagePromise<LicenseKeyInstancesDefaultPageNumberPagination, LicenseKeyInstance> {
    return this._client.getAPIList(
      '/license_key_instances',
      DefaultPageNumberPagination<LicenseKeyInstance>,
      { query, ...options },
    );
  }
}

export type LicenseKeyInstancesDefaultPageNumberPagination = DefaultPageNumberPagination<LicenseKeyInstance>;

export interface LicenseKeyInstance {
  id: string;

  business_id: string;

  created_at: string;

  license_key_id: string;

  name: string;
}

export interface LicenseKeyInstanceUpdateParams {
  name: string;
}

export interface LicenseKeyInstanceListParams extends DefaultPageNumberPaginationParams {
  /**
   * Filter by license key ID
   */
  license_key_id?: string | null;
}

export declare namespace LicenseKeyInstances {
  export {
    type LicenseKeyInstance as LicenseKeyInstance,
    type LicenseKeyInstancesDefaultPageNumberPagination as LicenseKeyInstancesDefaultPageNumberPagination,
    type LicenseKeyInstanceUpdateParams as LicenseKeyInstanceUpdateParams,
    type LicenseKeyInstanceListParams as LicenseKeyInstanceListParams,
  };
}

```
Page 1/8FirstPrevNextLast