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.
- 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:goodsIdandmaxGoodsQuantitymust be provided together.goodsCategoryandmaxCategoryQuantitymust be provided together.- Only one of those two groups may appear in a single create call.
Why do I get a parameter error mentioning my merchant ID?
Because themerchantId 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
merchantRequestIdper intended rule, not per attempt. If you generate a fresh one on retry, you will end up with two rules. - Do not reuse a
merchantRequestIdfor a different rule. Reusing an idempotency key with different contents is whatA0011Idempotent param mismatch error is for.
How do I change or stop a rule?
Callupdate 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?
Callinquiry 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
goodsIdhas 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.
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
startTimein the past is in force for the next order. - A rule created with a future
startTimedoes 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.
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 withD0001
(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.
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?
- Order Creation Field Guide — the goods fields you pass on order creation.
- Limits, Currency & Configuration Errors — the amount-based ceilings and which code means which.
- Confirming Payment Results — how a declined payment is reported to you.
ruleId.