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

# Bilateral Instructions

> How both parties submit settlement instructions independently.

Bilateral instruction submission is the primary way to initiate settlements in KeyStone. Both counterparties independently submit their side of the trade. When both instructions arrive, KeyStone automatically matches them and creates a settlement on-chain.

## How it works

```mermaid theme={null}
%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#1a3a35', 'actorBorder': '#2DD4A8', 'actorTextColor': '#e0f2ef', 'actorLineColor': '#2DD4A8', 'signalColor': '#2DD4A8', 'signalTextColor': '#e0f2ef', 'noteBkgColor': '#162e2a', 'noteTextColor': '#e0f2ef', 'noteBorderColor': '#1AAF8B', 'activationBkgColor': '#1a3a35', 'activationBorderColor': '#2DD4A8', 'labelBoxBkgColor': '#1a3a35', 'labelBoxBorderColor': '#2DD4A8', 'labelTextColor': '#e0f2ef', 'loopTextColor': '#2DD4A8'}}}%%
sequenceDiagram
    participant PA as Platform A (Seller)
    participant KS as KeyStone API
    participant PB as Platform B (Buyer)

    PA->>KS: POST /instructions (seller side)
    KS-->>PA: trade_reference: "KS-abc123"

    Note over PA,PB: Platform A shares trade_reference<br/>with Platform B (out-of-band)

    PB->>KS: POST /instructions (buyer side,<br/>trade_reference: "KS-abc123")
    KS->>KS: Match instructions automatically
    KS-->>PB: status: "matched", settlement_id: "..."
```

1. **First party submits**: Platform A sends an instruction without a `trade_reference`. KeyStone generates one (format: `KS-{uuid}`).
2. **Share the reference**: Platform A shares the trade reference with Platform B through their own channels (email, chat, API, etc.).
3. **Second party submits**: Platform B sends an instruction with the same `trade_reference`. KeyStone finds the matching pending instruction.
4. **Automatic matching**: KeyStone validates that both instructions are compatible and creates a settlement on-chain with both parties confirmed.

## Step 1: Submit the first instruction

