# Inventory

Build an inventory system with Blnk. Track each SKU across available, sold, in-transit, and delivered stages, and rebuild any past stock count.

Inventory is how many units of a product you have, and where each one is: in stock, sold, on the way to a buyer, or delivered. Each of those stages is a balance, and moving units from one stage to the next is a transaction, so every count has a history you can replay.

This tutorial shows how to implement that with Blnk.

<TutorialSteps>
  <TutorialStep title="Design the money movement">
    Before we start building, we need to define our [money movement](/ledgers/money-movement-map). This helps us outline how Alice's phones move from her stock to Bob.

    Alice sells the iPhone 16 Black 128GB. She gets 30 phones from her supplier. We move them into her available balance from `@StockIntake`, an [internal balance](/balances/internal-balances) for stock that comes from outside.

    Bob orders 2 phones. They move from available to sold, then to in transit, then to delivered.

    We hold the sale as [inflight](/transactions/inflight/create-inflight) when Bob checks out, so no one else can buy his 2 phones, and commit it when he pays. We record the pickup and the delivery when each one happens.

    <TutorialFrame plain>
      <Map src="https://map.blnkfinance.xyz/bU0dXBFEfd" title="Alice's inventory" />
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Create the ledgers">
    Before we start recording transactions, we need to set up our [ledgers](/ledgers/introduction) to track Alice's stock. We create one ledger for each stage: available, sold, in transit, and delivered.

    Each ledger holds that stage for every product Alice sells. Listing the balances in the available ledger then shows how many of each product she can still sell.

    Create the four ledgers:

<TutorialCode>
<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/ledgers" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "{{available_name}}"
  }'

curl -X POST "{{CORE_URL}}/ledgers" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "{{sold_name}}"
  }'

curl -X POST "{{CORE_URL}}/ledgers" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "{{in_transit_name}}"
  }'

curl -X POST "{{CORE_URL}}/ledgers" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "{{delivered_name}}"
  }'
```

```typescript TypeScript wrap
const available = await blnk.Ledgers.create({
  name: '{{available_name}}',
});

const sold = await blnk.Ledgers.create({
  name: '{{sold_name}}',
});

const inTransit = await blnk.Ledgers.create({
  name: '{{in_transit_name}}',
});

const delivered = await blnk.Ledgers.create({
  name: '{{delivered_name}}',
});
```

```go Go wrap
available, _, _ := client.Ledger.Create(blnkgo.CreateLedgerRequest{
    Name: "{{available_name}}",
})

sold, _, _ := client.Ledger.Create(blnkgo.CreateLedgerRequest{
    Name: "{{sold_name}}",
})

inTransit, _, _ := client.Ledger.Create(blnkgo.CreateLedgerRequest{
    Name: "{{in_transit_name}}",
})

delivered, _, _ := client.Ledger.Create(blnkgo.CreateLedgerRequest{
    Name: "{{delivered_name}}",
})
```

```python Python wrap
available = blnk.ledgers.create({
  "name": "{{available_name}}",
})

sold = blnk.ledgers.create({
  "name": "{{sold_name}}",
})

in_transit = blnk.ledgers.create({
  "name": "{{in_transit_name}}",
})

delivered = blnk.ledgers.create({
  "name": "{{delivered_name}}",
})
```

```java Java wrap
ApiResponse<JsonNode> available = blnk.ledgers().create(
    CreateLedger.create()
        .name("{{available_name}}"));

ApiResponse<JsonNode> sold = blnk.ledgers().create(
    CreateLedger.create()
        .name("{{sold_name}}"));

ApiResponse<JsonNode> inTransit = blnk.ledgers().create(
    CreateLedger.create()
        .name("{{in_transit_name}}"));

ApiResponse<JsonNode> delivered = blnk.ledgers().create(
    CreateLedger.create()
        .name("{{delivered_name}}"));
