# Reconcile a settlement

> Everything needed to match the money Ozow pays into your bank account against the payments you collected, line item by line item, including the fees that make the two differ.

A settlement is one payment from Ozow into your bank account covering many
customer payments, minus fees. Reconciling it means proving the amount that
arrived is the amount you were owed, and finding the difference when it is not.

List your settlements, fetch each one's line items, match the settlement to your
bank statement and the line items to your own records. What is left over is what
needs investigating.

**Both APIs are here, because a settlement does not move when an integration
does.** Implement against the One API guide on a new integration. The Payments
API guide is the same task on the older contract, and it stays correct for as
long as you are collecting through it. The migration guide has no settlements
step, so there is nothing to port beyond the endpoints themselves.

**Settlements cover payins only.** Float is the opposite direction, money you
pay Ozow to fund refunds and payouts, and a settlement never covers it.

Fees and rounding are where the arithmetic stops matching. Read the guide's
sections on both: a reconciliation written without them reports every settlement
as short.

## What this was built from

- Ozow Hub, commit `e0b2a572`
- `one-api` version 1.0, OpenAPI document: https://hub.ozow.com/api-reference/specs/one-api.yaml
- `payments-api` version 1.0, OpenAPI document: https://hub.ozow.com/api-reference/specs/payments-api.yaml
- Build against `https://one.ozow.com/v1` for `one-api`
- Build against `https://api.ozow.com` for `payments-api`
- 6 pages, 5 operations, inlined in full below
- The same package as links: https://hub.ozow.com/bundles/reconcile-a-settlement.md

---

# Implement against these

Every field name, order and format below is exact. Copy them as written.

---

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

---

# Reconcile settlements

> Pull settlement data from the Payments API, the legacy path, and tie it back to the deposits in your bank account and the orders in your own system.

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

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