The first party submits their side of the trade. Leave `trade_reference` null to have KeyStone generate one.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.keystoneos.xyz/v1/instructions \
    -H "Authorization: Bearer $PLATFORM_A_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "idempotency_key": "seller-instruction-001",
      "template_slug": "dvp-standard",
      "role": "seller",
      "party": {
        "external_reference": "seller-acct-001",
        "name": "Acme Securities",
        "wallet_address": "0xSellerWallet..."
      },
      "legs": [
        {
          "leg_type": "asset_delivery",
          "instrument_id": "0xTokenContractAddress",
          "quantity": "1000",
          "direction": "deliver",
          "chain_id": 11155111
        },
        {
          "leg_type": "payment",
          "instrument_id": "0xUSDCAddress",
          "quantity": "250000",
          "direction": "receive",
          "chain_id": 11155111
        }
      ],
      "timeout_at": "2026-03-20T00:00:00Z"
    }'
  ```

  ```typescript TypeScript SDK theme={null}
  const instruction = await client.instructions.submit({
    idempotencyKey: client.generateIdempotencyKey(),
    templateSlug: "dvp-standard",
    role: "seller",
    party: {
      externalReference: "seller-acct-001",
      name: "Acme Securities",
      walletAddress: "0xSellerWallet...",
    },
    legs: [
      { legType: "asset_delivery", instrumentId: "0xTokenContractAddress", quantity: "1000", direction: "deliver", chainId: 11155111 },
      { legType: "payment", instrumentId: "0xUSDCAddress", quantity: "250000", direction: "receive", chainId: 11155111 },
    ],
    timeoutAt: "2026-03-20T00:00:00Z",
  });

  const tradeReference = instruction.tradeReference; // "KS-abc123..."
  ```

  ```python Python SDK theme={null}
  from keystoneos import KeystoneClient

  async with KeystoneClient(
      client_id="YOUR_CLIENT_ID",
      client_secret="YOUR_CLIENT_SECRET",
  ) as client:
      instruction = await client.instructions.submit({
          "idempotency_key": "seller-instruction-001",
          "template_slug": "dvp-standard",
          "role": "seller",
          "party": {
              "external_reference": "seller-acct-001",
              "name": "Acme Securities",
              "wallet_address": "0xSellerWallet...",
          },
          "legs": [
              {"leg_type": "asset_delivery", "instrument_id": "0xTokenContractAddress", "quantity": "1000", "direction": "deliver", "chain_id": 11155111},
              {"leg_type": "payment", "instrument_id": "0xUSDCAddress", "quantity": "250000", "direction": "receive", "chain_id": 11155111},
          ],
          "timeout_at": "2026-03-20T00:00:00Z",
      })

      trade_reference = instruction.trade_reference  # "KS-abc123..."
  ```
</CodeGroup>

Response:

```json theme={null}
{
  "id": "inst-...",
  "trade_reference": "KS-abc123-def4-5678-...",
  "status": "pending_match",
  "role": "seller",
  "settlement_id": null,
  "created_at": "2026-03-19T10:00:00Z"
}
```

## Step 2: Share the trade reference

The trade reference (`KS-abc123...`) is shared between counterparties through their own communication channels. KeyStone does not handle this exchange - it is an out-of-band coordination step.

Common methods:

* Platform-to-platform API integration
* Pre-agreed trade reference via OTC desk
* Shared trade confirmation system

## Step 3: Submit the matching instruction

The second party submits their instruction with the trade reference from Step 1.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.keystoneos.xyz/v1/instructions \
    -H "Authorization: Bearer $PLATFORM_B_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "idempotency_key": "buyer-instruction-001",
      "trade_reference": "KS-abc123-def4-5678-...",
      "template_slug": "dvp-standard",
      "role": "buyer",
      "party": {
        "external_reference": "buyer-acct-042",
        "name": "Beta Capital",
        "wallet_address": "0xBuyerWallet..."
      },
      "legs": [
        {
          "leg_type": "asset_delivery",
          "instrument_id": "0xTokenContractAddress",
          "quantity": "1000",
          "direction": "receive",
          "chain_id": 11155111
        },
        {
          "leg_type": "payment",
          "instrument_id": "0xUSDCAddress",
          "quantity": "250000",
          "direction": "deliver",
          "chain_id": 11155111
        }
      ],
      "timeout_at": "2026-03-20T00:00:00Z"
    }'
  ```

  ```typescript TypeScript SDK theme={null}
  const buyerInstruction = await platformBClient.instructions.submit({
    idempotencyKey: platformBClient.generateIdempotencyKey(),
    tradeReference: "KS-abc123-def4-5678-...",
    templateSlug: "dvp-standard",
    role: "buyer",
    party: {
      externalReference: "buyer-acct-042",
      name: "Beta Capital",
      walletAddress: "0xBuyerWallet...",
    },
    legs: [
      { legType: "asset_delivery", instrumentId: "0xTokenContractAddress", quantity: "1000", direction: "receive", chainId: 11155111 },
      { legType: "payment", instrumentId: "0xUSDCAddress", quantity: "250000", direction: "deliver", chainId: 11155111 },
    ],
    timeoutAt: "2026-03-20T00:00:00Z",
  });

  console.log(buyerInstruction.status);       // "matched"
  console.log(buyerInstruction.settlementId); // "550e8400-..."
  ```

  ```python Python SDK theme={null}
  buyer_instruction = await platform_b_client.instructions.submit({
      "idempotency_key": "buyer-instruction-001",
      "trade_reference": "KS-abc123-def4-5678-...",
      "template_slug": "dvp-standard",
      "role": "buyer",
      "party": {
          "external_reference": "buyer-acct-042",
          "name": "Beta Capital",
          "wallet_address": "0xBuyerWallet...",
      },
      "legs": [
          {"leg_type": "asset_delivery", "instrument_id": "0xTokenContractAddress", "quantity": "1000", "direction": "receive", "chain_id": 11155111},
          {"leg_type": "payment", "instrument_id": "0xUSDCAddress", "quantity": "250000", "direction": "deliver", "chain_id": 11155111},
      ],
      "timeout_at": "2026-03-20T00:00:00Z",
  })

  print(buyer_instruction.status)         # "matched"
  print(buyer_instruction.settlement_id)  # "550e8400-..."
  ```
