> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thedecard.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quota query and adjustment

> Query a card's or employee's merged effective limits and current usage (query-quota quota objects explained), plus rule update, enable/disable, detail and list queries.

## 📄 Guide

A subject may be constrained by several rules at once; the system merges them by taking the strictest value per dimension to produce the "effective limits". `query-quota` returns that merged result together with current usage, and the platform has already computed the remainder for you. Rules themselves are adjusted through `update-rule` (full replacement) and `change-rule-status` (enable/disable), and inspected through `query-rule` and `list-rules`. Rule creation is covered in [Creating velocity rules](./creating-rules); the merge model is introduced in [Limits and statements](../basic-concepts/limits-and-billing).

## Querying effective quota

**`GET /open-api-corp/velocity/v1/query-quota`** — the merged effective limits and current usage for a subject (card / employee).

### Request parameters (query string)

| Field            | Type   | Required | Description                                                          |
| ---------------- | ------ | -------- | -------------------------------------------------------------------- |
| `organizationId` | String | Yes      | ≤36; the organization the subject belongs to                         |
| `subjectType`    | String | Yes      | `CARD` (any card) / `CUSTOMER` (employee)                            |
| `subjectId`      | String | Yes      | ≤36; the subject identifier matching `subjectType`                   |
| `currency`       | String | No       | `USD` / `HKD`; omit to return every currency relevant to the subject |

```
GET /open-api-corp/velocity/v1/query-quota?organizationId=e7c9a1f0-3b52-4d8e-9a67-1f2b3c4d5e6f&subjectType=CARD&subjectId=5185740066240790530&currency=USD
```

### The quota object: the key to reading the response

Every period node in the response (`daily` / `monthly` / `quarterly` / `yearly`) is a "quota object". The amount side and the count side use different field names but map one to one:

| Amount quota object | Count quota object | Description                                                                                                             |
| ------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `limitAmount`       | `limitCount`       | The merged effective cap (min across rules); `null` means uncapped                                                      |
| `usedAmount`        | `usedCount`        | Usage in the current period; still accumulated and returned when the cap is `null`                                      |
| `remainingAmount`   | `remainingCount`   | Cap minus usage; `null` when the cap is `null`; negative means the cap has been exceeded (after a mid-period reduction) |
| `utilizationRate`   | `utilizationRate`  | Utilization percentage, two decimals; `null` when the cap is `null`                                                     |
| `periodKey`         | `periodKey`        | Current period key: day `20260730` / month `202607` / quarter `2026Q3` / year `2026`                                    |
| `sourceRuleId`      | `sourceRuleId`     | The rule the cap came from (the winner after taking the min)                                                            |

<Note>
  **The per-transaction limit is the exception**: the `perTransaction` quota object carries only `limitAmount` and `sourceRuleId` — per-transaction limits do not accumulate, so there is no usage, remainder, utilization rate or period key.
</Note>

### Response data structure

| Field                                       | Type           | Description                                                                                                                                  |
| ------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `subjectType` / `subjectId`                 | String         | Echoes the request                                                                                                                           |
| `asOfTime`                                  | String         | Reference timestamp for the quota query, ISO-8601 UTC                                                                                        |
| `purchase.countLimits`                      | Object         | Purchase count quota (across currencies), four period nodes of count quota objects                                                           |
| `purchase.amountLimits`                     | Array          | Purchase amount quota, one entry per currency                                                                                                |
| `purchase.amountLimits[].currency`          | String         | `USD` / `HKD`, the companion currency for every amount in the group                                                                          |
| `purchase.amountLimits[].effective`         | Boolean        | Whether this currency group actually applies                                                                                                 |
| `purchase.amountLimits[].ineffectiveReason` | String         | Set only when `effective=false`. Current values: `NOT_CARD_SETTLEMENT_CURRENCY` — the currency is not among the card's settlement currencies |
| `purchase.amountLimits[].perTransaction`    | Object         | Per-transaction quota object; only `limitAmount` and `sourceRuleId`                                                                          |
| `purchase.amountLimits[].daily` … `yearly`  | Object         | Daily / monthly / quarterly / yearly cumulative amount quota objects                                                                         |
| `cashAdvance.allowed`                       | Boolean        | Merged cash-advance switch: **false if any rule says false**                                                                                 |
| `cashAdvance.sourceRuleId`                  | String         | When `allowed=false`, names the rule that turned it off                                                                                      |
| `cashAdvance.countLimits` / `amountLimits`  | Object / Array | Cash-advance count and amount quota, same structure as the purchase section                                                                  |
| `appliedRules`                              | Array          | The rules participating in this merge: `ruleId` / `ruleName` / `status`                                                                      |
| `appliedRules[].status`                     | String         | Always `ACTIVE` — `INACTIVE` rules do not participate in the merge and never appear here                                                     |

