<!-- x-generated: Generated by the Hyperscale artifact pipeline; do not edit by hand -->

# Card dispute

> Evidence-backed challenge to one posted card transaction.

A card dispute is filed with evidence against one posted transaction, enters review, and ends won or lost. A win returns the immutable disputed amount to the card account; an unresolved case is lost at its response deadline.

- Instrument id: `card_dispute`
- Initial state: `filed`
- Terminal states: `won`, `lost`
- Composed by: [smb_expense_card](../programs/smb_expense_card.md)

## Lifecycle

The diagram draws only the transitions a composing Product exposes as an action. A cell reading "no step any Product exposes" means no drawn edge enters or leaves that state, so the move belongs to a lifecycle step none of the composing Products exposes. Terminal states read "none" under Left by, because stopping there is the declared design rather than a missing step. Where an unexposed step carries a price it is listed under Steps no Product exposes, below.

```mermaid
stateDiagram-v2
    [*] --> filed: create
    filed --> under_review: review
    under_review --> won: win
    won --> [*]
    lost --> [*]
```

| State | Kind | Entered by | Left by |
| --- | --- | --- | --- |
| `filed` | initial | create | review |
| `under_review` | intermediate | review | win |
| `won` | terminal | win | none |
| `lost` | terminal | no step any Product exposes | none |

## Fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `amount` | money<SAR> (minor_unit_integer_string) | yes | Positive minor-unit integer amount serialized as a string (at most 18 digits) |
| `currency` | text | yes | ISO 4217 currency code |
| `reason` | text | yes |  |
| `evidence` | array<object{kind, summary, submittedAt, reference}> | yes |  |
| `responseDueAt` | datetime | yes |  |

## References

- `cardTransactionId`: `card_transaction`, required
- `cardId`: `card`, required
- `cardAccountId`: `account`, required
- `settlementAccountId`: `account`, required

## Actions

### create

Files an evidence-backed challenge against one transaction that is still posted. The disputed amount, currency, card, and account pair are derived from the locked transaction -- never caller-supplied.

- From: none
- To: no state change
- Hints: readOnly=false, destructive=false, idempotent=true, moneyMovement=false

Exposed as:

| Program | Public action | Route |
| --- | --- | --- |
| [smb_expense_card](../programs/smb_expense_card.md) | `fileExpenseDispute` | `POST /v1/operations/card_dispute.fileExpenseDispute`, scopes card_dispute:write, idempotency required, receipt yes |

Input:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `cardTransactionId` | ref<card_transaction> | yes | Posted card transaction challenged by this dispute |
| `reason` | text | yes |  |
| `evidence` | array<object{kind, summary, submittedAt, reference}> | yes |  |
| `responseDueAt` | datetime | yes |  |

Effects:

- reads:
  - `reads.requires_refs` from `requiresRefs`

Errors beyond the 18 shared with every action on this instrument:

