> ## 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.

# Binding and unbinding

> Bind velocity rules to cards or employees: batch bind/unbind with partial-success semantics, batch-level and per-item errors, and binding queries in both directions.

## 📄 Guide

Rules and targets (card `CARD` / employee `CUSTOMER`) are bound many-to-many: a rule constrains nothing after creation and only takes effect once bound. Both bind and unbind are batch endpoints that address **one rule and a batch of subjects**: rule-level problems (missing / not ACTIVE / not owned by the company) **reject the whole batch**, while subject-level problems are **item-independent with partial success** — the response splits results into `successTargets` and `failTargets`, with per-item reasons in `failTargets[].errorCode`. Rule creation is covered in [Creating velocity rules](./creating-rules); the full code set is in the [Error code dictionary](../customer-success/error-codes).

<Note>
  Besides the `bind` endpoint on this page, rules can also be bound at issue time through the `ruleIds` field of the card application (no more than 5) — see [Applying for virtual cards](./applying-virtual-cards). Whichever path you take, **a single card or a single employee carries at most 5 rules**.
</Note>

## Batch bind

**`POST /open-api-corp/velocity/v1/bind`**

### Request parameters

| Field                   | Type   | Required | Description                                                                               |
| ----------------------- | ------ | -------- | ----------------------------------------------------------------------------------------- |
| `organizationId`        | String | Yes      | ≤36; the company that owns the rule and the subjects                                      |
| `ruleId`                | String | Yes      | ≤32; the rule to bind (one request addresses a single rule)                               |
| `targets`               | Array  | Yes      | non-empty; the subjects to bind                                                           |
| `targets[].subjectType` | Enum   | Yes      | `CARD` / `CUSTOMER`; any other value rejects the whole batch (`SUBJECT_TYPE_NOT_ALLOWED`) |
| `targets[].subjectId`   | String | Yes      | ≤36; `cardId` (numeric string) for `CARD` / `customerId` for `CUSTOMER`                   |

### Response data

| Field                           | Type   | Description                                                                                |
| ------------------------------- | ------ | ------------------------------------------------------------------------------------------ |
| `successTargets`                | Array  | Successful entries                                                                         |
| `successTargets[].subjectType`  | Enum   | `CARD` / `CUSTOMER`                                                                        |
| `successTargets[].subjectId`    | String | The subject's identifier                                                                   |
| `successTargets[].errorCode`    | String | Always `null` for successful entries                                                       |
| `successTargets[].errorMessage` | String | Always `null` for successful entries                                                       |
| `failTargets`                   | Array  | Failed entries, same shape; `errorCode` / `errorMessage` carry the per-item failure reason |

> The endpoint returns no `successCount` / `failCount`: both equal the array lengths, and a second source of truth is avoided on purpose.

Per-item `errorCode` values for bind:

| errorCode                      | Description                                                                                                                           |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `SUBJECT_INVALID`              | Subject missing, not ACTIVE, or not under the given company                                                                           |
| `CARD_RULE_DUPLICATE`          | The subject is already bound to this rule (no silent dedup)                                                                           |
| `CARD_RULE_CURRENCY_NOT_MATCH` | No overlap between the rule's currencies and the card's chargeable currencies (chargeable = opened VA currencies ∪ card BIN currency) |
| `CARD_RULE_LIMIT_EXCEEDED`     | The subject already has the maximum number of rules (5)                                                                               |

### Request example

```json theme={null}
{
  "organizationId": "e7c9a1f0-3b52-4d8e-9a67-1f2b3c4d5e6f",
  "ruleId": "5136744097353943556",
  "targets": [
    { "subjectType": "CARD",     "subjectId": "5136759791164443137" },
    { "subjectType": "CUSTOMER", "subjectId": "a3f8d2b1-6c94-4e07-b512-9d8e7f6a5b4c" },
    { "subjectType": "CARD",     "subjectId": "5136765336134994434" }
  ]
}
```

