Skip to main content
This capability has to be switched on for your account before the endpoints will answer. If a call comes back with a permission error, contact your Waffo integration contact rather than debugging your request.
If you sell digital goods, game currency, event tickets or anything else that resellers like to buy in bulk, you can cap how many units one payment instrument may buy — by product, or by product category. Waffo enforces the cap at payment time and declines the payment when it is exceeded. This page covers the three signed endpoints that let you manage those rules from your own systems. It is not about amount ceilings: for the minimum/maximum order amount on a payment method, and for the per-account single-transaction and daily amount limits, see Limits, Currency & Configuration Errors. Signing, keys and headers work exactly as they do for every other API — see Authentication & Signature.

What exactly can I limit, and what happens when a rule is hit?

You register a maximum quantity per payment instrument for either:
  • one specific product — you supply the product identifier and the maximum quantity allowed for it, or
  • one product category — you supply the category and the maximum quantity allowed across everything in it.
From then on, when you create an order you pass the goods information and the quantity being bought. If the order would push that instrument past the registered maximum, the payment is declined — the shopper cannot complete it. The order does not silently succeed with a reduced quantity, and there is no partial acceptance. Two consequences worth designing for:
  • The rule is a hard decline, not a review queue. If you want a softer outcome (a warning, a manual review, a per-customer allowance), you have to build that on your side before calling order creation.
  • A declined attempt is a normal risk decline on the order. Show the shopper a neutral message and do not retry the same order automatically — retrying will hit the same rule.

Which endpoints exist, and what can each one do?

All three are signed endpoints on the same host, with the same headers and the same signing algorithm as order creation. They sit in the payin scope of an API key, so a key issued for withdrawals or payouts alone cannot call them.

What do I send on create?

Both maximum-quantity values must be 1 or greater. A maximum of zero is not a way to block a product outright — if you want a product unavailable, do not offer it.

Can I set a product limit and a category limit in the same request?

No. The two groups are mutually exclusive per request:
  • goodsId and maxGoodsQuantity must be provided together.
  • goodsCategory and maxCategoryQuantity must be provided together.
  • Only one of those two groups may appear in a single create call.
If you want both a per-product cap and a per-category cap, create two rules.

Why do I get a parameter error mentioning my merchant ID?

Because the merchantId in the body did not match the merchant ID that your API key is issued for. All three endpoints assert that the two agree before doing anything else, and reject the call as A0003 Parameter validation failed with the detail mid in params is inconsistent with apiKey. The same check applies to subMerchantId: if you send one, it must exist and belong to the merchant ID in the request. Otherwise you get A0003 with the detail subMerchantId is invalid or does not belong to the provided merchantId. This is the single most common integration mistake here, and it is almost always one of two things: a key from a different account (sandbox key against production, or a sub-account’s key), or a hardcoded merchant ID left over from a copied sample.

What comes back, and what does ruleStatus mean?

Create and update both answer with the identifiers plus the rule’s status: Note that ACTIVE describes the rule’s own lifecycle, not whether it is currently in force: a rule with a future startTime is ACTIVE and simply has not started yet.

How does idempotency work if my create call times out?

merchantRequestId is the idempotency key. Resend the identical request with the same merchantRequestId and you get the existing rule back rather than a second one — which is what you want after a timeout, since you cannot tell from a timeout whether the rule was created. Two rules follow from that:
  • Generate one merchantRequestId per intended rule, not per attempt. If you generate a fresh one on retry, you will end up with two rules.
  • Do not reuse a merchantRequestId for a different rule. Reusing an idempotency key with different contents is what A0011 Idempotent param mismatch error is for.

How do I change or stop a rule?

Call update and identify the rule by either merchantRequestId or ruleId — one of the two is required, and ruleId is the safer choice because it is unambiguous. On update, the limit fields are optional and follow the same pairing rule as on create: send maxGoodsQuantity whenever you send goodsId, and maxCategoryQuantity whenever you send goodsCategory. You can also move startTime and endTime. To stop a rule, set ruleStatus to TERMINATED. There is no delete: a terminated rule stays readable through inquiry, which is useful when you need to explain a historical decline.