```

</CodeGroup>
</TutorialCode>

    <TutorialFrame>
      <Tabs>
        <Tab title="Available">
          <TutorialForm id="create-available-ledger" method="POST" path="/ledgers" save="available_ledger_id" response="ledger_id">
            <TutorialInput name="available_name" as="name" label="Ledger name" value="SKUs available" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>

    <TutorialFrame>
      <Tabs>
        <Tab title="Sold">
          <TutorialForm id="create-sold-ledger" method="POST" path="/ledgers" save="sold_ledger_id" response="ledger_id">
            <TutorialInput name="sold_name" as="name" label="Ledger name" value="SKUs sold" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>

    <TutorialFrame>
      <Tabs>
        <Tab title="In transit">
          <TutorialForm id="create-in-transit-ledger" method="POST" path="/ledgers" save="in_transit_ledger_id" response="ledger_id">
            <TutorialInput name="in_transit_name" as="name" label="Ledger name" value="SKUs in transit" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>

    <TutorialFrame>
      <Tabs>
        <Tab title="Delivered">
          <TutorialForm id="create-delivered-ledger" method="POST" path="/ledgers" save="delivered_ledger_id" response="ledger_id">
            <TutorialInput name="delivered_name" as="name" label="Ledger name" value="SKUs delivered" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Create an identity">
    An [identity](/identities/introduction) is how we record the iPhone. We'll link its four balances to it, one per stage, so we can see where every unit is.

    Blnk has two identity types, individual and organization. We use organization for the phone and pass its name and category.

    One identity is one seller's stock of one SKU. If another seller lists the same phone, they get their own identity.

    Anything else we want to keep goes in [metadata](/search/metadata). We'll store the phone's SKU (stock keeping unit) there as `sku`, so we can find every transaction for this phone later. We'll also store `item_code` for the iPhone 16 family and `seller_id` for Alice.

    Create the identity:

<TutorialCode>
<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/identities" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "identity_type": "organization",
    "organization_name": "{{organization_name}}",
    "category": "{{category}}",
    "meta_data": {
      "seller_id": "{{seller_id}}",
      "item_code": "{{item_code}}",
      "sku": "{{sku}}"
    }
  }'
```

```typescript TypeScript wrap
const response = await blnk.Identity.create({
  identity_type: 'organization',
  organization_name: '{{organization_name}}',
  category: '{{category}}',
  meta_data: {
    seller_id: '{{seller_id}}',
    item_code: '{{item_code}}',
    sku: '{{sku}}',
  },
});
```

```go Go wrap
identity, _, _ := client.Identity.Create(blnkgo.Identity{
    IdentityType: blnkgo.Organization,
    OrganizationName: "{{organization_name}}",
    Category: "{{category}}",
    MetaData: blnkgo.MetaData{
        "seller_id": "{{seller_id}}",
        "item_code": "{{item_code}}",
        "sku": "{{sku}}",
    },
})
```

```python Python wrap
response = blnk.identity.create({
  "identity_type": "organization",
  "organization_name": "{{organization_name}}",
  "category": "{{category}}",
  "meta_data": {
    "seller_id": "{{seller_id}}",
    "item_code": "{{item_code}}",
    "sku": "{{sku}}",
  },
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.identity().create(
    IdentityData.create()
        .identityType("organization")
        .organizationName("{{organization_name}}")
        .category("{{category}}")
        .metaData(Map.of(
            "seller_id", "{{seller_id}}",
            "item_code", "{{item_code}}",
            "sku", "{{sku}}"
        )));
```

</CodeGroup>
</TutorialCode>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="create-identity" method="POST" path="/identities" save="identity_id">
            <TutorialValue name="identity_type" value="organization" />
            <TutorialInput name="organization_name" label="Product" value="iPhone 16 Black 128GB" />
            <TutorialInput name="category" label="Category" value="electronics" />
            <TutorialInput name="seller_id" label="Seller id" value="sel_9f2a" group="meta_data" />
            <TutorialInput name="item_code" label="Item code" value="IP16" group="meta_data" />
            <TutorialInput name="sku" label="SKU" value="IP16-BLK-128" group="meta_data" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Create the stage balances">
    Alice's iPhone needs a balance in each of the four ledgers. Each request takes a `ledger_id` from step 2 and the same `identity_id` from step 3.

    We set the currency to `UNIT`. When Alice receives her 30 phones in the next step, her available balance will show 30.

    Create the four balances:

<TutorialCode>
<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/balances" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "ledger_id": "{{available_ledger}}",
    "identity_id": "{{identity_id}}",
    "currency": "UNIT"
  }'

curl -X POST "{{CORE_URL}}/balances" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "ledger_id": "{{sold_ledger}}",
    "identity_id": "{{identity_id}}",
    "currency": "UNIT"
  }'

curl -X POST "{{CORE_URL}}/balances" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "ledger_id": "{{in_transit_ledger}}",
    "identity_id": "{{identity_id}}",
    "currency": "UNIT"
  }'

curl -X POST "{{CORE_URL}}/balances" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "ledger_id": "{{delivered_ledger}}",
    "identity_id": "{{identity_id}}",
    "currency": "UNIT"
  }'
