# Reconcile settlements

> Pull settlement data from One API and tie it back to the deposits in your bank account and the records in your own system, line item by line item.

Source: https://hub.ozow.com/integration-methods/apis/settlements/reconcile-settlements/

This guide shows you how to pull settlement data out of the One API and tie it back to the deposits
in your bank account and the records in your own system.

## Where settlements fit

When a customer pays you through Ozow, the money lands with Ozow first. Ozow then transfers what
you're owed to your bank account. That transfer is a settlement.

How payments are grouped into settlements depends on your settlement model and the payment method.
By default Ozow groups transactions together into settlements, but there are several settlement
models, and yours may include a custom arrangement. Your specific setup is agreed during onboarding;
if you're not sure what applies to you, speak to your account manager.

For reconciliation, the practical consequence is: don't hard-code assumptions about how many records
a settlement contains, or how often settlements arrive. Read what the API returns.

There are two separate things to reconcile:

- **Did the customer pay?** Answered by transaction status.
- **Did Ozow pay me?** Answered by settlement data, this guide.

A transaction reaching `Complete` doesn't mean the money has reached you. Settlement happens later,
on a cycle that depends on the payment method. See
[Settlements](https://hub.ozow.com/payment-products/settlements-and-float/settlements.md) for how that works.

If you reconcile occasionally, the [Ozow Dashboard](https://dash.ozow.com) shows all of this with no
development work. Build this when you're reconciling regularly or at volume.

## What you'll build

Three endpoints, all under `https://one.ozow.com/v1`:

| Endpoint | Gives you |
|---|---|
| `GET /settlements` | Settlements for a date range, with totals |
| `GET /settlements/{id}` | A single settlement |
| `GET /settlements/{id}/lineitems` | Every record that makes up that settlement |

A typical daily job:

1. List settlements for the period.
2. Fetch the line items for each one.
3. Match each settlement to a deposit on your bank statement.
4. Match each line item to a record in your system.
5. Flag whatever doesn't match.

For complete field lists, all parameters and error responses, see the [One API reference](https://hub.ozow.com/api-reference/one-api.md).

## Before you start

You'll need your One API credentials from the [Ozow Dashboard](https://dash.ozow.com) and an OAuth
2.0 access token. See [Redirect to Ozow](https://hub.ozow.com/integration-methods/apis/payin/redirect-to-ozow.md) for how to obtain one.

Your One API client must have the `settlements` scope, and your token must request it. A token
issued for `payments` alone is refused by both endpoints on this page.

Every request takes a bearer token:

```http
Authorization: Bearer YOUR_ACCESS_TOKEN
```

> ℹ️ Send an `X-Correlation-ID` header with each request, any UUID you generate. It's returned in
> the response and passed through Ozow's internal systems, which makes it the fastest way for Ozow
> Support to trace a specific call. If you don't send one, Ozow generates it for you. Log whichever
> you end up with.

---

## Step 1: List your settlements

```endpoint
GET https://one.ozow.com/v1/settlements?fromDate=2026-09-01&toDate=2026-09-30
```

`fromDate` and `toDate` are required. You can also filter by `siteCode` or by a specific settlement
`reference`, and page with `limit` (max 50) and `offset`.

**cURL**

```bash
curl -X GET "https://one.ozow.com/v1/settlements?fromDate=2026-09-01&toDate=2026-09-30&limit=50" \
  -H "Authorization: Bearer <YOUR_ACCESS_TOKEN>" \
  -H "Accept: application/json" \
  -H "X-Correlation-ID: 8f14e45f-ceea-467a-9575-9f0e5a3f1b2c"
```

**C#**

```csharp
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Add("X-Correlation-ID", Guid.NewGuid().ToString());

var url = "https://one.ozow.com/v1/settlements" + "?fromDate=2026-09-01&toDate=2026-09-30&limit=50";

var response = await client.GetAsync(url);
var body = await response.Content.ReadAsStringAsync();
```

**PHP**

```php
$url = 'https://one.ozow.com/v1/settlements'
     . '?fromDate=2026-09-01&toDate=2026-09-30&limit=50';

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $accessToken,
        'Accept: application/json',
        'X-Correlation-ID: ' . $correlationId,
    ],
]);
$response = curl_exec($ch);
curl_close($ch);
```

**JavaScript**

```javascript
const params = new URLSearchParams({
  fromDate: "2026-09-01",
  toDate: "2026-09-30",
  limit: "50",
});

const response = await fetch(`https://one.ozow.com/v1/settlements?${params}`, {
  headers: {
    "Authorization": `Bearer ${accessToken}`,
    "Accept": "application/json",
    "X-Correlation-ID": crypto.randomUUID(),
  },
});
const data = await response.json();
```

**Python**

```python
import requests, uuid

response = requests.get(
    "https://one.ozow.com/v1/settlements",
    params={"fromDate": "2026-09-01", "toDate": "2026-09-30", "limit": 50},
    headers={
        "Authorization": f"Bearer {access_token}",
        "Accept": "application/json",
        "X-Correlation-ID": str(uuid.uuid4()),
    },
)
data = response.json()
```

### What comes back

```json
{
  "links": { "self": "...", "next": "..." },
  "results": [
    {
      "links": {
        "self": "https://one.ozow.com/v1/settlements/497f6eca-...",
        "lineItems": "https://one.ozow.com/v1/settlements/497f6eca-.../lineitems"
      },
      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
      "totalSettlement": { "currency": "ZAR", "value": 12450.00 },
      "totalFees": { "currency": "ZAR", "value": 187.25 },
      "lineItemCounts": {
        "total": 63,
        "transaction": 60,
        "refund": 2,
        "adjustment": 1,
        "payout": 0
      },
      "reference": "OZOW20260901ACME",
      "date": "2026-09-01",
      "bank": {
        "institutionId": "7c02f304-...",
        "institutionName": "Example Bank",
        "accountNumber": "1234567890",
        "branchCode": "5040"
      }
    }
  ],
  "meta": { "totalPages": 1, "totalItems": 1 }
}
```

The fields you'll use:

| Field | Use it for |
|---|---|
| `id` | Your primary key, and the path parameter for line items |
| `reference` | **Matching to your bank statement**: this is the reference that appears there |
| `totalSettlement` | The amount transferred to you |
| `totalFees` | Ozow fees across the settlement |
| `lineItemCounts` | A quick check that you've retrieved everything |
| `links.lineItems` | The URL for step 2, follow it rather than building it yourself |
| `bank` | Which of your accounts the settlement went to |

> ℹ️ Amounts are objects, not numbers: `{ "currency": "ZAR", "value": 12450.00 }`. Read `value`, and
> check `currency` if you take payments in more than one.

> ⚠️ There's no `status` on a settlement in the One API. If you need to know whether the money has
> actually landed, your bank statement is the answer.

---

## Step 2: Fetch the line items

Follow the `links.lineItems` URL from step 1, or build it:

```endpoint
GET https://one.ozow.com/v1/settlements/{id}/lineitems?limit=50&offset=0
```

**cURL**

```bash
curl -X GET "https://one.ozow.com/v1/settlements/497f6eca-6276-4993-bfeb-53cbbbba6f08/lineitems?limit=50" \
  -H "Authorization: Bearer <YOUR_ACCESS_TOKEN>" \
  -H "Accept: application/json"
```

**C#**

```csharp
var url = $"https://one.ozow.com/v1/settlements/{settlementId}/lineitems" + "?limit=50&offset=0";

var response = await client.GetAsync(url);
var body = await response.Content.ReadAsStringAsync();
```

**PHP**

```php
$url = "https://one.ozow.com/v1/settlements/{$settlementId}/lineitems"
     . '?limit=50&offset=0';

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $accessToken,
        'Accept: application/json',
    ],
]);
$response = curl_exec($ch);
curl_close($ch);
```

**JavaScript**

```javascript
const response = await fetch(
  `https://one.ozow.com/v1/settlements/${settlementId}/lineitems?limit=50&offset=0`,
  {
    headers: {
      Authorization: `Bearer ${accessToken}`,
      Accept: "application/json",
    },
  },
);
const data = await response.json();
```

**Python**

```python
response = requests.get(
    f"https://one.ozow.com/v1/settlements/{settlement_id}/lineitems",
    params={"limit": 50, "offset": 0},
    headers={
        "Authorization": f"Bearer {access_token}",
        "Accept": "application/json",
    },
)
data = response.json()
```

Each line item looks like this:

```json
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "siteCode": "ACM-ACM-001",
  "reference": "ORDER-10432",
  "description": null,
  "amount": { "currency": "ZAR", "value": 250.00 },
  "fee": { "currency": "ZAR", "value": 3.75 },
  "settlementAmount": { "currency": "ZAR", "value": 246.25 },
  "date": "2026-08-29",
  "type": "transaction"
}
```

| Field | Use it for |
|---|---|
| `reference` | **Matching to your own records**: this is your reference for the transaction, refund or payout |
| `type` | What kind of record this is. See below, this matters more than you'd expect |
| `amount` | The original amount of the record |
| `fee` | The Ozow fee for it |
| `settlementAmount` | `amount` − `fee`. What this record contributed to the settlement |
| `siteCode` | Which of your sites it belongs to |
| `date` | The date the record applies to |
| `description` | Mostly populated on adjustments: read it when you see one |

---

## Understanding line item types

A settlement is not just customer payments. The `type` field tells you what each record is, and your
reconciliation needs to handle all of them:

| Type | What it is |
|---|---|
| `transaction` | A customer payment |
| `refund` | A refund you issued |
| `payout` | A payout you sent |
| `returnedrefund` | A refund that came back |
| `returnedpayout` | A payout that came back |
| `fee` | A fee charged as its own line |
| `adjustment` | A manual correction. Read `description` |
| `chargeback` | A disputed card payment reversed |
| `withheldreserve` | Funds held back as reserve |
| `releasedreserve` | Reserve funds released back to you |
| `withheldchargeback` | Funds held against a chargeback |
| `releasedchargeback` | Chargeback funds released back to you |

Two consequences worth designing for.

**Don't assume every line item is a sale.** Code that treats all line items as customer payments
will double-count refunds and misreport your revenue. Branch on `type`.

**Handle unknown types gracefully.** New types get added. Log anything you don't recognise and
surface it for review rather than dropping it or crashing.

## Fees and rounding

Fees are applied per record and the settlement total is calculated from them, so `settlementAmount`
on each line is `amount` − `fee`.

> ⚠️ **The line item fees will not always add up to `totalFees`.** Fees on individual records are
> rounded, while `totalFees` is calculated off total amounts or counts. Small differences are
> expected and are not an error. If your reconciliation asserts that the two match exactly, it will
> fail on real data.

Reconcile using `totalSettlement` against your bank deposit, and use `settlementAmount` per line for
allocating to your own records.

## Paging

Both list endpoints page: `limit` (max 50) and `offset`, with `meta.totalPages` and
`meta.totalItems` telling you how much is there, and `links.next` giving you the next page.

Follow `links.next` until it's absent rather than incrementing `offset` yourself, it's less to get
wrong. Use `lineItemCounts.total` from step 1 to confirm you've retrieved every line item for a
settlement.

## Step 3: Match settlements to your bank account

Match on **`reference`**. It's the settlement reference that appears on your bank statement, which
makes it a reliable join. Confirm `totalSettlement.value` agrees with the deposit.

Don't match on amount and date alone, two settlements of the same value in the same week is entirely
possible.

## Step 4: Match line items to your records

Match on the line item **`reference`**, which is your own reference for the transaction, refund or
payout. That's the most reliable join because it's your identifier; so set a meaningful one when you
create payments and payouts.

Adjustments are the exception: Ozow generates the reference, and `description` is what tells you
what happened.

If you run multiple sites, key on `siteCode` and `reference` together rather than `reference` alone.

## Step 5: Flag what doesn't match

Check both directions:

- **Line items you can't place**: a settled record with no matching entry in your system
- **Records you expected to see settled but didn't**: a completed payment that hasn't appeared in
  any settlement

Give the second a tolerance window: a transaction that completed just before this run and hasn't
settled yet is normal; one well past its expected cycle isn't.

Also flag any `adjustment`, `chargeback` or reserve line item. These are not routine and they
originate outside your system, so review each one.

## Next steps

- Full field lists, parameters and error responses; [One API reference](https://hub.ozow.com/api-reference/one-api.md)
- How settlements work, [Settlements](https://hub.ozow.com/payment-products/settlements-and-float/settlements.md)
- Statuses across transactions and settlements, [Transaction and settlement statuses](https://hub.ozow.com/integration-methods/statuses.md)
- On the legacy API?, [Reconcile settlements](https://hub.ozow.com/integration-methods/apis/deprecated-integrations/reconcile-settlements.md)