> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.thepennyinc.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.thepennyinc.com/_mcp/server.

# Setting Spend Controls

This covers the common pattern: create one spend control, assign it to several cards, then adjust the limit later without touching each card. See [Spend Controls](/spend-controls) for the rule types and [Velocity Rules](/spend-controls/velocity-rules) for the weekly limit shape. Spend control requests go to Penny Banking (`https://sandbox.api.thepennyinc.com`); card and cardholder assignment requests go to Penny Issuing (`https://issuing.sandbox.api.thepennyinc.com`).

## 1. Look up the rule types you need

Check the current schema and an example for each rule type before you build the request body:

**`cURL`**

```bash title="cURL"
curl https://sandbox.api.thepennyinc.com/spend_controls/rule_types \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

**`Python`**

```python title="Python"
import requests

BANKING = "https://sandbox.api.thepennyinc.com"
headers = {"Authorization": f"Bearer {access_token}"}

rule_types = requests.get(f"{BANKING}/spend_controls/rule_types", headers=headers).json()
```

**`JavaScript`**

```javascript title="JavaScript"
const BANKING = "https://sandbox.api.thepennyinc.com";
const headers = { Authorization: `Bearer ${accessToken}` };

const ruleTypes = await fetch(`${BANKING}/spend_controls/rule_types`, { headers }).then((res) =>
  res.json()
);
```

## 2. Create a spend control with more than one rule

This example limits spend to \$500 per transaction and \$2,000 per calendar week (Monday 00:00 UTC to the following Monday):

**`cURL`**

```bash title="cURL"
curl -X POST https://sandbox.api.thepennyinc.com/spend_controls/ \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rule_configs": [
      {
        "rule_type": "transaction_amount",
        "applicable_transaction_authorizations": ["debit"],
        "applicable_card_issuance_modes": ["managed"],
        "configuration": {"USD": {"maximum": "500.00"}}
      },
      {
        "rule_type": "velocity",
        "applicable_transaction_authorizations": ["debit"],
        "applicable_card_issuance_modes": ["managed"],
        "configuration": {"velocity_controls": {"USD": {"week": {"amount_limit": "2000.00"}}}}
      }
    ]
  }'
```

**`Python`**

```python title="Python"
spend_control = requests.post(
    f"{BANKING}/spend_controls/",
    headers=headers,
    json={
        "rule_configs": [
            {
                "rule_type": "transaction_amount",
                "applicable_transaction_authorizations": ["debit"],
                "applicable_card_issuance_modes": ["managed"],
                "configuration": {"USD": {"maximum": "500.00"}},
            },
            {
                "rule_type": "velocity",
                "applicable_transaction_authorizations": ["debit"],
                "applicable_card_issuance_modes": ["managed"],
                "configuration": {"velocity_controls": {"USD": {"week": {"amount_limit": "2000.00"}}}},
            },
        ]
    },
).json()
spend_control_id = spend_control["spend_control_id"]
```

**`JavaScript`**

```javascript title="JavaScript"
const spendControl = await fetch(`${BANKING}/spend_controls/`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    rule_configs: [
      {
        rule_type: "transaction_amount",
        applicable_transaction_authorizations: ["debit"],
        applicable_card_issuance_modes: ["managed"],
        configuration: { USD: { maximum: "500.00" } },
      },
      {
        rule_type: "velocity",
        applicable_transaction_authorizations: ["debit"],
        applicable_card_issuance_modes: ["managed"],
        configuration: { velocity_controls: { USD: { week: { amount_limit: "2000.00" } } } },
      },
    ],
  }),
}).then((res) => res.json());
const spendControlId = spendControl.spend_control_id;
```

## 3. Assign it to multiple cards

**`cURL`**

```bash title="cURL"
for CARD_ID in card_019375de-a2a0-7f31-884a-a2a0f3184abd card_01937b28-f048-77de-8ef4-f0487deef42d card_01937c93-1d95-76f4-8400-1d956f44007c; do
  curl -X PUT "https://issuing.sandbox.api.thepennyinc.com/cards/$CARD_ID/spend_control" \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"spend_control_id\": \"$SPEND_CONTROL_ID\"}"
done
```

**`Python`**

```python title="Python"
for card_id in ["card_019375de-a2a0-7f31-884a-a2a0f3184abd", "card_01937b28-f048-77de-8ef4-f0487deef42d", "card_01937c93-1d95-76f4-8400-1d956f44007c"]:
    requests.put(
        f"https://issuing.sandbox.api.thepennyinc.com/cards/{card_id}/spend_control",
        headers=headers,
        json={"spend_control_id": spend_control_id},
    )
