Contents

Checks

Check documents and read results.

The CheckResult object

One item's result. Every field is present (null when absent).

Attributes

  • id required Type: string

    The result's id.

  • item_index required Type: integer
  • kind required Type: "check" "signer_match"

    The item's kind. A signer match has its answer in signer_match; its file, subject_reference, decision, signatures and input_hashes are empty.

  • file required Type: string null
  • subject_reference required Type: string null

    Your id for the person compared against.

  • reference required Type: CheckReference null
  • process required Type: Process null
  • metadata required Type: Metadata null
  • status required Type: "completed" "failed"
  • error required Type: ItemError null
  • decision required Type: Decision null
  • input_type required Type: string null
  • pages required Type: integer null
  • notes required Type: string[]

    Document notes: code or code:detail, pages from 1.

  • signatures required Type: Signature[]
  • anchor_probability required Type: number null
  • signer_match required Type: SignerMatchResult null
  • versions required Type: Versions
  • input_hashes required Type: object null
  • timings_ms required Type: object

CheckResult on its own page

The CheckResult object
{
  "id": "ver_01j9x3m8v7k2q4r5s6t7u8v9w0",
  "item_index": 0,
  "kind": "check",
  "file": "questioned",
  "subject_reference": "cust-88",
  "reference": "APP-1042",
  "process": "loan-application",
  "metadata": {
    "branch": "leeds"
  },
  "status": "completed",
  "error": null,
  "decision": "APPROVE",
  "input_type": "photo",
  "pages": 1,
  "notes": [],
  "signatures": [
    {
      "index": 0,
      "page": 1,
      "bbox": [
        412,
        1880,
        498,
        160
      ],
      "located_by": "template",
      "quality": {
        "ok": true,
        "effective_dpi": 214,
        "sharpness": 812.5,
        "contrast": 180,
        "issues": []
      },
      "match_probability": 0.94,
      "band": [
        0.88,
        0.97
      ],
      "reference_count": 2,
      "per_reference": [
        {
          "ref": "ref_01j9x3m8v7k2q4r5s6t7u8v9w1",
          "probability": 0.95
        },
        {
          "ref": "ref_01j9x3m8v7k2q4r5s6t7u8v9w2",
          "probability": 0.92
        }
      ],
      "flags": [],
      "decision": "APPROVE",
      "reasons": [
        "Compared against 2 references",
        "Overall appearance is within this writer's usual range",
        "Shape alignment is within this writer's usual range",
        "Approved, not auto-approved: the lowest likely score (88.0%) is under the auto-approve bar (97.0%), and auto-approve is off in policy default@7"
      ],
      "best_match": true
    }
  ],
  "anchor_probability": 0.94,
  "signer_match": null,
  "versions": {
    "model": "nib-2.6",
    "policy": "default@7",
    "api_version": "2026-09-27"
  },
  "input_hashes": {
    "questioned": "sha256:8550d9b6affca27a5645d75b058479d2e081b16a135e2d4255e5e83714ebf987",
    "references": [
      "sha256:070a8e98f85afe04ba05542428664ec3684db24bc356133c11d41f9fab27f135",
      "sha256:1cf1da50e3f0be0b50197022be0eedf20fa688cf20ca1154b388098a9d2e29e1"
    ]
  },
  "timings_ms": {
    "total": 1840
  }
}

The Job object

Attributes

  • job_id required Type: string
  • status required Type: "queued" "running" "completed" "failed" "rejected"
  • source required Type: "api" "bucket" "desk"
  • items required Type: integer
  • created_at required Type: string
  • started_at required Type: string null
  • completed_at required Type: string null
  • status_url required Type: string
  • summary required Type: JobSummary null
  • results required Type: CheckResult[]
  • next_cursor required Type: string null

    Pass as cursor; null on the last page.

Job on its own page

The Job object
{
  "job_id": "inv-2291",
  "status": "running",
  "source": "api",
  "items": 40,
  "created_at": "2026-09-25T10:15:30.123Z",
  "started_at": "2026-09-25T10:15:31.000Z",
  "completed_at": null,
  "status_url": "/nib/checks/inv-2291",
  "summary": null,
  "results": [],
  "next_cursor": null
}

List checks

GET /nib/checks

Needs nib:checks:read: Read checks and results

Newest first. Page with cursor.

Parameters

  • view queryoptional Type: "jobs" "items" "summary"

    jobs (default), items, or summary (counts per decision).

  • cursor queryoptional Type: string

    The previous page's next_cursor.

  • limit queryoptional Type: integer
  • since queryoptional Type: string

    Created at or after (RFC 3339).

  • until queryoptional Type: string

    Created before (RFC 3339).

  • status queryoptional Type: "queued" "running" "completed" "failed" "rejected"

    view=jobs only.

  • source queryoptional Type: "api" "bucket" "desk"

    view=jobs only. desk: checks made by unforged/desk.

  • reference queryoptional Type: CheckReference

    view=items or summary: exact check reference.

  • process queryoptional Type: Process

    view=items or summary: exact process.

  • subject_reference queryoptional Type: Id

    view=items or summary: exact subject reference.

  • decision queryoptional Type: string

    Comma-separated decisions; ERROR for items without one.

  • input_type queryoptional Type: string

    view=items or summary: e.g. photo, scan, form, signature_image.

  • q queryoptional Type: string

    view=items or summary: prefix of the reference, subject, file name or check id.

