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

# Transactions

> List and read transactions: ledger fields, flattened customer identity, reconstructed line items, applied offers, and related money lineage.

## Overview

A transaction is the closed record of one settled interaction: who was identified, what they bought, which offers applied, how they paid, and the amounts that moved. Two read operations are available today: list transactions and read one transaction's full detail.

Both reads authenticate with an `x-api-key` and are scoped to the integration in the path. Reads do not carry a request envelope. Money is always a minor-units integer paired with an explicit ISO-4217 `currency` (for example `amount_minor: 4500`, `currency: QAR`).

<Info>
  A transaction is produced by accepting a wallet payment ([Payments](/guides/payments)) or running a top-up ([Top-up](/guides/topup)). This page covers reading those records back.
</Info>

## Operations

<Columns cols="2">
  <Card title="List transactions" icon="list" href="#list-transactions">
    `GET /{integrationId}/transactions`. A cursor-paged feed of transaction summaries.
  </Card>

  <Card title="Get one transaction" icon="file-text" href="#get-one-transaction">
    `GET /{integrationId}/transactions/{transactionId}`. Full detail for a single record.
  </Card>
</Columns>

## List transactions

`GET /{integrationId}/transactions` returns a cursor-paged feed of transaction summaries, scoped to the integration named in the path. The summary flattens the customer-identity context to top-level fields (`customer_phone`, `customer_name`, `identified_method`) for a cashier till view.

By default the list returns only identified transactions. Pass `include_unidentified=true` to merge in anonymous-session rows. Redemption secrets held in transaction metadata (session code, QR nonce, reward code) are stripped server-side and never returned.

### Query parameters

<ParamField query="status" param-type="string" required="false">
  Filter by transaction status. One of `pending`, `completed`, `failed`, `partially_refunded`, `refunded`.
</ParamField>

<ParamField query="include_unidentified" param-type="boolean" required="false">
  When `true`, merge in transactions from anonymous sessions (no resolved customer). Default `false`.
</ParamField>

<ParamField query="branch_id" param-type="string" required="false">
  Filter to a single branch (UUID).
</ParamField>

<ParamField query="created_from" param-type="string" required="false">
  Inclusive lower bound on `created_at`, RFC3339 date-time.
</ParamField>

<ParamField query="created_to" param-type="string" required="false">
  Inclusive upper bound on `created_at`, RFC3339 date-time.
</ParamField>

<ParamField query="cursor" param-type="string" required="false">
  Opaque cursor from the prior page's `meta.next_cursor`.
</ParamField>

<ParamField query="limit" param-type="integer" required="false">
  Page size. Default 50, maximum 200.
</ParamField>

<CodeGroup>
  ```bash 200 OK wrap="true" theme={null}
  curl "https://api.feddi.io/v1/partner/{integrationId}/transactions?status=completed&limit=50" \
    -H "x-api-key: $FEDDI_API_KEY"
  ```

  ```json 200 OK theme={null}
  {
    "ok": true,
    "data": [
      {
        "transaction_id": "tx_77c1",
        "receipt_number": "RCT-2026-000812",
        "transaction_type": "PAYMENT",
        "transaction_subtype": "BALANCE",
        "status": "completed",
        "amount_minor": 4500,
        "currency": "QAR",
        "applied_discount_minor": 500,
        "customer_phone": "+97433001122",
        "customer_name": "Layla A.",
        "identified_method": "qr",
        "branch_id": "22222222-2222-2222-2222-222222222222",
        "branch_name": "City Center",
        "partner_session_id": "ord-99812",
        "feddi_session_id": "33333333-3333-3333-3333-333333333333",
        "created_at": "2026-06-05T09:14:00Z"
      }
    ],
    "error": null,
    "meta": {
      "request_id": "req_tl1",
      "idempotency_replayed": false,
      "api_version": "2026-06-01",
      "next_cursor": "eyJjIjoxMDB9",
      "has_more": true,
      "data_completeness_score": 71
    }
  }
  ```

  ```json 400 Bad request theme={null}
  {
    "ok": false,
    "data": null,
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "Invalid cursor."
    },
    "meta": {
      "request_id": "req_tl2",
      "api_version": "2026-06-01"
    }
  }
  ```
