Appearance
Get a Request
Returns the current state of a request you raised. Use it when:
- you did not give a
callbackURL, or - your callback server was down for a long time and you want to catch up, or
- you just want to double-check before acting.
Endpoint
| Method | URL |
|---|---|
GET | /api/requests/:requestID |
| Path parameter | Description |
|---|---|
requestID | The requestID you got when you created the request, or from a callback |
<timestamp>.GET./api/requests/6650f1a2b3c4d5e6f7a8b9e1.You can only see requests raised with your API key. That includes child requests made from them.
Response
data is the request summary. It has the same fields as a callback, in the same order, without event, deliveryID and changedAt:
| Field | Type | Description |
|---|---|---|
requestID | string | The request's ID |
requestNumber | string | Its number in Johari |
status | string | Current status. See Request Statuses |
statusTitle | string | Status as people see it in Johari, e.g. "Pending Payment" |
description | string | What the payment is for |
amount | number | The request amount |
currency | string | e.g. "TZS" |
amountRemaining | number | null | Only for a request being paid in parts: how much is still unpaid. Otherwise null |
modeOfPayment | string | "cash" or "bank" |
isUrgent | boolean | true if the request is marked urgent |
company | object | The requesting company: { "id": string, "name": string } |
site | object | The requesting site: { "id": string, "name": string } |
costCode | string | The cost code value |
isChildRequest | boolean | true for one part of a request being paid in parts. See Partial payments |
mainRequestID | string | null | For a child request, the request it belongs to. Otherwise null |
note | string | null | Only the note written with the latest status change, e.g. the rejection reason, the return reason, a payment note or an approval comment. null if that change had no note. Never an older note |
createdAt | string | When the request was created (ISO 8601, UTC) |
createdBy | string | Name of the person in Johari who created it. For requests you raise, the API key's user's name followed by (API), e.g. "Neema Mollel (API)". Johari shows the same in the request, its log and the WhatsApp notifications, so people can see a system raised it |
approvedAt | string | null | When it was approved (ISO 8601, UTC) |
approvedBy | string | null | Name of the person who approved it. If the request was approved automatically (because the key's user may approve it themselves), it is also the key's user's name followed by (API) |
rejectedAt | string | null | When it was rejected (ISO 8601, UTC) |
rejectedBy | string | null | Name of the person who rejected it |
confirmedAt | string | null | When accounts confirmed it, sending it for payment (ISO 8601, UTC) |
confirmedBy | string | null | Name of the person who confirmed it |
paidAt | string | null | When it was paid (ISO 8601, UTC) |
paidBy | string | null | Name of the person who paid it |
efdAt | string | null | When the EFD (tax) receipt was uploaded (ISO 8601, UTC) |
efdBy | string | null | Name of the person who uploaded the EFD receipt |
returnedAt | string | null | When it was returned (ISO 8601, UTC) |
returnedBy | string | null | Name of the person who returned it |
returnedTo | string | null | Who the cashier returned it to: "creator", "approver" or "confirmer". null if it was never returned. While status is "returned" and this is "creator", the request is waiting for your system to answer. Keeps its last value after the request moves on, so always check status too. See Returned requests |
files | array | Every file on the request. See Files |
The ...At fields say when that step happened. The ...By fields are the name of the person in Johari who did it. Each one is null until that step happens.
note is only ever the note from the latest status change. Johari's other internal notes are not shared.
Returned requests
returnedTo says who the cashier returned the request to.
- While
statusis"returned"andreturnedTois"creator", the request is waiting for your system to answer. See Respond to a Return. - When it is
"approver"or"confirmer", that person answers it in Johari. You just receive the callbacks.
returnedTo keeps its last value after the request moves on, so always check status too. See Returned.
Files
files lists every file on the request:
- the files sent when the request was created,
- the proof of payment the cashier uploads when paying,
- the EFD receipt,
- any file people attach to the request in Johari (for example an accountant's upload),
- any file your system adds with Add Files.
Each item:
| Field | Type | Description |
|---|---|---|
id | string | The file's ID. Use it with Get a File to download it |
name | string | The file name, e.g. "invoice-1042.pdf" |
type | string | null | The file type, e.g. "application/pdf", or null |
size | number | null | The size in bytes, or null |
There is no download link in files. Ask Get a File for a fresh link each time you need one. Internal storage paths are never shown.
Example
bash
REQUEST_ID="6650f1a2b3c4d5e6f7a8b9e1"
TS=$(date +%s)
SIG=$(printf '%s' "$TS.GET./api/requests/$REQUEST_ID." \
| openssl dgst -sha256 -hmac "$SIGNING_SECRET" \
| sed 's/^.* //')
curl "$BASE_URL/api/requests/$REQUEST_ID" \
-H "x-api-key: $API_KEY" \
-H "x-timestamp: $TS" \
-H "x-signature: $SIG"js
const requestID = "6650f1a2b3c4d5e6f7a8b9e1";
const result = await callJohari("GET", `/api/requests/${requestID}`);
console.log(result.data.status);python
request_id = "6650f1a2b3c4d5e6f7a8b9e1"
result = call_johari("GET", f"/api/requests/{request_id}")
print(result["data"]["status"])Response 200, for a request that has been paid and completed:
json
{
"status": "success",
"data": {
"requestID": "6650f1a2b3c4d5e6f7a8b9e1",
"requestNumber": "1042",
"status": "completed",
"statusTitle": "Completed",
"description": "Cement for slab",
"amount": 150000,
"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": false,
"mainRequestID": null,
"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-29T13:40:18.230Z",
"confirmedBy": "Grace Kimaro",
"paidAt": "2026-10-30T07:55:02.918Z",
"paidBy": "Joseph Mwakyusa",
"efdAt": "2026-10-30T11:20:45.067Z",
"efdBy": "Joseph Mwakyusa",
"returnedAt": null,
"returnedBy": null,
"returnedTo": null,
"files": [
{
"id": "0b6f2c1e-8d4a-4f3b-9e7c-5a1d2b3c4e5f",
"name": "invoice-1042.pdf",
"type": "application/pdf",
"size": 48213
},
{
"id": "5e9a1b7c-3d2f-4a6e-8b1c-9d0e2f3a4b5c",
"name": "payment-slip-1042.pdf",
"type": "application/pdf",
"size": 102944
},
{
"id": "a2c4e6f8-1b3d-4f5a-9c7e-0d2b4f6a8c1e",
"name": "efd-receipt-1042.jpg",
"type": "image/jpeg",
"size": 231570
}
]
},
"message": "Request fetched successfully"
}Response 404:
json
{
"status": "error",
"data": null,
"message": "Request not found"
}You get 404 when the ID is wrong, when the request was raised with a different API key, or when it was deleted in Johari. If the request has a callbackURL, a deletion also sends a request.deleted callback.
Polling
If you rely on this endpoint instead of callbacks, do not call it in a tight loop. Requests move at human speed: checking each open request every 5 to 15 minutes is plenty. Stop checking once the status is final (completed, rejected or master-partial-payment-completed), or once you get 404 because the request was deleted.