Contents

Checks

Check documents, see what was found and record the decision.

The LoupeCheck object

A check. result is set once status is completed, and error says why when it is failed. documents_deleted_at is when we deleted the documents; what the check found stays. folio says where they went once someone asked for them to be kept in Folio.

Attributes

  • id required Type: string
  • status required Type: "queued" "running" "completed" "failed"
  • created_by required Type: string
  • created_at required Type: string
  • completed_at required Type: string null
  • documents_deleted_at required Type: string null
  • documents required Type: LoupeDocument[]
  • policy required Type: LoupePolicy
  • result required Type: LoupeResult null
  • error required Type: string null
  • review required Type: LoupeReview null
  • assigned_to required Type: string null

    Who is deciding on the check.

  • assigned_at required Type: string null
  • folio required Type: LoupeFolioKept null

LoupeCheck on its own page

The LoupeCheck object
{
  "id": "lck_01jb2r6x8k4m7q9s2t5v8w1y3z",
  "status": "queued",
  "created_by": "api_key:key_01jb2r5n3c8d6f9h2j4k7m1p5r",
  "created_at": "2026-10-22T09:14:03Z",
  "completed_at": null,
  "documents_deleted_at": null,
  "documents": [
    {
      "index": 0,
      "name": "statement-september.pdf",
      "sha256": "3f1c9a0e5b7d2c4f6a8b1d3e5f7a9c0b2d4e6f8a1c3e5b7d9f0a2c4e6b8d1f3a",
      "size_bytes": 184211
    }
  ],
  "policy": {
    "name": "default",
    "version": 1,
    "fail": 0.9,
    "flag": 0.5,
    "auto": 0.15,
    "auto_approve": true,
    "weights": {},
    "disabled": []
  },
  "result": null,
  "error": null,
  "review": null,
  "assigned_to": null,
  "assigned_at": null,
  "folio": null
}

The LoupeResult object

What the check found. signals here are the findings that involve more than one document.

Attributes

LoupeResult on its own page

List checks

GET /loupe/checks

Needs loupe:checks:read: See document checks and their findings

Newest first, 50 a page (limit up to 200); next_cursor continues the list. With queue=waiting you get the checks that still need someone to decide, oldest first, and with queue=decided the ones your team has decided, latest decision first; assigned_to narrows either to me, nobody or one member.

Parameters

  • cursor queryoptional Type: string

    The next_cursor of the page before.

  • limit queryoptional Type: integer
  • queue queryoptional Type: "waiting" "decided"
  • assigned_to queryoptional Type: string

    me, nobody or a member as user:…. Only with queue.

  • q queryoptional Type: string

    A document name to look for, forgiving small typos. You get the newest checks with a document named like it, each with those documents, on one page.

Returns

  • 200 Type: LoupeCheckPage
    A page of checks
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
GET /loupe/checks · curl
curl "https://api.unforged.sh/loupe/checks" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /loupe/checks · Python
import requests