### Response example

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": null,
  "data": {
    "successTargets": [
      { "subjectType": "CARD",     "subjectId": "5136759791164443137",
        "errorCode": null, "errorMessage": null },
      { "subjectType": "CARD",     "subjectId": "5136765336134994434",
        "errorCode": null, "errorMessage": null }
    ],
    "failTargets": [
      { "subjectType": "CUSTOMER", "subjectId": "a3f8d2b1-6c94-4e07-b512-9d8e7f6a5b4c",
        "errorCode": "SUBJECT_INVALID",
        "errorMessage": "target not found or not active" }
    ]
  }
}
```

### Batch-level error codes

| Error code                     | Description                                                                 |
| ------------------------------ | --------------------------------------------------------------------------- |
| `ORGANIZATION_INVALID`         | Company missing, not ACTIVE, or not owned by this partner                   |
| `VELOCITY_RULE_NOT_FOUND`      | Rule missing, or not owned by the given company — whole batch rejected      |
| `VELOCITY_RULE_STATUS_INVALID` | Rule not ACTIVE, cannot be bound — whole batch rejected                     |
| `SUBJECT_TYPE_NOT_ALLOWED`     | Any entry's `subjectType` is not `CARD` / `CUSTOMER` (whole batch rejected) |

## Batch unbind

**`POST /open-api-corp/velocity/v1/unbind`**

Same shape as bind: one request addresses a single rule, items are processed independently, and one failure does not affect the rest.

### Request parameters

| Field                   | Type   | Required | Description                                                             |
| ----------------------- | ------ | -------- | ----------------------------------------------------------------------- |
| `organizationId`        | String | Yes      | ≤36; the company that owns the rule and the subjects                    |
| `ruleId`                | String | Yes      | ≤32; the rule to unbind                                                 |
| `targets`               | Array  | Yes      | non-empty; the subjects to unbind                                       |
| `targets[].subjectType` | Enum   | Yes      | `CARD` / `CUSTOMER`                                                     |
| `targets[].subjectId`   | String | Yes      | ≤36; `cardId` (numeric string) for `CARD` / `customerId` for `CUSTOMER` |

The response has the same shape as bind (`successTargets` / `failTargets`); per-item `errorCode` values for unbind:

| errorCode                    | Description                                                 |
| ---------------------------- | ----------------------------------------------------------- |
| `SUBJECT_INVALID`            | Subject missing, not ACTIVE, or not under the given company |
| `VELOCITY_BINDING_NOT_FOUND` | The subject is not bound to this rule                       |

### Request example

```json theme={null}
{
  "organizationId": "e7c9a1f0-3b52-4d8e-9a67-1f2b3c4d5e6f",
  "ruleId": "5136744097353943556",
  "targets": [
    { "subjectType": "CARD",     "subjectId": "5136759791164443137" },
    { "subjectType": "CUSTOMER", "subjectId": "a3f8d2b1-6c94-4e07-b512-9d8e7f6a5b4c" },
    { "subjectType": "CARD",     "subjectId": "5136765336134994434" }
  ]
}
```

### Response example

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": null,
  "data": {
    "successTargets": [
      { "subjectType": "CUSTOMER", "subjectId": "a3f8d2b1-6c94-4e07-b512-9d8e7f6a5b4c",
        "errorCode": null, "errorMessage": null },
      { "subjectType": "CARD", "subjectId": "5136765336134994434",
        "errorCode": null, "errorMessage": null }
    ],
    "failTargets": [
      { "subjectType": "CARD", "subjectId": "5136759791164443137",
        "errorCode": "VELOCITY_BINDING_NOT_FOUND",
        "errorMessage": "the subject is not bound to this rule" }
    ]
  }
}
```

### Batch-level error codes

| Error code                 | Description                                                                 |
| -------------------------- | --------------------------------------------------------------------------- |
| `ORGANIZATION_INVALID`     | Company missing, not ACTIVE, or not owned by this partner                   |
| `VELOCITY_RULE_NOT_FOUND`  | Rule missing, or not owned by the given company                             |
| `SUBJECT_TYPE_NOT_ALLOWED` | Any entry's `subjectType` is not `CARD` / `CUSTOMER` (whole batch rejected) |