```

```typescript TypeScript wrap
const available = await blnk.LedgerBalances.create({
  ledger_id: '{{available_ledger}}',
  identity_id: '{{identity_id}}',
  currency: 'UNIT',
});

const sold = await blnk.LedgerBalances.create({
  ledger_id: '{{sold_ledger}}',
  identity_id: '{{identity_id}}',
  currency: 'UNIT',
});

const inTransit = await blnk.LedgerBalances.create({
  ledger_id: '{{in_transit_ledger}}',
  identity_id: '{{identity_id}}',
  currency: 'UNIT',
});

const delivered = await blnk.LedgerBalances.create({
  ledger_id: '{{delivered_ledger}}',
  identity_id: '{{identity_id}}',
  currency: 'UNIT',
});
```

```go Go wrap
available, _, _ := client.LedgerBalance.Create(blnkgo.CreateLedgerBalanceRequest{
    LedgerID: "{{available_ledger}}",
    IdentityID: "{{identity_id}}",
    Currency: "UNIT",
})

sold, _, _ := client.LedgerBalance.Create(blnkgo.CreateLedgerBalanceRequest{
    LedgerID: "{{sold_ledger}}",
    IdentityID: "{{identity_id}}",
    Currency: "UNIT",
})

inTransit, _, _ := client.LedgerBalance.Create(blnkgo.CreateLedgerBalanceRequest{
    LedgerID: "{{in_transit_ledger}}",
    IdentityID: "{{identity_id}}",
    Currency: "UNIT",
})

delivered, _, _ := client.LedgerBalance.Create(blnkgo.CreateLedgerBalanceRequest{
    LedgerID: "{{delivered_ledger}}",
    IdentityID: "{{identity_id}}",
    Currency: "UNIT",
})
```

```python Python wrap
available = blnk.ledger_balances.create({
  "ledger_id": "{{available_ledger}}",
  "identity_id": "{{identity_id}}",
  "currency": "UNIT",
})

sold = blnk.ledger_balances.create({
  "ledger_id": "{{sold_ledger}}",
  "identity_id": "{{identity_id}}",
  "currency": "UNIT",
})

in_transit = blnk.ledger_balances.create({
  "ledger_id": "{{in_transit_ledger}}",
  "identity_id": "{{identity_id}}",
  "currency": "UNIT",
})

delivered = blnk.ledger_balances.create({
  "ledger_id": "{{delivered_ledger}}",
  "identity_id": "{{identity_id}}",
  "currency": "UNIT",
})
```

```java Java wrap
ApiResponse<JsonNode> available = blnk.ledgerBalances().create(
    CreateLedgerBalance.create()
        .ledgerId("{{available_ledger}}")
        .identityId("{{identity_id}}")
        .currency("UNIT"));

ApiResponse<JsonNode> sold = blnk.ledgerBalances().create(
    CreateLedgerBalance.create()
        .ledgerId("{{sold_ledger}}")
        .identityId("{{identity_id}}")
        .currency("UNIT"));

ApiResponse<JsonNode> inTransit = blnk.ledgerBalances().create(
    CreateLedgerBalance.create()
        .ledgerId("{{in_transit_ledger}}")
        .identityId("{{identity_id}}")
        .currency("UNIT"));

ApiResponse<JsonNode> delivered = blnk.ledgerBalances().create(
    CreateLedgerBalance.create()
        .ledgerId("{{delivered_ledger}}")
        .identityId("{{identity_id}}")
        .currency("UNIT"));
