Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Guide for creating and managing products, pricing, add-ons, product collections, images, and digital product delivery
.claude/skills/hashgraph-online-product-catalog-management/SKILL.md| Model | Eval pass | Runs |
|---|---|---|
| gemini-3.6-flash | 95% | 21 |
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 105% | 0% |
| case-21 | ✗→✓ | ▲ Improved | 49% | 0% |
| case-22 | ✗→✓ | ▲ Improved | 64% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 118% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 77% | 0% |
This skill covers the full product lifecycle: creating products with pricing models, managing add-ons and collections, uploading product images, and delivering digital files to customers.
Product model: Every product has exactly one pricing model selected at creation. The three models are:
one_time_price: charge once per purchase.recurring_price: charge on a configurable payment frequency and subscription period.usage_based_price: charge per unit consumed (see usage-based-billing skill for meter setup).Pricing is nested inside the product object. There is no separate top-level Price resource.
Pricing structure: Each price object contains:
type: one of the three models above.currency: ISO 4217 code (e.g., USD, AED, INR).price: amount in the smallest currency unit (cents for USD).discount: optional discount amount in the same unit.Tax category: Required at product creation. Dodo uses this to calculate and collect sales tax. Supported values are digital_products, saas, e_book, and edtech.
Lifecycle: Products support list, retrieve, update, and archive/unarchive. There is no delete endpoint. Archived products remain in your history but don't appear in new checkouts.
Images: Presigned upload URLs expire after 60 seconds. Download the URL immediately after requesting it.
Digital delivery: Entitlements grant customers access to files. Download URLs expire after roughly 15 minutes.
All products require a name, a pricing model, and a tax category.
typescriptimport DodoPayments from 'dodopayments'; const client = new DodoPayments({ bearerToken: process.env.DODO_PAYMENTS_API_KEY, environment: 'test_mode', }); // One-time purchase const product = await client.products.create({ name: 'Pro Bundle', tax_category: 'digital_products', price: { type: 'one_time_price', currency: 'USD', price: 9900, // $99.00 discount: 0, purchasing_power_parity: false, }, }); console.log(product.product_id); // pdt_...
For recurring products, specify the billing cycle:
typescriptconst subscription = await client.products.create({ name: 'Premium Plan', tax_category: 'saas', price: { type: 'recurring_price', currency: 'USD', price: 2999, // $29.99/month discount: 0, payment_frequency_count: 1, payment_frequency_interval: 'Month', subscription_period_count: 1, subscription_period_interval: 'Month', purchasing_power_parity: false, }, });
This skill is the canonical source for product creation request shapes. For a usage-based product, create the meter as described in the usage-based-billing skill, then attach it through the singular price.meters array:
typescriptconst metered = await client.products.create({ name: 'API Calls', tax_category: 'saas', price: { type: 'usage_based_price', currency: 'USD', discount: 0, fixed_price: 0, // No fixed monthly charge; amounts use the smallest currency unit payment_frequency_count: 1, payment_frequency_interval: 'Month', subscription_period_count: 1, subscription_period_interval: 'Month', purchasing_power_parity: false, meters: [ { meter_id: 'mtr_api_calls', price_per_unit: '0.01', free_threshold: 1000, }, ], }, });
price_per_unit is a decimal string in the configured currency's smallest unit. The optional free_threshold excludes that many aggregated units before per-unit charging begins.
typescript// List all products const products = await client.products.list(); // Retrieve a single product const product = await client.products.retrieve('pdt_pro_bundle');
You can update the name, description, and metadata. You cannot change the pricing model or price of a live product. Create a new product instead.
typescriptawait client.products.update('pdt_pro_bundle', { name: 'Pro Bundle (Updated)', description: 'Includes all pro features', metadata: { category: 'software', tier: 'premium', }, });
Archive a product to hide it from new checkouts without deleting it:
typescriptawait client.products.archive('pdt_pro_bundle'); // Restore it later await client.products.unarchive('pdt_pro_bundle');
Upload a product image via presigned URL. The URL is valid for 60 seconds.
typescriptimport fs from 'node:fs'; // Request a presigned upload URL const uploadUrl = await client.products.images.update('pdt_pro_bundle', { force_update: true, }); // Upload immediately (within 60 seconds) const response = await fetch(uploadUrl.url, { method: 'PUT', body: fs.readFileSync('./product.png'), headers: { 'Content-Type': 'image/png' }, }); if (!response.ok) { throw new Error('Image upload failed'); }
Add-ons are optional extras customers can purchase alongside a product. Create them independently, then reference them in checkout.
typescript// Create an add-on const addon = await client.addons.create({ name: 'Priority Support', tax_category: 'saas', currency: 'USD', price: 4900, // $49.00 }); // Upload an image for the add-on const addonImageUrl = await client.addons.updateImages(addon.id); // List add-ons const addons = await client.addons.list();
Group products into collections for themed checkouts. Collections have nested groups and items.
typescript// Create a collection const collection = await client.productCollections.create({ name: 'Starter Bundle', description: 'Everything you need to get started', groups: [ { group_name: 'Core Tools', products: [ { product_id: 'pdt_tool_a' }, { product_id: 'pdt_tool_b' }, ], }, ], }); // Add another group later await client.productCollections.groups.create(collection.id, { group_name: 'Optional Tools', products: [ { product_id: 'pdt_tool_c' }, { product_id: 'pdt_tool_d' }, ], }); // Checkout with a collection const session = await client.checkoutSessions.create({ product_collection_id: collection.id, product_cart: [], // Required: pass an empty array for collection checkout return_url: 'https://yoursite.com/return', }); window.location.href = session.checkout_url;
Entitlements grant customers access to downloadable files. Create an entitlement, upload files, and generate download links.
typescript// Create an entitlement const entitlement = await client.entitlements.create({ name: 'Pro Bundle Files', integration_type: 'digital_files', integration_config: { digital_file_ids: [], }, }); // Upload and attach a file; the response identifies the attached file const { file_id } = await client.entitlements.files.upload(entitlement.id); // Attach the entitlement to a product await client.products.update('pdt_pro_bundle', { entitlements: [{ entitlement_id: entitlement.id }], }); // A grant is produced by fulfillment when the customer purchases the product. // Grants can be listed, revoked, or fulfilled with a manually managed license key; // they cannot be created directly through the SDK. // List grants for a customer const grants = await client.customers.listEntitlementGrants('cus_customer_123'); // Generate a download URL (valid for roughly 15 minutes) const grant = grants.items[0]; const downloadUrl = grant?.digital_product_delivery?.files[0]?.download_url;
Create short, shareable links to products:
typescriptconst shortLink = await client.products.shortLinks.create('pdt_pro_bundle', { slug: 'pro-bundle-deal', }); console.log(shortLink.short_url); // https://dodo.link/pro-bundle-deal
For country or currency-specific pricing, use the localized-pricing skill. Do not hardcode prices in the client.
Expecting a delete endpoint: Products don't have a delete method. Archive them instead. Archived products remain in your history for reconciliation.
Trying to change the pricing model: Once a product is created with one_time_price, you can't change it to recurring_price. Create a new product and migrate customers to it.
Hardcoding prices in the client: Always fetch the product catalog from the API. Prices change, and your client code will become stale. Read client.products.retrieve(id) to get the current price.
Forgetting the tax category: Every product requires a tax_category. Dodo uses this to calculate and remit sales tax. Omitting it will cause the create request to fail.
Uploading images after the 60-second window: Request the presigned URL and upload immediately. If the URL expires, request a new one.
Passing a non-empty product_cart for collection checkout: Collection checkout requires product_cart: []. The collection itself defines what's in the cart.
Confusing purchasing_power_parity with localized pricing: The OpenAPI schema includes a purchasing_power_parity field in examples, but it's marked unavailable. Use the localized-pricing skill for country and currency pricing instead.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | fail→pass | 16,808 | 10,178 | -39% | 1 | 1 | 0% | 1,831 | 3,745 | +105% | 0 | 0 | — |
case-21 | fail→pass | 18,352 | 10,178 | -45% | 1 | 1 | 0% | 2,989 | 4,464 | +49% | 0 | 0 | — |
case-22 | fail→pass | 20,544 | 9,492 | -54% | 1 | 1 | 0% | 2,138 | 3,510 | +64% | 0 | 0 | — |
case-01 | fail→pass | 17,521 | 13,926 | -21% | 1 | 1 | 0% | 1,852 | 4,036 | +118% | 0 | 0 | — |
case-02 | fail→pass | 10,960 | 13,017 | +19% | 1 | 1 | 0% | 2,183 | 3,863 | +77% | 0 | 0 | — |
case-03 | fail→pass | 22,994 | 12,092 | -47% | 1 | 1 | 0% | 3,710 | 4,090 | +10% | 0 | 0 | — |
case-04 | pass→pass | 16,702 | 15,026 | -10% | 1 | 1 | 0% | 2,124 | 4,674 | +120% | 0 | 0 | — |
case-05 | fail→fail | 20,475 | 19,380 | -5% | 1 | 1 | 0% | 2,890 | 4,803 | +66% | 0 | 0 | — |
case-06 | pass→pass | 10,193 | 13,053 | +28% | 1 | 1 | 0% | 1,857 | 4,047 | +118% | 0 | 0 | — |
case-08 | fail→pass | 34,183 | 6,157 | -82% | 1 | 1 | 0% | 5,534 | 3,962 | -28% | 0 | 0 | — |
case-09 | pass→pass | 17,667 | 6,585 | -63% | 1 | 1 | 0% | 1,884 | 4,068 | +116% | 0 | 0 | — |
case-10 | fail→pass | 10,222 | 5,797 | -43% | 1 | 1 | 0% | 1,697 | 3,510 | +107% | 0 | 0 | — |
case-11 | fail→pass | 9,956 | 2,995 | -70% | 1 | 1 | 0% | 871 | 3,021 | +247% | 0 | 0 | — |
case-12 | fail→pass | 20,751 | 10,455 | -50% | 1 | 1 | 0% | 2,521 | 3,618 | +44% | 0 | 0 | — |
case-13 | fail→pass | 20,797 | 11,148 | -46% | 1 | 1 | 0% | 2,085 | 3,813 | +83% | 0 | 0 | — |
case-14 | fail→pass | 12,597 | 12,246 | -3% | 1 | 1 | 0% | 2,376 | 3,758 | +58% | 0 | 0 | — |
case-15 | fail→pass | 11,738 | 3,074 | -74% | 1 | 1 | 0% | 981 | 2,949 | +201% | 0 | 0 | — |
case-16 | pass→pass | 12,011 | 9,341 | -22% | 1 | 1 | 0% | 1,403 | 3,588 | +156% | 0 | 0 | — |
case-23 | fail→pass | 12,051 | 7,467 | -38% | 1 | 1 | 0% | 1,313 | 4,180 | +218% | 0 | 0 | — |
case-17 | fail→pass | 11,619 | 12,625 | +9% | 1 | 1 | 0% | 1,335 | 3,761 | +182% | 0 | 0 | — |
case-18 | fail→pass | 15,253 | 9,044 | -41% | 1 | 1 | 0% | 2,015 | 3,220 | +60% | 0 | 0 | — |
case-19 | fail→pass | 7,068 | 3,432 | -51% | 1 | 1 | 0% | 1,338 | 3,318 | +148% | 0 | 0 | — |
case-20 | fail→pass | 7,782 | 10,012 | +29% | 1 | 1 | 0% | 1,333 | 3,403 | +155% | 0 | 0 | — |
case-24 | pass→pass | 12,503 | 6,354 | -49% | 1 | 1 | 0% | 1,317 | 3,985 | +203% | 0 | 0 | — |
case-25 | pass→pass | 14,849 | 9,224 | -38% | 1 | 1 | 0% | 1,344 | 3,212 | +139% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +72 percentage points is the difference between those two pass rates over the 25 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.