</CodeGroup>

### Response fields

Each row in `data` is a transaction summary.

<ResponseField name="transaction_id" field-type="string" required="true">
  The transaction id.
</ResponseField>

<ResponseField name="receipt_number" field-type="string">
  The stable receipt-of-record number, when minted. Nullable.
</ResponseField>

<ResponseField name="transaction_type" field-type="string" required="true">
  One of `PAYMENT`, `DEPOSIT`, `WITHDRAW`, `TRANSFER`, `REWARD`, `BONUS_CREDIT`.
</ResponseField>

<ResponseField name="transaction_subtype" field-type="string">
  One of `BANK_TRANSFER`, `CASH`, `BALANCE`, `CRYPTO`, `POINTS`, `CARD`, `CASHBACK`, `BONUS_CREDIT`, `INCENTIVE_CLAWBACK`. Nullable.
</ResponseField>

<ResponseField name="status" field-type="string" required="true">
  One of `pending`, `completed`, `failed`, `partially_refunded`, `refunded`.
</ResponseField>

<ResponseField name="amount_minor" field-type="integer" required="true">
  Transaction amount in minor units. Server-authoritative.
</ResponseField>

<ResponseField name="currency" field-type="string" required="true">
  ISO-4217 currency code, 3 characters.
</ResponseField>

<ResponseField name="applied_discount_minor" field-type="integer">
  Total discount applied to this transaction, minor units. Nullable.
</ResponseField>

<ResponseField name="customer_phone" field-type="string">
  E.164 phone of the identified customer. `null` when unidentified.
</ResponseField>

<ResponseField name="customer_name" field-type="string">
  Identified customer name. Nullable.
</ResponseField>

<ResponseField name="identified_method" field-type="string">
  How the payer was resolved. One of `phone`, `qr`, `short_code`, `card_fingerprint`, `provider_customer_id`, `unidentified`. Nullable.
</ResponseField>

<ResponseField name="branch_id" field-type="string">
  Branch UUID. Nullable.
</ResponseField>

<ResponseField name="branch_name" field-type="string">
  Branch name. Nullable.
</ResponseField>

<ResponseField name="partner_session_id" field-type="string">
  The partner's order reference. Nullable.
</ResponseField>

<ResponseField name="feddi_session_id" field-type="string">
  The Feddi session UUID that produced this transaction. Nullable.
</ResponseField>

<ResponseField name="created_at" field-type="string" required="true">
  RFC3339 date-time the transaction was created.
</ResponseField>

The `meta` block carries the pagination cursor.

<ResponseField name="next_cursor" field-type="string">
  Pass this back as the `cursor` query parameter to fetch the next page. Absent on the last page.
</ResponseField>

<ResponseField name="has_more" field-type="boolean">
  `true` when another page exists. Stop paging when it is `false`.
</ResponseField>

<ResponseField name="data_completeness_score" field-type="integer">
  An opaque integer Feddi may use internally. No action required.
</ResponseField>

## Get one transaction

`GET /{integrationId}/transactions/{transactionId}` returns the full detail for one record: the flattened `customer`, the related wallet, program, branch, brand, and enterprise inlined; reconstructed `line_items`; the `applied_offers` that touched the charge; and `related_transactions` so you can render the full money lineage of an order (refunds, the original deposit, sibling rows that share a multi-transaction group).

All amounts are minor-units integers with an explicit `currency`. Metadata is sanitized server-side, so redemption secrets never appear in the response. The endpoint is read-only.

### Path parameters

<ParamField path="integrationId" param-type="string" required="true">
  The calling integration's id (UUID). Must match the integration the API key is scoped to.
