Skip to content

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 ​

statusstatusTitleWhat it meansFinal?
pendingPending ApprovalWaiting for an approver in JohariNo
approvedApprovedApproved. Waiting for finance to confirm it for paymentNo
pending-paymentPending PaymentConfirmed. Waiting for the cashier to pay itNo
returnedReturnedSent 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 ReturnedNo
efdEFD UploadPaid. Waiting for the EFD (tax) receipt to be uploadedNo
completedCompletedPaid, and all paperwork is doneYes
rejectedRejectedRejected. It will not be paid. The reason is in noteYes
master-partial-paymentMaster Partial PaymentBeing paid in parts. See Partial paymentsNo
master-partial-payment-completedPartial Payment CompletedAll parts have been raised. See Partial paymentsYes

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:

returnedToWho answersWhat you do
"creator"Your system. The request was raised with your API key, so your key's user is its creatorAnswer 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 JohariNothing. Wait for their answer: you get the callback
"approver"The request's approver, in JohariNothing. 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.
  • After that, Get a 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 stateJohari status
Waitingpending, approved, pending-payment, returned, master-partial-payment
Paidefd, completed, master-partial-payment-completed
Rejectedrejected
DeletedNo 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.