| Code | HTTP | Cause | Action | Remediation | Retryable | Idempotency safe |
| --- | --- | --- | --- | --- | --- | --- |
| `account_not_found` | 404 | No Account matches the `accountId` this request referenced (`account not found`). | List Accounts in the same tenant, product, and environment, then retry with the `accountId` that read returns. | Create or select the Account in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `customer_access_compliance_forbidden` | 403 | The request contradicts the Customer access the platform has already durably recorded (`customer access compliance forbidden`). | Re-read the Customer access with `customer_access.retrieve` and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the Customer access as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `customer_access_required` | 403 | The operation cannot run against this Customer access until the `customer access required` prerequisite exists. | Supply the field, capability, evidence, or configuration the error details name for this Customer access, then call the operation again. | Complete the prerequisite through the surface that owns it -- the operation reference for this Customer access names it -- and the same call then succeeds unchanged. | no | yes |
| `customer_not_found` | 404 | No Customer matches the `customerId` this request referenced (`customer not found`). | List Customers in the same tenant, product, and environment, then retry with the `customerId` that read returns. | Create or select the Customer in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `customer_status_forbidden` | 403 | The Customer's current lifecycle state does not allow this transition (`customer status forbidden`). | Read the Customer with `customer.retrieve` and pick the lifecycle operation its current status allows. | Apply a transition the Customer's current status permits. The reference itself is still valid, so no new Customer is needed. | no | yes |
| `entity_not_found` | 404 | No Entity matches the `entityId` this request referenced (`entity not found`). | List Entities in the same tenant, product, and environment, then retry with the `entityId` that read returns. | Create or select the Entity in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `entity_status_forbidden` | 403 | The Entity's current lifecycle state does not allow this transition (`entity status forbidden`). | Read the Entity with `entity.retrieve` and pick the lifecycle operation its current status allows. | Apply a transition the Entity's current status permits. The reference itself is still valid, so no new Entity is needed. | no | yes |
| `instrument_reference_not_found` | 404 | No resource matches the `id` this request referenced (`instrument reference not found`). | List the collection this request referenced in the same tenant, product, and environment, then retry with an `id` that listing returns. | Create or select the resource in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `instrument_reference_required` | 409 | The request contradicts the resource the platform has already durably recorded (`instrument reference required`). | Re-read the resource this request referenced and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the resource as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `instrument_reference_status_forbidden` | 409 | The resource's current lifecycle state does not allow this transition (`instrument reference status forbidden`). | Read the resource this request referenced and pick the lifecycle operation its current status allows. | Apply a transition the resource's current status permits. The reference itself is still valid, so no new resource is needed. | no | yes |
| `instrument_retired` | 409 | The request contradicts the resource the platform has already durably recorded (`instrument retired`). | Re-read the resource this request referenced and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the resource as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `kyc_tier_insufficient` | 403 | Opening an account of this role requires the owning customer's entity to have ATTAINED a KYC tier it has not reached: either required fields are missing or the profile is not verified. | Complete and verify the entity's KYC profile to the tier this account role requires, then retry. The error details name the required tier and the entity's attained tier. | Submit the missing fields (`entity.kyc.submit`), run verification (`entity.kyc.verification.start`), and once the outcome is verified at the required tier the account open succeeds unchanged. `entity.kyc.retrieve` shows exactly which fields stand between the entity and the required tier. | no | yes |

### review

Accepts the filed evidence for an outcome decision before the response deadline.

- From: `filed`
- To: `under_review`
- Hints: readOnly=false, destructive=false, idempotent=true, moneyMovement=false
- Handle key: `cardDisputeId`

Exposed as:

| Program | Public action | Route |
| --- | --- | --- |
| [smb_expense_card](../programs/smb_expense_card.md) | `reviewExpenseDispute` | `POST /v1/operations/card_dispute.reviewExpenseDispute`, scopes card_dispute:write, idempotency required, receipt yes |

Input:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `cardDisputeId` | ref<card_dispute> | yes | Card dispute instance ID |

This action records no effect rows.

Errors beyond the 18 shared with every action on this instrument:

| Code | HTTP | Cause | Action | Remediation | Retryable | Idempotency safe |
| --- | --- | --- | --- | --- | --- | --- |
| `instrument_instance_not_found` | 404 | No Instrument instance matches the `instrumentInstanceId` this request referenced (`instrument instance not found`). | List Instrument instances in the same tenant, product, and environment, then retry with the `instrumentInstanceId` that read returns. | Create or select the Instrument instance in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `invalid_instrument_transition` | 409 | The request contradicts the resource the platform has already durably recorded (`invalid instrument transition`). | Re-read the resource this request referenced and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the resource as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |

### win

Asks the configured card policy for the final outcome and returns the disputed amount when the cardholder wins.

- From: `under_review`
- To: `won`
- Hints: readOnly=false, destructive=true, idempotent=true, moneyMovement=true
- Handle key: `cardDisputeId`

Exposed as:

| Program | Public action | Route |
| --- | --- | --- |
| [smb_expense_card](../programs/smb_expense_card.md) | `winExpenseDispute` | `POST /v1/operations/card_dispute.winExpenseDispute`, scopes card_dispute:write, transfer:write, idempotency required, receipt yes |

Input:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `cardDisputeId` | ref<card_dispute> | yes | Card dispute instance ID |

Effects:

- decides:
  - `decides.banking_or_card_issuing` from `decision`
- moves:
  - `moves.transfer.internal` from `moves[0]`
- reads:
  - `reads.requires_refs` from `requiresRefs`

Errors beyond the 18 shared with every action on this instrument:

| Code | HTTP | Cause | Action | Remediation | Retryable | Idempotency safe |
| --- | --- | --- | --- | --- | --- | --- |
| `account_frozen` | 409 | The account is frozen. Debits always refuse while frozen; some freezes also refuse credits. The error message carries the only reason wording Hyperscale exposes. | Use a different account for this transfer leg, or have the freeze lifted before retrying. | Unfreeze the account through its lifecycle surface if you imposed the freeze; a freeze you cannot lift was imposed by the platform and clears only when the platform lifts it. | no | yes |
| `account_ledger_mapping_conflict` | 409 | The request contradicts the Account the platform has already durably recorded (`account ledger mapping conflict`). | Re-read the Account with `account.retrieve` and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the Account as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `account_not_found` | 404 | No Account matches the `accountId` this request referenced (`account not found`). | List Accounts in the same tenant, product, and environment, then retry with the `accountId` that read returns. | Create or select the Account in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `cross_tenant_transfer_forbidden` | 409 | The request contradicts the resource the platform has already durably recorded (`cross tenant transfer forbidden`). | Re-read the resource this request referenced and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the resource as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `currency_invalid` | 500 | The platform could not durably complete the `currency invalid` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `currency_mismatch` | 400 | The currency on the request does not match the settlement currency of the account or transfer it targets. Hyperscale never converts between currencies, so the two must be identical. | Read the target account's currency (for example with `account.balance.retrieve`) and resend with that same currency and a minor-unit amount denominated in it. | This is deterministic. Retrying with the same currency fails identically. Correct the request's currency field, or move the money through an account already denominated in the required currency, then retry. | no | yes |
| `customer_access_compliance_forbidden` | 403 | The request contradicts the Customer access the platform has already durably recorded (`customer access compliance forbidden`). | Re-read the Customer access with `customer_access.retrieve` and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the Customer access as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `customer_access_required` | 403 | The operation cannot run against this Customer access until the `customer access required` prerequisite exists. | Supply the field, capability, evidence, or configuration the error details name for this Customer access, then call the operation again. | Complete the prerequisite through the surface that owns it -- the operation reference for this Customer access names it -- and the same call then succeeds unchanged. | no | yes |
| `customer_not_found` | 404 | No Customer matches the `customerId` this request referenced (`customer not found`). | List Customers in the same tenant, product, and environment, then retry with the `customerId` that read returns. | Create or select the Customer in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `customer_status_forbidden` | 403 | The Customer's current lifecycle state does not allow this transition (`customer status forbidden`). | Read the Customer with `customer.retrieve` and pick the lifecycle operation its current status allows. | Apply a transition the Customer's current status permits. The reference itself is still valid, so no new Customer is needed. | no | yes |
| `destination_account_status_forbidden` | 400 | The destination (credit) account is not open. It is frozen or closed, so Hyperscale will not credit it. | Use a destination account whose status is `open`, or unfreeze the intended account, before retrying. | Read the account status, unfreeze it (or select an open account in the same currency and product), then resend the transfer. | no | yes |
| `entity_not_found` | 404 | No Entity matches the `entityId` this request referenced (`entity not found`). | List Entities in the same tenant, product, and environment, then retry with the `entityId` that read returns. | Create or select the Entity in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `entity_status_forbidden` | 403 | The Entity's current lifecycle state does not allow this transition (`entity status forbidden`). | Read the Entity with `entity.retrieve` and pick the lifecycle operation its current status allows. | Apply a transition the Entity's current status permits. The reference itself is still valid, so no new Entity is needed. | no | yes |
| `instrument_decision_declined` | 409 | The request contradicts the resource the platform has already durably recorded (`instrument decision declined`). | Re-read the resource this request referenced and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the resource as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `instrument_decision_timeout` | 409 | The request contradicts the resource the platform has already durably recorded (`instrument decision timeout`). | Re-read the resource this request referenced and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the resource as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `instrument_instance_not_found` | 404 | No Instrument instance matches the `instrumentInstanceId` this request referenced (`instrument instance not found`). | List Instrument instances in the same tenant, product, and environment, then retry with the `instrumentInstanceId` that read returns. | Create or select the Instrument instance in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `instrument_reference_not_found` | 404 | No resource matches the `id` this request referenced (`instrument reference not found`). | List the collection this request referenced in the same tenant, product, and environment, then retry with an `id` that listing returns. | Create or select the resource in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `instrument_reference_required` | 409 | The request contradicts the resource the platform has already durably recorded (`instrument reference required`). | Re-read the resource this request referenced and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the resource as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `instrument_reference_status_forbidden` | 409 | The resource's current lifecycle state does not allow this transition (`instrument reference status forbidden`). | Read the resource this request referenced and pick the lifecycle operation its current status allows. | Apply a transition the resource's current status permits. The reference itself is still valid, so no new resource is needed. | no | yes |
| `insufficient_balance` | 409 | The source (debit) account's available balance is below the transfer amount, so the ledger refused the transfer. No money moved. The transfer was not created and its charge/transfer rows are recorded as failed. | Fund the source (debit) account so its available balance in minor units covers the full amount, or lower the amount, then retry. Confirm the shortfall first with `account.balance.retrieve`. | This is a definitive refusal, not a transient failure: replaying the exact request with the same Idempotency-Key safely returns this same refusal and never double-charges, so rotating to a fresh key is not the fix. Funding is. Top up the source account (or reduce the amount), then submit the transfer as a new request. | no | yes |
| `invalid_instrument_transition` | 409 | The request contradicts the resource the platform has already durably recorded (`invalid instrument transition`). | Re-read the resource this request referenced and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the resource as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `ledger_account_rejected` | 500 | The platform could not durably complete the `ledger account rejected` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_account_result_missing` | 500 | The platform could not durably complete the `ledger account result missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_address_missing` | 500 | The platform could not durably complete the `ledger address missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_amount_invalid` | 500 | The platform could not durably complete the `ledger amount invalid` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_cluster_id_invalid` | 500 | The platform could not durably complete the `ledger cluster id invalid` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_cluster_id_missing` | 500 | The platform could not durably complete the `ledger cluster id missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_confirmation_mismatch` | 500 | The platform could not durably complete the `ledger confirmation mismatch` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_effect_missing` | 500 | The platform could not durably complete the `ledger effect missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_effect_not_declared` | 500 | The platform could not durably complete the `ledger effect not declared` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_evidence_missing` | 500 | The platform could not durably complete the `ledger evidence missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_linked_transfer_chain_open` | 500 | The platform could not durably complete the `ledger linked transfer chain open` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_lookup_confirmation_mismatch` | 500 | The platform could not durably complete the `ledger lookup confirmation mismatch` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_mapping_missing` | 500 | The platform could not durably complete the `ledger mapping missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_pending_transfer_id_missing` | 500 | The platform could not durably complete the `ledger pending transfer id missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_timeout_invalid` | 500 | The platform could not durably complete the `ledger timeout invalid` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_timeout_phase_invalid` | 500 | The platform could not durably complete the `ledger timeout phase invalid` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_transfer_lookup_conflict` | 500 | The platform could not durably complete the `ledger transfer lookup conflict` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_transfer_lookup_request_missing` | 500 | The platform could not durably complete the `ledger transfer lookup request missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_transfer_rejected` | 409 | The request contradicts the resource the platform has already durably recorded (`ledger transfer rejected`). | Re-read the resource this request referenced and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the resource as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `ledger_transfer_result_count_mismatch` | 500 | The platform could not durably complete the `ledger transfer result count mismatch` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_transfer_result_missing` | 500 | The platform could not durably complete the `ledger transfer result missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `ledger_unavailable` | 503 | The platform could not durably complete the `ledger unavailable` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | yes | no |
| `source_account_status_forbidden` | 400 | The source (debit) account is not open. It is frozen or closed, so Hyperscale will not debit it. | Use a source account whose status is `open`, or unfreeze the intended account, before retrying. | Read the account status, unfreeze it (or select an open account in the same currency and product), then resend the transfer. | no | yes |
| `source_operation_not_found` | 404 | No resource matches the `id` this request referenced (`source operation not found`). | List the collection this request referenced in the same tenant, product, and environment, then retry with an `id` that listing returns. | Create or select the resource in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `tenant_code_invalid` | 500 | The platform could not durably complete the `tenant code invalid` step, so the Company may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the Company was written. | Treat the Company's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `transfer_destination_role_forbidden` | 400 | The submitted value is not one this resource accepts (`transfer destination role forbidden`). | Replace the rejected value with one this resource allows, as listed in the API reference or set by product policy. | Rebuild the request from the generated SDK types for this resource, which carry the allowed values as unions rather than free strings. | no | yes |
| `transfer_lineage_conflict` | 409 | The request contradicts the resource the platform has already durably recorded (`transfer lineage conflict`). | Re-read the resource this request referenced and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the resource as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `transfer_not_found` | 404 | No resource matches the `id` this request referenced (`transfer not found`). | List the collection this request referenced in the same tenant, product, and environment, then retry with an `id` that listing returns. | Create or select the resource in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `transfer_role_pair_forbidden` | 400 | The submitted value is not one this resource accepts (`transfer role pair forbidden`). | Replace the rejected value with one this resource allows, as listed in the API reference or set by product policy. | Rebuild the request from the generated SDK types for this resource, which carry the allowed values as unions rather than free strings. | no | yes |
| `transfer_source_role_forbidden` | 400 | The submitted value is not one this resource accepts (`transfer source role forbidden`). | Replace the rejected value with one this resource allows, as listed in the API reference or set by product policy. | Rebuild the request from the generated SDK types for this resource, which carry the allowed values as unions rather than free strings. | no | yes |
| `transfer_status_forbidden` | 409 | The transfer is not in the state this operation requires (for example, posting needs a reserved transfer; returning or reversing needs a posted one). The current and required states are described in the error message. | Read the transfer's current status and call the operation that is valid for that status. | Follow the transfer lifecycle (reserve → post, then return/reverse) and issue the operation matching the transfer's current state instead of the rejected one. | no | yes |