## Listing a rule's targets

**`GET /open-api-corp/velocity/v1/list-rule-targets`** — lists the cards and employees bound to a rule, paginated, newest binding first.

| Field            | Type    | Required | Description                                                              |
| ---------------- | ------- | -------- | ------------------------------------------------------------------------ |
| `organizationId` | String  | Yes      | ≤20                                                                      |
| `ruleId`         | String  | Yes      | ≤32                                                                      |
| `page`           | Integer | No       | Page number, from 1, default 1                                           |
| `pageSize`       | Integer | No       | Page size, default 20, capped at 100 (values above are truncated to 100) |

The response `data` is a page: `total` / `page` / `pageSize` plus `result[]`, each entry carrying `subjectType` (`CARD` / `CUSTOMER`), `subjectId`, and `modifyTime` (ISO-8601).

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": null,
  "data": {
    "total": 2,
    "page": 1,
    "pageSize": 20,
    "result": [
      { "subjectType": "CUSTOMER", "subjectId": "a3f8d2b1-6c94-4e07-b512-9d8e7f6a5b4c", "modifyTime": "2026-08-12T11:05:00Z" },
      { "subjectType": "CARD",     "subjectId": "5136759791164443137",                  "modifyTime": "2026-08-12T11:00:00Z" }
    ]
  }
}
```

| Error code                | Description                                                                                                                                                                                 |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `VELOCITY_RULE_NOT_FOUND` | Rule does not exist, or does not belong to the requesting `organizationId` (no empty page is returned — an empty page would be indistinguishable from "the rule exists but has no targets") |

<Note>
  Returning an error instead of an **empty page** when the rule does not exist is deliberate: an empty page would be indistinguishable from "the rule exists but has no targets". When you receive an empty `result`, you can be sure the rule exists and simply has nothing bound to it yet.
</Note>

## Listing the rules constraining a target

**`GET /open-api-corp/velocity/v1/list-target-rules`** — lists the rules constraining a card / employee, paginated.

| Field            | Type    | Required | Description                                                                    |
| ---------------- | ------- | -------- | ------------------------------------------------------------------------------ |
| `organizationId` | String  | Yes      | ≤20                                                                            |
| `subjectType`    | Enum    | Yes      | `CARD` / `CUSTOMER`                                                            |
| `subjectId`      | String  | Yes      | ≤36; `cardId` (numeric string) for `CARD` / `customerId` for `CUSTOMER`        |
| `status`         | Boolean | No       | Default `true`: return `ACTIVE` rules only; pass `false` to include `INACTIVE` |
| `page`           | Integer | No       | Page number, from 1, default 1                                                 |
| `pageSize`       | Integer | No       | Page size, default 20, capped at 100 (values above are truncated to 100)       |

The response `data` is a page: `total` (the number of rules bound to this target) / `page` / `pageSize` plus `result[]`, each entry carrying `ruleId`, `ruleName`, `status` (`ACTIVE` / `INACTIVE`), and `modifyTime`.

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": null,
  "data": {
    "total": 2,
    "page": 1,
    "pageSize": 20,
    "result": [
      { "ruleId": "R000457", "ruleName": "Cash advance control",     "status": "ACTIVE", "modifyTime": "2026-08-12T11:10:00Z" },
      { "ruleId": "R000456", "ruleName": "Employee travel", "status": "ACTIVE", "modifyTime": "2026-08-12T11:00:00Z" }
    ]
  }
}
```

| Error code                 | Description                              |
| -------------------------- | ---------------------------------------- |
| `SUBJECT_TYPE_NOT_ALLOWED` | `subjectType` is not `CARD` / `CUSTOMER` |

## Next steps

* Query the merged quota and current usage once targets take effect: [Quota query and adjustment](./quota-and-adjustment)
* How bound rules participate in authorization: [Authorization and 3DS](./authorization-and-3ds)