r = requests.get(
    "https://api.unforged.sh/loupe/checks",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
GET /loupe/checks · Node.js
const res = await fetch('https://api.unforged.sh/loupe/checks', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());

Check documents

POST /loupe/checks

Needs loupe:checks:create: Send documents to be checked for forgery

Send each document as a file part: a PDF, an image (PNG, JPEG, TIFF, WebP or HEIC), a Word or Excel file, an email saved as .eml, or one ZIP holding several (up to 500 documents, 50 MB in one request). An email's attachments are checked as documents of their own. Documents sent together are checked together, so a page that turns up twice or details that don't agree between them are found too. The check runs straight away and usually finishes within a minute; get it by its id, or list your checks, to see what it found. We delete the documents 24 hours after the check and keep what it found. A policy part (JSON) picks one of your saved policies by name (and version, for one other than the latest), or gives a policy in full. Without one, the check uses your saved default policy, or ours if you haven't saved one.

Parameters

  • Idempotency-Key headerrequired Type: string

    1–255 printable ASCII characters, kept 24 hours.

Returns

  • 202 Type: LoupeCheck
    The check, queued
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 402 Type: Problem
    billing_required
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 409 Type: Problem
    conflict, idempotency_conflict, idempotency_in_progress or idempotency_not_replayable
  • 413 Type: Problem
    too_large
  • 415 Type: Problem
    unsupported_media
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
POST /loupe/checks · curl
curl -X POST "https://api.unforged.sh/loupe/checks" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
POST /loupe/checks · Python
import requests

r = requests.post(
    "https://api.unforged.sh/loupe/checks",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
POST /loupe/checks · Node.js
const res = await fetch('https://api.unforged.sh/loupe/checks', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());
Response 202
{
  "id": "lck_01jb2r6x8k4m7q9s2t5v8w1y3z",
  "status": "queued",
  "created_by": "api_key:key_01jb2r5n3c8d6f9h2j4k7m1p5r",
  "created_at": "2026-10-22T09:14:03Z",
  "completed_at": null,
  "documents_deleted_at": null,
  "documents": [
    {
      "index": 0,
      "name": "statement-september.pdf",
      "sha256": "3f1c9a0e5b7d2c4f6a8b1d3e5f7a9c0b2d4e6f8a1c3e5b7d9f0a2c4e6b8d1f3a",
      "size_bytes": 184211
    }
  ],
  "policy": {
    "name": "default",
    "version": 1,
    "fail": 0.9,
    "flag": 0.5,
    "auto": 0.15,
    "auto_approve": true,
    "weights": {},
    "disabled": []
  },
  "result": null,
  "error": null,
  "review": null,
  "assigned_to": null,
  "assigned_at": null,
  "folio": null
}

Get a check

GET /loupe/checks/{check_id}

Needs loupe:checks:read: See document checks and their findings

The check and, once it has finished, what it found: a verdict for the whole submission and for each document, the reasons, and each finding with where it is on the page.

Parameters

  • check_id pathrequired Type: string

    The check's id.

Returns

  • 200 Type: LoupeCheck
    The check
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
GET /loupe/checks/{check_id} · curl
curl "https://api.unforged.sh/loupe/checks/{check_id}" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /loupe/checks/{check_id} · Python
import requests

r = requests.get(
    "https://api.unforged.sh/loupe/checks/{check_id}",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
GET /loupe/checks/{check_id} · Node.js
const res = await fetch('https://api.unforged.sh/loupe/checks/{check_id}', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());
Response 200
{
  "id": "lck_01jb2r6x8k4m7q9s2t5v8w1y3z",
  "status": "queued",
  "created_by": "api_key:key_01jb2r5n3c8d6f9h2j4k7m1p5r",
  "created_at": "2026-10-22T09:14:03Z",
  "completed_at": null,
  "documents_deleted_at": null,
  "documents": [
    {
      "index": 0,
      "name": "statement-september.pdf",
      "sha256": "3f1c9a0e5b7d2c4f6a8b1d3e5f7a9c0b2d4e6f8a1c3e5b7d9f0a2c4e6b8d1f3a",
      "size_bytes": 184211
    }
  ],
  "policy": {
    "name": "default",
    "version": 1,
    "fail": 0.9,
    "flag": 0.5,
    "auto": 0.15,
    "auto_approve": true,
    "weights": {},
    "disabled": []
  },
  "result": null,
  "error": null,
  "review": null,
  "assigned_to": null,
  "assigned_at": null,
  "folio": null
}

Download the evidence pack

GET /loupe/checks/{check_id}/evidence

Needs loupe:checks:read: See document checks and their findings

The check as a PDF to keep with your case: the result, each finding marked on its page, what each file says about itself, the policy and versions that gave the verdict, and the decision your team recorded. It stays available after we've deleted the documents, and from then on lists each finding and where it was without the pages.

Parameters

  • check_id pathrequired Type: string

    The check's id.

Returns

  • 200
    The evidence pack
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
GET /loupe/checks/{check_id}/evidence · curl
curl "https://api.unforged.sh/loupe/checks/{check_id}/evidence" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /loupe/checks/{check_id}/evidence · Python
import requests

r = requests.get(
    "https://api.unforged.sh/loupe/checks/{check_id}/evidence",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
GET /loupe/checks/{check_id}/evidence · Node.js
const res = await fetch('https://api.unforged.sh/loupe/checks/{check_id}/evidence', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());

Record the decision

POST /loupe/checks/{check_id}/review

Needs loupe:checks:review: Record the decision on a document check

What the person who looked at the documents decided, and a note saying why. We never reject a document on the check alone, so this is where the decision is kept; sending a new one replaces it. A check can be reviewed once it has finished.

Parameters

  • Idempotency-Key headerrequired Type: string

    1–255 printable ASCII characters, kept 24 hours.

  • check_id pathrequired Type: string

    The check's id.

Request body

LoupeReviewRequest

  • outcome required Type: "genuine" "fraud" "inconclusive"
  • note required Type: string

    Why you reached this decision.

Returns

  • 200 Type: LoupeCheck
    The check, with the decision
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 409 Type: Problem
    conflict, idempotency_conflict, idempotency_in_progress or idempotency_not_replayable
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
POST /loupe/checks/{check_id}/review · curl
curl -X POST "https://api.unforged.sh/loupe/checks/{check_id}/review" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14" \
  -H "Content-Type: application/json" \
  -d '{
  "outcome": "fraud",
  "note": "The closing balance doesn'\''t match the copy the bank sent us."
}'
POST /loupe/checks/{check_id}/review · Python
import requests

r = requests.post(
    "https://api.unforged.sh/loupe/checks/{check_id}/review",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
    json={
        "outcome": "fraud",
        "note": "The closing balance doesn't match the copy the bank sent us."
    },
)
print(r.json())
POST /loupe/checks/{check_id}/review · Node.js
const res = await fetch('https://api.unforged.sh/loupe/checks/{check_id}/review', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "outcome": "fraud",
    "note": "The closing balance doesn't match the copy the bank sent us."
  }),
});
console.log(await res.json());
Response 200
{
  "id": "lck_01jb2r6x8k4m7q9s2t5v8w1y3z",
  "status": "queued",
  "created_by": "api_key:key_01jb2r5n3c8d6f9h2j4k7m1p5r",
  "created_at": "2026-10-22T09:14:03Z",
  "completed_at": null,
  "documents_deleted_at": null,
  "documents": [
    {
      "index": 0,
      "name": "statement-september.pdf",
      "sha256": "3f1c9a0e5b7d2c4f6a8b1d3e5f7a9c0b2d4e6f8a1c3e5b7d9f0a2c4e6b8d1f3a",
      "size_bytes": 184211
    }
  ],
  "policy": {
    "name": "default",
    "version": 1,
    "fail": 0.9,
    "flag": 0.5,
    "auto": 0.15,
    "auto_approve": true,
    "weights": {},
    "disabled": []
  },
  "result": null,
  "error": null,
  "review": null,
  "assigned_to": null,
  "assigned_at": null,
  "folio": null
}