### Response example

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": null,
  "data": {
    "subjectType": "CARD",
    "subjectId": "5136759791164443137",
    "asOfTime": "2026-08-12T15:30:00Z",
    "purchase": {
      "countLimits": {
        "daily":     { "limitCount": 5,    "usedCount": 2,   "remainingCount": 3,    "utilizationRate": "40.00", "periodKey": "20260812", "sourceRuleId": "5136744097353943556" },
        "monthly":   { "limitCount": 100,  "usedCount": 37,  "remainingCount": 63,   "utilizationRate": "37.00", "periodKey": "202608",   "sourceRuleId": "5136744097353943556" },
        "quarterly": { "limitCount": null, "usedCount": 61,  "remainingCount": null, "utilizationRate": null,    "periodKey": "2026Q3",   "sourceRuleId": null },
        "yearly":    { "limitCount": null, "usedCount": 214, "remainingCount": null, "utilizationRate": null,    "periodKey": "2026",     "sourceRuleId": null }
      },
      "amountLimits": [
        {
          "currency": "USD",
          "effective": true,
          "ineffectiveReason": null,
          "perTransaction": { "limitAmount": "3000", "sourceRuleId": "5136744097353943556" },
          "daily":     { "limitAmount": "10000",  "usedAmount": "2400",  "remainingAmount": "7600",   "utilizationRate": "24.00", "periodKey": "20260812", "sourceRuleId": "5136744097353943556" },
          "monthly":   { "limitAmount": null,     "usedAmount": "18600", "remainingAmount": null,     "utilizationRate": null,    "periodKey": "202608",   "sourceRuleId": null },
          "quarterly": { "limitAmount": "200000", "usedAmount": "45200", "remainingAmount": "154800", "utilizationRate": "22.60", "periodKey": "2026Q3",   "sourceRuleId": "5136744097353943559" },
          "yearly":    { "limitAmount": "600000", "usedAmount": "96100", "remainingAmount": "503900", "utilizationRate": "16.02", "periodKey": "2026",     "sourceRuleId": "5136744097353943556" }
        },
        {
          "currency": "HKD",
          "effective": false,
          "ineffectiveReason": "NOT_CARD_SETTLEMENT_CURRENCY",
          "perTransaction": null,
          "daily": null, "monthly": null, "quarterly": null, "yearly": null
        }
      ]
    },
    "cashAdvance": {
      "allowed": true,
      "sourceRuleId": null,
      "countLimits": {
        "daily":   { "limitCount": null, "usedCount": 0, "remainingCount": null, "utilizationRate": null, "periodKey": "20260812", "sourceRuleId": null },
        "monthly": { "limitCount": null, "usedCount": 1, "remainingCount": null, "utilizationRate": null, "periodKey": "202608",   "sourceRuleId": null }
      },
      "amountLimits": [
        {
          "currency": "USD",
          "effective": true,
          "ineffectiveReason": null,
          "perTransaction": { "limitAmount": "500", "sourceRuleId": "5136744097353943557" },
          "daily":   { "limitAmount": "2000",  "usedAmount": "0",   "remainingAmount": "2000", "utilizationRate": "0.00", "periodKey": "20260812", "sourceRuleId": "5136744097353943557" },
          "monthly": { "limitAmount": "10000", "usedAmount": "500", "remainingAmount": "9500", "utilizationRate": "5.00", "periodKey": "202608",   "sourceRuleId": "5136744097353943557" }
        }
      ]
    },
    "appliedRules": [
      { "ruleId": "5136744097353943556", "ruleName": "Sales team daily cap", "status": "ACTIVE" },
      { "ruleId": "5136744097353943557", "ruleName": "Cash advance control", "status": "ACTIVE" },
      { "ruleId": "5136744097353943559", "ruleName": "Quarterly ceiling",    "status": "ACTIVE" }
    ]
  }
}
```

(The example omits some cash-advance period nodes for brevity; a real response carries all four.)

### Error codes

| Error code                 | Description                                                                                                        |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `SUBJECT_TYPE_NOT_ALLOWED` | The velocity domain accepts only `CARD` / `CUSTOMER`; any other type lands here — a caller bug, fix the code       |
| `SUBJECT_INVALID`          | The type is valid but the subject does not exist, is not `ACTIVE`, or does not belong to the organization supplied |
| `UNSUPPORTED_CURRENCY`     | Unsupported currency; only `USD` / `HKD`                                                                           |

## Updating a rule

**`POST /open-api-corp/velocity/v1/update-rule`** — **full replacement**: any dimension or control group absent from the request is deleted, and the change takes effect on bound subjects **immediately**.

<Warning>
  An update is a full submission, not an incremental merge: omitting `remark` clears it; omitting a control group deletes that group (which means no limit); currency groups absent from `amountLimits` are deleted. At least one of the five control groups must still be present — to disable a whole rule use the enable/disable endpoint, do not clear its control groups. Call `query-rule` first to fetch the complete configuration, modify it, and submit the whole thing.
</Warning>

The request carries the same five control groups as [rule creation](./creating-rules); only the top level differs:

| Field            | Type    | Required | Description                                                          |
| ---------------- | ------- | -------- | -------------------------------------------------------------------- |
| `organizationId` | String  | Yes      | ≤36                                                                  |
| `ruleId`         | String  | Yes      | ≤32                                                                  |
| `ruleVersion`    | Integer | Yes      | Optimistic lock; a mismatch returns `VELOCITY_RULE_VERSION_CONFLICT` |
| `ruleName`       | String  | Yes      | ≤64; rule name (submitted in full)                                   |
| `remark`         | String  | No       | ≤256; rule remark; omitting it clears the value                      |

The response `data` is the complete updated rule object (same structure as the create response) with `ruleVersion` incremented.

```json theme={null}
{
  "ruleId": "5136744097353943556",
  "ruleRef": "rule-20260825-0001",
  "organizationId": "e7c9a1f0-3b52-4d8e-9a67-1f2b3c4d5e6f",
  "ruleName": "Sales team daily cap",
  "remark": "Travel card purchase limits",
  "status": "ACTIVE",
  "ruleVersion": 2,
  "bindingCount": "3",
  "createTime": "2026-08-12T09:00:00Z",
  "modifyTime": "2026-08-13T11:00:00Z",
  "purchaseControl": {
    "amountLimits": [
      { "currency": "USD", "perTransaction": "3000", "daily": "12000", "monthly": "80000" }
    ],
    "countLimits": { "daily": 5, "monthly": 100 }
  },
  "cashAdvanceControl": null,
  "currencyControl": { "filterType": "WHITELIST", "values": ["USD", "HKD"] },
  "mccControl": null,
  "geographicControl": null
}
```

<Note>
  An unconfigured control group comes back as `null`, not an empty object — an empty object would read as "an empty list was configured".
</Note>

On top of the rule-creation error codes, an update can return:

| Error code                       | Description                                                                                                 |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `VELOCITY_RULE_NOT_FOUND`        | The rule does not exist, or does not belong to the `organizationId` supplied / to your partner account      |
| `VELOCITY_RULE_VERSION_CONFLICT` | `ruleVersion` does not match the server; a concurrent modification happened — re-query the detail and retry |
| `VELOCITY_RULE_STATUS_INVALID`   | The rule's current status does not allow modification                                                       |

## Enabling and disabling a rule

**`POST /open-api-corp/velocity/v1/change-rule-status`** — toggles between `ACTIVE` and `INACTIVE`. An `INACTIVE` rule takes no part in transaction checks but **keeps its existing bindings**, so re-enabling restores the constraints without unbinding anything.

| Field            | Type    | Required | Description                          |
| ---------------- | ------- | -------- | ------------------------------------ |
| `organizationId` | String  | Yes      | ≤36                                  |
| `ruleId`         | String  | Yes      | ≤32                                  |
| `status`         | String  | Yes      | Target status: `ACTIVE` / `INACTIVE` |
| `ruleVersion`    | Integer | Yes      | Optimistic lock                      |

The response `data` carries `ruleId`, `ruleName` (so the client can confirm it changed the right rule), `status` (after the change), `ruleVersion` (the new value, required for the next update), `bindingCount` and `modifyTime`.

<Tip>
  Before disabling, use `bindingCount` from the response to warn the customer: this rule currently constrains N subjects, and they will no longer be controlled by it.
</Tip>

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": null,
  "data": {
    "ruleId": "5136744097353943556",
    "ruleName": "Sales team daily cap",
    "status": "INACTIVE",
    "ruleVersion": 3,
    "bindingCount": "3",
    "modifyTime": "2026-08-13T11:00:00Z"
  }
}
```