```

</CodeGroup>
</TutorialCode>

    <TutorialFrame>
      <Tabs>
        <Tab title="Available">
          <TutorialForm id="create-available-balance" method="POST" path="/balances" save="available_balance_id" response="balance_id">
            <TutorialValue name="currency" value="UNIT" />
            <TutorialInput name="available_ledger" as="ledger_id" label="Ledger" from="available_ledger_id" />
            <TutorialInput name="identity_id" label="Identity" from="identity_id" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>

    <TutorialFrame>
      <Tabs>
        <Tab title="Sold">
          <TutorialForm id="create-sold-balance" method="POST" path="/balances" save="sold_balance_id" response="balance_id">
            <TutorialValue name="currency" value="UNIT" />
            <TutorialInput name="sold_ledger" as="ledger_id" label="Ledger" from="sold_ledger_id" />
            <TutorialInput name="identity_id" label="Identity" from="identity_id" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>

    <TutorialFrame>
      <Tabs>
        <Tab title="In transit">
          <TutorialForm id="create-in-transit-balance" method="POST" path="/balances" save="in_transit_balance_id" response="balance_id">
            <TutorialValue name="currency" value="UNIT" />
            <TutorialInput name="in_transit_ledger" as="ledger_id" label="Ledger" from="in_transit_ledger_id" />
            <TutorialInput name="identity_id" label="Identity" from="identity_id" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>

    <TutorialFrame>
      <Tabs>
        <Tab title="Delivered">
          <TutorialForm id="create-delivered-balance" method="POST" path="/balances" save="delivered_balance_id" response="balance_id">
            <TutorialValue name="currency" value="UNIT" />
            <TutorialInput name="delivered_ledger" as="ledger_id" label="Ledger" from="delivered_ledger_id" />
            <TutorialInput name="identity_id" label="Identity" from="identity_id" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Receive the stock">
    Alice's 30 phones arrive from her supplier. We move them from `@StockIntake` to her available balance, with the SKU in metadata so this transaction shows up when we read the phone's history in step 10.

    A phone can't be split into fractions, so we set `precision` to `1`. The amount is then just the number of phones, 30.

    `skip_queue` records the transaction straight away, so the 30 phones are ready to sell before Bob orders.

    After the transaction, `@StockIntake` shows -30, one for each phone Alice received.

    <Note>
      For this transaction to go through, we need to turn on [overdraft](/transactions/overdrafts). Without it, `@StockIntake` is created with a 0 balance, and Blnk would reject the record because the source has insufficient units.
    </Note>

    Receive the stock:

<TutorialCode>
<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/transactions" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "precise_amount": {{quantity_precise}},
    "precision": 1,
    "reference": "RCV-20261019-090000",
    "description": "Stock received",
    "currency": "UNIT",
    "source": "@StockIntake",
    "destination": "{{destination}}",
    "allow_overdraft": true,
    "skip_queue": true,
    "meta_data": {
      "sku": "{{receipt_sku}}"
    }
  }'
```

```typescript TypeScript wrap
const response = await blnk.Transactions.create({
  precise_amount: {{quantity}},
  precision: 1,
  reference: 'RCV-20261019-090000',
  description: 'Stock received',
  currency: 'UNIT',
  source: '@StockIntake',
  destination: '{{destination}}',
  allow_overdraft: true,
  skip_queue: true,
  meta_data: {
    sku: '{{receipt_sku}}',
  },
});
```

```go Go wrap
transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
    ParentTransaction: blnkgo.ParentTransaction{
        PreciseAmount: big.NewInt({{quantity}}),
        Precision: 1,
        Reference: "RCV-20261019-090000",
        Description: "Stock received",
        Currency: "UNIT",
        Source: "@StockIntake",
        Destination: "{{destination}}",
        SkipQueue: true,
        MetaData: blnkgo.MetaData{
            "sku": "{{receipt_sku}}",
        },
    },
    AllowOverdraft: true,
})
```

```python Python wrap
response = blnk.transactions.create({
  "precise_amount": {{quantity}},
  "precision": 1,
  "reference": "RCV-20261019-090000",
  "description": "Stock received",
  "currency": "UNIT",
  "source": "@StockIntake",
  "destination": "{{destination}}",
  "allow_overdraft": True,
  "skip_queue": True,
  "meta_data": {
    "sku": "{{receipt_sku}}",
  },
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.transactions().create(
    CreateTransactions.create()
        .preciseAmount({{quantity}})
        .precision(1)
        .reference("RCV-20261019-090000")
        .description("Stock received")
        .currency("UNIT")
        .source("@StockIntake")
        .destination("{{destination}}")
        .allowOverdraft(true)
        .skipQueue(true)
        .metaData(Map.of("sku", "{{receipt_sku}}")));
```

