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

# Listing

> Subject-backed offer published by a seller.

A listing starts as a private draft, becomes buyable when published, stays unavailable while funded escrow reserves it, and ends sold, withdrawn, or expired without ever moving money itself.

- Instrument id: `listing`
- Initial state: `draft`
- Terminal states: `sold`, `withdrawn`
- Composed by: [asset_backed_membership](../programs/asset_backed_membership.md), [b2b_wholesale_marketplace](../programs/b2b_wholesale_marketplace.md), [escrow_marketplace_sandbox](../programs/escrow_marketplace_sandbox.md), [fractional_art_shares](../programs/fractional_art_shares.md), [local_services_marketplace](../programs/local_services_marketplace.md), [luxury_goods_consignment_escrow](../programs/luxury_goods_consignment_escrow.md), [personalized_shopping_concierge](../programs/personalized_shopping_concierge.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
    [*] --> draft: create
    draft --> active: publish
    reserved --> active: reopen
    active --> reserved: reserve
    reserved --> sold: sell
    draft --> withdrawn: withdraw
    active --> withdrawn: withdraw
    sold --> [*]
    withdrawn --> [*]
```

| State | Kind | Entered by | Left by |
| --- | --- | --- | --- |
| `draft` | initial | create | publish, withdraw |
| `active` | intermediate | publish, reopen | reserve, withdraw |
| `reserved` | intermediate | reserve | reopen, sell |
| `sold` | terminal | sell | none |
| `withdrawn` | terminal | withdraw, withdraw | none |

## Fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `headline` | text | yes |  |
| `askingPrice` | 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 |
| `description` | text | no |  |
| `expiresAt` | datetime | no |  |
| `imageUrl` | text | no |  |

## References

- `sellerAccountId`: `account`, required
- `escrowOrderId`: `escrow_order`, optional

## Actions

### create

Creates a private offer against one subject. Publishing is a separate, explicit decision.

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

Exposed as:

| Program | Public action | Route |
| --- | --- | --- |
| [asset_backed_membership](../programs/asset_backed_membership.md) | `registerMemberAsset` | `POST /v1/operations/listing.registerMemberAsset`, scopes listing:write, idempotency required, receipt yes |
| [b2b_wholesale_marketplace](../programs/b2b_wholesale_marketplace.md) | `publishSupplierCatalogItem` | `POST /v1/operations/listing.publishSupplierCatalogItem`, scopes listing:write, idempotency required, receipt yes |
| [escrow_marketplace_sandbox](../programs/escrow_marketplace_sandbox.md) | `createMarketplaceListing` | `POST /v1/operations/listing.createMarketplaceListing`, scopes listing:write, idempotency required, receipt yes |
| [fractional_art_shares](../programs/fractional_art_shares.md) | `openShareOffering` | `POST /v1/operations/listing.openShareOffering`, scopes listing:write, idempotency required, receipt yes |
| [local_services_marketplace](../programs/local_services_marketplace.md) | `postServiceRequest` | `POST /v1/operations/listing.postServiceRequest`, scopes listing:write, idempotency required, receipt yes |
| [luxury_goods_consignment_escrow](../programs/luxury_goods_consignment_escrow.md) | `registerConsignedAsset` | `POST /v1/operations/listing.registerConsignedAsset`, scopes listing:write, idempotency required, receipt yes |
| [personalized_shopping_concierge](../programs/personalized_shopping_concierge.md) | `proposeRecommendation` | `POST /v1/operations/listing.proposeRecommendation`, scopes listing:write, idempotency required, receipt yes |

Input:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `sellerAccountId` | ref<account> | yes | Hyperscale account ID |
| `headline` | text | yes |  |
| `askingPrice` | 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 |
| `description` | text | no |  |
| `escrowOrderId` | ref<escrow_order> | no | Escrow order whose held funds reserve this listing |
| `expiresAt` | datetime | no |  |
| `imageUrl` | text | no |  |
| `subject` | text | yes | ID of an existing subject created with Product operation; its registered kind must be one of: vehicle, merchandise. Discover registered kinds and their attribute schemas with Product operation. |

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 |
| --- | --- | --- | --- | --- | --- | --- |
| `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_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 |
| `instrument_subject_kind_forbidden` | 409 | The request contradicts the resource the platform has already durably recorded (`instrument subject kind 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 |
| `instrument_subject_not_found` | 409 | No resource matches the `id` this request referenced (`instrument subject 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 |
| `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 |

### publish

Makes the draft visible and buyable.

- From: `draft`
- To: `active`
- Hints: readOnly=false, destructive=false, idempotent=true, moneyMovement=false
- Handle key: `listingId`

Exposed as:

| Program | Public action | Route |
| --- | --- | --- |
| [asset_backed_membership](../programs/asset_backed_membership.md) | `publishMemberAsset` | `POST /v1/operations/listing.publishMemberAsset`, scopes listing:write, idempotency required, receipt yes |
| [escrow_marketplace_sandbox](../programs/escrow_marketplace_sandbox.md) | `publishMarketplaceListing` | `POST /v1/operations/listing.publishMarketplaceListing`, scopes listing:write, idempotency required, receipt yes |
| [luxury_goods_consignment_escrow](../programs/luxury_goods_consignment_escrow.md) | `publishConsignedAsset` | `POST /v1/operations/listing.publishConsignedAsset`, scopes listing:write, idempotency required, receipt yes |

Input:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `listingId` | ref<listing> | yes | Listing 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 |

### reopen

Returns the offer to the market after escrow closes without releasing money to the seller.

- From: `reserved`
- To: `active`
- Hints: readOnly=false, destructive=false, idempotent=true, moneyMovement=false
- Handle key: `listingId`

Exposed as:

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

Input:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `listingId` | ref<listing> | yes | Listing instance ID |

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 |
| --- | --- | --- | --- | --- | --- | --- |
| `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_incongruent` | 409 | The request contradicts the resource the platform has already durably recorded (`instrument reference incongruent`). | 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_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 |
| `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 |

### reserve

Takes the offer off the market only while its referenced escrow order holds the buyer's funds for this exact listing at its exact economics.

- From: `active`
- To: `reserved`
- Hints: readOnly=false, destructive=false, idempotent=true, moneyMovement=false
- Handle key: `listingId`

Exposed as:

| Program | Public action | Route |
| --- | --- | --- |
| [asset_backed_membership](../programs/asset_backed_membership.md) | `reserveMemberAsset` | `POST /v1/operations/listing.reserveMemberAsset`, scopes listing:write, idempotency required, receipt yes |
| [escrow_marketplace_sandbox](../programs/escrow_marketplace_sandbox.md) | `reserveMarketplaceListing` | `POST /v1/operations/listing.reserveMarketplaceListing`, scopes listing:write, idempotency required, receipt yes |
| [local_services_marketplace](../programs/local_services_marketplace.md) | `acceptProviderOffer` | `POST /v1/operations/listing.acceptProviderOffer`, scopes listing:write, idempotency required, receipt yes |
| [luxury_goods_consignment_escrow](../programs/luxury_goods_consignment_escrow.md) | `reserveConsignedAsset` | `POST /v1/operations/listing.reserveConsignedAsset`, scopes listing:write, idempotency required, receipt yes |

Input:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `listingId` | ref<listing> | yes | Listing instance ID |
| `escrowOrderId` | ref<escrow_order> | yes | Escrow order whose held funds reserve this listing |

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 |
| --- | --- | --- | --- | --- | --- | --- |
| `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_incongruent` | 409 | The request contradicts the resource the platform has already durably recorded (`instrument reference incongruent`). | 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_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 |
| `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 |

### sell

Marks the subject sold only after the escrow opened against this exact listing has released the full asking price to this seller.

- From: `reserved`
- To: `sold`
- Hints: readOnly=false, destructive=true, idempotent=true, moneyMovement=false
- Handle key: `listingId`

Exposed as:

| Program | Public action | Route |
| --- | --- | --- |
| [asset_backed_membership](../programs/asset_backed_membership.md) | `completeMemberAssetSale` | `POST /v1/operations/listing.completeMemberAssetSale`, scopes listing:write, idempotency required, receipt yes |
| [escrow_marketplace_sandbox](../programs/escrow_marketplace_sandbox.md) | `completeMarketplaceSale` | `POST /v1/operations/listing.completeMarketplaceSale`, scopes listing:write, idempotency required, receipt yes |
| [fractional_art_shares](../programs/fractional_art_shares.md) | `liquidateArtwork` | `POST /v1/operations/listing.liquidateArtwork`, scopes listing:write, idempotency required, receipt yes |
| [luxury_goods_consignment_escrow](../programs/luxury_goods_consignment_escrow.md) | `completeConsignedSale` | `POST /v1/operations/listing.completeConsignedSale`, scopes listing:write, idempotency required, receipt yes |
| [personalized_shopping_concierge](../programs/personalized_shopping_concierge.md) | `completeMerchantOrder` | `POST /v1/operations/listing.completeMerchantOrder`, scopes listing:write, idempotency required, receipt yes |

Input:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `listingId` | ref<listing> | yes | Listing instance ID |

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 |
| --- | --- | --- | --- | --- | --- | --- |
| `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_incongruent` | 409 | The request contradicts the resource the platform has already durably recorded (`instrument reference incongruent`). | 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_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 |
| `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 |

### withdraw

Closes a draft or active offer before any escrow reservation exists.

- From: `draft`, `active`
- To: `withdrawn`
- Hints: readOnly=false, destructive=true, idempotent=true, moneyMovement=false
- Handle key: `listingId`

Exposed as:

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

Input:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `listingId` | ref<listing> | yes | Listing 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 |

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

### expire

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

## Reads

- `listing.list` as `listing_list`: List listings
  - Route: `GET /v1/listings`, scopes listing:read, idempotency not_required, receipt yes
  - Output: items, nextCursor, statusCounts
- `listing.retrieve` as `listing_retrieve`: Retrieve a listing
  - Route: `GET /v1/listings/{listingId}`, scopes listing:read, idempotency not_required, receipt yes
  - Output: listingId, tenantId, productId, instrumentId, status, fields, refs, createdAt, updatedAt

## Cost

| Action | Effect | Meter | Rate | Table version |
| --- | --- | --- | --- | --- |
| `expire` | `schedules.due` | `instrument.event.count` | 0 | 2026-09-02.1 |
| `reopen` | `reads.requires_refs` | `instrument.event.count` | 0 | 2026-09-02.1 |
| `reserve` | `reads.requires_refs` | `instrument.event.count` | 0 | 2026-09-02.1 |
| `sell` | `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 |