| Error code                       | Description                                                                                            |
| -------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `VELOCITY_RULE_NOT_FOUND`        | The rule does not exist, or does not belong to the `organizationId` supplied / to your partner account |
| `VELOCITY_RULE_VERSION_CONFLICT` | `ruleVersion` does not match the server; a concurrent modification happened                            |
| `VELOCITY_RULE_STATUS_INVALID`   | The target status is invalid, or identical to the current one                                          |

## Querying rule details and lists

**Detail**: `GET /open-api-corp/velocity/v1/query-rule` — pass `organizationId` + `ruleId` to get the complete rule object (same structure as the create response, including `ruleVersion`, `bindingCount` and the five control groups). If the rule does not exist or does not belong to the `organizationId` supplied, the call returns `VELOCITY_RULE_NOT_FOUND`.

**List**: `GET /open-api-corp/velocity/v1/list-rules` — paged query of the organization's rules, summaries only.

| Field            | Type    | Required | Description                                        |
| ---------------- | ------- | -------- | -------------------------------------------------- |
| `organizationId` | String  | Yes      | ≤36                                                |
| `status`         | String  | No       | `ACTIVE` / `INACTIVE`; omit to return every status |
| `page`           | Integer | No       | Page number, starting at 1, default 1              |
| `pageSize`       | Integer | No       | Page size, default 20, maximum 100                 |

The response `data` is the standard paging envelope: `page` / `pageSize` / `total` + `result[]`, each entry carrying `ruleId`, `ruleName`, `status`, `bindingCount` and `modifyTime`.

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": null,
  "data": {
    "page": 1,
    "pageSize": 20,
    "total": 2,
    "result": [
      { "ruleId": "5136744097353943557", "ruleName": "Cash advance control", "status": "ACTIVE", "bindingCount": "1", "modifyTime": "2026-08-13T09:00:00Z" },
      { "ruleId": "5136744097353943556", "ruleName": "Sales team daily cap", "status": "ACTIVE", "bindingCount": "3", "modifyTime": "2026-08-13T09:00:00Z" }
    ]
  }
}
```

<Note>
  `total` is a number while `bindingCount` is a string — the latter is declared as a boxed `Long` and the serializer emits it as a string. Parse them separately.
</Note>

## Next steps

* Adjusting bindings (bind, unbind and the two-way queries): [Binding and unbinding](./binding-and-unbinding)
* How limits participate in the authorization path: [Transaction authorization and 3DS](./authorization-and-3ds)
