Docs

Add non-catalog products and prices to a transaction

Charge for an item without having to add it to your product catalog by passing price or product attributes when working with a transaction.

Product area

  • Transactions

Tooling

  • Platform

Released

December 19, 2023

Status

Released

API version

Version 1

What's new?

We updated the Paddle platform so that now you can create transactions for products and prices that aren't in your catalog.

When creating or updating a transaction, you can now pass price and product attributes directly to a transaction, as well as passing existing price IDs.

How it works

As well as creating transactions for items in your product catalog, you can create transactions for non-catalog items by passing a price and (optionally) a product object directly to a transaction when creating or updating.

Adding non-catalog items to a transaction is great for one-off or bespoke items that are specific to that transaction. They let you manage your product catalog outside of Paddle. When adding a custom item to a transaction, you can:

  • Pass a price object for an existing product to charge for a custom price for a catalog product
  • Pass a price object and a product object to charge for a custom price for a custom product

This means you have the flexibility to manage your catalog in the way that best suits the way that you work. For example:

  • If you work with third-party app stores, you can manage your product catalog centrally and pass custom prices and products to transactions.
  • When invoicing enterprise customers, you can agree custom prices for existing products when negotiating.
  • You can use Paddle to bill for large product catalogs where prices may change daily, like eBook resellers or customers who may show personalized prices to different users.

Examples

Create a transaction for a one-time item

This example creates a draft transaction for a one-time non-catalog item. It's for a custom product.

If successful, Paddle responds with a copy of the new transaction entity.

The created transaction is draft. You can pass this transaction to a checkout to capture customer and address information, and collect for payment.

POST /transactions
Request
{
"items": [
{
"quantity": 1,
"price": {
"description": "New user price (FTUE)",
"name": "Welcome price",
"unit_price": {
"amount": "999",
"currency_code": "USD"
},
"product": {
"name": "Invigaron Berries Hoard",
"tax_category": "standard",
"description": "Start the game with 20 extra seconds play time!"
}
}
}
],
"currency_code": "USD"
}
Response
{
"data": {
"id": "txn_01hj3s8yt41c6kaqm8rx9zfgtf",
"status": "draft",
"customer_id": null,
"address_id": null,
"business_id": null,
"custom_data": null,
"origin": "api",
"collection_mode": "automatic",
"subscription_id": null,
"invoice_id": null,
"invoice_number": null,
"billing_details": null,
"billing_period": null,
"currency_code": "USD",
"discount_id": null,
"created_at": "2023-12-20T14:15:04.47996325Z",
"updated_at": "2023-12-20T14:15:04.47996325Z",
"billed_at": null,
"items": [
{
"price": {
"id": "pri_01hj3s8yyfhtfvv9z9amcp6m55",
"description": "New user price (FTUE)",
"type": "custom",
"name": "Welcome price",
"product_id": "pro_01hj3s8yw3rpmp1y1n1sy8ty2x",
"billing_cycle": null,
"trial_period": null,
"tax_mode": "account_setting",
"unit_price": {
"amount": "999",
"currency_code": "USD"
},
"unit_price_overrides": [],
"custom_data": null,
"quantity": {
"minimum": 1,
"maximum": 100
},
"status": "active"
},
"quantity": 1
}
],
"details": {
"tax_rates_used": [
{
"tax_rate": "0",
"totals": {
"subtotal": "999",
"discount": "0",
"tax": "0",
"total": "999"
}
}
],
"totals": {
"subtotal": "999",
"tax": "0",
"discount": "0",
"total": "999",
"grand_total": "999",
"fee": null,
"credit": "0",
"credit_to_balance": "0",
"balance": "999",
"earnings": null,
"currency_code": "USD"
},
"adjusted_totals": {
"subtotal": "999",
"tax": "0",
"total": "999",
"grand_total": "999",
"fee": "0",
"earnings": "0",
"currency_code": "USD"
},
"payout_totals": null,
"adjusted_payout_totals": null,
"line_items": [
{
"id": "txnitm_01hj3s8z0gswx71j2agzgp42zz",
"price_id": "pri_01hj3s8yyfhtfvv9z9amcp6m55",
"quantity": 1,
"totals": {
"subtotal": "999",
"tax": "0",
"discount": "0",
"total": "999"
},
"product": {
"id": "pro_01hj3s8yw3rpmp1y1n1sy8ty2x",
"name": "Invigaron Berries Hoard",
"description": "Start the game with 20 extra seconds play time!",
"type": "custom",
"tax_category": "standard",
"image_url": null,
"custom_data": null,
"status": "active"
},
"tax_rate": "0",
"unit_totals": {
"subtotal": "999",
"tax": "0",
"discount": "0",
"total": "999"
}
}
]
},
"payments": [],
"checkout": {
"url": "https://aeroedit.com/pay?_ptxn=txn_01hj3s8yt41c6kaqm8rx9zfgtf"
}
},
"meta": {
"request_id": "0ebcb490-0a6e-42ad-9d1d-b35d5a772030"
}
}

Create a transaction for a recurring item

This example creates a draft invoice for a 50-user enterprise plan. It's for an existing product, related using the product_id field.

Collection mode is manual, meaning this transaction is an invoice. Once issued, Paddle sends an invoice that must be paid manually.

If successful, Paddle responds with a copy of the new transaction entity.