```

**`JavaScript`**

```javascript title="JavaScript"
for (const cardId of ["card_019375de-a2a0-7f31-884a-a2a0f3184abd", "card_01937b28-f048-77de-8ef4-f0487deef42d", "card_01937c93-1d95-76f4-8400-1d956f44007c"]) {
  await fetch(`https://issuing.sandbox.api.thepennyinc.com/cards/${cardId}/spend_control`, {
    method: "PUT",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({ spend_control_id: spendControlId }),
  });
}
```

The three cards share one set of rules and one weekly budget: together they can spend \$2,000 per week, and raising the weekly cap raises it for all three at once. To give each card its own weekly budget, create a separate spend control for each card.

To confirm the assignments, list the objects the control is assigned to:

**`cURL`**

```bash title="cURL"
curl "https://sandbox.api.thepennyinc.com/spend_controls/$SPEND_CONTROL_ID/associations" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

Each item in `items` has `spend_control_id`, `associated_object_id`, and `associated_object_type` (`card` here).

## 4. Update the limit later

`PATCH` replaces each rule you send with the same `rule_type` and leaves the other rules unchanged, so this raises the weekly cap and keeps the \$500 transaction limit.

**`cURL`**

```bash title="cURL"
curl -X PATCH "https://sandbox.api.thepennyinc.com/spend_controls/$SPEND_CONTROL_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rule_configs": [{
      "rule_type": "velocity",
      "applicable_transaction_authorizations": ["debit"],
      "applicable_card_issuance_modes": ["managed"],
      "configuration": {"velocity_controls": {"USD": {"week": {"amount_limit": "3000.00"}}}}
    }]
  }'
```

**`Python`**

```python title="Python"
requests.patch(
    f"{BANKING}/spend_controls/{spend_control_id}",
    headers=headers,
    json={
        "rule_configs": [
            {
                "rule_type": "velocity",
                "applicable_transaction_authorizations": ["debit"],
                "applicable_card_issuance_modes": ["managed"],
                "configuration": {"velocity_controls": {"USD": {"week": {"amount_limit": "3000.00"}}}},
            }
        ]
    },
).json()
```

**`JavaScript`**

```javascript title="JavaScript"
await fetch(`${BANKING}/spend_controls/${spendControlId}`, {
  method: "PATCH",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    rule_configs: [
      {
        rule_type: "velocity",
        applicable_transaction_authorizations: ["debit"],
        applicable_card_issuance_modes: ["managed"],
        configuration: { velocity_controls: { USD: { week: { amount_limit: "3000.00" } } } },
      },
    ],
  }),
}).then((res) => res.json());
```

## 5. Remove one rule, or the whole control

**`cURL`**

```bash title="cURL"
# Drop the velocity rule, keep transaction_amount
curl -X DELETE "https://sandbox.api.thepennyinc.com/spend_controls/$SPEND_CONTROL_ID/rules/velocity" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Unassign from a single card (the control itself still exists)
curl -X DELETE "https://issuing.sandbox.api.thepennyinc.com/cards/$CARD_ID/spend_control" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

**`Python`**

```python title="Python"
# Drop the velocity rule, keep transaction_amount
requests.delete(f"{BANKING}/spend_controls/{spend_control_id}/rules/velocity", headers=headers)

# Unassign from a single card (the control itself still exists)
requests.delete(
    f"https://issuing.sandbox.api.thepennyinc.com/cards/{card_id}/spend_control",
    headers=headers,
)
```

**`JavaScript`**

```javascript title="JavaScript"
// Drop the velocity rule, keep transaction_amount
await fetch(`${BANKING}/spend_controls/${spendControlId}/rules/velocity`, {
  method: "DELETE",
  headers,
});

// Unassign from a single card (the control itself still exists)
await fetch(`https://issuing.sandbox.api.thepennyinc.com/cards/${cardId}/spend_control`, {
  method: "DELETE",
  headers,
});
```

To pause a spend control you might reuse, deactivate it with `PATCH /spend_controls/{spend_control_id}/deactivate` and resume it later with `PATCH /spend_controls/{spend_control_id}/activate`.

`DELETE /spend_controls/{spend_control_id}` removes the control from every card, cardholder, account, and business it is assigned to. The response returns the control with `status: "deleting"`; it then moves to `deleted` and is kept for historical reference.