</CodeGroup>
</TutorialCode>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="receive-stock" method="POST" path="/transactions">
            <TutorialValue name="reference" value="RCV-20261019-090000" />
            <TutorialValue name="description" value="Stock received" />
            <TutorialValue name="currency" value="UNIT" />
            <TutorialValue name="source" value="@StockIntake" />
            <TutorialValue name="allow_overdraft" value="true" type="boolean" />
            <TutorialValue name="skip_queue" value="true" type="boolean" />
            <TutorialInput name="quantity" label="Units" value="30" type="amount" precision="1" />
            <TutorialInput name="destination" label="Destination" from="available_balance_id" />
            <TutorialInput name="receipt_sku" as="sku" label="SKU" value="IP16-BLK-128" group="meta_data" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Hold the sale">
    Bob checks out with 2 phones. We record the sale from Alice's available balance to her sold balance as [inflight](/transactions/inflight/create-inflight), so his 2 phones are reserved and no one else can buy them.

    Each transaction's reference starts with Bob's order ID, `ord_4412`, and the SKU goes in metadata. `skip_queue` records the hold straight away, so we can commit it in the next step.

    Blnk rejects a second transaction with a reference it has already seen. If a request times out and your code sends it again, the phones still move only once.

    <Tip>
      If Bob's payment fails or he cancels, [void](/transactions/inflight/void-inflight) the hold, and the 2 phones go back to Alice's available balance. Set `inflight_expiry_date` as well, so an abandoned checkout releases the phones on its own.
    </Tip>

    Hold the sale:

<TutorialCode>
<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/transactions" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "precise_amount": 2,
    "precision": 1,
    "reference": "ord_4412_sold",
    "description": "Checkout paid",
    "currency": "UNIT",
    "source": "{{sale_source}}",
    "destination": "{{sale_destination}}",
    "inflight": true,
    "skip_queue": true,
    "meta_data": {
      "order_id": "ord_4412",
      "sku": "IP16-BLK-128"
    }
  }'
