Spend Controls

The rules that decide what a card, account, or cardholder is allowed to do.

A Spend Control is a named bundle of rules you can assign to a card, cardholder, account, or your whole business. One spend control can be shared across many objects: assign the same spend_control_id to ten cards, and updating the control updates the limit for all ten at once.

Spend controls are created and managed on Penny Banking (https://sandbox.api.thepennyinc.com). Card and cardholder assignment endpoints are on Penny Issuing (https://issuing.sandbox.api.thepennyinc.com).

Rule types

Every spend control is made of one or more rules. In a request, each rule is a rule_type plus a typed configuration object whose shape depends on that type:

Rule typeControls
transaction_amountMinimum and/or maximum amount per transaction
velocityCount or amount limits per calendar hour, day, week, month, quarter, or year (UTC), or over the lifetime of the rule
merchantAllowed or blocked merchant IDs, merchant category codes, and merchant names
geographicAllowed or blocked merchant countries, subdivisions (states or provinces), and postal codes
currencyAllowed or blocked transaction currencies
processingAllowed or blocked PAN entry modes
live_decisioningA real-time approve or decline decision from your own endpoint

Use the rule guides for configuration shapes and evaluation behavior: Transaction Amount, Velocity, Merchant, Geographic, Currency, Processing, and Live Decisioning. See Missing Transaction Details for how checks behave when a required value is unavailable. Fetch the current schema and an example for every rule type before building a request:

GET /spend_controls/rule_types

Allowlists and denylists

The merchant, geographic, currency, and processing rules use allowlist and denylist arrays. Configure one list per block:

  • Allowlist only: only the listed values pass.
  • Denylist only: every value passes except the listed ones.

A value can’t appear in both lists; the request is rejected. If you set both lists on the same block, the denylist governs: a value passes when it is on the allowlist or absent from the denylist, so the allowlist doesn’t narrow anything further. Merchant category codes (merchant_category_codes_rules) are the exception: when that block has an allowlist, only the allowlist is checked and the denylist is ignored.

Creating a spend control

Each rule must explicitly list its applicable_transaction_authorizations and applicable_card_issuance_modes. See Rule Applicability for the supported values. Each rule_type can appear at most once in a spend control.

POST /spend_controls/
{
"rule_configs": [
{
"rule_type": "transaction_amount",
"applicable_transaction_authorizations": ["debit"],
"applicable_card_issuance_modes": ["managed"],
"configuration": {
"USD": { "minimum": "10.00", "maximum": "500.00" }
}
},
{
"rule_type": "velocity",
"applicable_transaction_authorizations": ["debit"],
"applicable_card_issuance_modes": ["managed"],
"configuration": {
"velocity_controls": {
"USD": { "week": { "usage_limit": 20, "amount_limit": "2000.00" } }
}
}
}
]
}

The spend control response

Responses return the control’s rules in a rules array. Each rule has its own rule_id, version, and version_time, and its configuration fields appear directly on the rule object rather than inside a configuration wrapper. Field names differ by rule type. A transaction_amount rule returns its limits as currency_amount_limits, and a velocity rule returns each window’s current usage and balance:

201 Created
{
"spend_control_id": "spend_control_019379d9-a170-796b-867a-a17096b67a21",
"version": 1,
"version_time": "2026-10-01T14:05:12Z",
"active": true,
"status": "active",
"rules": [
{
"rule_id": "spend_control_rule_019370e9-c1a6-78ad-8fee-c1a68adfee20",
"version": 1,
"version_time": "2026-10-01T14:05:12Z",
"applicable_card_issuance_modes": ["managed"],
"applicable_transaction_authorizations": ["debit"],
"rule_type": "transaction_amount",
"currency_amount_limits": {
"USD": { "minimum": "10.00", "maximum": "500.00" }
}
},
{
"rule_id": "spend_control_rule_019370ea-2b41-7c1e-9d3a-2b417c1e9d3a",
"version": 1,
"version_time": "2026-10-01T14:05:12Z",
"applicable_card_issuance_modes": ["managed"],
"applicable_transaction_authorizations": ["debit"],
"rule_type": "velocity",
"velocity_controls": {
"USD": {
"week": {
"usage": {
"count": 0,
"count_limit": 20,
"limit_window": "week",
"last_reset_time": "2026-09-28T00:00:00Z"
},
"balance": {
"currency": "USD",
"available_balance": "2000.00",
"amount_limit": "2000.00",
"limit_window": "week",
"last_reset_time": "2026-09-28T00:00:00Z"
}
}
}
}
}
],
"business_id": "business_01937b95-484e-75af-8c68-484e5afc68d3"
}

status is one of active, deactivated, deleting, or deleted. Read-back field names for the other rule types:

Rule typeResponse fields
merchantmerchant_id_rules, merchant_category_codes_rules, merchant_name_rules
geographiccountry_rules, subdivision_rules, zip_code_rules
currencycurrency_rules
processingpan_entry_mode_rules
live_decisioningnotification_endpoint_id, fallback_result

Managing a spend control

All of these endpoints are on Penny Banking:

ActionEndpoint
CreatePOST /spend_controls/
ListGET /spend_controls/
RetrieveGET /spend_controls/{spend_control_id}
Add or replace rulesPATCH /spend_controls/{spend_control_id}
Remove one ruleDELETE /spend_controls/{spend_control_id}/rules/{rule_type}
Pause enforcementPATCH /spend_controls/{spend_control_id}/deactivate
Resume enforcementPATCH /spend_controls/{spend_control_id}/activate
DeleteDELETE /spend_controls/{spend_control_id}

PATCH /spend_controls/{spend_control_id} replaces each rule you send with the same rule_type and leaves the other rules unchanged.

Deactivating a control pauses enforcement of its rules; resume enforcement with activate. Deleting a control removes it from every object it is assigned to. The delete response returns the control with status: "deleting" while removal is in progress; it then moves to deleted and is kept for historical reference.

Assigning it

A spend control applies once it is assigned to the object you want it to govern. Each object has at most one spend control; assigning another replaces it.

Attach toEndpointAPI
CardPUT /cards/{card_id}/spend_controlPenny Issuing
CardholderPUT /cardholders/{cardholder_id}/spend_controlPenny Issuing
Capital AccountPUT /accounts/capital/{account_id}/spend_controlPenny Banking
Control AccountPUT /accounts/control/{account_id}/spend_controlPenny Banking
BusinessPUT /businesses/spend_controlPenny Banking

Each of these accepts a body of { "spend_control_id": "spend_control_019379d9-a170-796b-867a-a17096b67a21" }. Remove an assignment with the matching DELETE on the same path.

Associations

The associations endpoints on Penny Banking let you look up assignments from either side:

ActionEndpoint
List the objects a control is assigned toGET /spend_controls/{spend_control_id}/associations
Get the control assigned to an objectGET /spend_controls/associations/{associated_object_id}
Remove the control assigned to an objectDELETE /spend_controls/associations/{associated_object_id}

The list endpoint is paginated with page_size and next_page_token. Each item has spend_control_id, associated_object_id, and associated_object_type (business, business_entity, program, account, cardholder, or card). GET /spend_controls/associations/{associated_object_id} returns the full spend control. The DELETE returns 204 No Content.

Business-wide rules

To apply a rule to every card, cardholder, and account in your business, such as blocking a merchant category everywhere or restricting all spend to a set of countries, attach a spend control to the business itself:

curl -X PUT https://sandbox.api.thepennyinc.com/businesses/spend_control \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"spend_control_id": "spend_control_019379d9-a170-796b-867a-a17096b67a21"}'

A business-wide control is checked on every authorization, alongside any control attached to the card, cardholder, or account involved. Remove it with DELETE /businesses/spend_control.

Where a limit applies

Spend controls can attach at several levels, and more than one can apply to a single transaction, for example a card’s own limit, its cardholder’s limit, and a business-wide rule. Penny checks every attached control, from the most specific level to the broadest:

Card → Cardholder → Control Account (if present) → Capital Account → Program → Business Entity → Business

When a card uses a Control Account, its attached control is checked at the narrower Control Account level, and the funding Capital Account’s control is checked at the broader level. Both apply when both are attached. Without a Control Account, the Capital Account level still applies.

The transaction is authorized only if it passes every rule in every applicable control. No level overrides another, so when two attached controls apply the same rule_type to the same transaction, such as a transaction_amount limit on both the card and its cardholder, each is enforced and the most restrictive one determines the outcome. A $500 per-transaction limit on a card and a $200 limit on its cardholder together decline anything over $200.

live_decisioning is the exception: it runs once per authorization, at the most specific level where a live_decisioning rule is attached. A live_decisioning rule at a broader level is skipped when a more specific level already has one. See Live Decisioning.

See Setting Spend Controls for a worked example of building and assigning one.