</CodeGroup>

If the instructions match, the response includes the settlement:

```json theme={null}
{
  "id": "inst-...",
  "trade_reference": "KS-abc123-def4-5678-...",
  "status": "matched",
  "settlement_id": "550e8400-...",
  "created_at": "2026-03-19T10:05:00Z"
}
```

## What matching validates

Both instructions must agree on:

| Field           | Validation                                                  |
| --------------- | ----------------------------------------------------------- |
| `template_slug` | Must be identical                                           |
| `role`          | Must be different (one seller, one buyer)                   |
| Instruments     | Same set of `instrument_id` values                          |
| Quantities      | Must match per instrument                                   |
| Directions      | Must be complementary (seller delivers what buyer receives) |

If validation fails, the second instruction is still saved as `pending_match`. It will not be automatically paired with the first instruction. The submitting platform can cancel it and resubmit with corrected details.

## Optional fields

| Field        | Description                                                                                                                                                                                          |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fee_mode`   | Override the default fee allocation for this settlement. Options: `seller_pays`, `buyer_pays`, `split`. Defaults to the platform environment setting.                                                |
| `trade_type` | Set to `repo_open` for repo opening legs. See [Tokenised Repo](/guides/tokenised-repo).                                                                                                              |
| `repo_terms` | Required for `repo_open` - the repo economics (`tenor_days`, `rate_bps`, `haircut_bps`, `margin_band_lower`, `margin_band_upper`). Maturity and the closing repayment amount are derived from these. |

## Cancellation

Cancel a pending instruction if it was submitted in error or the trade is no longer needed:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE https://api.keystoneos.xyz/v1/instructions/$INSTRUCTION_ID \
    -H "Authorization: Bearer $TOKEN"
  ```

  ```typescript TypeScript SDK theme={null}
  await client.instructions.cancel("inst-...");
  ```

  ```python Python SDK theme={null}
  await client.instructions.cancel("inst-...")
  ```
</CodeGroup>

Only instructions with `status: pending_match` can be cancelled. Matched or expired instructions cannot be cancelled.

## Expiry

Instructions have a default 24-hour time-to-live. After expiry, they are no longer eligible for matching. This prevents stale instructions from accidentally matching with new ones submitted days later.

## Listing instructions

View your platform's instructions:

<CodeGroup>
  ```bash cURL theme={null}
  # All instructions
  curl https://api.keystoneos.xyz/v1/instructions \
    -H "Authorization: Bearer $TOKEN"

  # Filter by status
  curl "https://api.keystoneos.xyz/v1/instructions?status=pending_match" \
    -H "Authorization: Bearer $TOKEN"
  ```

  ```typescript TypeScript SDK theme={null}
  // All instructions
  const { items } = await client.instructions.list();

  // Filter by status
  const pending = await client.instructions.list({ status: "pending_match" });
  ```

  ```python Python SDK theme={null}
  # All instructions
  result = await client.instructions.list()

  # Filter by status
  pending = await client.instructions.list(status="pending_match")
  ```
</CodeGroup>

## Single-platform use

Bilateral instructions work for single-platform settlements too. When both instructions come from the same platform, the settlement is created as `single_platform` type. This is useful when your platform intermediates trades between its own users.

<CardGroup cols={2}>
  <Card title="Escrow Deposits" icon="lock" href="/guides/escrow-deposits">
    How parties deposit into the settlement contract with commitment secrets.
  </Card>

  <Card title="Escrow Deposits" icon="lock" href="/guides/escrow-deposits">
    How parties deposit to escrow using commitment secrets.
  </Card>
</CardGroup>