Returns

  • 200
    A page of jobs or items, or the counts
  • 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)
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
GET /nib/checks · curl
curl "https://api.unforged.sh/nib/checks" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /nib/checks · Python
import requests

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

Check one document or a batch

POST /nib/checks

Needs nib:checks:create: Submit checks, sessions, live

Multipart: a JSON request part (CheckRequest) and one part per file, or JSON with URL inputs. One item of up to 10 pages returns 200; larger checks are queued (202). Accept: text/event-stream streams the check. A signer_match item (SignerMatchItem) is always queued, can't be streamed, and counts one against your monthly quota for each of its documents.

Parameters

  • Idempotency-Key headerrequired Type: string

    1–255 printable ASCII characters, kept 24 hours.

  • Accept headeroptional Type: string

    text/event-stream to stream the check.

Request body

CheckRequest

  • items required Type: CheckItem SignerMatchItem[]
  • policy optional Type: string
  • process optional Type: Process

    Default process for items.

  • metadata optional Type: Metadata

    Default metadata for items.

  • job_id optional Type: Id

Returns

  • 200 Type: CheckResult
    The result, or the event stream
  • 202 Type: CheckAccepted
    Queued; poll status_url
  • 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)
  • 408 Type: Problem
    timeout
  • 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
  • 422 Type: Problem
    fetch_failed or not_allowlisted
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • 503 Type: Problem
    busy, not_ready, org_moving or storage_provisioning (your organisation's storage is still being set up); retry after Retry-After
  • 504 Type: Problem
    timeout; a check continues asynchronously (job_id, status_url)
  • default Type: Problem
    internal
POST /nib/checks · curl
curl -X POST "https://api.unforged.sh/nib/checks" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "file": {
        "url": "https://files.example.com/app-1041.png?sig=abc"
      },
      "subject_reference": "cust-88",
      "target": {
        "page": 1,
        "box": "applicant_signature"
      }
    }
  ],
  "policy": "default",
  "metadata": {
    "case_ref": "A-1234"
  }
}'
POST /nib/checks · Python
import requests

r = requests.post(
    "https://api.unforged.sh/nib/checks",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
    json={
        "items": [
            {
                "file": {
                    "url": "https://files.example.com/app-1041.png?sig=abc"
                },
                "subject_reference": "cust-88",
                "target": {
                    "page": 1,
                    "box": "applicant_signature"
                }
            }
        ],
        "policy": "default",
        "metadata": {
            "case_ref": "A-1234"
        }
    },
)
print(r.json())
POST /nib/checks · Node.js
const res = await fetch('https://api.unforged.sh/nib/checks', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "items": [
      {
        "file": {
          "url": "https://files.example.com/app-1041.png?sig=abc"
        },
        "subject_reference": "cust-88",
        "target": {
          "page": 1,
          "box": "applicant_signature"
        }
      }
    ],
    "policy": "default",
    "metadata": {
      "case_ref": "A-1234"
    }
  }),
});
console.log(await res.json());
Response 200
{
  "id": "ver_01j9x3m8v7k2q4r5s6t7u8v9w0",
  "item_index": 0,
  "kind": "check",
  "file": "questioned",
  "subject_reference": "cust-88",
  "reference": "APP-1042",
  "process": "loan-application",
  "metadata": {
    "branch": "leeds"
  },
  "status": "completed",
  "error": null,
  "decision": "APPROVE",
  "input_type": "photo",
  "pages": 1,
  "notes": [],
  "signatures": [
    {
      "index": 0,
      "page": 1,
      "bbox": [
        412,
        1880,
        498,
        160
      ],
      "located_by": "template",
      "quality": {
        "ok": true,
        "effective_dpi": 214,
        "sharpness": 812.5,
        "contrast": 180,
        "issues": []
      },
      "match_probability": 0.94,
      "band": [
        0.88,
        0.97
      ],
      "reference_count": 2,
      "per_reference": [
        {
          "ref": "ref_01j9x3m8v7k2q4r5s6t7u8v9w1",
          "probability": 0.95
        },
        {
          "ref": "ref_01j9x3m8v7k2q4r5s6t7u8v9w2",
          "probability": 0.92
        }
      ],
      "flags": [],
      "decision": "APPROVE",
      "reasons": [
        "Compared against 2 references",
        "Overall appearance is within this writer's usual range",
        "Shape alignment is within this writer's usual range",
        "Approved, not auto-approved: the lowest likely score (88.0%) is under the auto-approve bar (97.0%), and auto-approve is off in policy default@7"
      ],
      "best_match": true
    }
  ],
  "anchor_probability": 0.94,
  "signer_match": null,
  "versions": {
    "model": "nib-2.6",
    "policy": "default@7",
    "api_version": "2026-09-27"
  },
  "input_hashes": {
    "questioned": "sha256:8550d9b6affca27a5645d75b058479d2e081b16a135e2d4255e5e83714ebf987",
    "references": [
      "sha256:070a8e98f85afe04ba05542428664ec3684db24bc356133c11d41f9fab27f135",
      "sha256:1cf1da50e3f0be0b50197022be0eedf20fa688cf20ca1154b388098a9d2e29e1"
    ]
  },
  "timings_ms": {
    "total": 1840
  }
}