## Steps no Product exposes

The cost table prices 1 lifecycle step that no Product in this catalog exposes as an action, so none of them is callable through a Product API. They run inside the instrument. The IR carries only their price, so price is all this page can state about them.

### lose

| Action | Effect | Meter | Rate | Table version |
| --- | --- | --- | --- | --- |
| `lose` | `schedules.due` | `instrument.event.count` | 0 | 2026-09-02.1 |

## Reads

- `card_dispute.list` as `card_dispute_list`: List card disputes
  - Route: `GET /v1/card-disputes`, scopes card_dispute:read, idempotency not_required, receipt yes
  - Output: items, nextCursor, statusCounts
- `card_dispute.retrieve` as `card_dispute_retrieve`: Retrieve a card dispute
  - Route: `GET /v1/card-disputes/{cardDisputeId}`, scopes card_dispute:read, idempotency not_required, receipt yes
  - Output: cardDisputeId, tenantId, productId, instrumentId, status, fields, refs, createdAt, updatedAt

## Cost

| Action | Effect | Meter | Rate | Table version |
| --- | --- | --- | --- | --- |
| `create` | `reads.requires_refs` | `instrument.event.count` | 0 | 2026-09-02.1 |
| `lose` | `schedules.due` | `instrument.event.count` | 0 | 2026-09-02.1 |
| `win` | `decides.banking_or_card_issuing` | `instrument.event.count` | 0 | 2026-09-02.1 |
| `win` | `moves.transfer.internal` | `transfer.internal.volume_sar` | 75+11bps | 2026-09-02.1 |
| `win` | `reads.requires_refs` | `instrument.event.count` | 0 | 2026-09-02.1 |