How do I read a rule back?

Call inquiry with merchantInfo plus either merchantRequestId or ruleId. The answer restates the rule as Waffo holds it — identifiers, ruleStatus, the limit configuration, and the effective window. Use it for three things:
  • Reconciling after a timeout, before deciding whether to create or update.
  • Verifying what is actually live before a sale, rather than trusting your own copy of the configuration.
  • Explaining a decline to your support team by showing the rule that was in force.

Do I have to change anything about my order creation?

Yes — a rule can only bite on information you actually send. Registering a rule does nothing on its own: at order creation you must pass the goods identifier and/or the goods category the rule is registered against, together with the quantity being bought. An order that carries no goods information cannot be matched against a quantity rule. Two practical notes:
  • Use the same identifiers on both sides. The value you register as goodsId has to be the value you send at order creation, character for character. Mismatched casing or a different internal SKU scheme is the usual reason a rule “does not work”.
  • Category rules need a category on the order. If your catalogue does not carry a category concept today, a per-product rule is the cheaper starting point.
Field-level details for the goods fields are in the Order Creation Field Guide.

Can I list all my rules in one call?

No. inquiry is a lookup by identifier — ruleId or merchantRequestId — not a paged list of everything you have registered. So keep your own register of the rules you created, storing the ruleId next to the product or category it covers. That register is what you audit against before a launch: read each rule back through inquiry and compare, rather than assuming your configuration store and Waffo agree.

How quickly does a change take effect?

A rule is evaluated against its own effective window, so:
  • A rule created with a startTime in the past is in force for the next order.
  • A rule created with a future startTime does nothing until then, which is the clean way to arm a cap ahead of a launch or a flash sale.
  • Terminating a rule stops it applying; payments that already succeeded are unaffected.
One consequence people miss: an order you created before arming the rule is still evaluated against it if the shopper pays afterwards. Screening happens when the payment is actually attempted, not when the order was created, so an order sitting on a checkout page when you arm a cap will decline on payment. Only payments that already succeeded are out of reach of a new rule. Do not schedule a rule to start at the exact second a sale opens. Arm it comfortably before, and verify with inquiry that it reads back as you expect.

Which error does the shopper’s payment get when my rule fires?

A risk decline, not a rule-specific code: the payment fails with D0001 (risk rejection) or D0002 (risk transaction limit), HTTP 406, exactly as any other risk decision would. There is no code that says “the merchant’s own rule declined this”. Two things follow:
  • Keep your own record of what you restricted. On order inquiry and on the payment notification the failure is reported under a generic rejection category, so you cannot attribute a decline to your own rule after the fact.
  • Tell the shopper something useful. Since the code does not distinguish a cap you set from any other decline, the wording in your interface has to come from your own knowledge of the rule, not from the response.
See Risk Declines & 3D Secure for how the two codes behave on each surface.

Which errors should I handle explicitly?

These are the codes worth branching on. The general families are documented in Common Errors Troubleshooting. On the payment side, an order that trips a registered rule is declined as a risk rejection, not as a parameter error. Treat it as final for that order.

Does a rule apply to sub-merchants?

A rule is registered against the merchant ID in the request, optionally narrowed to one sub-merchant. If you operate several sub-merchants and want the same cap everywhere, register the rule per sub-merchant — a rule scoped to one sub-merchant does not cover the others, and the endpoints validate that the sub-merchant you name really belongs to you. If you are new to the sub-merchant model, start with Multi-brand & sub-merchant setups.

How is this different from the amount limits I already have?

They are unrelated checks that fail differently, which matters when you are diagnosing a decline: A quantity rule will not stop a single very large order, and an amount limit will not stop a hundred small ones. If bulk-buying is your problem, you need the quantity rule.

Where do I look next?

Still unclear whether a decline came from one of your own rules? Contact support with the order ID and the ruleId.