A job and its results

GET /nib/checks/{job_id}

Needs nib:checks:read: Read checks and results

Paged by item_index. format=ndjson streams a finished job's results (409 conflict while it runs).

Parameters

  • job_id pathrequired Type: Id
  • cursor queryoptional Type: string

    The previous page's next_cursor.

  • limit queryoptional Type: integer
  • format queryoptional Type: "json" "ndjson"
  • decision queryoptional Type: string

    Comma-separated decisions; ERROR for items without one.

Returns

  • 200 Type: Job
    The job
  • 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
GET /nib/checks/{job_id} · curl
curl "https://api.unforged.sh/nib/checks/{job_id}" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /nib/checks/{job_id} · Python
import requests

r = requests.get(
    "https://api.unforged.sh/nib/checks/{job_id}",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
GET /nib/checks/{job_id} · Node.js
const res = await fetch('https://api.unforged.sh/nib/checks/{job_id}', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());
Response 200
{
  "job_id": "inv-2291",
  "status": "running",
  "source": "api",
  "items": 40,
  "created_at": "2026-09-25T10:15:30.123Z",
  "started_at": "2026-09-25T10:15:31.000Z",
  "completed_at": null,
  "status_url": "/nib/checks/inv-2291",
  "summary": null,
  "results": [],
  "next_cursor": null
}

The questioned signature, cropped (PNG)

GET /nib/checks/{job_id}/items/{index}/image

Needs nib:checks:read: Read checks and results

Greyscale, at most 640 px. 404 if none was located or the input has expired (inbox_ttl after the check, 24 h by default).

Parameters

  • job_id pathrequired Type: Id
  • index pathrequired Type: integer

    The item's index in the request.

  • signature queryoptional Type: integer

    The signature's index (default: the best match).

Returns

  • 200
    The crop
  • 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
  • 503 Type: Problem
    busy, not_ready, org_moving or storage_provisioning (your organisation's storage is still being set up); retry after Retry-After
  • default Type: Problem
    internal
GET /nib/checks/{job_id}/items/{index}/image · curl
curl "https://api.unforged.sh/nib/checks/{job_id}/items/{index}/image" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /nib/checks/{job_id}/items/{index}/image · Python
import requests

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

A specimen signature of a signer match (PNG)

GET /nib/checks/{job_id}/items/{index}/specimens/{n}/image

Needs nib:checks:read: Read checks and results

The specimen signature a signer match compared, as listed in its result: n counts the specimens across the item's people, in order, from 0. The result's image gives the path. We keep these crops with the result, so they're deleted when your history retention deletes the job. 404 for a set that needed marking, or a specimen we no longer have.

Parameters

  • job_id pathrequired Type: Id
  • index pathrequired Type: integer

    The item's index in the request.

  • n pathrequired Type: integer

    The specimen's place in the result, from 0.

Returns

  • 200
    The crop
  • 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 /nib/checks/{job_id}/items/{index}/specimens/{n}/image · curl
curl "https://api.unforged.sh/nib/checks/{job_id}/items/{index}/specimens/{n}/image" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /nib/checks/{job_id}/items/{index}/specimens/{n}/image · Python
import requests

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

Confirm an item's signature as genuine

POST /nib/checks/{job_id}/items/{index}/confirm

Needs nib:references:write: Enroll references, confirm-to-learn

If learning is on, the signature may become a reference. No body.

Parameters

  • job_id pathrequired Type: Id
  • index pathrequired Type: integer

    The item's index in the request.

  • Idempotency-Key headerrequired Type: string

    1–255 printable ASCII characters, kept 24 hours.

Returns

  • 200 Type: Confirmed
    Recorded
  • 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
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
POST /nib/checks/{job_id}/items/{index}/confirm · curl
curl -X POST "https://api.unforged.sh/nib/checks/{job_id}/items/{index}/confirm" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
POST /nib/checks/{job_id}/items/{index}/confirm · Python
import requests

r = requests.post(
    "https://api.unforged.sh/nib/checks/{job_id}/items/{index}/confirm",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
POST /nib/checks/{job_id}/items/{index}/confirm · Node.js
const res = await fetch('https://api.unforged.sh/nib/checks/{job_id}/items/{index}/confirm', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());
Response 200
{
  "job_id": "inv-2291",
  "item_index": 0,
  "confirmed_by": "api_key:key_01j9x3m8v7k2q4r5s6t7u8v9w0",
  "confirmed_at": "2026-09-25T11:00:00Z",
  "learning": "queued"
}