```

```typescript TypeScript wrap
const response = await blnk.Transactions.create({
  precise_amount: 2,
  precision: 1,
  reference: 'ord_4412_sold',
  description: 'Checkout paid',
  currency: 'UNIT',
  source: '{{sale_source}}',
  destination: '{{sale_destination}}',
  inflight: true,
  skip_queue: true,
  meta_data: {
    order_id: 'ord_4412',
    sku: 'IP16-BLK-128',
  },
});
```

```go Go wrap
transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
    ParentTransaction: blnkgo.ParentTransaction{
        PreciseAmount: big.NewInt(2),
        Precision: 1,
        Reference: "ord_4412_sold",
        Description: "Checkout paid",
        Currency: "UNIT",
        Source: "{{sale_source}}",
        Destination: "{{sale_destination}}",
        SkipQueue: true,
        MetaData: blnkgo.MetaData{
            "order_id": "ord_4412",
            "sku": "IP16-BLK-128",
        },
    },
    Inflight: true,
})
```

```python Python wrap
response = blnk.transactions.create({
  "precise_amount": 2,
  "precision": 1,
  "reference": "ord_4412_sold",
  "description": "Checkout paid",
  "currency": "UNIT",
  "source": "{{sale_source}}",
  "destination": "{{sale_destination}}",
  "inflight": True,
  "skip_queue": True,
  "meta_data": {
    "order_id": "ord_4412",
    "sku": "IP16-BLK-128",
  },
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.transactions().create(
    CreateTransactions.create()
        .preciseAmount(2)
        .precision(1)
        .reference("ord_4412_sold")
        .description("Checkout paid")
        .currency("UNIT")
        .source("{{sale_source}}")
        .destination("{{sale_destination}}")
        .inflight(true)
        .skipQueue(true)
        .metaData(Map.of("order_id", "ord_4412", "sku", "IP16-BLK-128")));
```

</CodeGroup>
</TutorialCode>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="hold-sale" method="POST" path="/transactions" save="sale_transaction_id" response="transaction_id">
            <TutorialValue name="sale_amount" as="precise_amount" value="2" type="number" />
            <TutorialValue name="sale_precision" as="precision" value="1" type="number" />
            <TutorialValue name="sale_reference" as="reference" value="ord_4412_sold" />
            <TutorialValue name="sale_description" as="description" value="Checkout paid" />
            <TutorialValue name="sale_currency" as="currency" value="UNIT" />
            <TutorialValue name="inflight" value="true" type="boolean" />
            <TutorialValue name="sale_skip_queue" as="skip_queue" value="true" type="boolean" />
            <TutorialValue name="sale_order" as="order_id" value="ord_4412" group="meta_data" />
            <TutorialValue name="sale_sku" as="sku" value="IP16-BLK-128" group="meta_data" />
            <TutorialInput name="sale_source" as="source" label="Source" from="available_balance_id" />
            <TutorialInput name="sale_destination" as="destination" label="Destination" from="sold_balance_id" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Commit the sale">
    Bob's payment goes through, so we [commit](/transactions/inflight/commit-inflight) the hold. The 2 phones move from Alice's available balance to her sold balance.

    After the commit, her available balance is 28 and her sold balance is 2.

    <Note>
      Blnk queues the commit, so the response can still show `INFLIGHT`. The balances update once the commit is processed.
    </Note>

    <Tip>
      A void only works before the commit. To undo a sale after it, record a new transaction from sold back to available.
    </Tip>

    Commit the sale:

<TutorialCode>
<CodeGroup>

```bash cURL wrap
curl -X PUT "{{CORE_URL}}/transactions/inflight/{{sale_transaction_id}}" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "commit"
  }'
```

```typescript TypeScript wrap
const response = await blnk.Transactions.updateStatus(
  '{{sale_transaction_id}}',
  {
    status: 'commit',
  },
);
```

```go Go wrap
transaction, _, _ := client.Transaction.Update(
    "{{sale_transaction_id}}",
    blnkgo.UpdateStatus{
        Status: blnkgo.InflightStatusCommit,
    },
)
```

```python Python wrap
response = blnk.transactions.update_status(
  "{{sale_transaction_id}}",
  {
    "status": "commit",
  },
)
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.transactions().updateStatus(
    "{{sale_transaction_id}}",
    UpdateTransactionStatus.create()
        .status("commit"));
```

</CodeGroup>
</TutorialCode>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="commit-sale" method="PUT" path="/transactions/inflight/{sale_transaction_id}">
            <TutorialValue name="status" value="commit" />
            <TutorialInput name="sale_transaction_id" label="Transaction" from="sale_transaction_id" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Record the pickup">
    The courier picks up Bob's phones. We record a transaction from Alice's sold balance to her in-transit balance.

    This one is not inflight, because the pickup has already happened. After it, her sold balance is back to 0 and her in-transit balance is 2.

    Record the pickup:

<TutorialCode>
<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/transactions" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "precise_amount": 2,
    "precision": 1,
    "reference": "ord_4412_in_transit",
    "description": "Picked up for delivery",
    "currency": "UNIT",
    "source": "{{pickup_source}}",
    "destination": "{{pickup_destination}}",
    "skip_queue": true,
    "meta_data": {
      "order_id": "ord_4412",
      "sku": "IP16-BLK-128"
    }
  }'
```

```typescript TypeScript wrap
const response = await blnk.Transactions.create({
  precise_amount: 2,
  precision: 1,
  reference: 'ord_4412_in_transit',
  description: 'Picked up for delivery',
  currency: 'UNIT',
  source: '{{pickup_source}}',
  destination: '{{pickup_destination}}',
  skip_queue: true,
  meta_data: {
    order_id: 'ord_4412',
    sku: 'IP16-BLK-128',
  },
});
```

```go Go wrap
transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
    ParentTransaction: blnkgo.ParentTransaction{
        PreciseAmount: big.NewInt(2),
        Precision: 1,
        Reference: "ord_4412_in_transit",
        Description: "Picked up for delivery",
        Currency: "UNIT",
        Source: "{{pickup_source}}",
        Destination: "{{pickup_destination}}",
        SkipQueue: true,
        MetaData: blnkgo.MetaData{
            "order_id": "ord_4412",
            "sku": "IP16-BLK-128",
        },
    },
})
```

```python Python wrap
response = blnk.transactions.create({
  "precise_amount": 2,
  "precision": 1,
  "reference": "ord_4412_in_transit",
  "description": "Picked up for delivery",
  "currency": "UNIT",
  "source": "{{pickup_source}}",
  "destination": "{{pickup_destination}}",
  "skip_queue": True,
  "meta_data": {
    "order_id": "ord_4412",
    "sku": "IP16-BLK-128",
  },
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.transactions().create(
    CreateTransactions.create()
        .preciseAmount(2)
        .precision(1)
        .reference("ord_4412_in_transit")
        .description("Picked up for delivery")
        .currency("UNIT")
        .source("{{pickup_source}}")
        .destination("{{pickup_destination}}")
        .skipQueue(true)
        .metaData(Map.of("order_id", "ord_4412", "sku", "IP16-BLK-128")));
```

</CodeGroup>
</TutorialCode>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="record-pickup" method="POST" path="/transactions">
            <TutorialValue name="pickup_amount" as="precise_amount" value="2" type="number" />
            <TutorialValue name="pickup_precision" as="precision" value="1" type="number" />
            <TutorialValue name="pickup_reference" as="reference" value="ord_4412_in_transit" />
            <TutorialValue name="pickup_description" as="description" value="Picked up for delivery" />
            <TutorialValue name="pickup_currency" as="currency" value="UNIT" />
            <TutorialValue name="pickup_skip_queue" as="skip_queue" value="true" type="boolean" />
            <TutorialValue name="pickup_order" as="order_id" value="ord_4412" group="meta_data" />
            <TutorialValue name="pickup_sku" as="sku" value="IP16-BLK-128" group="meta_data" />
            <TutorialInput name="pickup_source" as="source" label="Source" from="sold_balance_id" />
            <TutorialInput name="pickup_destination" as="destination" label="Destination" from="in_transit_balance_id" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Record the delivery">
    Bob receives his phones. We record a transaction from Alice's in-transit balance to her delivered balance.

    After it, her in-transit balance is back to 0 and her delivered balance is 2.

    Record the delivery:

<TutorialCode>
<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/transactions" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "precise_amount": 2,
    "precision": 1,
    "reference": "ord_4412_delivered",
    "description": "Customer confirmed delivery",
    "currency": "UNIT",
    "source": "{{delivery_source}}",
    "destination": "{{delivery_destination}}",
    "skip_queue": true,
    "meta_data": {
      "order_id": "ord_4412",
      "sku": "IP16-BLK-128"
    }
  }'