</ParamField>

<ParamField path="transactionId" param-type="string" required="true">
  The transaction id (UUID).
</ParamField>

<CodeGroup>
  ```bash 200 OK wrap="true" theme={null}
  curl "https://api.feddi.io/v1/partner/{integrationId}/transactions/tx_77c1" \
    -H "x-api-key: $FEDDI_API_KEY"
  ```

  ```json 200 OK theme={null}
  {
    "ok": true,
    "data": {
      "transaction_id": "tx_77c1",
      "receipt_number": "RCT-2026-000812",
      "transaction_type": "PAYMENT",
      "transaction_subtype": "BALANCE",
      "status": "completed",
      "amount_minor": 4500,
      "currency": "QAR",
      "applied_discount_minor": 500,
      "customer": {
        "wallet_user_id": "wu_5512",
        "phone": "+97433001122",
        "name": "Layla A.",
        "identified_method": "qr",
        "customer_state": "verified"
      },
      "wallet_id": "wal_5512",
      "wallet_program_id": "wp_77",
      "wallet_program_name": "VIP Rewards",
      "branch": { "id": "22222222-2222-2222-2222-222222222222", "name": "City Center", "status": "ACTIVE" },
      "brand": { "id": "br_01", "name": "Acme Coffee", "status": "ACTIVE" },
      "enterprise": { "id": "11111111-1111-1111-1111-111111111111", "name": "Acme Corp" },
      "partner_session_id": "ord-99812",
      "feddi_session_id": "33333333-3333-3333-3333-333333333333",
      "line_items": [
        { "sku": "LATTE-L", "name": "Large Latte", "quantity": 2, "unit_price_minor": 2000, "extended_price_minor": 4000, "discount_minor": 0, "tax_minor": 200, "category": "beverages" },
        { "sku": "CROISSANT", "name": "Butter Croissant", "quantity": 1, "unit_price_minor": 1000, "extended_price_minor": 1000, "discount_minor": 500, "tax_minor": 25, "category": "bakery" }
      ],
      "applied_offers": [
        { "offer_id": "off_22", "name": "Croissant 50% off", "discount_minor": 500, "source": "rule" }
      ],
      "related_transactions": [
        { "transaction_id": "tx_dep_44", "transaction_type": "DEPOSIT", "amount_minor": 10000, "status": "completed", "created_at": "2026-06-04T18:00:00Z" }
      ],
      "created_at": "2026-06-05T09:14:00Z",
      "updated_at": "2026-06-05T09:14:00Z"
    },
    "error": null,
    "meta": { "request_id": "req_td1", "idempotency_replayed": false, "api_version": "2026-06-01", "data_completeness_score": 92 }
  }
  ```

  ```json 404 Not found theme={null}
  {
    "ok": false,
    "data": null,
    "error": {
      "code": "NOT_FOUND",
      "message": "No transaction with this id is visible to the calling integration."
    },
    "meta": {
      "request_id": "req_td2",
      "api_version": "2026-06-01"
    }
  }
  ```
</CodeGroup>

### Response fields

The `data` object is a transaction detail. It carries the same top-level ledger fields as the summary (`transaction_id`, `transaction_type`, `transaction_subtype`, `status`, `amount_minor`, `currency`, `applied_discount_minor`, `receipt_number`, `created_at`, plus `updated_at`), with these additional structured fields.

<ResponseField name="customer" field-type="object">
  The identified customer, flattened. Fields: `wallet_user_id` (string, nullable), `phone` (string, nullable, E.164), `name` (string, nullable), `identified_method` (string, nullable, same enum as the summary), and `customer_state` (string, nullable, one of `verified`, `pending_proof`).
</ResponseField>

<ResponseField name="wallet_id" field-type="string">
  The receiver or sender wallet id depending on transaction direction. Nullable.
</ResponseField>