Assign a check

POST /loupe/checks/{check_id}/assign

Needs loupe:checks:review: Record the decision on a document check

Gives the check to the person who'll decide on it: me, a member as user:…, or null to take it off whoever has it.

Parameters

  • Idempotency-Key headerrequired Type: string

    1–255 printable ASCII characters, kept 24 hours.

  • check_id pathrequired Type: string

    The check's id.

Request body

LoupeAssignRequest

  • to required Type: string null

    me, a member as user:…, or null for nobody.

Returns

  • 200 Type: LoupeCheck
    The check, assigned
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 409 Type: Problem
    conflict, idempotency_conflict, idempotency_in_progress or idempotency_not_replayable
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
POST /loupe/checks/{check_id}/assign · curl
curl -X POST "https://api.unforged.sh/loupe/checks/{check_id}/assign" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "me"
}'
POST /loupe/checks/{check_id}/assign · Python
import requests

r = requests.post(
    "https://api.unforged.sh/loupe/checks/{check_id}/assign",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
    json={
        "to": "me"
    },
)
print(r.json())
POST /loupe/checks/{check_id}/assign · Node.js
const res = await fetch('https://api.unforged.sh/loupe/checks/{check_id}/assign', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "to": "me"
  }),
});
console.log(await res.json());
Response 200
{
  "id": "lck_01jb2r6x8k4m7q9s2t5v8w1y3z",
  "status": "queued",
  "created_by": "api_key:key_01jb2r5n3c8d6f9h2j4k7m1p5r",
  "created_at": "2026-10-22T09:14:03Z",
  "completed_at": null,
  "documents_deleted_at": null,
  "documents": [
    {
      "index": 0,
      "name": "statement-september.pdf",
      "sha256": "3f1c9a0e5b7d2c4f6a8b1d3e5f7a9c0b2d4e6f8a1c3e5b7d9f0a2c4e6b8d1f3a",
      "size_bytes": 184211
    }
  ],
  "policy": {
    "name": "default",
    "version": 1,
    "fail": 0.9,
    "flag": 0.5,
    "auto": 0.15,
    "auto_approve": true,
    "weights": {},
    "disabled": []
  },
  "result": null,
  "error": null,
  "review": null,
  "assigned_to": null,
  "assigned_at": null,
  "folio": null
}

Keep a check's documents in Folio

POST /loupe/checks/{check_id}/folio

Needs loupe:checks:read: See document checks and their findings

Files the check's documents and its evidence pack in a Folio folder, under the retention class you choose or the folder's own. You also need folio:records:write, and Folio switched on for your organisation. The check answers straight away with folio.state set to queued; it reads filed once they're in Folio, or failed if they couldn't go, when you can ask again. You can't keep a check's documents while it's running, after they've been deleted, or a second time.

