---
url: /guide/statuses.md
---
# Request Statuses

Every payment request has a `status`. It is in the response when you create the request, in every [callback](/guide/callbacks), and in [Get a Request](/api/get-request).

Use `status` in your code. `statusTitle` is the same thing as people see it in Johari, for display only.

## All statuses

| `status` | `statusTitle` | What it means | Final? |
|---|---|---|---|
| `pending` | Pending Approval | Waiting for an approver in Johari | No |
| `approved` | Approved | Approved. Waiting for finance to confirm it for payment | No |
| `pending-payment` | Pending Payment | Confirmed. Waiting for the cashier to pay it | No |
| `returned` | Returned | Sent back to the person in `returnedTo` to fix or explain something: by an approver before approving it, or by the cashier before paying it. See [Returned](#returned) | No |
| `efd` | EFD Upload | Paid. Waiting for the EFD (tax) receipt to be uploaded | No |
| `completed` | Completed | Paid, and all paperwork is done | **Yes** |
| `rejected` | Rejected | Rejected. It will not be paid. The reason is in `note` | **Yes** |
| `master-partial-payment` | Master Partial Payment | Being paid in parts. See [Partial payments](#partial-payments) | No |
| `master-partial-payment-completed` | Partial Payment Completed | All parts have been raised. See [Partial payments](#partial-payments) | **Yes** |

## The normal path

```
pending  ◄──►  returned   (an approver can return it to its creator)
   │   (can be rejected here)
   ▼
approved
   │   (can be rejected here)
   ▼
pending-payment  ◄──►  returned
   │
   ▼
efd   (skipped if no EFD receipt is needed)
   │
   ▼
completed
```

Step by step:

1. **`pending`**: you raised the request. An approver looks at it.
2. **`approved`**: the approver said yes. Finance now confirms the payment details.
3. **`pending-payment`**: finance confirmed. The cashier will pay it.
4. **`efd`**: the cashier paid. Johari now waits for the EFD receipt. If no EFD receipt is needed, this step is skipped.
5. **`completed`**: done.

### Starting as `approved`

Some requests are approved straight away when they are created. `POST /api/requests` then returns `"status": "approved"` instead of `"pending"`. If it was approved automatically because the key's user may approve it themselves, `approvedBy` is that user's name followed by ` (API)`. There is **no callback** for this first status, because you already have it in the response.

### Rejected

A request can be rejected while it is `pending` or `approved`. The reason is in `note`. `rejected` is final: to try again, raise a new request with a **new** `idempotencyKey`.

### Returned

A request can be returned at two points, to fix or explain something. It becomes `returned`, and the reason is in `note`:

* While it is `pending`, an **approver** can return it before approving it. This kind of return always goes to its creator.
* While it is `pending-payment`, the **cashier** can return it before paying it, to its creator or its confirmer.

`returnedTo` says who it was returned to, and so who must answer:

| `returnedTo` | Who answers | What you do |
|---|---|---|
| `"creator"` | **Your system.** The request was raised with your API key, so your key's user is its creator | Answer it with [Respond to a Return](/api/respond-to-return). If you were asked for a document, add it first with [Add Files](/api/add-files) |
| `"confirmer"` | The person who confirmed it for payment, in Johari | Nothing. Wait for their answer: you get the callback |
| `"approver"` | The request's approver, in Johari | Nothing. Wait for their answer: you get the callback |

A request raised through the API has no separate approver recorded, so in practice it is returned to the creator or the confirmer.

Once the return is answered, the request goes back to the stage it was returned from (`pending` or `pending-payment`) and you get a callback. This can happen more than once.

`returnedTo` keeps its last value after the request moves on. Always check `status` too: only `status: "returned"` means it is waiting for an answer now.

### Deleted

A request can be deleted in Johari. There is no `deleted` status. Instead:

* If the request has a `callbackURL`, you get a callback with `event` set to `request.deleted`. It carries the request as it was just before deletion, so its `status` is the status it had then. See [Callbacks](/guide/callbacks#events).
* After that, [Get a Request](/api/get-request) returns `404` for it.

## Partial payments

Sometimes a request is not paid in one go. Finance marks it to be paid in parts.

* The original request becomes the **main request**, with status `master-partial-payment`. `amountRemaining` shows how much is still to be paid.
* Each part is a new **child request**, with its own `requestID` and `requestNumber`, `isChildRequest: true`, and `mainRequestID` set to the main request.
* Each child request goes through `pending-payment` → `efd` → `completed` like a normal request.
* When the parts add up to the full amount, the main request becomes `master-partial-payment-completed`.

Example: a request of 150,000 paid in two parts of 100,000 and 50,000.

```
Main request #1042 (150,000)
   approved
   ▼
   master-partial-payment            amountRemaining: 150,000
   │
   ├── Child request #1057 (100,000)  amountRemaining: 50,000
   │      pending-payment ► efd ► completed
   │
   └── Child request #1063 (50,000)   amountRemaining: 0
          pending-payment ► completed
   ▼
   master-partial-payment-completed
```

You get callbacks for the main request **and** for each child request, all sent to the main request's `callbackURL`. A child request's `requestID` is new to you: link it to your record using `mainRequestID`.

If a child request is deleted in Johari, its amount goes back to the main request. The main request returns to `master-partial-payment` and you get a `request.status-changed` callback for it. You also get a `request.deleted` callback for the child.

Example callback for a child request:

```json
{
  "event": "request.status-changed",
  "deliveryID": "6650f1a2b3c4d5e6f7a8ba11",
  "requestID": "6650f1a2b3c4d5e6f7a8ba10",
  "requestNumber": "1057",
  "status": "pending-payment",
  "statusTitle": "Pending Payment",
  "description": "Cement for slab",
  "amount": 100000,
  "currency": "TZS",
  "amountRemaining": null,
  "modeOfPayment": "bank",
  "isUrgent": false,
  "company": {
    "id": "6650f1a2b3c4d5e6f7a8b901",
    "name": "Example Construction Ltd"
  },
  "site": {
    "id": "3c9a7f52-1d4e-4b8a-9f6c-2e1d5a7b8c90",
    "name": "Block A Apartments"
  },
  "costCode": "1003",
  "isChildRequest": true,
  "mainRequestID": "6650f1a2b3c4d5e6f7a8b9e1",
  "note": null,
  "createdAt": "2026-10-29T09:58:04.112Z",
  "createdBy": "Neema Mollel (API)",
  "approvedAt": "2026-10-29T10:12:36.571Z",
  "approvedBy": "Amina Juma",
  "rejectedAt": null,
  "rejectedBy": null,
  "confirmedAt": "2026-10-30T08:01:11.870Z",
  "confirmedBy": "Grace Kimaro",
  "paidAt": null,
  "paidBy": null,
  "efdAt": null,
  "efdBy": null,
  "returnedAt": null,
  "returnedBy": null,
  "returnedTo": null,
  "files": [
    {
      "id": "0b6f2c1e-8d4a-4f3b-9e7c-5a1d2b3c4e5f",
      "name": "invoice-1042.pdf",
      "type": "application/pdf",
      "size": 48213
    }
  ],
  "changedAt": "2026-10-30T08:01:12.004Z"
}
```

## Mapping statuses in your system

Most systems only need three states. A simple mapping:

| Your state | Johari `status` |
|---|---|
| Waiting | `pending`, `approved`, `pending-payment`, `returned`, `master-partial-payment` |
| Paid | `efd`, `completed`, `master-partial-payment-completed` |
| Rejected | `rejected` |
| Deleted | No status: a `request.deleted` callback, or `404` from [Get a Request](/api/get-request) |

::: tip Handle unknown statuses
New statuses may be added in future. If you receive a `status` you do not know, store it and show `statusTitle`. Do not crash.
:::

## Related pages

* [Callbacks](/guide/callbacks)
* [Get a Request](/api/get-request)
* [Respond to a Return](/api/respond-to-return)