> ℹ️ **You're reading the legacy integration.** This applies if your integration posts to
> `api.ozow.com` and builds a SHA512 hash. Build a new integration against [Reconcile
> settlements](https://hub.ozow.com/integration-methods/apis/settlements/reconcile-settlements.md) on One API instead.

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

Two endpoints do the work, and you need both:

| Endpoint | Gives you | Answers |
|---|---|---|
| `GET /secure/settlements` | One row per settlement | What did Ozow pay me? |
| `GET /secure/settlements/getsitesettlements` | One row per settled transaction | Which payments made that up? |

A typical daily job:

1. Fetch your latest settlements and store them.
2. Fetch the transactions behind them for the period.
3. Match each settlement to a deposit on your bank statement.
4. Match each transaction to an order in your system.
5. Flag whatever doesn't match.

The rest of this guide walks through those steps. For complete field lists, all parameters and error
responses, see the [Payments API reference](https://hub.ozow.com/api-reference/payments-api.md).

## Before you start

You'll need your API key from the [Ozow Dashboard](https://dash.ozow.com).

Both endpoints are simple GETs authenticated with your API key in a header:

```http
ApiKey: YOUR_API_KEY
```

---

## Step 1: Fetch your settlements

```endpoint
GET https://api.ozow.com/secure/settlements?count=100
```

Staging: `https://stagingapi.ozow.com/secure/settlements`

The only parameter is `count`, how many of the most recent settlements to return, up to 100.

**cURL**

```bash
curl -X GET "https://api.ozow.com/secure/settlements?count=100" \
  -H "ApiKey: <YOUR_API_KEY>" \
  -H "Accept: application/json"
```

**C#**

```csharp
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("ApiKey", apiKey);
client.DefaultRequestHeaders.Add("Accept", "application/json");

var response = await client.GetAsync("https://api.ozow.com/secure/settlements?count=100");
var body = await response.Content.ReadAsStringAsync();
```

**PHP**

```php
$ch = curl_init('https://api.ozow.com/secure/settlements?count=100');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['ApiKey: ' . $apiKey, 'Accept: application/json'],
]);
$response = curl_exec($ch);
curl_close($ch);
```

**JavaScript**

```javascript
const response = await fetch(
  "https://api.ozow.com/secure/settlements?count=100",
  { headers: { ApiKey: apiKey, Accept: "application/json" } },
);
const data = await response.json();
```

**Python**

```python
import requests

response = requests.get(
    "https://api.ozow.com/secure/settlements",
    params={"count": 100},
    headers={"ApiKey": api_key, "Accept": "application/json"},
)
data = response.json()
```

You get back a settlements array. Three fields do most of the work:

| Field | Use it for |
|---|---|
| `id` | Your primary key, and the join to the transaction detail in step 2 |
| `bankReference` | Matching to the deposit on your bank statement |
| `amount` | The amount actually transferred, in the settlement currency |

`status` will be `Pending` (the settlement was created) or `Complete` (the payment has been initiated).

> ⚠️ `Complete` does not mean the money has arrived. It means Ozow has initiated the payment. Your
> bank statement is the confirmation.

Check the `errors` array in the response before processing the settlements.

### Store what you fetch

This endpoint has no date filter. You can only ask for the latest *n*, capped at 100; so you can't
come back later and request last March.

Poll on a schedule, store every settlement keyed on `id`, and skip the ones you already have. Choose
a frequency where you can't possibly accumulate more than 100 new settlements between runs; daily is
comfortable for most merchants. If you do fall further behind than that, the missing settlements
aren't retrievable here and you'll need the Dashboard or Ozow Support.

---

## Step 2: Fetch the transactions behind them

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

Staging: `https://stagingapi.ozow.com/secure/settlements/getsitesettlements`

Both `fromDate` and `toDate` are required, in `yyyy-mm-dd` format. Unlike step 1, this one does take
a date range; so it's where you go for history.

**cURL**

```bash
curl -X GET "https://api.ozow.com/secure/settlements/getsitesettlements?fromDate=2026-09-01&toDate=2026-09-30" \
  -H "ApiKey: <YOUR_API_KEY>" \
  -H "Accept: application/json"
```

**C#**

```csharp
var url =
    "https://api.ozow.com/secure/settlements/getsitesettlements"
    + "?fromDate=2026-09-01&toDate=2026-09-30";

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("ApiKey", apiKey);
client.DefaultRequestHeaders.Add("Accept", "application/json");

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

**PHP**

```php
$url = 'https://api.ozow.com/secure/settlements/getsitesettlements'
     . '?fromDate=2026-09-01&toDate=2026-09-30';

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

**JavaScript**

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

const response = await fetch(
  `https://api.ozow.com/secure/settlements/getsitesettlements?${params}`,
  { headers: { ApiKey: apiKey, Accept: "application/json" } },
);
const data = await response.json();
```

**Python**

```python
import requests

response = requests.get(
    "https://api.ozow.com/secure/settlements/getsitesettlements",
    params={"fromDate": "2026-09-01", "toDate": "2026-09-30"},
    headers={"ApiKey": api_key, "Accept": "application/json"},
)
data = response.json()
```

You get an array with one entry per settled transaction. The fields you'll use:

| Field | Use it for |
|---|---|
| `settlementId` | Grouping transactions by settlement, joins to `id` from step 1 |
| `transactionReference` | Matching to the order in your own system |
| `transactionId` | Ozow's identifier for the payment, as a fallback join |
| `amount` | The settled amount for that transaction |

Group the rows by `settlementId`. Each group should total that settlement's `amount` from step 1. If
it does, your two views agree and you can reconcile with confidence. If a group does not total its
settlement, contact [support@ozow.com](mailto:support@ozow.com) with the `settlementId`.

---

## Step 3: Match settlements to your bank account

Match on **`bankReference`**. It's Ozow's reference for the settlement and it's what appears on your
statement, which makes it a reliable join. Confirm the `amount` agrees.

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

## Step 4: Match transactions to your orders

Match on **`transactionReference`** where you set your own reference when creating the payment.
That's the most reliable join, because it's your identifier.

Where you don't have one, match on **`transactionId`**: the Ozow identifier you stored when the
payment completed. If you aren't storing that at payment time, start; reconciliation is much harder
without it.

**Never match on amount alone.** Two customers paying the same amount on the same day is routine,
and amount-matching produces quietly wrong results rather than obvious failures.

## Step 5: Flag what doesn't match

Check both directions:

- **Line items you can't place**: a settled transaction with no matching order
- **Orders 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 completed transaction that hasn't settled yet is normal, one
that hasn't settled well past its expected cycle isn't.

---

## What not to reconcile against

Three assumptions that produce apparent differences where none exist:

**Refunds don't reduce settlements.** Refunds come out of your
[float](https://hub.ozow.com/payment-products/settlements-and-float/float-top-up.md), not your settlement. A
refunded transaction still settles in full.

**Fees don't reduce settlements either.** Billing is handled separately from settlement.

**Only `Complete` transactions settle.** Cancelled, abandoned, errored, voided and pending
transactions never appear in settlement data.

Settlement periods also won't line up with your accounting periods, a settlement can span a month
boundary. Reconcile by settlement, then map settlements to periods, not the other way round.

## Statuses

Settlement statuses are separate from transaction statuses and reuse many of the same words, so
check which one you're reading before you branch on it. See [Transaction and settlement
statuses](https://hub.ozow.com/integration-methods/statuses.md).

## Next steps

- Full field lists, parameters and error responses; [Payments API reference](https://hub.ozow.com/api-reference/payments-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)
- Moving to One API, [Migrating to One API](https://hub.ozow.com/integration-methods/apis/deprecated-integrations/migrating-to-one-api.md)

---

# Background

Context for the above. Nothing here is implemented against.

---

# How Ozow works

> How Ozow connects you to South African banks and payment methods, and the two directions money moves: payins from customers, payouts to recipients.

Source: https://hub.ozow.com/getting-started/

Ozow is a payment infrastructure layer that connects merchants to multiple payment methods and
banking rails. Instead of building separate integrations for each bank or payment method, you
integrate with Ozow to gain access to the full suite of Ozow payment products.

## The two directions of money movement

Every Ozow integration moves money in one of two directions.

**Payin**: a customer pays you. The customer initiates the payment, Ozow processes it, and you
receive the funds. This covers checkout and any other payment collection.

**Payout**: you send funds to a recipient. Your system initiates the transfer, Ozow processes it,
and the recipient receives the funds in their bank account. This covers disbursements, refunds to
bank accounts, and bulk payments.

The distinction runs through everything: different APIs, different credentials, different approval
processes, and a different structure in these docs. Work out which direction you need before you
start. The [Integration methods](https://hub.ozow.com/integration-methods.md) section is
organised around it.

## One integration, every way to pay

Payment methods are enabled on your Ozow account, not in your code.

Pay by Bank is enabled by default. Other methods you opt into (card, PayShap Request, voucher, buy
now pay later, crypto) are enabled by Ozow on your account, and they then appear on the Ozow payment
page automatically. You don't build a new integration or call a different endpoint for each one.

This means you can go live with Pay by Bank and add methods later as a commercial decision rather
than a development project.

## The core payment flows

### Payin

```mermaid
sequenceDiagram
    participant C as Customer
    participant M as Your system
    participant O as Ozow

    C->>M: Reaches checkout
    M->>O: Creates payment request
    O-->>M: Returns payment URL
    M->>C: Sends customer to Ozow
    C->>O: Completes payment
    O-->>M: Notifies your webhook of the outcome
    M->>O: Verifies the status
    M-->>C: Updates the order
```

Two things to notice. The payment request is created by **your server**, never by the customer's
browser. And the outcome arrives on **your webhook**, not in the customer's redirect back to your
site; the customer landing on your success page is not proof of payment. See [Building a secure
integration](https://hub.ozow.com/getting-started/building-a-secure-integration.md).

### Payout

There is no customer-facing step. The whole flow happens between your backend and Ozow.

```mermaid
sequenceDiagram
    participant M as Your system
    participant O as Ozow
    participant R as Recipient bank

    M->>O: Checks payout availability
    M->>O: Sends payout request
    O->>M: Calls your verification webhook
    M-->>O: Confirms the payout
    O->>R: Submits the payout to the bank
    R-->>O: Confirms the outcome
    O-->>M: Notifies your webhook of the final status
```

Before Ozow moves any money, it calls back to your system to confirm the payout is genuine.
If that call fails or can't be reached, the payout does not proceed. That's deliberate, and
it's why payout integrations require testing and sign-off before they go live.

## Getting paid: transactions and settlements

A completed transaction is not money in your bank account. These are two separate stages with two
separate status vocabularies.

```mermaid
flowchart LR
    A["Customer pays"] --> B["Transaction completes"]
    B --> C["Included in a settlement"]
    C --> D["Funds in your bank account"]
```

The transaction status tells you whether the customer's payment succeeded. The settlement status
tells you whether the money has actually reached you. Settlement happens on a delay that depends on
the payment method.

Use transaction status to fulfil orders. Use settlement status to reconcile your bank account. See
[Transaction and settlement statuses](https://hub.ozow.com/integration-methods/statuses.md).

## Paying out: your float

Money leaving Ozow doesn't come out of your incoming payments. It comes from a **float**: a balance
you pre-fund by transferring money to Ozow.

Both payouts and refunds draw on the float. If it's empty, they won't process. Payins don't need a
float at all, so if you're only collecting payments you can ignore this entirely.

See [Float top-up](https://hub.ozow.com/payment-products/settlements-and-float/float-top-up.md).

## Environments

Ozow provides separate staging and production environments. They're completely isolated, and staging
credentials are different from your production credentials.

Testing requirements differ by direction. Payin integrations can go straight to production. We
recommend working through the [payin test
cases](https://hub.ozow.com/integration-methods/testing/payin-test-cases-one-api.md), but you don't need to submit anything.
Payout integrations require mandatory staging testing and formal sign-off from Ozow before they're
enabled in production.

**Getting your credentials:**

- **Production credentials** are available to you directly in the [Ozow
  Dashboard](https://dash.ozow.com). Ozow will never send them to you.
- **Staging credentials** are issued on request. Ask your account manager or contact [support@ozow.com](mailto:support@ozow.com).

> 🚨 Ozow will never share your production credentials with you, and will never ask you for them. If
> anyone contacts you offering to send production credentials, or asking you to share yours, treat
> it as fraudulent and report it to [support@ozow.com](mailto:support@ozow.com).

## Where to go next

How you integrate depends on how much control you want over the payment experience and how much you
want to build, from no-code payment requests through to a full API integration.

Head to [Integration methods: overview](https://hub.ozow.com/integration-methods.md) to choose
the right path.

---

# Prerequisites and onboarding

> What to have in place before you write any code: a merchant account, Dashboard access, your credentials, and payout eligibility if you need it.

Source: https://hub.ozow.com/getting-started/prerequisites-and-onboarding/

Before you start integrating Ozow, make sure you have everything in place. This page covers what you
need before writing a single line of code.

## 1. Register as an Ozow merchant

You need an active Ozow merchant account before you can integrate. If you don't have one yet,
[join our merchant family](https://ozow.com/merchants) or speak to your account manager to get set up.
 If you signed up through a commercial manager or already have an account, you can skip this step and
 log in to the [Ozow Dashboard](https://dash.ozow.com) directly.

If you need assistance with your account, contact [support@ozow.com](mailto:support@ozow.com) or
reach out to your account manager.

## 2. Access the Ozow Dashboard

Once your merchant account is active, you can log in to your [Ozow Dashboard](https://dash.ozow.com).
The Dashboard is where you'll find everything you need to begin your integration.

## 3. Retrieve your credentials

Which credentials you need depends on the API you are integrating against. Collect the row for
yours and ignore the rest.

| | One API | Payments API | Payouts API |
|---|---|---|---|
| Client ID and Client Secret | **Yes** | No | No |
| API key | No | Yes | Yes, a **different** key |
| Private key | No | Yes, to sign the hash | Yes, to sign the hash |
| Where to find them | One API Clients | Merchant Details and Site | Issued once payouts are approved |

**On One API, the Client ID and Client Secret are all you need.** You exchange them for an access
token. There is no API key to send and no hash to compute.

**On the Payments API and the Payouts API you need both keys, and they do different things.** The
API key goes in the `ApiKey` header. The private key is never sent: you use it to compute the
`hashCheck` field on the request, and again to verify the hash on a notification.

**The Payouts API takes its own API key, not the one the Payments API takes.** Sending the Payments
API key to a payout endpoint is rejected.

Your **site code** identifies which of your sites a request belongs to and is in the Site section of
the Dashboard. Every path needs one. It is not a secret.

> ℹ️ **One API clients and payout API keys are scoped per site or per merchant.** A key issued for
> one site does not work for another, so check which you have been given before you assume it covers
> your whole account.

> ⚠️ **Security note**: Keep your credentials secure at all times. Never expose them in client-side
> code, public repositories, or logs. Ozow does not publish credentials publicly and will never ask
> you to share them in an unsecured channel.

## 4. Understand your project setup

When you log in to the Ozow Dashboard, you'll see your merchant account. Within your account, you
can have one or more sites; each representing a separate website, merchant, or integration point.

Each site has its own unique site code, and your site code and API credentials work together to
identify which site a payment belongs to. Payment requests and transactions are always tied to a
specific site, so it's important to use the correct site code for the integration you're building.

```mermaid
graph TD
    A[Ozow Dashboard] --> B[Site 1\nsite code: ABC-001]
    A --> C[Site 2\nsite code: ABC-002]
    A --> D[Site 3\nsite code: ABC-003]
    B --> E[Payments & transactions\ntied to Site 1]
    C --> F[Payments & transactions\ntied to Site 2]
    D --> G[Payments & transactions\ntied to Site 3]
```

## 5. Integrating payouts? Check your eligibility first

If you intend to integrate payouts, you must be approved by Ozow's onboarding team before you can
begin. Payout credentials are not issued until this approval is in place; you will not be able to
start a payout integration without them.

Contact your account manager or [support@ozow.com](mailto:support@ozow.com) to request payout eligibility.

> ℹ️ **Note**: Payin credentials are issued automatically as part of standard merchant onboarding.
> Payout credentials require a separate approval process before they are issued.

## 6. Choose your integration path

Once your credentials are in place, you're ready to choose how you'll integrate. Head to
[choose your integration](https://hub.ozow.com/integration-methods.md) to understand your
options and choose the right path for your use case.

If you're new to Ozow and want to get to your first payment as quickly as possible, go straight to
the [quick start guide](https://hub.ozow.com/getting-started/quick-start.md).

---

# Settlements

> How Ozow pays what you have collected into your bank account: what a settlement contains, when it arrives, how to track it, and how it meets your float.

Source: https://hub.ozow.com/payment-products/settlements-and-float/settlements/

A settlement is Ozow paying the money you've collected into your bank account.

When a customer pays you through Ozow, the funds may land with Ozow first. Ozow then groups those
payments together, works out what you're owed, and transfers it to you. That transfer is a
settlement.

## Settlements apply to pay-ins only

Only money coming *in* gets settled. Payouts and refunds move money the other way, out of your
[float](https://hub.ozow.com/payment-products/settlements-and-float/float-top-up.md), so they're never part of a settlement.

```mermaid
flowchart LR
    A["Customers pay you"] --> B["Funds held by Ozow"]
    B --> C["Settlement"]
    C --> D["Your bank account"]
    E["Your float"] --> F["Payouts and refunds"]
    F --> G["Recipients and customers"]
```

If you only send payouts and never collect payments, you won't have settlements at all.

> ℹ️ Ozow has more than one settlement model. By default, funds from your payments are
> aggregated and then settled to you. Your arrangement is agreed during onboarding, so speak to
> your account manager if you're not sure which applies.

## What's in a settlement

A settlement is the money you're owed from the payin transactions in that settlement period.
Nothing is netted off it.

- **Ozow fees are billed separately.** They aren't deducted from your settlement.
- **Refunds and payouts come out of your [float](https://hub.ozow.com/payment-products/settlements-and-float/float-top-up.md)**, not your settlement.

Your settlement therefore reconciles against your completed payin transactions for the period.

## When you get paid

Settlement timing depends on the payment method the funds came in through; they don't all settle on
the same cycle. If you enable a new payment method, your settlement pattern may change.

Your specific settlement arrangement is confirmed during onboarding. If you're not sure what applies
to your account, speak to your account manager.

## Tracking settlements

Settlements have their own statuses, separate from transaction statuses. A transaction reaching
`Complete` means the customer's payment succeeded. It does not mean the money has reached your bank
account; the settlement status tells you that.

The two vocabularies use many of the same words, so check which one you're looking at before acting
on it. See [Transaction and settlement statuses](https://hub.ozow.com/integration-methods/statuses.md).

You can view your settlements in the [Ozow Dashboard](https://dash.ozow.com) or via API.

## Settlements and your float

These are easy to confuse, and they move in opposite directions.

| | Settlement | Float |
|---|---|---|
| Direction | Ozow pays you | You pay Ozow |
| What it's for | Paying out what you've collected | Funding refunds and payouts |
| Applies to | Pay-ins | Payouts and refunds |

If you process refunds or payouts, you need a funded float; a settlement won't cover them. See
[Float top-up](https://hub.ozow.com/payment-products/settlements-and-float/float-top-up.md).

---

# Transaction and settlement statuses

> Every payin, payout, refund and settlement status, which are final, and what to do about each.

Source: https://hub.ozow.com/integration-methods/statuses/

Every payment through Ozow moves through a series of statuses. This page lists all of them, tells
you which ones are final, and tells you what to do about each.

> 🚨 **Read this first.** `Pending` and `Complete` appear on a payin, on a settlement and on a
> refund. `Complete` on a payin is a customer's payment succeeding, on a settlement it is the
> payment out to you confirmed by Ozow's bank, and on a refund it is your customer having their
> money back. Refunds spell theirs as integers rather than strings. A status value on its own tells
> you nothing. Always check which object it belongs to before you act on it.

## How the lifecycles relate

A payment has more than one lifecycle. The transaction completing and the money reaching your bank
account are two separate events with two separate status vocabularies.

```mermaid
flowchart TB
    subgraph in["Money in"]
        direction LR
        A["Customer<br/>pays"] --> B["Payin<br/>Complete"]
        B --> C["Settlement<br/>Complete"]
        C --> D["Your bank<br/>account"]
    end
    subgraph out["Money out"]
        direction LR
        T["Your bank<br/>account"] -. "top up" .-> E["Your<br/>float"]
        E --> F["PayoutComplete"]
        E --> H["Refund"]
        F --> G["Recipient's<br/>bank account"]
        H --> G
    end
    in ~~~ out
```

**A completed transaction is not settled money.** A payin status of `Complete` means the customer's
payment succeeded and the funds *will* be settled to you. The settlement status tells you whether
that has actually happened. Don't use payin status to reconcile your bank account. Every other final
payin status settles nothing.

**Refunds and payouts draw on your float, not on the original transaction.** A refund needs a funded
float balance even though it's returning money the customer already paid, because that money has
already been settled to you. See [Float
top-up](https://hub.ozow.com/payment-products/settlements-and-float/float-top-up.md).

## Look up a status

[Status decoder](https://hub.ozow.com/integration-methods/statuses/), a tool on this page.

The tables below list every status in full.

## How to read a status

### Final vs non-final

A **final** status will not change on its own. A **non-final** status will be followed by another
update, and you must wait for it rather than acting.

Getting this wrong is the most common integration bug in payments. Never release goods, mark an
order paid, or notify a customer on a non-final status.

> ⚠️ One exception: payin `PendingInvestigation` is final in the sense that no automatic update is
> coming, but Ozow Support can change it to `Complete` or `Error` after a manual check. Treat it as
> needing human action, not as a settled outcome.

### Status and sub-status

Payouts return a status and, in most cases, a sub-status. **Branch on the sub-status whenever one is
present.**

**The API sends the code, not the name.** `subStatus` on a payout status response is a number, so
`405` is what arrives where this page says `PayoutProcessingError_InvalidAccountNumber`. Both are
listed. The name is for reading; the code is what your `switch` matches.

This matters because a parent status can be non-final while the sub-status under it is final.
`PayoutReceived` is non-final, but `Payout_ValidationFailed` beneath it is a final failure. If you
only read the parent status, you will wait forever for a payout that has already failed.

### Never infer status from the browser redirect

The customer's browser returning to your success page is not proof of payment. Statuses arrive on
your notification or webhook URL, and you can query them with the status check API. See [Building a
secure integration](https://hub.ozow.com/getting-started/building-a-secure-integration.md).

---

## Payin statuses

```mermaid
stateDiagram-v2
    direction LR

    state "Not paid" as NotPaid {
        Cancelled
        Abandoned
        Voided
        Error
    }

    [*] --> Created
    Created --> PendingInvestigation
    Created --> Pending
    Created --> Complete
    Created --> NotPaid

    PendingInvestigation --> Complete
    PendingInvestigation --> NotPaid

    Pending --> Complete
    Pending --> NotPaid
```

Which of the four unpaid outcomes you can get depends on where the payment was when it stopped.
`Voided` follows only `Created`. From `Pending` the only unpaid outcome is `Error`, and from
`PendingInvestigation` it is `Cancelled`, `Abandoned` or `Error`.

**Non-final:** `Created`, `Pending`
**Final:** `Complete`, `PendingInvestigation`, `Cancelled`, `Abandoned`, `Voided`, `Error`

**The `One API` column is the value `GET /payments/{id}/transactions` returns for that status.**

| Status | One API | Final? | Settles? | What it means | What to do |
|---|---|---|---|---|---|
| `Created` | `Incomplete` | No | No | The transaction has been created and the customer has opened the payment page, but hasn't completed it. | Wait for a final status. |
| `Complete` | `Successful` | Yes | **Yes** | The payment succeeded and the funds will be settled to you. | Nothing. Fulfil the order. |
| `Cancelled` | `Error` | Yes | No | The transaction was cancelled: either the customer pressed cancel, or the payment failed Ozow's verification with the bank. `StatusMessage` says which. | Nothing. If they still want to pay, ask them to start a new transaction. |
| `Abandoned` | `Error` | Yes | No | The customer started the transaction but didn't finish it, typically they closed the Ozow payment page. | Nothing. If they still want to pay, ask them to start a new transaction. |
| `Voided` | `Error` | Yes | No | The transaction was invalidated and won't be processed. Happens when the customer changes bank partway through the Ozow flow, or when the saved profile they chose is deleted, deactivated or fails to load. | Nothing. A new transaction will have been created for the method they switched to. **No notification is sent for a voided transaction**, so find these by polling rather than by waiting. |
| `Error` | `Error` | Yes | No | An error occurred while the transaction was being processed. Not caused by anything the customer did. | Ask the customer to retry the transaction. |
| `Pending` | `Pending` | No | No | The payment still has to be verified. The outcome follows once it has been. | Wait for the update on your notification URL. Do not release anything on it. |
| `PendingInvestigation` | `Pending` | Yes | No | Ozow could not complete its verification with the bank, so the payment has to be checked manually against your bank statement. | Check your bank statement. If the funds arrived, contact Ozow Support with proof of payment to have the status updated to `Complete`. If they didn't, contact Support to have it set to `Error`. |

> ℹ️ `Pending` is non-final: an update follows. Handle it as "wait", never as an outcome. It means
> neither that the payment failed nor that it succeeded.

> ⚠️ Refunding a transaction does not change its status. If you need to know whether a transaction
> has been refunded, check the refund records rather than relying on the transaction status.

> ℹ️ Settlement timing depends on the payment method.

---

## Payout statuses

```mermaid
stateDiagram-v2
    direction LR

    state "Ended without paying" as Ended {
        PayoutProcessingError
        PayoutReturned
    }

    [*] --> PayoutReceived
    PayoutReceived --> Verification
    Verification --> SubmittedForProcessing
    SubmittedForProcessing --> PayoutComplete
    PayoutComplete --> PayoutReturned

    Verification --> Ended
    SubmittedForProcessing --> Ended

    Verification --> PayoutPendingInvestigation
    SubmittedForProcessing --> PayoutPendingInvestigation
    PayoutPendingInvestigation --> PayoutComplete
    PayoutPendingInvestigation --> Ended
```

The happy path is `PayoutReceived` → `Verification` → `SubmittedForProcessing` → `PayoutComplete`.
`Verification` can also reach `PayoutComplete` directly, so a payout that never appears in
`SubmittedForProcessing` has not skipped a step. `PayoutProcessingError` and `PayoutReturned` are
grouped because nothing follows either of them.

> ⚠️ **`PayoutComplete` is not final, though it almost never changes.** A completed payout moves
> to `PayoutReturned` if the destination bank sends the money back, which is a rare event rather
> than one to plan a flow around. Keep handling status updates for a payout you have already marked
> paid, and you will hear about it on the day it happens. The only two statuses nothing follows are
> `PayoutProcessingError` and `PayoutReturned`.

**Payout timing:** with `isRtc` set to `true` the payout is instant. With `isRtc` set to `false` it
takes 1-2 business days.

> ⚠️ Branch on the **sub-status**, not the parent status. `PayoutReceived`, `Verification` and
> `PayoutPendingInvestigation` are all non-final while carrying sub-statuses that are final
> failures.

### PayoutReceived

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | No | The payout request has been received. | Wait for a final status. |
| `Payout_Unclassified` | 100 | Yes | No sub-status could be determined. The payout has failed. | Rare edge case. Treat as a failure and investigate. |
| `Payout_ValidationFailed` | 101 | Yes | Request validation failed. | Check the `ErrorMessage` field for the reason, correct the request, then resubmit. |

### Verification

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | No | The payout is being verified. | Wait for a final status. |
| `Verification_Pending` | 201 | No | Ozow is waiting for a response from your verification webhook. | Wait for a final status. |
| `Verification_Success` | 203 | No | Your webhook verified the payout successfully. Processing continues. | Wait for a final status. |
| `Verification_Failed` | 202 | Yes | Your verification webhook returned a response that failed verification. | Check why your webhook rejected it, then resubmit the payout. |
| `Verification_Error` | 204 | Yes | Ozow couldn't reach your verification webhook. | Check that your webhook is reachable. If you've changed its URL, confirm Ozow has the new one. Then resubmit. |
| `Verification_AccountNumberDecryptionFailed` | 205 | Yes | The key returned by your webhook failed to decrypt the account number. | Check your webhook's key handling, then resubmit the payout. |
| `Verification_Success_Awaiting_Funds` | 206 | No | The payout verified, but your float balance is too low to cover it. | Top up your float. The payout continues on its own once the funds are there. Do not resubmit. |
| `Verification_Success_Awaiting_Submission` | 207 | No | The payout verified and is queued for submission. | Wait for a final status. |

### SubmittedForProcessing

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | No | The payout is being processed. | Wait for a final status. |
| `SubmittedForProcessing_PayoutAddedToBatch` | 301 | No | The payout has been added to a batch. | Wait for a final status. |
| `SubmittedForProcessing_PayoutSubmittedToBank` | 302 | No | The batch has been processed and submitted to the bank. | Wait for a final status. |
| `SubmittedForProcessing_PayoutSubmittedToPpi` | 303 | No | The payout has been submitted for processing. | Wait for a final status. |

### PayoutComplete

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | No | The payout completed successfully. | Release whatever the payout was for, and keep handling updates: this can still become `PayoutReturned`. |

### PayoutProcessingError

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | Yes | An error occurred while processing the payout. | Resubmit the payout. |
| `PayoutProcessingError_PayoutRejected` | 401 | Yes | The bank rejected the payout. | Resubmit the payout. |
| `PayoutProcessingError_PayoutCancelled` | 402 | Yes | Ozow stopped the payout before it was paid. | Check the `ErrorMessage` field, then resubmit. |
| `PayoutProcessingError_Insufficient_Balance` | 403 | Yes | Your float balance was too low to cover the payout. | **Top up your float. Do not resubmit**: see the warning below. |
| `PayoutProcessingError_PayoutInternalError` | 404 | Yes | An internal error occurred during processing. | Resubmit the payout. |
| `PayoutProcessingError_InvalidAccountNumber` | 405 | Yes | The account number is invalid. | Correct the account number, then resubmit. |

> 🚨 **On `PayoutProcessingError_Insufficient_Balance` you must not resubmit.** Top up your float
> instead. Once the float is allocated, the payout processes automatically. Resubmitting risks
> paying the recipient twice.

### PayoutReturned

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | Yes | The payout couldn't be paid into the recipient's account. | Resubmit the payout. |
| `PayoutReturned_Unpaid` | 9001 | Yes | The destination bank rejected the payment. | Check that the destination account is still active, then resubmit. |

### PayoutPendingInvestigation

| Sub-status | Code | Final? | What it means | What to do |
|---|---|---|---|---|
| None | None | No | The payout is under investigation. | Wait for a final status. |
| `PayoutPendingInvestigation_AmountMismatch` | 601 | Yes | The payout failed because the amounts didn't match. | Check the payout details, then resubmit. |

> ⚠️ **A payout cannot be cancelled once it is submitted.** Check the request before you send it:
> the account number, the amount and the reference are all final from that point.

---

## Refund statuses

A refund returns money to a customer for a payin you've already received. Refunds draw on your float
balance, not on the original transaction; the funds from that transaction have already been settled
to you.

> ⚠️ Refund statuses are returned as **integers**, not strings. This is different from payin, payout
> and settlement statuses, which are returned as text.

> ⚠️ **The numeric values are not in lifecycle order.** A refund progresses `0` → `2` → `1`. Don't
> treat a higher value as further along, and don't use greater-than comparisons to test progress.

```mermaid
stateDiagram-v2
    [*] --> Pending: 0
    Pending --> Submitted: 2
    Pending --> Cancelled: 4
    Submitted --> Complete: 1
    Submitted --> Failed: 3
    Submitted --> Returned: 5
```

This is the path a refund takes. Three further statuses sit outside it, `Invalid` (-1),
`PendingInvestigation` (-2) and `Error` (-3). Each one has a row below. Handle them wherever they
turn up rather than by position.

**Non-final:** `Pending` (0), `Submitted` (2), `PendingInvestigation` (-2)
**Final:** `Complete` (1), `Failed` (3), `Cancelled` (4), `Returned` (5), `Invalid` (-1), `Error` (-3)

**The negative values are real.** `Invalid`, `PendingInvestigation` and `Error` are returned by the
API alongside the six above. Switch on all nine, or give your default branch something safe to do.

| Value | Status | Final? | What it means | What to do |
|---|---|---|---|---|
| `0` | `Pending` | No | The refund request has been submitted and accepted, but not yet processed. | Wait for a final status. |
| `2` | `Submitted` | No | The refund has been assigned to a batch and is being processed. | Wait for a final status. |
| `1` | `Complete` | Yes | The refund was paid successfully. The customer has their money. | Nothing. |
| `3` | `Failed` | Yes | The refund payment failed. | Check that your float is funded, then submit a new refund. |
| `4` | `Cancelled` | Yes | The refund was cancelled before it was submitted for processing. | Nothing. If the customer is still owed a refund, submit a new one. |
| `5` | `Returned` | Yes | The refund was paid and came back. The destination account did not accept the credit, or the destination bank could not apply it. | Confirm the account details with the customer before you try again. Resubmitting the same details without checking returns the money a second time. |
| `-1` | `Invalid` | Yes | The refund could not be accepted as submitted. | Check the refund details against the original transaction, then submit a corrected refund. |
| `-2` | `PendingInvestigation` | No | The refund is being checked manually. | Wait. Ozow reports this as `Pending` on a `thin` webhook, so a handler reading the webhook rather than the API will not see this value. |
| `-3` | `Error` | Yes | The refund failed because of an error rather than a rejection. | Ozow reports this as `Failed` on a `thin` webhook. Treat it as a failure and submit a new refund. |

> ⚠️ **On `Returned`, do not resubmit the same details without checking them.** The money came back
> from the destination, so sending it again the same way returns it again. Confirm the account with
> the customer first.

> ℹ️ Refunds require a funded float. If your float is empty, refunds will not process. See [Float top-up](https://hub.ozow.com/payment-products/settlements-and-float/float-top-up.md).

> ℹ️ **Refund status is the source of truth for whether a customer has been refunded.** The original
> payin transaction keeps the status it had, so don't use transaction status to check refund state.

---

## Settlement statuses

A settlement is the transfer of your collected funds into your bank account. Settlement statuses
tell you where that transfer is, and they are **not** the same thing as the status of the
transactions inside it.

```mermaid
stateDiagram-v2
    direction LR
    [*] --> Pending
    Pending --> Submitted
    Submitted --> Complete
    Submitted --> PendingInvestigation
    PendingInvestigation --> Complete
```

**Non-final:** `Pending`, `Submitted`, `PendingInvestigation`
**Final:** `Complete`

**A settlement has four statuses, and none of them is a payin status.**

| Status | Final? | What it means | What to do |
|---|---|---|---|
| `Pending` | No | The settlement has been created. The payment out to you has not started. | Wait. If it stays here for an extended period, email [support@ozow.com](mailto:support@ozow.com). |
| `Submitted` | No | The settlement has been submitted to Ozow's bank. It might not have reached yours yet. | Wait. If it stays here for an extended period, email [support@ozow.com](mailto:support@ozow.com). |
| `Complete` | Yes | Ozow has submitted the payment and confirmed with Ozow's bank that it went out. Your own bank still has to clear it. | Reconcile against your bank statement rather than against this status. `Complete` is Ozow's leg finished; the clearing leg is your bank's and can lag it. |
| `PendingInvestigation` | No | The settlement is being checked manually. | Wait. If it stays here for an extended period, email [support@ozow.com](mailto:support@ozow.com) with your settlement reference. |

Settlement timing depends on the payment method the funds came in through.

---

## Which statuses mean money moved

Only these mean money moved:

- **Payin:** `Complete`, and only `Complete` settles
- **Payout:** `PayoutComplete`, which can still become `PayoutReturned` if the bank sends it back
- **Refund:** `Complete` (`1`), the integer and not the string
- **Settlement:** `Complete`, which is the payment out confirmed by Ozow's bank, with your own bank
  still to clear it

Everything else is either in progress or a failure. In particular:

- `Pending`, `Created`, `Verification`, `SubmittedForProcessing` and `PayoutReceived` are **in
  progress**. Wait.
- A payin `PendingInvestigation` needs a human. It is not a success.
- A payin `Complete` is not settled money. Check the settlement status for that.

---

# The contract

The operations those pages declare, as the specification defines them.

---

# List Settlements

> GET `/settlements`
> Part of the One API reference. Source: https://hub.ozow.com/api-reference/one-api/get-settlements/

Server: `https://one.ozow.com/v1` (Production)

Other environments: `https://stagingone.ozow.com/v1` (Staging)

Retrieve a list of settlements.

## Authentication

- `Authentication` (oauth2)
  - Scopes: `settlements`

## Query parameters

- `limit` (integer) - The maximum number of items to return.
- `offset` (integer) - The number of items to discard in this paging operation.
- `fromDate` (string, required) - The date from which to filter, inclusive. Whole days only, so any time sent with it is discarded.
- `toDate` (string, required) - The date to filter up to, inclusive. Whole days only, so any time sent with it is discarded.
- `siteCode` (string) - The merchant site code.
- `reference` (string) - The settlement reference.

## Header parameters

- `X-Correlation-ID` (string) - Optional correlation id for the request, if not supplied a new one will be generated and passed onto all underlying requests and returned as a header.

## Responses

### 200 OK

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

- `links` (object, required) - Standard [JSON API pagination links](https://jsonapi.org/format/#fetching-pagination). Each link repeats the query that produced the collection and carries its own `limit` and `offset`. Follow the link rather than building the next URI. The example below is the second of three pages.
- `results` (array of Settlement, required)
- `meta` (object, required)

### 400 Bad Request.

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

- `id` (string, uuid, required) - a unique identifier for this particular occurrence of the problem.
- `links` (object, nullable) - Present on an authentication or authorisation failure, and null otherwise.
  - `about` (string, uri) - A link that leads to further details about this particular occurrence of the problem. When derefenced, this URI SHOULD return a human-readable description of the error.
  - `type` (string, uri) - A link that identifies the type of error that this particular error is an instance of. This URI SHOULD be dereferencable to a human-readable explanation of the general error.
- `code` (string, required) - An application-specific error code, expressed as a string value. Key on this rather than on `title` or `detail`, which are written for a person. A rejection at the transport level uses the status name, one of `BadRequest`, `Unauthorized`, `Forbidden`, `NotFound`, `NotAllowed`, `Conflict`, `UnsupportedMediaType`, `BadGateway` or `InternalServerError`. An operation refusing a request on its own rules returns a code of its own.
- `title` (string, required) - A short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization.
- `detail` (string, required) - A human-readable explanation specific to this occurrence of the problem. Like title, this field’s value can be localized.
- `source` (object, nullable) - Where in the request the problem was found. All three keys are present whenever `source` is, with the ones that do not apply set to null. `source` itself is null where the failure is not about a part of the request.
  - `pointer` (string, json-pointer, nullable) - a JSON Pointer [RFC6901](https://tools.ietf.org/html/rfc6901) to the value in the request document that caused the error [e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute]. This MUST point to a value in the request document that exists; if it doesn’t, the client SHOULD simply ignore the pointer.
  - `parameter` (string, nullable) - A string indicating which URI query parameter caused the error.
  - `header` (string, nullable) - A string indicating the name of a single request header which caused the error.
- `meta` (object, nullable) - A [meta object](https://jsonapi.org/format/#document-meta) containing non-standard meta-information about the error. Null where the request carried no `X-Correlation-ID`, and on an authentication failure, which does not echo it.
  - `correlationId` (string) - The `X-Correlation-ID` sent with the request, echoed back so it can be quoted to support. Absent when the request carried no correlation header.

Example (A field in the request body did not validate):

```json
{
  "id": "3a6c9e01-5f2b-4d8a-9c47-1e0b7d5a2f83",
  "links": null,
  "code": "BadRequest",
  "title": "Bad Request",
  "detail": "amount: Amount must be greater than 0",
  "source": {
    "pointer": "/amount",
    "parameter": null,
    "header": null
  },
  "meta": {
    "correlationId": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
  }
}
```

### 401 Unauthorised.

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

`Error`, the same schema listed in full earlier in this document. Its own page: https://hub.ozow.com/api-reference/one-api/schemas/error.md

Example (No usable access token on the request):

```json
{
  "id": "1cecc2b7-1c29-418a-b26a-bf7546926083",
  "links": {
    "about": "https://ozow.stoplight.io/docs/one-api/zi18vomr0jm8c-generate-authentication-token",
    "type": "https://tools.ietf.org/html/rfc7235#section-3.1"
  },
  "code": "Unauthorized",
  "title": "Unauthorized Request",
  "detail": "Authorization header is missing or invalid.",
  "source": {
    "pointer": null,
    "parameter": null,
    "header": "Authorization"
  },
  "meta": null
}
```

### 500 Internal Server Error.

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

`Error`, the same schema listed in full earlier in this document. Its own page: https://hub.ozow.com/api-reference/one-api/schemas/error.md

Example (The request failed on the Ozow side):

```json
{
  "id": "9e0d5a83-6b24-4c19-8f7a-2d1b3e6c0a97",
  "links": null,
  "code": "InternalServerError",
  "title": "Internal Server Error",
  "detail": "Error occurred while processing request.",
  "source": {
    "pointer": "/data",
    "parameter": null,
    "header": null
  },
  "meta": {
    "correlationId": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
  }
}
```


---

# List Settlement Line Items

> GET `/settlements/{id}/lineitems`
> Part of the One API reference. Source: https://hub.ozow.com/api-reference/one-api/get-settlements-id-lineitems/

Server: `https://one.ozow.com/v1` (Production)

Other environments: `https://stagingone.ozow.com/v1` (Staging)

Retrieve the line items associated with a settlement by it's unique identifier.

## Authentication

- `Authentication` (oauth2)
  - Scopes: `settlements`

## Path parameters

- `id` (string, required) - The settlement identifier.

## Query parameters

- `limit` (integer) - The maximum number of items to return.
- `offset` (integer) - The number of items to discard in this paging operation.

## Header parameters

- `X-Correlation-ID` (string) - Optional correlation id for the request, if not supplied a new one will be generated and passed onto all underlying requests and returned as a header.

## Responses

### 200 OK

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

- `links` (object, required) - Links related to this resource.
- `results` (array of SettlementLineItem, required) - The settlement line items.
- `meta` (object, required)

### 400 Bad Request.

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

- `id` (string, uuid, required) - a unique identifier for this particular occurrence of the problem.
- `links` (object, nullable) - Present on an authentication or authorisation failure, and null otherwise.
  - `about` (string, uri) - A link that leads to further details about this particular occurrence of the problem. When derefenced, this URI SHOULD return a human-readable description of the error.
  - `type` (string, uri) - A link that identifies the type of error that this particular error is an instance of. This URI SHOULD be dereferencable to a human-readable explanation of the general error.
- `code` (string, required) - An application-specific error code, expressed as a string value. Key on this rather than on `title` or `detail`, which are written for a person. A rejection at the transport level uses the status name, one of `BadRequest`, `Unauthorized`, `Forbidden`, `NotFound`, `NotAllowed`, `Conflict`, `UnsupportedMediaType`, `BadGateway` or `InternalServerError`. An operation refusing a request on its own rules returns a code of its own.
- `title` (string, required) - A short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization.
- `detail` (string, required) - A human-readable explanation specific to this occurrence of the problem. Like title, this field’s value can be localized.
- `source` (object, nullable) - Where in the request the problem was found. All three keys are present whenever `source` is, with the ones that do not apply set to null. `source` itself is null where the failure is not about a part of the request.
  - `pointer` (string, json-pointer, nullable) - a JSON Pointer [RFC6901](https://tools.ietf.org/html/rfc6901) to the value in the request document that caused the error [e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute]. This MUST point to a value in the request document that exists; if it doesn’t, the client SHOULD simply ignore the pointer.
  - `parameter` (string, nullable) - A string indicating which URI query parameter caused the error.
  - `header` (string, nullable) - A string indicating the name of a single request header which caused the error.
- `meta` (object, nullable) - A [meta object](https://jsonapi.org/format/#document-meta) containing non-standard meta-information about the error. Null where the request carried no `X-Correlation-ID`, and on an authentication failure, which does not echo it.
  - `correlationId` (string) - The `X-Correlation-ID` sent with the request, echoed back so it can be quoted to support. Absent when the request carried no correlation header.

Example (A field in the request body did not validate):

```json
{
  "id": "3a6c9e01-5f2b-4d8a-9c47-1e0b7d5a2f83",
  "links": null,
  "code": "BadRequest",
  "title": "Bad Request",
  "detail": "amount: Amount must be greater than 0",
  "source": {
    "pointer": "/amount",
    "parameter": null,
    "header": null
  },
  "meta": {
    "correlationId": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
  }
}
```

### 401 Unauthorised.

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

`Error`, the same schema listed in full earlier in this document. Its own page: https://hub.ozow.com/api-reference/one-api/schemas/error.md

Example (No usable access token on the request):

```json
{
  "id": "1cecc2b7-1c29-418a-b26a-bf7546926083",
  "links": {
    "about": "https://ozow.stoplight.io/docs/one-api/zi18vomr0jm8c-generate-authentication-token",
    "type": "https://tools.ietf.org/html/rfc7235#section-3.1"
  },
  "code": "Unauthorized",
  "title": "Unauthorized Request",
  "detail": "Authorization header is missing or invalid.",
  "source": {
    "pointer": null,
    "parameter": null,
    "header": "Authorization"
  },
  "meta": null
}
```

### 500 Internal Server Error.

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

`Error`, the same schema listed in full earlier in this document. Its own page: https://hub.ozow.com/api-reference/one-api/schemas/error.md

Example (The request failed on the Ozow side):

```json
{
  "id": "9e0d5a83-6b24-4c19-8f7a-2d1b3e6c0a97",
  "links": null,
  "code": "InternalServerError",
  "title": "Internal Server Error",
  "detail": "Error occurred while processing request.",
  "source": {
    "pointer": "/data",
    "parameter": null,
    "header": null
  },
  "meta": {
    "correlationId": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
  }
}
```


---

# Generate Authentication Token

> POST `/token`
> Part of the One API reference. Source: https://hub.ozow.com/api-reference/one-api/post-token/

Server: `https://one.ozow.com/v1` (Production)

Other environments: `https://stagingone.ozow.com/v1` (Staging)

Retrieve an authentication token as per the [OpenAuth 2.0 Client Credential Flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4).

## Authentication

This operation takes no credentials.

## Header parameters

- `X-Correlation-ID` (string) - Optional correlation id for the request, if not supplied a new one will be generated and passed onto all underlying requests and returned as a header.

## Request body

- `client_id` (string, required) - The unique client id provided to the merchant during credential exchange.
- `client_secret` (string, required) - The secret provided to the merchant during credential exchange.
- `scope` (string, required) - The scope of permissions required. Can be just one or a list of space-delimited, case-sensitive strings.
- `grant_type` (string, required) - Must be _client_credentials_.

## Responses

### 200 Succesfull response as per [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-5.1)

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

- `access_token` (string, required) - The access token issued by the authorization server.
- `token_type` (string, required) - Must be `bearer` as in [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750).
- `expires_in` (string, required) - The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will expire in one hour from the time the response was generated.
- `scope` (string) - OPTIONAL, if identical to the scope requested by the client; otherwise, REQUIRED.

### 400 Bad Request.

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

- `id` (string, uuid, required) - a unique identifier for this particular occurrence of the problem.
- `links` (object, nullable) - Present on an authentication or authorisation failure, and null otherwise.
  - `about` (string, uri) - A link that leads to further details about this particular occurrence of the problem. When derefenced, this URI SHOULD return a human-readable description of the error.
  - `type` (string, uri) - A link that identifies the type of error that this particular error is an instance of. This URI SHOULD be dereferencable to a human-readable explanation of the general error.
- `code` (string, required) - An application-specific error code, expressed as a string value. Key on this rather than on `title` or `detail`, which are written for a person. A rejection at the transport level uses the status name, one of `BadRequest`, `Unauthorized`, `Forbidden`, `NotFound`, `NotAllowed`, `Conflict`, `UnsupportedMediaType`, `BadGateway` or `InternalServerError`. An operation refusing a request on its own rules returns a code of its own.
- `title` (string, required) - A short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization.
- `detail` (string, required) - A human-readable explanation specific to this occurrence of the problem. Like title, this field’s value can be localized.
- `source` (object, nullable) - Where in the request the problem was found. All three keys are present whenever `source` is, with the ones that do not apply set to null. `source` itself is null where the failure is not about a part of the request.
  - `pointer` (string, json-pointer, nullable) - a JSON Pointer [RFC6901](https://tools.ietf.org/html/rfc6901) to the value in the request document that caused the error [e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute]. This MUST point to a value in the request document that exists; if it doesn’t, the client SHOULD simply ignore the pointer.
  - `parameter` (string, nullable) - A string indicating which URI query parameter caused the error.
  - `header` (string, nullable) - A string indicating the name of a single request header which caused the error.
- `meta` (object, nullable) - A [meta object](https://jsonapi.org/format/#document-meta) containing non-standard meta-information about the error. Null where the request carried no `X-Correlation-ID`, and on an authentication failure, which does not echo it.
  - `correlationId` (string) - The `X-Correlation-ID` sent with the request, echoed back so it can be quoted to support. Absent when the request carried no correlation header.

Example (A field in the request body did not validate):

```json
{
  "id": "3a6c9e01-5f2b-4d8a-9c47-1e0b7d5a2f83",
  "links": null,
  "code": "BadRequest",
  "title": "Bad Request",
  "detail": "amount: Amount must be greater than 0",
  "source": {
    "pointer": "/amount",
    "parameter": null,
    "header": null
  },
  "meta": {
    "correlationId": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
  }
}
```

### 401 Unauthorised. A scope in `scope` is not one this client is allowed. Scopes are granted per client, so a client can authenticate and still be refused a scope.

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

`Error`, the same schema listed in full earlier in this document. Its own page: https://hub.ozow.com/api-reference/one-api/schemas/error.md

Example (A requested scope is not granted to this client):

```json
{
  "id": "4f7c0d2b-8a15-4e93-b06d-7c2e9f1a5b48",
  "links": {
    "about": "https://ozow.stoplight.io/docs/one-api/zi18vomr0jm8c-generate-authentication-token",
    "type": "https://tools.ietf.org/html/rfc7235#section-3.1"
  },
  "code": "Unauthorized",
  "title": "Unauthorized Request",
  "detail": "Consumer does not have access to requested scope",
  "source": {
    "pointer": null,
    "parameter": null,
    "header": "Authorization"
  },
  "meta": null
}
```

### 404 Not found. No client matches `client_id`, or the client has been deactivated.

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

`Error`, the same schema listed in full earlier in this document. Its own page: https://hub.ozow.com/api-reference/one-api/schemas/error.md

Example (The client id does not resolve):

```json
{
  "id": "cccb6fb8-505e-4ff9-90b5-815ecf42424e",
  "links": null,
  "code": "NotFound",
  "title": "Not Found",
  "detail": "Consumer could not be found for client id YOUR_CLIENT_ID.",
  "source": {
    "pointer": "/data/clientId",
    "parameter": "/v1/token",
    "header": null
  },
  "meta": {
    "correlationId": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
  }
}
```

### 500 Internal Server Error.

- Header `X-Correlation-ID`: The correlation id for the request that was processed.

`Error`, the same schema listed in full earlier in this document. Its own page: https://hub.ozow.com/api-reference/one-api/schemas/error.md

Example (The request failed on the Ozow side):

```json
{
  "id": "9e0d5a83-6b24-4c19-8f7a-2d1b3e6c0a97",
  "links": null,
  "code": "InternalServerError",
  "title": "Internal Server Error",
  "detail": "Error occurred while processing request.",
  "source": {
    "pointer": "/data",
    "parameter": null,
    "header": null
  },
  "meta": {
    "correlationId": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
  }
}
```


---

# Get Settlements

> GET `/secure/settlements`
> Part of the Payments API reference. Source: https://hub.ozow.com/api-reference/payments-api/get-secure-settlements/

Server: `https://api.ozow.com` (Production)

Other environments: `https://stagingapi.ozow.com` (Staging)

Retrieve the lastest settlements created for the merchant.

## Authentication

- `BearerToken` (HTTP bearer)
  - The token generated by the `Get API token` operation. The same token is used for every request until it expires.

## Query parameters

- `count` (integer) - The number of settlements to return. The max allowed value is 100.

## Responses

### 200 Success

**application/json**

- `settlements` (array of Settlement, required) - The latest settlements for the merchant.
- `errors` (array of string, required) - Any errors that occurred for the request.

**application/xml**

`SettlementList`, the same schema listed in full earlier in this document. Its own page: https://hub.ozow.com/api-reference/payments-api/schemas/settlement-list.md

### 400 Bad Request. The operation could not be carried out. The body is a message rather than a structured error.

string

Example (example 1):

```json
There was an error processing your request
```

### 401 Unauthorized. The `ApiKey` header is missing or does not match the site, or for a `Secure` operation the bearer token is missing, expired or invalid.

string

Example (example 1):

```json
API key is missing or invalid.
```


---

# Get Site Settlements

> GET `/secure/settlements/getsitesettlements`
> Part of the Payments API reference. Source: https://hub.ozow.com/api-reference/payments-api/get-secure-settlements-getsitesettlements/

Server: `https://api.ozow.com` (Production)

Other environments: `https://stagingapi.ozow.com` (Staging)

The settlements created for a merchant site over a selected period.

## Authentication

- `BearerToken` (HTTP bearer)
  - The token generated by the `Get API token` operation. The same token is used for every request until it expires.

## Query parameters

- `fromDate` (string, required) - Desired start date to retrieve respective merchant site settlements. Format: yyyy-mm-dd
- `toDate` (string, required) - Desired end date to retrieve respective merchant site settlements. Format: yyyy-mm-dd

## Responses

### 200 OK

**application/json**

array of SiteSettlement

**application/xml**

array of SiteSettlement

### 400 Bad Request. The operation could not be carried out. The body is a message rather than a structured error.

string

Example (example 1):

```json
There was an error processing your request
```

### 401 Unauthorized. The `ApiKey` header is missing or does not match the site, or for a `Secure` operation the bearer token is missing, expired or invalid.

string

Example (example 1):

```json
API key is missing or invalid.
```