```

```typescript TypeScript wrap
const response = await blnk.Transactions.create({
  precise_amount: 2,
  precision: 1,
  reference: 'ord_4412_delivered',
  description: 'Customer confirmed delivery',
  currency: 'UNIT',
  source: '{{delivery_source}}',
  destination: '{{delivery_destination}}',
  skip_queue: true,
  meta_data: {
    order_id: 'ord_4412',
    sku: 'IP16-BLK-128',
  },
});
```

```go Go wrap
transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
    ParentTransaction: blnkgo.ParentTransaction{
        PreciseAmount: big.NewInt(2),
        Precision: 1,
        Reference: "ord_4412_delivered",
        Description: "Customer confirmed delivery",
        Currency: "UNIT",
        Source: "{{delivery_source}}",
        Destination: "{{delivery_destination}}",
        SkipQueue: true,
        MetaData: blnkgo.MetaData{
            "order_id": "ord_4412",
            "sku": "IP16-BLK-128",
        },
    },
})
```

```python Python wrap
response = blnk.transactions.create({
  "precise_amount": 2,
  "precision": 1,
  "reference": "ord_4412_delivered",
  "description": "Customer confirmed delivery",
  "currency": "UNIT",
  "source": "{{delivery_source}}",
  "destination": "{{delivery_destination}}",
  "skip_queue": True,
  "meta_data": {
    "order_id": "ord_4412",
    "sku": "IP16-BLK-128",
  },
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.transactions().create(
    CreateTransactions.create()
        .preciseAmount(2)
        .precision(1)
        .reference("ord_4412_delivered")
        .description("Customer confirmed delivery")
        .currency("UNIT")
        .source("{{delivery_source}}")
        .destination("{{delivery_destination}}")
        .skipQueue(true)
        .metaData(Map.of("order_id", "ord_4412", "sku", "IP16-BLK-128")));
```

</CodeGroup>
</TutorialCode>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="record-delivery" method="POST" path="/transactions">
            <TutorialValue name="delivery_amount" as="precise_amount" value="2" type="number" />
            <TutorialValue name="delivery_precision" as="precision" value="1" type="number" />
            <TutorialValue name="delivery_reference" as="reference" value="ord_4412_delivered" />
            <TutorialValue name="delivery_description" as="description" value="Customer confirmed delivery" />
            <TutorialValue name="delivery_currency" as="currency" value="UNIT" />
            <TutorialValue name="delivery_skip_queue" as="skip_queue" value="true" type="boolean" />
            <TutorialValue name="delivery_order" as="order_id" value="ord_4412" group="meta_data" />
            <TutorialValue name="delivery_sku" as="sku" value="IP16-BLK-128" group="meta_data" />
            <TutorialInput name="delivery_source" as="source" label="Source" from="in_transit_balance_id" />
            <TutorialInput name="delivery_destination" as="destination" label="Destination" from="delivered_balance_id" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Read the phone's history">
    Every transaction for the phone has its SKU in metadata, so one [filter](/search/db/filtering) on `meta_data.sku` returns its whole history. We sort it by `created_at`, newest first.

    The results show the 30 phones Alice received, the sale hold with its commit, the pickup, and the delivery. If Alice asks why her count changed, this is the record you show her.

    The filter returns 20 results by default. Pass `limit` (up to 100) and `offset` to page through a longer history.

    <Tip>
      With more than one seller, the same SKU can belong to several identities. Add `seller_id` to each transaction's metadata and filter on both fields.
    </Tip>

    Read the history:

<TutorialCode>
<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/transactions/filter" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": [
      {
        "field": "meta_data.sku",
        "operator": "eq",
        "value": "{{history_sku}}"
      }
    ],
    "sort_by": "created_at",
    "sort_order": "desc"
  }'
```

```typescript TypeScript wrap
const response = await blnk.Search.filter(
  {
    filters: [
      {
        field: 'meta_data.sku',
        operator: 'eq',
        value: '{{history_sku}}',
      },
    ],
    sort_by: 'created_at',
    sort_order: 'desc',
  },
  'transactions',
);
```

```go Go wrap
results, _, _ := client.Transaction.Filter(blnkgo.FilterParams{
    Filters: []blnkgo.Filter{
        {
            Field: "meta_data.sku",
            Operator: blnkgo.OpEqual,
            Value: "{{history_sku}}",
        },
    },
    SortBy: "created_at",
    SortOrder: "desc",
})
```

```python Python wrap
response = blnk.search.filter(
  {
    "filters": [
      {
        "field": "meta_data.sku",
        "operator": "eq",
        "value": "{{history_sku}}",
      },
    ],
    "sort_by": "created_at",
    "sort_order": "desc",
  },
  "transactions",
)
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.search().filter(
    FilterParams.create()
        .filters(List.of(
            FilterCondition.create()
                .field("meta_data.sku")
                .operator("eq")
                .value("{{history_sku}}")))
        .sortBy("created_at")
        .sortOrder("desc"),
    "transactions");
```

</CodeGroup>
</TutorialCode>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="read-history" method="POST" path="/transactions/filter">
            <TutorialValue name="filter_field" as="field" value="meta_data.sku" list="filters" index="0" />
            <TutorialValue name="filter_operator" as="operator" value="eq" list="filters" index="0" />
            <TutorialValue name="sort_by" value="created_at" />
            <TutorialValue name="sort_order" value="desc" />
            <TutorialInput name="history_sku" as="value" label="SKU" value="IP16-BLK-128" list="filters" index="0" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Read a past stock count">
    Alice may ask how many phones she had available at an earlier time. A [historical balance](/balances/historical-balances) returns her available balance as it was then, not as it is today.

    Pass her available `balance_id` and a timestamp from your own run, in RFC 3339 format. A time after the transaction in step 5 and before the hold in step 6 shows 30.

    A time after the sale is held returns 28. The historical balance counts the 2 held phones from the moment of the hold, even though her live `balance` still shows 30 until the commit.

    Read the available balance at a past time:

<CodeGroup>

```bash cURL wrap
curl -X GET "{{CORE_URL}}/balances/{{available_balance_id}}/at?timestamp={{timestamp}}" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}"
```

```typescript TypeScript wrap
const response = await blnk.LedgerBalances.getAt(
  '{{available_balance_id}}',
  {
    timestamp: '{{timestamp}}',
  },
);
```

```go Go wrap
timestamp, _ := time.Parse(time.RFC3339, "{{timestamp}}")

balance, _, _ := client.LedgerBalance.GetHistorical(
    "{{available_balance_id}}",
    timestamp,
    false,
)
```

```python Python wrap
response = blnk.ledger_balances.get_at(
  "{{available_balance_id}}",
  {
    "timestamp": "{{timestamp}}",
  },
)
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.ledgerBalances().getAt(
    "{{available_balance_id}}",
    GetBalanceAtRequest.create()
        .timestamp("{{timestamp}}"));
```

</CodeGroup>

  </TutorialStep>

  <TutorialStep title="Confirmation">
    Alice should now have 28 phones in her available balance and 2 in her delivered balance. Her sold and in-transit balances are back to 0, and `@StockIntake` is at -30.

    If you have a Blnk Cloud account or an Enterprise license, you can confirm this by checking the dashboard or using the [CLI](/cli/balances).

    Print every balance:

```bash List balances wrap
blnk balances list
```

    <Tip>
      If Bob sends a phone back, add a returned balance and move the unit from delivered to returned. After inspection, move it back to available, or to a damaged balance if it can't be sold again.
    </Tip>
  </TutorialStep>
</TutorialSteps>