Describe the bug
Lago's numericality validation is applied inconsistently across entity types. Coupons reject negative amount_cents with 422 (value_is_out_of_range), and add-ons do the same. But plans and taxes have no equivalent check — negative values are stored and flow into billing calculations. The coupon application override path also bypasses the validation that exists on coupon creation.
-
Plans accept negative amounts — amount_cents: -100 returns 200. Subscribed customer would receive credits instead of being charged.
-
Taxes accept negative rates — rate: "-10.0" returns 200. A negative tax rate subtracts from invoice totals. A rate of -100 would zero out every invoice.
-
Coupon application override bypasses validation — A valid 20% coupon applied with percentage_rate: "-50.0" override returns 200. The applied coupon surcharges instead of discounts. Also accepts "200.0" override.
-
Plans accept negative trial periods — trial_period: -30 returns 200. No real-world meaning; could cause undefined behavior in billing period calculations.
-
Coupon percentage rate can be negative at creation — percentage_rate: "-25.0" on a percentage coupon returns 200. Note: amount_cents on the same model correctly rejects negatives.
-
Plans accept negative minimum commitment amounts — minimum_commitment: {amount_cents: -1000} returns 200. Inverts the billing floor logic.
To Reproduce
Environment:
- Lago API (
getlago/api:v1.48.1)
- PostgreSQL 15 with pg_partman
- Redis
- Env vars:
LAGO_DISABLE_SEGMENT=true, LAGO_DISABLE_WALLET_REFRESH=true
Setup:
Organization with API key (via LAGO_CREATE_ORG=true), a customer.
Control — negative coupon amount correctly rejected:
POST /api/v1/coupons
Authorization: Bearer {api_key}
Content-Type: application/json
{
"coupon": {
"name": "Negative Amount Coupon",
"code": "neg-amount-coupon",
"coupon_type": "fixed_amount",
"amount_cents": -500,
"amount_currency": "USD",
"frequency": "once",
"expiration": "no_expiration",
"reusable": true
}
}
Returns 422 with value_is_out_of_range. Same for negative add-on amounts.
Bug 1 — negative plan amount:
POST /api/v1/plans
Authorization: Bearer {api_key}
Content-Type: application/json
{
"plan": {
"name": "Negative Plan",
"code": "neg-plan-001",
"interval": "monthly",
"amount_cents": -100,
"amount_currency": "USD",
"pay_in_advance": false
}
}
Returns 200.
Bug 2 — negative tax rate:
POST /api/v1/taxes
Authorization: Bearer {api_key}
Content-Type: application/json
{
"tax": {
"name": "Negative Tax",
"code": "negative-tax",
"rate": "-10.0"
}
}
Returns 200.
Bug 3 — coupon override bypasses validation:
Create a valid 20% coupon, then apply with negative override:
POST /api/v1/applied_coupons
Authorization: Bearer {api_key}
Content-Type: application/json
{
"applied_coupon": {
"external_customer_id": "coupon-cust-001",
"coupon_code": "override-pct-coupon",
"percentage_rate": "-50.0"
}
}
Returns 200. Also accepts "200.0".
Bug 4 — negative trial period:
POST /api/v1/plans
Authorization: Bearer {api_key}
Content-Type: application/json
{
"plan": {
"name": "Negative Trial Plan",
"code": "neg-trial-plan",
"interval": "monthly",
"amount_cents": 5000,
"amount_currency": "USD",
"pay_in_advance": false,
"trial_period": -30
}
}
Returns 200.
Bug 5 — negative coupon percentage at creation:
POST /api/v1/coupons
Authorization: Bearer {api_key}
Content-Type: application/json
{
"coupon": {
"name": "Negative Pct Coupon",
"code": "neg-pct-coupon",
"coupon_type": "percentage",
"percentage_rate": "-25.0",
"frequency": "once",
"expiration": "no_expiration",
"reusable": true
}
}
Returns 200. Note: amount_cents on the same model correctly rejects negatives — only percentage_rate is missing the check.
Bug 6 — negative minimum commitment:
POST /api/v1/plans
Authorization: Bearer {api_key}
Content-Type: application/json
{
"plan": {
"name": "Neg Commitment Plan",
"code": "neg-commit-plan",
"interval": "monthly",
"amount_cents": 5000,
"amount_currency": "USD",
"pay_in_advance": false,
"minimum_commitment": {
"amount_cents": -1000,
"invoice_display_name": "Negative Minimum"
}
}
}
Returns 200.
Reproducing with Dokkimi:
Dokkimi is an open source testing tool that stands up isolated Docker environments from declarative YAML definitions — services, databases, seed data, test steps, and assertions all in one file. The definition for this bug is in dokkimi/dokkimi-in-the-wild/.dokkimi/lago — dokkimi run reproduces it from scratch.
Expected behavior
All financial fields should have consistent numericality validation:
- Plans should reject negative
amount_cents (like coupons and add-ons already do)
- Tax rates should reject negative values
- Coupon application overrides should validate
percentage_rate the same way coupon creation validates amount_cents
- Trial periods should reject negative values
- Coupon
percentage_rate should have the same greater_than: 0 check that amount_cents has
- Minimum commitment amounts should reject negative values
Screenshots
N/A
Additional context
- A plan with
amount_cents: -100 generates invoices that credit the customer instead of charging them
- A -10% tax rate reduces invoice totals; -100% zeros them out, bypassing Lago's credit notes system
- A -50% coupon override surcharges the customer with no indication the "discount" is a price increase
- Lago has a dedicated credit notes system for refunds and adjustments, making negative values on these fields unnecessary
Version
Describe the bug
Lago's numericality validation is applied inconsistently across entity types. Coupons reject negative
amount_centswith 422 (value_is_out_of_range), and add-ons do the same. But plans and taxes have no equivalent check — negative values are stored and flow into billing calculations. The coupon application override path also bypasses the validation that exists on coupon creation.Plans accept negative amounts —
amount_cents: -100returns 200. Subscribed customer would receive credits instead of being charged.Taxes accept negative rates —
rate: "-10.0"returns 200. A negative tax rate subtracts from invoice totals. A rate of-100would zero out every invoice.Coupon application override bypasses validation — A valid 20% coupon applied with
percentage_rate: "-50.0"override returns 200. The applied coupon surcharges instead of discounts. Also accepts"200.0"override.Plans accept negative trial periods —
trial_period: -30returns 200. No real-world meaning; could cause undefined behavior in billing period calculations.Coupon percentage rate can be negative at creation —
percentage_rate: "-25.0"on a percentage coupon returns 200. Note:amount_centson the same model correctly rejects negatives.Plans accept negative minimum commitment amounts —
minimum_commitment: {amount_cents: -1000}returns 200. Inverts the billing floor logic.To Reproduce
Environment:
getlago/api:v1.48.1)LAGO_DISABLE_SEGMENT=true,LAGO_DISABLE_WALLET_REFRESH=trueSetup:
Organization with API key (via
LAGO_CREATE_ORG=true), a customer.Control — negative coupon amount correctly rejected:
Returns 422 with
value_is_out_of_range. Same for negative add-on amounts.Bug 1 — negative plan amount:
Returns 200.
Bug 2 — negative tax rate:
Returns 200.
Bug 3 — coupon override bypasses validation:
Create a valid 20% coupon, then apply with negative override:
Returns 200. Also accepts
"200.0".Bug 4 — negative trial period:
Returns 200.
Bug 5 — negative coupon percentage at creation:
Returns 200. Note:
amount_centson the same model correctly rejects negatives — onlypercentage_rateis missing the check.Bug 6 — negative minimum commitment:
Returns 200.
Reproducing with Dokkimi:
Dokkimi is an open source testing tool that stands up isolated Docker environments from declarative YAML definitions — services, databases, seed data, test steps, and assertions all in one file. The definition for this bug is in dokkimi/dokkimi-in-the-wild/.dokkimi/lago —
dokkimi runreproduces it from scratch.Expected behavior
All financial fields should have consistent numericality validation:
amount_cents(like coupons and add-ons already do)percentage_ratethe same way coupon creation validatesamount_centspercentage_rateshould have the samegreater_than: 0check thatamount_centshasScreenshots
N/A
Additional context
amount_cents: -100generates invoices that credit the customer instead of charging themVersion