Appearance
Errors
When something goes wrong you get an HTTP status other than 200, and the usual envelope with "status": "error".
json
{
"status": "error",
"data": "Please provide a modeOfPayment of cash or bank",
"message": "Please provide a modeOfPayment of cash or bank"
}Read the HTTP status first, then the text. For most errors data and message hold the same text. For authentication errors, message is always "Unauthorized to access" and data says exactly what is wrong.
HTTP status codes
| HTTP | Meaning | Retry? |
|---|---|---|
200 | It worked | |
400 | Something in your data is wrong or missing | No. Fix the data first |
401 | Signing or API key problem | No. Fix your signing, key or clock |
403 | The key's user cannot use Johari yet, or may not do this (for example, a request was returned to someone else) | No. Read the message |
404 | Not found, not visible to your key, or a wrong path | No |
429 | Too many calls. See Too many requests | Yes, after the Retry-After header's number of seconds. For POST, use the same idempotencyKey and body |
409 | That idempotencyKey was already used | Depends on data. With data: no, it worked, data is what the first call returned. With data: null: yes, after a few seconds with the same key. See Idempotency |
500 | Something went wrong on Johari's side | Yes, after a short wait. For POST, use the same idempotencyKey and body |
Authentication errors
These can come from any endpoint.
| HTTP | data | Cause | Fix |
|---|---|---|---|
401 | Please provide the x-api-key, x-signature and x-timestamp headers | One or more headers missing. The message does not say which one | Send all three. See Signing |
401 | Request timestamp is too old or invalid | x-timestamp is more than 60 seconds away from Johari's clock, or is not only digits (Unix seconds) | Use the current time in seconds, digits only, made fresh for each call. Check your server clock |
401 | Invalid or disabled API key | The key is wrong, it was disabled in Johari, or it was rotated and you are still using the old pair | Check the key: the whole value, jh_key_ included. If it was rotated, use the new pair. If it was disabled, get a new key. See Getting Started |
401 | Invalid signature | The signature does not match, or x-signature is not exactly 64 hex characters | Sign with the whole signing secret, jh_secret_ included. After a rotation, use the new secret. See Common mistakes. Check your code against the test vectors |
401 | The owner of this API key is disabled or no longer exists | The Johari user the key belongs to was disabled | Ask your Johari administrator |
403 | The owner of this API key must review and accept their permission agreement before using the system. Please sign in to the web portal to complete this. | The key's user has not accepted Johari's permissions agreement | That user must sign in to Johari once and accept it |
Example:
json
{
"status": "error",
"data": "Invalid signature",
"message": "Unauthorized to access"
}Too many requests
These can come from any endpoint. Johari answers 429 when:
- an API key goes over its limit. Per API key, per server, per minute: at most 120 calls in total, and at most 30 writes within those. Writes are creates, responses to returns and file uploads together (
POST /api/requests,POST /api/requests/:requestID/respond-to-returnandPOST /api/requests/:requestID/files), or - one address had 30 failed sign-ins (any
401) within a minute. Every call from that address then gets429for the rest of that minute, even with a valid key.
The answer has a Retry-After header: the number of seconds until the minute resets. The body is always:
json
{
"status": "error",
"data": "Too many requests. Please wait before trying again",
"message": "Too many requests. Please wait before trying again"
}Wait Retry-After seconds, then send the call again. For a POST, send the same idempotencyKey and the same body, so nothing is done twice.
Lookup errors
| Endpoint | HTTP | message | Cause |
|---|---|---|---|
GET /api/companies/:companyID/sites | 404 | Company not found | Wrong ID, or the key's user cannot raise requests for this company |
GET /api/companies/:companyID/cost-codes | 404 | Company not found | Same as above |
GET /api/sites/:siteID/approvers | 404 | Site not found | Wrong ID, or the key's user cannot raise requests at this site |
Create request errors
POST /api/requests. Checks run in the order below, and you get the first problem found. In three stages: (1) the idempotencyKey, so a key that was already used is answered before any other field is looked at; (2) each field's type and format, in the order of the table, then callbackURL, then files; (3) whether requestingCompany, requestingSite, costCode and admin are in your lookup lists.
In the files[0] messages, the number is the position of the bad file in files, starting at 0. For example, files[2] is the third file.
| HTTP | message | Fix |
|---|---|---|
400 | Please provide an idempotencyKey of up to 255 characters | Send a text idempotencyKey, 1 to 255 characters |
400 | This idempotencyKey was already used for a different request. Use a new key for a new request | Every new request needs a new key. A key already used for a response or an upload cannot be used either. On a retry, send exactly the same body. See Idempotency |
400 | Please provide a description | Send a non-empty text description |
400 | description cannot be longer than 500 characters | Shorten it |
400 | Please provide an amount greater than 0 | Send a number above 0, or a string like "150000", "150,000" or "150,000.50" |
400 | amount must be a number such as 150000, or a string such as "150,000.50" | A string amount held something other than digits, thousands commas and one decimal part (e.g. "0x10", "1e3", "-5", ".5"). Send a JSON number, or a string like "150,000.50" |
400 | Amount cannot have more than 2 decimal places | Round to 2 decimal places |
400 | amount is too large | The amount is above 90,071,992,547,409.91, the most Johari can store. Check the amount |
400 | Please provide a valid requestingCompany | Send a company id from List Companies |
400 | Please provide a requestingSite | Send a site id as text |
400 | Please provide a costCode | Send the cost code value as text: "1003", not 1003 |
400 | Please provide a modeOfPayment of cash or bank | Use "cash" or "bank" |
400 | Please provide a currency of USD, TZS, KES, ZAR, AED, GBP | Use one of those, or leave currency out for TZS |
400 | isUrgent must be true or false | Send true or false, not "yes" |
400 | notes must be text | Send a string |
400 | notes cannot be longer than 2000 characters | Shorten it |
400 | Please provide an admin: an approver id from the site's approvers list | admin is missing, or is not a 24-hex-character ID. It is required: send an approver id from List Approvers for requestingSite |
400 | callbackURL cannot be longer than 2048 characters | Shorten it. callbackURL is at most 2048 characters |
400 | Please provide a valid callbackURL | Send a full https:// URL your server answers on. On the live server it must point to a public address: localhost, and IP addresses that are loopback, private, link-local, carrier-grade NAT, multicast or otherwise reserved, are refused. See Turning callbacks on |
400 | files must be a list | Send files as a JSON array, or leave it out |
400 | You can attach at most 5 files | Send 5 files or fewer |
400 | files[0] must be an object with name, contentType and content | Each item in files must be an object. The number in files[N] is the position of the bad file, starting at 0 |
400 | files[0].name must be a file name of up to 255 characters, without slashes or invisible characters | Send a non-empty name of up to 255 characters, with no / or \, and no control or invisible formatting characters (for example right-to-left marks) |
400 | files[0].contentType must be one of application/pdf, image/jpeg, image/png, image/webp, image/heic, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | Use one of the listed types, exactly as written. See Attaching files |
400 | files[0].content must be the file encoded as base64 | Send the file as standard base64: A–Z, a–z, 0–9, +, /, with = padding, length a multiple of 4. No data: prefix, no line breaks |
400 | files[0].content is empty | The file has no bytes. Send the whole file |
400 | files[0].content does not match its contentType | The file's first bytes are not of the type in contentType (for example a PDF without %PDF- in its first 1024 bytes). Pick contentType from the file's extension with the table, and send the real file, not placeholder text |
400 | The attached files cannot be more than 25 MB together | Keep all files together at or under 25 MB (decoded) |
400 | requestingCompany was not found among the companies you can raise requests for | Use an id from List Companies |
400 | requestingSite was not found among this company's sites you can raise requests for | Use an id from List Sites for the same company |
400 | costCode is not one of this company's cost codes | Use a value from List Cost Codes for the same company |
400 | admin is not one of this site's approvers | Use an id from List Approvers for the same site as requestingSite |
400 | Requesting company is disabled | The company was disabled in Johari. Ask your administrator |
400 | An error occurred, please try again | The body is not valid JSON. Check your JSON. (data is null here) |
413 | An error occurred, please try again | The body is over 35 MB, usually because of the attached files. Keep the files at or under 25 MB together, which keeps the body under 35 MB. (data is null here) |
409 | A request with this idempotencyKey has already been processed | Not a failure: data has the request that was created. See Idempotency |
409 | A request with this idempotencyKey is still being processed | Wait a few seconds and send it again with the same key |
500 | Failed to upload the files, please try again later | Retry after a short wait with the same idempotencyKey and the same body |
500 | Failed to create request, please try again later | Retry after a short wait with the same idempotencyKey |
Get request errors
GET /api/requests/:requestID
| HTTP | message | Cause |
|---|---|---|
404 | Request not found | Wrong ID, the request was raised with a different API key, or it was deleted in Johari |
Get file errors
GET /api/requests/:requestID/files/:fileID
| HTTP | message | Cause |
|---|---|---|
404 | Request not found | Wrong request ID, the request was raised with a different API key, or it was deleted in Johari |
404 | File not found | The request has no file with that fileID. Use a files[].id from Get a Request or a callback |
For both, data is null.
Respond to a Return errors
POST /api/requests/:requestID/respond-to-return. Checks run in the order below, and you get the first problem found. data holds the same text as message, except where noted.
| HTTP | message | Fix |
|---|---|---|
400 | Please provide an idempotencyKey of up to 255 characters | Send a text idempotencyKey, 1 to 255 characters |
400 | This idempotencyKey was already used for a different request. Use a new key for a new request | The key was already used with a different body, for a different request, or for another call (a create or an upload). Use a new key for each new response. On a retry, send exactly the same body to the same URL. See Idempotency |
409 | A request with this idempotencyKey has already been processed | Not a failure: your response was already submitted. data is what that call returned. See Idempotency |
409 | A request with this idempotencyKey is still being processed | Wait a few seconds and send it again with the same key. (data is null here) |
404 | Request not found | Wrong request ID, the request was raised with a different API key, or it was deleted in Johari |
403 | This request was returned to its confirmer (Amina Juma), so only they can respond to it | The request was returned to its approver or confirmer (the message names them), not to your key's user. That person answers it in Johari. Wait for the callback |
400 | Please provide a response | note is missing, empty or not text. Send your answer as text |
400 | note cannot be longer than 2000 characters | Shorten it |
400 | This request is not in returned status | The request is not returned: it was never returned, or the return was already answered. Check it with Get a Request |
403 | You do not have permission | Your key's user can no longer raise requests at the request's site. Ask your Johari administrator |
A body that is not valid JSON gets 400, and a body over 35 MB gets 413, before any of these checks. Both are the same as for creating a request (message An error occurred, please try again, data null).
Add Files errors
POST /api/requests/:requestID/files. Checks run in the order below, and you get the first problem found. data holds the same text as message, except where noted.
| HTTP | message | Fix |
|---|---|---|
400 | Please provide an idempotencyKey of up to 255 characters | Send a text idempotencyKey, 1 to 255 characters |
400 | This idempotencyKey was already used for a different request. Use a new key for a new request | The key was already used with a different body, for a different request, or for another call (a create or a response). Use a new key for each new upload. On a retry, send exactly the same body to the same URL. See Idempotency |
409 | A request with this idempotencyKey has already been processed | Not a failure: the files were already added. data is what that call returned. See Idempotency |
409 | A request with this idempotencyKey is still being processed | Wait a few seconds and send it again with the same key. (data is null here) |
404 | Request not found | Wrong request ID, the request was raised with a different API key, or it was deleted in Johari |
400 | You can only upload up to 3 files | Send 1 to 3 files. For more, make another call with a new key |
400 | files must be a list | Send files as a JSON array |
400 | Any files[0]... message | The same messages and fixes as for creating a request. The number is the position of the bad file in files, starting at 0 |
400 | The attached files cannot be more than 25 MB together | Keep all files together at or under 25 MB (decoded) |
400 | Please provide a file | files is missing or an empty list. Send 1 to 3 files |
500 | Failed to upload the files, please try again later | Retry after a short wait with the same idempotencyKey and the same body |
403 | You do not have permission | Your key's user no longer has access to the request's site. Ask your Johari administrator |
A body that is not valid JSON gets 400, and a body over 35 MB gets 413, before any of these checks. Both are the same as for creating a request (message An error occurred, please try again, data null).
Server errors
| HTTP | message | What to do |
|---|---|---|
500 | An error occurred, please try again (data is null) | Retry after a short wait. For a POST, use the same idempotencyKey and body, so nothing is done twice. If it keeps happening, contact your Johari administrator |
Wrong path
A path that does not exist (for example a typo like /api/companys, or /api/requests without an ID) returns:
json
{
"status": "error",
"data": null,
"message": "This endpoint does not exist"
}with HTTP 404. The signature is checked before the path, so an unsigned call to a wrong path gets the 401 for missing headers instead. If a signed call that works on other paths gets 404, check the path for a typo.
When to retry
Network error or timeout ──► retry (same idempotencyKey)
429 ──► retry after Retry-After seconds (same idempotencyKey)
500 ──► retry after a short wait (same idempotencyKey)
409 with data null ──► retry after a few seconds (same idempotencyKey)
409 with data ──► stop: it worked
400 / 401 / 403 / 404 ──► stop: fix the cause first