The created invoice is ready, since it includes all the required fields for it to be issued. Issue it to send it the customer.

POST /transactions
Request
{
"items": [
{
"quantity": 50,
"price": {
"product_id": "pro_01gsz4vmqbjk3x4vvtafffd540",
"description": "Globex annual 2024",
"name": "Annual (per seat) deal for Globex",
"billing_cycle": {
"interval": "year",
"frequency": 1
},
"unit_price": {
"amount": "50000",
"currency_code": "USD"
}
}
}
],
"customer_id": "ctm_01h8441jn5pcwrfhwh78jqt8hk",
"address_id": "add_01h848pep46enq8y372x7maj0p",
"currency_code": "USD",
"collection_mode": "manual",
"billing_details": {
"enable_checkout": false,
"purchase_order_number": "PO-2400",
"payment_terms": {
"interval": "day",
"frequency": 14
},
"additional_information": "Price agreed with Sam. Looking forward to working together in the year ahead!"
},
"billing_period": {
"starts_at": "2024-04-01T00:00:00Z",
"ends_at": "2025-03-31T23:59:00Z"
}
}
Response
{
"data": {
"id": "txn_01hj3ryktw234aj7s0wt5sp69g",
"status": "ready",
"customer_id": "ctm_01h8441jn5pcwrfhwh78jqt8hk",
"address_id": "add_01h848pep46enq8y372x7maj0p",
"business_id": null,
"custom_data": null,
"origin": "api",
"collection_mode": "manual",
"subscription_id": null,
"invoice_id": null,
"invoice_number": null,
"billing_details": {
"enable_checkout": false,
"payment_terms": {
"interval": "day",
"frequency": 14
},
"purchase_order_number": "PO-2400",
"additional_information": "Price agreed with Sam. Looking forward to working together in the year ahead!"
},
"billing_period": null,
"currency_code": "USD",
"discount_id": null,
"created_at": "2023-12-20T14:09:25.78998194Z",
"updated_at": "2023-12-20T14:09:25.78998194Z",
"billed_at": null,
"items": [
{
"price": {
"id": "pri_01hj3rykvg0r83rnpwp5h87wy8",
"description": "Globex annual 2024",
"type": "custom",
"name": "Annual (per seat) renewal deal for Globex",
"product_id": "pro_01gsz4vmqbjk3x4vvtafffd540",
"billing_cycle": {
"interval": "year",
"frequency": 1
},
"trial_period": null,
"tax_mode": "account_setting",
"unit_price": {
"amount": "50000",
"currency_code": "USD"
},
"unit_price_overrides": [],
"custom_data": null,
"quantity": {
"minimum": 1,
"maximum": 100
},
"status": "active"
},
"quantity": 50
}
],
"details": {
"tax_rates_used": [
{
"tax_rate": "0.08875",
"totals": {
"subtotal": "2500000",
"discount": "0",
"tax": "221875",
"total": "2721875"
}
}
],
"totals": {
"subtotal": "2500000",
"tax": "221875",
"discount": "0",
"total": "2721875",
"grand_total": "2721875",
"fee": null,
"credit": "0",
"credit_to_balance": "0",
"balance": "2721875",
"earnings": null,
"currency_code": "USD"
},
"adjusted_totals": {
"subtotal": "2500000",
"tax": "221875",
"total": "2721875",
"grand_total": "2721875",
"fee": "0",
"earnings": "0",
"currency_code": "USD"
},
"payout_totals": null,
"adjusted_payout_totals": null,
"line_items": [
{
"id": "txnitm_01hj3rym8ff2yrkv43vstzfrx5",
"price_id": "pri_01hj3rykvg0r83rnpwp5h87wy8",
"quantity": 50,
"totals": {
"subtotal": "2500000",
"tax": "221875",
"discount": "0",
"total": "2721875"
},
"product": {
"id": "pro_01gsz4vmqbjk3x4vvtafffd540",
"name": "ChatApp Enterprise",
"description": "The ultimate solution for businesses that require top-of-the-line features and customizations. Includes all the features of the Pro plan, plus personalized onboarding, dedicated account management, and the ability to pay via invoice.",
"type": "standard",
"tax_category": "standard",
"image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png",
"custom_data": null,
"status": "active"
},
"tax_rate": "0.08875",
"unit_totals": {
"subtotal": "50000",
"tax": "4437",
"discount": "0",
"total": "54437"
}
}
]
},
"payments": [],
"checkout": {
"url": null
}
},
"meta": {
"request_id": "ade613b5-d62e-4f0a-93cb-a64516598fee"
}
}

Next steps

This change is available in version 1 of the Paddle API.

It's a non-breaking change, meaning it doesn't impact existing integrations. You can continue creating transactions by passing price IDs for items in your product catalog. We recommend this for customers who sell a set of digital products at the same price points.

You can create or update transactions using the API to start charging for non-catalog items.

This is part of a set of changes around non-catalog items. In future updates, we'll release functionality to let you bill one-time non-catalog items to a subscription, then work with recurring non-catalog items on a subscription. These changes are scheduled for Q1 2024.

Summary of changes

Entity Field Change Type
transaction items[].price_id ~ Updated Field
This field is no longer required if you include a price object instead.
transaction items[].price + Added Field
You can send a price object instead of a price_id to bill for a non-catalog item.

Was this page helpful?