Appearance
Request Statuses
Every payment request has a status. It is in the response when you create the request, in every callback, and in Get a 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 | 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 | No |
master-partial-payment-completed | Partial Payment Completed | All parts have been raised. See 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)
│
▼
completedStep by step:
pending: you raised the request. An approver looks at it.approved: the approver said yes. Finance now confirms the payment details.pending-payment: finance confirmed. The cashier will pay it.efd: the cashier paid. Johari now waits for the EFD receipt. If no EFD receipt is needed, this step is skipped.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. If you were asked for a document, add it first with 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 witheventset torequest.deleted. It carries the request as it was just before deletion, so itsstatusis the status it had then. See Callbacks. - After that, Get a Request returns
404for 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.amountRemainingshows how much is still to be paid. - Each part is a new child request, with its own
requestIDandrequestNumber,isChildRequest: true, andmainRequestIDset to the main request. - Each child request goes through
pending-payment→efd→completedlike 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-completedYou 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 |
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.