<ResponseField name="wallet_program_id" field-type="string">
  The wallet program id. Nullable.
</ResponseField>

<ResponseField name="wallet_program_name" field-type="string">
  The wallet program name. Nullable.
</ResponseField>

<ResponseField name="branch" field-type="object">
  Inlined branch with `id`, `name`, `status`. Nullable.
</ResponseField>

<ResponseField name="brand" field-type="object">
  Inlined brand with `id`, `name`, `status`. Nullable.
</ResponseField>

<ResponseField name="enterprise" field-type="object">
  Inlined enterprise with `id`, `name`. Nullable.
</ResponseField>

<ResponseField name="partner_session_id" field-type="string">
  The partner's order reference. Nullable.
</ResponseField>

<ResponseField name="feddi_session_id" field-type="string">
  The Feddi session UUID. Nullable.
</ResponseField>

<ResponseField name="line_items" field-type="array">
  Reconstructed order lines. Empty for thin, amount-only settlements. Each line carries `quantity` (integer, required) and `extended_price_minor` (integer, required), plus `sku`, `name`, `unit_price_minor`, `discount_minor`, `tax_minor`, and `category` (all nullable). All money fields are minor units.
</ResponseField>

<ResponseField name="applied_offers" field-type="array">
  Offers and promotional grants that touched this charge. Each entry carries `offer_id` (string, required), `discount_minor` (integer, required, the value applied), `name` (string, nullable), and `source` (string, nullable, one of `rule`, `engine`, `merchant_note`).
</ResponseField>

<ResponseField name="related_transactions" field-type="array">
  Sibling transactions in the same money lineage (refund, original deposit, multi-transaction sibling). Each entry carries `transaction_id` (string, required), `transaction_type` (string, required), `amount_minor` (integer, required), `status` (string, required), and `created_at` (string, nullable).
</ResponseField>

<ResponseField name="updated_at" field-type="string">
  RFC3339 date-time of the last update. Nullable.
</ResponseField>

<Info>
  `PENDING` and `REFUNDED` transactions are returned with their current status rather than omitted or transformed. Read `status` to branch.
</Info>

## Errors and access scope

Both reads are tenant-scoped to the integration in the path and the `x-api-key`.

<ResponseField name="VALIDATION_ERROR" field-type="400">
  Malformed query on the list read (bad cursor, invalid date, limit out of range).
</ResponseField>

<ResponseField name="INVALID_API_KEY" field-type="401">
  Missing or invalid `x-api-key`.
</ResponseField>

<ResponseField name="FORBIDDEN" field-type="403">
  The `{integrationId}` in the path does not match the key's integration, the key lacks transaction read access, or the transaction belongs to another integration.
</ResponseField>

<ResponseField name="NOT_FOUND" field-type="404">
  On the detail read, no transaction with this id is visible to the calling integration.
</ResponseField>

<Warning>
  A transaction owned by another integration resolves to `FORBIDDEN` (403), never `NOT_FOUND`. An id that is genuinely not visible to your integration is `NOT_FOUND` (404). Cross-tenant isolation is enforced per record. The error `code` is a string enum, never a bare HTTP status number. Branch on `code`, not on the status. See [Idempotency and errors](/concepts/idempotency-and-errors).
</Warning>

## Where to go next

<Columns cols="2">
  <Card title="Payments" href="/guides/payments" icon="credit-card">
    The promo-first debit that produces a wallet-payment transaction.
  </Card>

  <Card title="Top-up" href="/guides/topup" icon="wallet">
    The confirm and SKU-sale credits that produce DEPOSIT transactions.
  </Card>

  <Card title="Money classes" href="/concepts/money-classes" icon="coins">
    The actual and promotional split a detail row breaks out.
  </Card>

  <Card title="Idempotency and errors" href="/concepts/idempotency-and-errors" icon="triangle-alert">
    The typed error model and 403 vs 404 cross-tenant rules.
  </Card>
</Columns>
