Three endpoints. Your platform posts a manuscript, we answer in under a second, and we call you back when the check finishes.
Every journal gets two: isi_test_… runs the full workflow and bills nothing, isi_live_… draws on your prepaid balance. Build against the sandbox for as long as you need — nothing is charged, and sandbox manuscripts are never added to the corpus.
Authorization: Bearer isi_live_9c21…
POST /api/v1/checks
curl -X POST https://verify.intelliscan.africa/api/v1/checks \
-H "Authorization: Bearer isi_live_9c21…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: jahs-2026-0412-r2" \
-d '{
"manuscript_id": "JAHS-2026-0412", // optional — omit and we assign one
"version": 2,
"title": "Serum ferritin as a predictor of post-partum anaemia",
"article_type": "research_article",
"corresponding_author": { "name": "Dr Chinwe Okeke", "email": "c.okeke@example.ac.ng" },
"co_authors": [ { "name": "Dr A. Adeyemi", "email": "a.adeyemi@example.ac.ng" } ],
"callback_url": "https://journal.example.ac.ng/hooks/verify",
"text": "Serum ferritin was measured at delivery and again…"
}'
202 Accepted{
"check_id": "ISI-88214",
"status": "queued",
"manuscript_id": "JAHS-2026-0412",
"manuscript_id_source": "journal", // or "assigned" if we minted it
"version": 2,
"authors_recognised": 2,
"estimated_seconds": 90,
"billing": { "charged_ngn": 3300, "wallet_balance_ngn": 125400 }
}
Use document in place of text. We unpack the .docx and read its body — headers, footers and footnotes are left out on purpose, so a running header repeated on ninety pages cannot dominate the comparison.
"document": { "filename": "manuscript-r2.docx", "content_base64": "UEsDBBQABgAIAAAAIQ…" }
415. Send .docx, or the extracted text; most platforms, OJS included, already hold it.Send Idempotency-Key on every submission. If your server retries a request that actually succeeded, you get the original check back with "idempotent_replay": true — not a second charge, and not a duplicate in the corpus. Keys are scoped to your API key, so your references cannot collide with another journal's.
GET /api/v1/checks/{check_id} — if you would rather poll than receive a callback.
When the check finishes we POST the result to your callback_url (HTTPS only, outside local development).
X-ISI-Event: check.completed
X-ISI-Signature: sha256=4b1f8e…
{
"event": "check.completed",
"check_id": "ISI-88214",
"manuscript_id": "JAHS-2026-0412",
"version": 2,
"status": "completed",
"result": {
"similarity": 7, "verbatim": 3, "ai_advisory": 18,
"band": "GREEN", "threshold": 15,
"verdict": "Approved", "decision": "awaiting_editor"
},
"excluded": [ {
"reason": "same_manuscript_id",
"reason_text": "Earlier version of this manuscript",
"check_id": "ISI-88090", "version": 1,
"match_percent": 94,
"authorised_by": "c.okeke@example.ac.ng"
} ],
"sources": [
{ "title": "Iron status in the puerperium…", "percent": 4, "class": "corpus" },
{ "title": null, "percent": 1, "class": "sealed" }
],
"certificate": null
}
excluded is always present. It lists every match we set aside — earlier rounds of the same manuscript — with the real overlap and whose authority it rested on. Show it to your editors. A report that hides what it set aside is exactly as untrustworthy as one that hides what it found."class": "sealed" source is another journal's unpublished manuscript. You are told a match exists and its size, and nothing else — which is precisely how your own manuscripts appear in everyone else's reports.Otherwise anyone who learns your callback URL can post a clean report into your editorial system.
// Node — compute over the RAW body, before any JSON parsing
const expected = 'sha256=' + require('crypto')
.createHmac('sha256', YOUR_WEBHOOK_SECRET)
.update(rawBody, 'utf8').digest('hex');
if (req.headers['x-isi-signature'] !== expected) return res.status(401).end();
GET /api/v1/account — mode, fee, threshold, wallet balance and checks_remaining, so you can warn your own staff before checks start failing.
| Status | Code | What it means |
|---|---|---|
| 400 | missing_title / missing_text / text_too_short | The manuscript was incomplete. |
| 401 | unauthorized | Missing, malformed or revoked key. |
| 402 | insufficient_balance | The wallet will not cover this check. We refuse rather than let a balance go negative; the response carries balance_ngn and required_ngn. |
| 404 | not_found | No check with that id on your account. |
| 415 | unsupported_document | A PDF or other format we do not read. Send .docx or extracted text. |
| 422 | document_unreadable / document_empty | The .docx was damaged, or holds no body text (a scan or an image). |
Neither is preferred, and nothing above changes between them — the plugin is a wrapper around exactly these endpoints.
| Route | Suits | What your team does |
|---|---|---|
| OJS plugin | Open Journal Systems 3.3.x | Copy the plugin folder in, enable it, paste a key. No code. |
| Ported plugin | OJS 3.4 / 3.5, or a fork | Same plugin, with the hook names and class loading updated — the README lists what moves. |
| Direct API | Any other platform, or a bespoke one | The three calls above. A PHP client is included and has no OJS dependency, so it drops into any PHP platform; anything else can call the endpoints directly. |
Talk to us and we will set your journal up on whichever fits.
Intelliscan Standards Institute · VERIFY for Journals · Privacy