Parameters

  • Idempotency-Key headerrequired Type: string

    1–255 printable ASCII characters, kept 24 hours.

  • check_id pathrequired Type: string

    The check's id.

Request body

LoupeFolioRequest

  • folder required Type: string

    The folder in Folio, such as Fraud cases/2026; a new one is made if it isn't there.

  • class optional Type: string null

    The retention class's id; the folder's own when left out.

  • name optional Type: string null

    The name in Folio of a check's only document; with several

Returns

  • 202 Type: LoupeCheck
    The check, its documents on their way to Folio
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 409 Type: Problem
    conflict, idempotency_conflict, idempotency_in_progress or idempotency_not_replayable
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
POST /loupe/checks/{check_id}/folio · curl
curl -X POST "https://api.unforged.sh/loupe/checks/{check_id}/folio" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14" \
  -H "Content-Type: application/json" \
  -d '{
  "folder": "Fraud cases/2026",
  "class": null,
  "name": null
}'
POST /loupe/checks/{check_id}/folio · Python
import requests

r = requests.post(
    "https://api.unforged.sh/loupe/checks/{check_id}/folio",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
    json={
        "folder": "Fraud cases/2026",
        "class": None,
        "name": None
    },
)
print(r.json())
POST /loupe/checks/{check_id}/folio · Node.js
const res = await fetch('https://api.unforged.sh/loupe/checks/{check_id}/folio', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "folder": "Fraud cases/2026",
    "class": null,
    "name": null
  }),
});
console.log(await res.json());
Response 202
{
  "id": "lck_01jb2r6x8k4m7q9s2t5v8w1y3z",
  "status": "queued",
  "created_by": "api_key:key_01jb2r5n3c8d6f9h2j4k7m1p5r",
  "created_at": "2026-10-22T09:14:03Z",
  "completed_at": null,
  "documents_deleted_at": null,
  "documents": [
    {
      "index": 0,
      "name": "statement-september.pdf",
      "sha256": "3f1c9a0e5b7d2c4f6a8b1d3e5f7a9c0b2d4e6f8a1c3e5b7d9f0a2c4e6b8d1f3a",
      "size_bytes": 184211
    }
  ],
  "policy": {
    "name": "default",
    "version": 1,
    "fail": 0.9,
    "flag": 0.5,
    "auto": 0.15,
    "auto_approve": true,
    "weights": {},
    "disabled": []
  },
  "result": null,
  "error": null,
  "review": null,
  "assigned_to": null,
  "assigned_at": null,
  "folio": null
}

Get a document

GET /loupe/checks/{check_id}/documents/{index}

Needs loupe:checks:read: See document checks and their findings

The document as it was sent, until we delete it 24 hours after the check.

Parameters

  • check_id pathrequired Type: string

    The check's id.

  • index pathrequired Type: integer

    The document's index in the check.

Returns

  • 200
    The document
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
GET /loupe/checks/{check_id}/documents/{index} · curl
curl "https://api.unforged.sh/loupe/checks/{check_id}/documents/{index}" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /loupe/checks/{check_id}/documents/{index} · Python
import requests

r = requests.get(
    "https://api.unforged.sh/loupe/checks/{check_id}/documents/{index}",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
GET /loupe/checks/{check_id}/documents/{index} · Node.js
const res = await fetch('https://api.unforged.sh/loupe/checks/{check_id}/documents/{index}', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());

Get a page as an image

GET /loupe/checks/{check_id}/documents/{index}/pages/{page}

Needs loupe:checks:read: See document checks and their findings

One page of a scanned or photographed document as a PNG, at the size we read it, so a finding's box in pixels falls in the right place. Pages are counted from 1. A PDF page that has no picture on it has no image; use its PDF instead.

Parameters

  • check_id pathrequired Type: string

    The check's id.

  • index pathrequired Type: integer

    The document's index in the check.

  • page pathrequired Type: integer

Returns

  • 200
    The page
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
GET /loupe/checks/{check_id}/documents/{index}/pages/{page} · curl
curl "https://api.unforged.sh/loupe/checks/{check_id}/documents/{index}/pages/{page}" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /loupe/checks/{check_id}/documents/{index}/pages/{page} · Python
import requests

r = requests.get(
    "https://api.unforged.sh/loupe/checks/{check_id}/documents/{index}/pages/{page}",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
GET /loupe/checks/{check_id}/documents/{index}/pages/{page} · Node.js
const res = await fetch('https://api.unforged.sh/loupe/checks/{check_id}/documents/{index}/pages/{page}', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());