## Errors on every action

The 18 errors every action on this instrument declares. An action's own section lists only what it adds beyond these.

| Code | HTTP | Cause | Action | Remediation | Retryable | Idempotency safe |
| --- | --- | --- | --- | --- | --- | --- |
| `invalid_credentials` | 401 | Hyperscale could not accept the account or credential request. Signup, login, and bearer authentication use one 401 for every case they reject, so the response exposes no credential-specific reason or registered-email verdict. The single exception is a key that is real but homed in the other environment: because the presented secret already matched a stored key, the message names the mismatch instead of leaving a working credential looking unknown. | For signup or login, re-check the submitted account details. For API calls, re-check the `Authorization: Bearer <key>` API key and whether it matches the target environment (sandbox vs live). | For signup, verify the submitted details or use login or password reset for an existing account. For login, re-enter the credentials or reset the password. For API or MCP calls, use an active key for the target environment, set HYPERSCALE_API_KEY, and retry. When the message reports an environment mismatch, `details.keyEnvironment` names where the key does work: send that environment's key, or set `X-Hyperscale-Environment` to it. | no | yes |
| `local_evidence_output_key_missing` | 500 | The platform could not durably complete the `local evidence output key missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `local_evidence_reference_missing` | 500 | The platform could not durably complete the `local evidence reference missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `local_evidence_resource_missing` | 500 | The platform could not durably complete the `local evidence resource missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `mfa_verification_required` | 403 | This tenant surface requires a session that completed TOTP at login. Tenant operations demand it while the organization's require-MFA policy (tenant.security.configure) is on; user.mfa.totp.disable demands it unconditionally, because removing a factor is the act that factor exists to guard. | Enroll and confirm a TOTP factor (user.mfa.totp.enrollment.begin, then user.mfa.totp.enrollment.confirm), then log in again and complete user.login.mfa.verify before retrying. | MFA verification is a mint-time session fact, so enrolling a factor mid-session never upgrades the current session. After the factor is active, a fresh login returns an MFA challenge; completing it mints the verified session this surface demands. Identity self-operations (logout, TOTP enrollment) stay open to a password-only session, so a factorless member is never locked out of enrolling. Disabling a factor is the exception: sign in again and complete the challenge first. | no | yes |
| `operation_insert_failed` | 500 | The platform could not durably complete the `operation insert failed` step, so the Operation may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the Operation was written. | Treat the Operation's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `operation_not_finalized` | 409 | The request contradicts the Operation the platform has already durably recorded (`operation not finalized`). | Re-read the Operation with `operation.retrieve` and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the Operation as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `product_not_found` | 404 | No Product matches the `productId` this request referenced (`product not found`). | List Products in the same tenant, product, and environment, then retry with the `productId` that read returns. | Create or select the Product in the environment the call runs against; ids never carry across tenants, or across sandbox and live. | no | yes |
| `session_expired` | 401 | The operation refused the request on its `session expired` contract condition. | Read the error details, then the operation reference for this resource, before retrying. | Correct the request against the generated SDK models and a fresh read of the resource; if the shape is already right, the product may be missing the capability this operation needs. | no | yes |
| `session_required` | 401 | The operation cannot run against this resource until the `session required` prerequisite exists. | Supply the field, capability, evidence, or configuration the error details name for this resource, then call the operation again. | Complete the prerequisite through the surface that owns it -- the operation reference for this resource names it -- and the same call then succeeds unchanged. | no | yes |
| `session_revoked` | 401 | The operation refused the request on its `session revoked` contract condition. | Read the error details, then the operation reference for this resource, before retrying. | Correct the request against the generated SDK models and a fresh read of the resource; if the shape is already right, the product may be missing the capability this operation needs. | no | yes |
| `tenant_code_missing` | 500 | The platform could not durably complete the `tenant code missing` step, so the Company may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the Company was written. | Treat the Company's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `usage_amount_missing` | 500 | The platform could not durably complete the `usage amount missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `usage_currency_mismatch` | 500 | The platform could not durably complete the `usage currency mismatch` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `usage_meter_missing` | 500 | The platform could not durably complete the `usage meter missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `usage_source_conflict` | 409 | The request contradicts the resource the platform has already durably recorded (`usage source conflict`). | Re-read the resource this request referenced and compare the submitted ids, currency, product, tenant, and expected state against what comes back. | Rebuild the request from the resource as it now stands, then retry under a NEW idempotency key -- reuse the original key only when you are deliberately replaying the original mutation. | no | yes |
| `usage_tenant_missing` | 500 | The platform could not durably complete the `usage tenant missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
| `usage_transfer_missing` | 500 | The platform could not durably complete the `usage transfer missing` step, so the resource may or may not have been written. | Capture the request id and the receipt or operation id before retrying or escalating; do not re-send under a fresh key until you know whether the resource was written. | Treat the resource's side effects as unknown unless a terminal receipt says otherwise; retry only operations documented as retryable, and replay them with the SAME idempotency key. | no | no |
