TruthKit

Send a file. Get measurements back, and a signature you can check.

The API runs the same eight checks as the website. It answers in JSON with every number it measured, says when it cannot tell, and signs what it found.

On this page

Quickstart

  1. Get a key

    Create one in the dashboard below, then put it in your shell. The Python examples use requests. The JavaScript examples are ES modules for Node 20 or newer, so save them as .mjs.

    export TRUTHKIT_API_KEY="tk_live_your_key"
  2. Start a check

    Send a file with what you know about where and when it was made. The answer comes straight back with an id, while the checks run.

    No account is needed to try it. Without a key, leave out the Authorization header and read the check with the access_token it returned.

    curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/checks" \
      -H "Authorization: Bearer $TRUTHKIT_API_KEY" \
      -F 'file=@photo.jpg' \
      -F 'claim={"place":{"mode":"city","label":"Sharjah, UAE","lat":25.35,"lon":55.42},"time":{"mode":"exact","iso":"2026-10-12T14:00:00","tz":"auto"}}'
    Real response202Captured 6 November 2026
    POST /v1/checks
    X-RateLimit-Remaining: 5
    {
      "id": "SWO9cpoo8wx3",
      "status": "queued",
      "access_token": "tkj_8ZMkT7ni[redacted on this page]",
      "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
      "kind": "image"
    }
  3. Read the result

    Ask for the check until its status is done. Each check's result appears as it finishes, and the overall verdict arrives last.

    export CHECK_ID="the id from the last step"
    curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/checks/$CHECK_ID" \
      -H "Authorization: Bearer $TRUTHKIT_API_KEY"
    Real response200Captured 6 November 2026
    GET /v1/checks/{id}, once finished
    {
      "id": "SWO9cpoo8wx3",
      "status": "done",
      "kind": "image",
      "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
      "checks": {
        "eyemirror": {
          "status": "done",
          "result": {
            "check": "eyemirror",
            "summary": "No face was found in this file, so there are no eyes to compare.",
            "verdict": "not_applicable",
            "version": "eyemirror@1.0.0",
            "evidence": {},
            "variants": {
              "runs": 0,
              "spread": null,
              "usable": 0,
              "agreement": 0
            },
            "artifacts": [],
            "applicable": false,
            "confidence": 0,
            "duration_ms": 752,
            "user_adjusted": false
          }
        },
        "humprint": {
          "status": "done",
          "result": {
            "check": "humprint",
            "summary": "HumPrint reads mains hum in sound, and a photo has none.",
            "verdict": "not_applicable",
            "version": "humprint@1.0.0",
            "evidence": {},
            "variants": {
              "runs": 0,
              "spread": null,
              "usable": 0,
              "agreement": 0
            },
            "artifacts": [],
            "applicable": false,
            "confidence": 0,
            "duration_ms": 0,
            "user_adjusted": false
          }
        },
        "patternscan": {
          "status": "done",
          "result": {
            "check": "patternscan",
            "summary": "PatternScan found none of the traces it looks for. That does not show the picture is real: cropping, resizing or renaming removes them.",
            "verdict": "inconclusive",
            "version": "patternscan@1.0.0",
            "evidence": {
              "runs": [
                {
                  "id": "size",
                  "note": "4032 x 3024 is not a size generators output.",
                  "unit": null,
                  "label": "Output size",
                  "value": null,
                  "verdict": "inconclusive",
                  "confidence": 0
                },
                {
                  "id": "filename",
                  "note": "The file name is not a generator's default name.",
                  "unit": null,
                  "label": "File name",
                  "value": null,
                  "verdict": "inconclusive",
                  "confidence": 0
                }
              ],
              "width": 4032,
              "camera": "Apple iPhone 12 Pro Max",
              "height": 3024,
              "reason": "no_trace",
              "signals": [],
              "generator_size": false,
              "filename_generator": null
            },
            "variants": {
              "runs": 2,
              "spread": null,
              "usable": 0,
              "agreement": 0
            },
            "artifacts": [],
            "applicable": true,
            "confidence": 0,
            "duration_ms": 9,
            "user_adjusted": false
          }
        },
        "provenance": {
          "status": "done",
          "result": {
            "check": "provenance",
            "summary": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.",
            "verdict": "inconclusive",
            "version": "provenance@1.0.0",
            "evidence": {
              "c2pa": {
                "tool": null,
                "valid": false,
                "issues": [],
                "signer": null,
                "actions": [],
                "present": false,
                "trusted": false,
                "signed_at": null,
                "unreadable": null,
                "signer_label": null,
                "validation_codes": [],
                "validation_status": null,
                "digital_source_type": null
              },
              "exif": {
                "lat": 39.479653,
                "lon": -86.058639,
                "make": "Apple",
                "model": "iPhone 12 Pro Max",
                "software": "15.5",
                "datetime_original": "2022-06-04T10:14:59.983-04:00"
              },
              "runs": [
                {
                  "id": "signature",
                  "note": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.",
                  "unit": null,
                  "label": "Credentials and metadata",
                  "value": null,
                  "verdict": "inconclusive",
                  "confidence": 0
                }
              ],
              "reason": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.",
              "synthid": {
                "url": "https://synthid.com",
                "note": "SynthID is Google's invisible watermark. It has no public API, so TruthKit does not scan for it. You can check a file yourself at synthid.com."
              },
              "ai_markers": [],
              "signed_and_valid": false
            },
            "variants": {
              "runs": 1,
              "spread": null,
              "usable": 0,
              "agreement": 0
            },
            "artifacts": [],
            "applicable": true,
            "confidence": 0,
            "duration_ms": 13,
            "user_adjusted": false
          }
        },
        "pulsecheck": {
          "status": "done",
          "result": {
            "check": "pulsecheck",
            "summary": "PulseCheck measures colour changes over time, so it needs a video. A single photo has none.",
            "verdict": "not_applicable",
            "version": "pulsecheck@1.0.0",
            "evidence": {},
            "variants": {
              "runs": 0,
              "spread": null,
              "usable": 0,
              "agreement": 0
            },
            "artifacts": [],
            "applicable": false,
            "confidence": 0,
            "duration_ms": 0,
            "user_adjusted": false
          }
        },
        "shadowproof": {
          "status": "done",
          "result": {
            "check": "shadowproof",
            "summary": "The 10 runs and the full analysis do not agree, so the check gives no answer.",
            "verdict": "inconclusive",
            "version": "shadowproof@1.0.0",
            "evidence": {
              "runs": [
                {
                  "id": "r1",
                  "note": "The measured sun elevation is within 8.5 degrees of the claim.",
                  "unit": "deg",
                  "label": "Without Person 1, jitter x1",
                  "value": 31.631751015051567,
                  "verdict": "verified",
                  "confidence": 0.165
                },
                {
                  "id": "r2",
                  "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
                  "unit": "deg",
                  "label": "Without Person 2, jitter x0.75",
                  "value": 19.51836558084403,
                  "verdict": "inconclusive",
                  "confidence": 0
                },
                {
                  "id": "r3",
                  "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
                  "unit": "deg",
                  "label": "Without Person 3, jitter x1.25",
                  "value": 20.586148238799428,
                  "verdict": "inconclusive",
                  "confidence": 0
                },
                {
                  "id": "r4",
                  "note": "The measured sun elevation is within 7.8 degrees of the claim.",
                  "unit": "deg",
                  "label": "Without Person 1, jitter x1",
                  "value": 32.3741008019022,
                  "verdict": "verified",
                  "confidence": 0.195
                },
                {
                  "id": "r5",
                  "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
                  "unit": "deg",
                  "label": "Without Person 2, jitter x0.75",
                  "value": 19.317814021779046,
                  "verdict": "inconclusive",
                  "confidence": 0
                },
                {
                  "id": "r6",
                  "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
                  "unit": "deg",
                  "label": "Without Person 3, jitter x1.25",
                  "value": 20.202704985735988,
                  "verdict": "inconclusive",
                  "confidence": 0
                },
                {
                  "id": "r7",
                  "note": "The measured sun elevation is within 7.7 degrees of the claim.",
                  "unit": "deg",
                  "label": "Without Person 1, jitter x1",
                  "value": 32.175667091905794,
                  "verdict": "verified",
                  "confidence": 0.205
                },
                {
                  "id": "r8",
                  "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
                  "unit": "deg",
                  "label": "Without Person 2, jitter x0.75",
                  "value": 19.35563487919962,
                  "verdict": "inconclusive",
                  "confidence": 0
                },
                {
                  "id": "r9",
                  "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
                  "unit": "deg",
                  "label": "Without Person 3, jitter x1.25",
                  "value": 20.773654290207592,
                  "verdict": "inconclusive",
                  "confidence": 0
                },
                {
                  "id": "r10",
                  "note": "The measured sun elevation is within 8.0 degrees of the claim.",
                  "unit": "deg",
                  "label": "Without Person 1, jitter x1",
                  "value": 31.73684883770895,
                  "verdict": "verified",
                  "confidence": 0.19
                }
              ],
              "image": {
                "width": 1600,
                "height": 1200
              },
              "pairs": [
                {
                  "id": "a1",
                  "tip": [
                    0.43312,
                    0.80296
                  ],
                  "top": [
                    0.58688,
                    0.48514
                  ],
                  "label": "Person 1",
                  "reason": null,
                  "source": "auto",
                  "usable": true,
                  "contact": [
                    0.58875,
                    0.64083
                  ],
                  "quality": 1,
                  "sigma_deg": 1.6,
                  "caster_class": "person",
                  "elevation_deg": 17.94
                },
                {
                  "id": "a2",
                  "tip": [
                    0.75949,
                    0.75486
                  ],
                  "top": [
                    0.7175,
                    0.51417
                  ],
                  "label": "Person 2",
                  "reason": null,
                  "source": "auto",
                  "usable": true,
                  "contact": [
                    0.71562,
                    0.68
                  ],
                  "quality": 1,
                  "sigma_deg": 3.66,
                  "caster_class": "person",
                  "elevation_deg": 32.34
                },
                {
                  "id": "a3",
                  "tip": [
                    0.43342,
                    0.69252
                  ],
                  "top": [
                    0.44656,
                    0.5025
                  ],
                  "label": "Person 3",
                  "reason": null,
                  "source": "auto",
                  "usable": true,
                  "contact": [
                    0.44312,
                    0.6425
                  ],
                  "quality": 1,
                  "sigma_deg": 4.81,
                  "caster_class": "person",
                  "elevation_deg": 30.66
                },
                {
                  "id": "a4",
                  "tip": [
                    0.00005,
                    0.99292
                  ],
                  "top": [
                    0.25562,
                    0.4925
                  ],
                  "label": "Person 4",
                  "reason": "The shadow runs into the image edge, so its end cannot be found.",
                  "source": "auto",
                  "usable": false,
                  "contact": [
                    0.29938,
                    0.7625
                  ],
                  "quality": 0,
                  "sigma_deg": null,
                  "caster_class": "person",
                  "elevation_deg": null
                },
                {
                  "id": "a5",
                  "tip": [
                    0.30125,
                    0.70583
                  ],
                  "top": [
                    0.32125,
                    0.47667
                  ],
                  "label": "Person 5",
                  "reason": "No shadow starts at the base of the object.",
                  "source": "auto",
                  "usable": false,
                  "contact": [
                    0.30125,
                    0.70583
                  ],
                  "quality": 0,
                  "sigma_deg": null,
                  "caster_class": "person",
                  "elevation_deg": null
                },
                {
                  "id": "a6",
                  "tip": [
                    0.08305,
                    0.66133
                  ],
                  "top": [
                    0.04688,
                    0.50083
                  ],
                  "label": "Person 6",
                  "reason": "The shadow is not bounded on both sides.",
                  "source": "auto",
                  "usable": false,
                  "contact": [
                    0.03812,
                    0.63833
                  ],
                  "quality": 0,
                  "sigma_deg": null,
                  "caster_class": "person",
                  "elevation_deg": null
                },
                {
                  "id": "a7",
                  "tip": [
                    0.96906,
                    0.63232
                  ],
                  "top": [
                    0.905,
                    0.50667
                  ],
                  "label": "Person 7",
                  "reason": "The shadow is too faint (contrast 6 against 10 needed).",
                  "source": "auto",
                  "usable": false,
                  "contact": [
                    0.89812,
                    0.6025
                  ],
                  "quality": 0,
                  "sigma_deg": null,
                  "caster_class": "person",
                  "elevation_deg": null
                },
                {
                  "id": "a8",
                  "tip": [
                    0.93254,
                    0.64704
                  ],
                  "top": [
                    0.9825,
                    0.51917
                  ],
                  "label": "Person 8",
                  "reason": "The shadow is not bounded on both sides.",
                  "source": "auto",
                  "usable": false,
                  "contact": [
                    0.98562,
                    0.63583
                  ],
                  "quality": 0,
                  "sigma_deg": null,
                  "caster_class": "person",
                  "elevation_deg": null
                }
              ],
              "camera": {
                "focal_px": 924.5,
                "reliable": true,
                "reported": {
                  "focal_px": 0,
                  "roll_deg": 1.218,
                  "pitch_deg": 2.084
                },
                "roll_deg": 0.31,
                "pitch_deg": 2.02,
                "focal_note": "EXIF FocalLengthIn35mmFilm 20 mm, 925 px here (GeoCalib alone estimated 1015 px).",
                "focal_source": "exif",
                "focal_uncertainty_px": 37,
                "gravity_uncertainty_deg": 2.08
              },
              "ground": {
                "flat": true,
                "note": "Ground around the 3 pairs used is consistent with a flat plane."
              },
              "reason": "The 10 runs and the full analysis do not agree, so the check gives no answer.",
              "daylight": {
                "note": "mean luma 130, 95th percentile 201",
                "looks_daylit": true
              },
              "estimate": {
                "unit": "deg",
                "median": 20.67990126450351,
                "spread": 12.021123949977586
              },
              "delta_deg": -10.03,
              "sigma_deg": 2.2,
              "consistency": {
                "runs": 10,
                "slack": 0.194511,
                "votes": {
                  "verified": 0,
                  "contradicted": 0,
                  "inconclusive": 10
                },
                "reason": "The shadow lines do not meet cleanly, but the 3D light directions agree within measurement noise, so this is not enough to say the shadows disagree.",
                "verdict": "inconclusive",
                "misfit_deg": 30.46,
                "scatter_deg": 10.58,
                "pairs_compared": 3,
                "slack_inflated": 0.171325,
                "noise_scatter_deg": 4.1
              },
              "usable_pairs": 3,
              "user_adjusted": false,
              "candidate_dates": [],
              "candidate_times": [
                "2022-06-04T12:22:21.442786+00:00",
                "2022-06-04T23:03:04.506349+00:00"
              ],
              "detection_notes": [
                "A vertical edge continues beyond where its top could be found, so it was skipped.",
                "A vertical edge continues beyond where its top could be found, so it was skipped.",
                "A vertical edge continues beyond where its top could be found, so it was skipped.",
                "A vertical edge continues beyond where its top could be found, so it was skipped.",
                "A vertical edge continues beyond where its top could be found, so it was skipped.",
                "A vertical edge continues beyond where its top could be found, so it was skipped."
              ],
              "allowed_band_deg": 5,
              "sun_below_horizon": false,
              "candidate_time_ranges": [
                {
                  "to": "2022-06-04T12:33:59.092995+00:00",
                  "from": "2022-06-04T12:10:39.185098+00:00"
                },
                {
                  "to": "2022-06-04T23:14:47.288106+00:00",
                  "from": "2022-06-04T22:51:26.370020+00:00"
                }
              ],
              "expected_elevation_deg": 31.54,
              "measured_elevation_deg": 21.51
            },
            "variants": {
              "runs": 10,
              "spread": 12.021123949977586,
              "usable": 4,
              "agreement": 1
            },
            "artifacts": [
              {
                "url": "/v1/checks/SWO9cpoo8wx3/artifacts/shadowproof/overlay.png?exp=1794036174&sig=[redacted]",
                "kind": "overlay_png"
              },
              {
                "url": "/v1/checks/SWO9cpoo8wx3/artifacts/shadowproof/preview.png?exp=1794036174&sig=[redacted]",
                "kind": "overlay_png"
              },
              {
                "url": "/v1/checks/SWO9cpoo8wx3/artifacts/shadowproof/sun_curve.json?exp=1794036174&sig=[redacted]",
                "kind": "plot_json"
              }
            ],
            "applicable": true,
            "confidence": 0,
            "duration_ms": 5600,
            "user_adjusted": false
          }
        },
        "synthscan": {
          "status": "done",
          "result": {
            "check": "synthscan",
            "summary": "No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion.",
            "verdict": "verified",
            "version": "synthscan@1.3.0",
            "evidence": {
              "p_ai": 0.058,
              "runs": [
                {
                  "id": "swinv2-full",
                  "note": "2% AI over the picture.",
                  "unit": "% AI",
                  "label": "Detector, whole frame",
                  "value": 2,
                  "verdict": "verified",
                  "confidence": 0.96
                },
                {
                  "id": "swinv2-centre",
                  "note": "2% AI over the picture.",
                  "unit": "% AI",
                  "label": "Detector, centre",
                  "value": 2,
                  "verdict": "verified",
                  "confidence": 0.96
                },
                {
                  "id": "swinv2-tiles",
                  "note": "2% AI over 6 tiles.",
                  "unit": "% AI",
                  "label": "Detector, fine texture",
                  "value": 2,
                  "verdict": "verified",
                  "confidence": 0.96
                },
                {
                  "id": "commfor-full",
                  "note": "2% AI over the picture.",
                  "unit": "% AI",
                  "label": "Community Forensics, whole frame",
                  "value": 2,
                  "verdict": "verified",
                  "confidence": 0.96
                },
                {
                  "id": "commfor-tiles",
                  "note": "3% AI over 6 tiles.",
                  "unit": "% AI",
                  "label": "Community Forensics, fine texture",
                  "value": 2.8,
                  "verdict": "verified",
                  "confidence": 0.944
                },
                {
                  "id": "photographer",
                  "note": "Natural depth of field and lens distortion; Realistic high noon harsh shadows and sensor dynamic range; Genuine photographic grain and micro-contrast",
                  "unit": "% AI",
                  "label": "Photographer",
                  "value": 5,
                  "verdict": "verified",
                  "confidence": 0.9
                },
                {
                  "id": "physicist",
                  "note": "Consistent shadows cast by all people and the pole; Accurate perspective lines on the ground and buildings; Correct handling of bright sunlight and overhead string lights",
                  "unit": "% AI",
                  "label": "Physicist",
                  "value": 5,
                  "verdict": "verified",
                  "confidence": 0.9
                },
                {
                  "id": "anatomist",
                  "note": "Normal human anatomy and limb proportions; Correct finger counts and hand structures; Natural posture and weight distribution of walking pedestrians",
                  "unit": "% AI",
                  "label": "Anatomist",
                  "value": 5,
                  "verdict": "verified",
                  "confidence": 0.9
                },
                {
                  "id": "detail",
                  "note": "Legible text on the green trash bin reading Ray's; Coherent lettering on the trailer signage; Consistent patterns on the string lights and tents",
                  "unit": "% AI",
                  "label": "Detail inspector",
                  "value": 10,
                  "verdict": "verified",
                  "confidence": 0.8
                },
                {
                  "id": "generalist",
                  "note": "Natural clutter and imperfect crowd arrangement of a real market; Normal lighting and surface textures without plastic sheen; Authentic everyday snapshot aesthetics",
                  "unit": "% AI",
                  "label": "Forensic generalist",
                  "value": 5,
                  "verdict": "verified",
                  "confidence": 0.9
                }
              ],
              "frames": 1,
              "reviews": [
                {
                  "cues": [
                    "Natural depth of field and lens distortion",
                    "Realistic high noon harsh shadows and sensor dynamic range",
                    "Genuine photographic grain and micro-contrast"
                  ],
                  "p_ai": 0.05,
                  "label": "Photographer"
                },
                {
                  "cues": [
                    "Consistent shadows cast by all people and the pole",
                    "Accurate perspective lines on the ground and buildings",
                    "Correct handling of bright sunlight and overhead string lights"
                  ],
                  "p_ai": 0.05,
                  "label": "Physicist"
                },
                {
                  "cues": [
                    "Normal human anatomy and limb proportions",
                    "Correct finger counts and hand structures",
                    "Natural posture and weight distribution of walking pedestrians"
                  ],
                  "p_ai": 0.05,
                  "label": "Anatomist"
                },
                {
                  "cues": [
                    "Legible text on the green trash bin reading Ray's",
                    "Coherent lettering on the trailer signage",
                    "Consistent patterns on the string lights and tents"
                  ],
                  "p_ai": 0.1,
                  "label": "Detail inspector"
                },
                {
                  "cues": [
                    "Natural clutter and imperfect crowd arrangement of a real market",
                    "Normal lighting and surface textures without plastic sheen",
                    "Authentic everyday snapshot aesthetics"
                  ],
                  "p_ai": 0.05,
                  "label": "Forensic generalist"
                }
              ],
              "signals": [],
              "estimate": {
                "unit": "% AI",
                "median": 5.8,
                "spread": null
              },
              "detectors": {
                "swinv2": "haywoodsloan/ai-image-detector-deploy",
                "commfor": "OwensLab/commfor-model-384"
              },
              "lead_cues": [
                "Natural depth of field and lens distortion",
                "Realistic high noon harsh shadows and sensor dynamic range",
                "Genuine photographic grain and micro-contrast"
              ],
              "tiles_p_ai": 0.02,
              "commfor_p_ai": 0.02,
              "persona_p_ai": 0.058,
              "detector_p_ai": 0.02,
              "frame_times_s": [
                0
              ],
              "reviewer_source": "gemini:gemini-3.5-flash-lite",
              "families_disagree": false,
              "commfor_tiles_p_ai": 0.028
            },
            "variants": {
              "runs": 10,
              "spread": 3,
              "usable": 10,
              "agreement": 1
            },
            "artifacts": [],
            "applicable": true,
            "confidence": 0.654,
            "duration_ms": 10587,
            "user_adjusted": false
          }
        },
        "weathercheck": {
          "status": "done",
          "result": {
            "check": "weathercheck",
            "summary": "What the photo shows (a clear sky, hard shadows and daylight) is consistent with the weather record for 2022-06-04 14:14 UTC: 0% cloud cover and 0.0 mm of rain in that hour.",
            "verdict": "verified",
            "version": "weathercheck@1.0.0",
            "evidence": {
              "rule": "consistent",
              "runs": [
                {
                  "id": "hour_+0",
                  "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
                  "unit": "% cloud cover",
                  "label": "Claimed hour",
                  "value": 0,
                  "verdict": "verified",
                  "confidence": 0.45
                },
                {
                  "id": "hour_-30",
                  "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
                  "unit": "% cloud cover",
                  "label": "Claimed time -30 min",
                  "value": 0,
                  "verdict": "verified",
                  "confidence": 0.45
                },
                {
                  "id": "hour_+30",
                  "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
                  "unit": "% cloud cover",
                  "label": "Claimed time +30 min",
                  "value": 0,
                  "verdict": "verified",
                  "confidence": 0.45
                },
                {
                  "id": "hour_-60",
                  "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
                  "unit": "% cloud cover",
                  "label": "Claimed time -60 min",
                  "value": 0,
                  "verdict": "verified",
                  "confidence": 0.45
                },
                {
                  "id": "hour_+60",
                  "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
                  "unit": "% cloud cover",
                  "label": "Claimed time +60 min",
                  "value": 0,
                  "verdict": "verified",
                  "confidence": 0.45
                },
                {
                  "id": "cell_ne",
                  "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
                  "unit": "% cloud cover",
                  "label": "North-east grid cell",
                  "value": 0,
                  "verdict": "verified",
                  "confidence": 0.45
                },
                {
                  "id": "cell_sw",
                  "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
                  "unit": "% cloud cover",
                  "label": "South-west grid cell",
                  "value": 0,
                  "verdict": "verified",
                  "confidence": 0.45
                },
                {
                  "id": "read_1",
                  "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
                  "unit": "% cloud cover",
                  "label": "Vision read 1",
                  "value": 0,
                  "verdict": "verified",
                  "confidence": 0.45
                },
                {
                  "id": "read_2",
                  "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
                  "unit": "% cloud cover",
                  "label": "Vision read 2",
                  "value": 0,
                  "verdict": "verified",
                  "confidence": 0.45
                },
                {
                  "id": "read_3",
                  "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
                  "unit": "% cloud cover",
                  "label": "Vision read 3",
                  "value": 0,
                  "verdict": "verified",
                  "confidence": 0.45
                }
              ],
              "scene": {
                "daylight": true,
                "sky_state": "clear",
                "confidence": 1,
                "wet_ground": false,
                "sky_visible": true,
                "hard_shadows": true
              },
              "reason": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "weather": {
                "model": "best_match",
                "is_day": 1,
                "source": "archive-api.open-meteo.com",
                "grid_lat": 39.5,
                "grid_lon": -86,
                "cloud_cover_pct": 0,
                "precipitation_mm": 0
              },
              "estimate": {
                "unit": "% cloud cover",
                "median": 0,
                "spread": 0
              },
              "grid_note": "Historical weather comes from a coarse grid of about 10 km (reanalysis), so a single hour can differ from the real sky above one street. This check is weighted lower than ShadowProof for that reason.",
              "neighbours": [
                {
                  "cell": "ne",
                  "label": "the cell to the north-east",
                  "is_day": 1,
                  "grid_lat": 39.6,
                  "grid_lon": -85.9,
                  "cloud_cover_pct": 0
                },
                {
                  "cell": "sw",
                  "label": "the cell to the south-west",
                  "is_day": 1,
                  "grid_lat": 39.3,
                  "grid_lon": -86.2,
                  "cloud_cover_pct": 0
                }
              ],
              "attribution": {
                "url": "https://open-meteo.com/",
                "name": "Open-Meteo.com",
                "license": "CC BY 4.0"
              },
              "scene_reads": [
                {
                  "daylight": true,
                  "sky_state": "clear",
                  "confidence": 1,
                  "wet_ground": false,
                  "sky_visible": true,
                  "hard_shadows": true
                },
                {
                  "daylight": true,
                  "sky_state": "clear",
                  "confidence": 1,
                  "wet_ground": false,
                  "sky_visible": true,
                  "hard_shadows": true
                },
                {
                  "daylight": true,
                  "sky_state": "clear",
                  "confidence": 1,
                  "wet_ground": false,
                  "sky_visible": true,
                  "hard_shadows": true
                }
              ],
              "vision_notes": [],
              "vision_source": "gemini:gemini-3.5-flash-lite",
              "claimed_window": {
                "hours": [
                  "2022-06-04T13:00:00+00:00",
                  "2022-06-04T14:00:00+00:00",
                  "2022-06-04T15:00:00+00:00",
                  "2022-06-04T16:00:00+00:00"
                ],
                "end_utc": "2022-06-04T15:14:59+00:00",
                "start_utc": "2022-06-04T13:14:59+00:00"
              }
            },
            "variants": {
              "runs": 10,
              "spread": 0,
              "usable": 10,
              "agreement": 1
            },
            "artifacts": [
              {
                "url": "/v1/checks/SWO9cpoo8wx3/artifacts/weathercheck/weather_window.json?exp=1794036174&sig=[redacted]",
                "kind": "plot_json"
              }
            ],
            "applicable": true,
            "confidence": 0.44999999999999996,
            "duration_ms": 4744,
            "user_adjusted": false
          }
        }
      },
      "overall": {
        "text": "The result is Consistent, but only weakly: SynthScan and WeatherCheck fit the claim, though none of them is a strong test on its own. No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion. Consistent means the measurements fit the claimed time and place, though these checks cannot rule out a staged scene or edits that they do not measure.",
        "weak": true,
        "reasons": [
          "SynthScan (Consistent, 65% confidence): No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion.",
          "WeatherCheck (Consistent, 45% confidence): What the photo shows (a clear sky, hard shadows and daylight) is consistent with the weather record for 2022-06-04 14:14 UTC: 0% cloud cover and 0.0 mm of rain in that hour."
        ],
        "verdict": "verified",
        "headline": "The evidence is weakly consistent with this claim.",
        "confidence": 0.598,
        "text_source": "template:guard_rejected",
        "checks_counted": [
          "synthscan",
          "weathercheck"
        ]
      },
      "error": null,
      "claim": {
        "lat": 39.479653,
        "lon": -86.058639,
        "label": "Franklin, Indiana, United States",
        "tz_name": "America/Indiana/Indianapolis",
        "utc_end": "2022-06-04T15:14:59+00:00",
        "local_iso": "2022-06-04T10:14:59-04:00",
        "time_mode": "plus_minus_1h",
        "utc_start": "2022-06-04T13:14:59+00:00",
        "place_mode": "exact",
        "utc_center": "2022-06-04T14:14:59+00:00",
        "time_source": "claim",
        "country_code": "US",
        "place_source": "claim"
      },
      "media": {
        "fps": null,
        "kind": "image",
        "mime": "image/jpeg",
        "tags": {},
        "width": 4032,
        "height": 3024,
        "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
        "bitrate": null,
        "has_audio": false,
        "has_video": false,
        "duration_s": null,
        "size_bytes": 6675621,
        "audio_codec": null,
        "video_codec": null,
        "audio_sample_rate": null
      },
      "shared": false,
      "share_id": null,
      "share_original": false,
      "sample_id": null,
      "created_at": "2026-11-06T07:22:31.400198+00:00",
      "started_at": "2026-11-06T07:22:32.960184+00:00",
      "finished_at": "2026-11-06T07:22:50.448949+00:00",
      "file_deleted_at": null,
      "filename": "real-photo.jpg"
    }

API keys and usage

Reading your keys from the engine.

Authentication

Send your key on every call. A key belongs to your account, and checks made with it are yours to read and delete.

Who you areWhat you sendWhat you get
API keyAuthorization: Bearer tk_live_...The monthly quota. Read and delete your checks, and see usage.
Signed-in sessionAuthorization: Bearer <session token>Everything above, plus creating and revoking keys. Keys cannot manage keys.
A check's own tokenX-TruthKit-Job-Token: tkj_...That one check and its report. It comes back once, when you start the check.
NobodyNo headerA smaller daily allowance per address. Checking a file never needs an account.

A credential that is wrong is a 401. It is never treated as if you had sent nothing.

Only a SHA-256 hash of each key is stored, so a lost key cannot be recovered. Revoke it and create another.

Checks

Start a check

POST /v1/checks

A multipart form. Send file, or url for a public address the engine should download. Answers 202 straight away. access_token is shown once and keeps the check private.

file
The photo, video or audio file.
url
A public http or https address, instead of file.
claim
JSON: where and when it was supposedly made. See the claim format.
checks
Run only these, comma separated. Default: all that apply.
second_file
A second clip, for HumPrint's two clip match.
webhook_url
Called when the check finishes. See webhooks.
curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/checks" \
  -H "Authorization: Bearer $TRUTHKIT_API_KEY" \
  -F 'file=@photo.jpg' \
  -F 'claim={"place":{"mode":"city","label":"Sharjah, UAE","lat":25.35,"lon":55.42},"time":{"mode":"exact","iso":"2026-10-12T14:00:00","tz":"auto"}}'
Real response202Captured 6 November 2026
POST /v1/checks
X-RateLimit-Remaining: 5
{
  "id": "SWO9cpoo8wx3",
  "status": "queued",
  "access_token": "tkj_8ZMkT7ni[redacted on this page]",
  "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
  "kind": "image"
}

Read a check

GET /v1/checks/{id}

Status, each check's result as it completes, and the overall verdict once it has finished. Access is by key, by the owner's session, or by the check's own token. Missing and forbidden give the same 404, so ids cannot be probed.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/checks/$CHECK_ID" \
  -H "Authorization: Bearer $TRUTHKIT_API_KEY"
Real response200Captured 6 November 2026
GET /v1/checks/{id}, straight after starting
{
  "id": "SWO9cpoo8wx3",
  "status": "queued",
  "kind": "image",
  "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
  "checks": {
    "eyemirror": {
      "status": "queued",
      "result": null
    },
    "humprint": {
      "status": "queued",
      "result": null
    },
    "patternscan": {
      "status": "queued",
      "result": null
    },
    "provenance": {
      "status": "queued",
      "result": null
    },
    "pulsecheck": {
      "status": "queued",
      "result": null
    },
    "shadowproof": {
      "status": "queued",
      "result": null
    },
    "synthscan": {
      "status": "queued",
      "result": null
    },
    "weathercheck": {
      "status": "queued",
      "result": null
    }
  },
  "overall": null,
  "error": null,
  "claim": null,
  "media": null,
  "shared": false,
  "share_id": null,
  "share_original": false,
  "sample_id": null,
  "created_at": "2026-11-06T07:22:31.400198+00:00",
  "started_at": null,
  "finished_at": null,
  "file_deleted_at": null,
  "filename": "real-photo.jpg"
}

Stream progress

GET /v1/checks/{id}/events

Server-sent events: queued, started, check_started and check_finished for each check, then job_finished or job_failed. Send Last-Event-ID to resume. Browsers cannot set headers on EventSource, so ?token= works here too.

curl -N "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/checks/$CHECK_ID/events" \
  -H "Authorization: Bearer $TRUTHKIT_API_KEY"
Real response200Captured 6 November 2026
GET /v1/checks/{id}/events
retry: 2000

id: 549
event: queued
data: {}

id: 550
event: started
data: {}

id: 551
event: check_started
data: {"check": "synthscan"}

id: 552
event: check_started
data: {"check": "patternscan"}

id: 553
event: check_started
data: {"check": "shadowproof"}

id: 554
event: check_started
data: {"check": "weathercheck"}

id: 555
event: check_started
data: {"check": "pulsecheck"}

id: 556
event: check_started
data: {"check": "provenance"}

id: 557
event: check_started
data: {"check": "eyemirror"}

id: 558
event: check_started
data: {"check": "humprint"}

id: 559
event: check_finished
data: {"check": "patternscan", "summary": "PatternScan found none of the traces it looks for. That does not show the picture is real: cropping, resizing or renaming removes them.", "verdict": "inconclusive", "confidence": 0.0}

id: 560
event: check_finished
data: {"check": "eyemirror", "summary": "No face was found in this file, so there are no eyes to compare.", "verdict": "not_applicable", "confidence": 0.0}

id: 561
event: check_finished
data: {"check": "humprint", "summary": "HumPrint reads mains hum in sound, and a photo has none.", "verdict": "not_applicable", "confidence": 0.0}

id: 562
event: check_finished
data: {"check": "provenance", "summary": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.", "verdict": "inconclusive", "confidence": 0.0}

id: 563
event: check_finished
data: {"check": "pulsecheck", "summary": "PulseCheck measures colour changes over time, so it needs a video. A single photo has none.", "verdict": "not_applicable", "confidence": 0.0}

id: 564
event: check_finished
data: {"check": "weathercheck", "summary": "What the photo shows (a clear sky, hard shadows and daylight) is consistent with the weather record for 2022-06-04 14:14 UTC: 0% cloud cover and 0.0 mm of rain in that hour.", "verdict": "verified", "confidence": 0.44999999999999996}

id: 565
event: check_finished
data: {"check": "shadowproof", "summary": "The 10 runs and the full analysis do not agree, so the check gives no answer.", "verdict": "inconclusive", "confidence": 0.0}

id: 566
event: check_finished
data: {"check": "synthscan", "summary": "No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion.", "verdict": "verified", "confidence": 0.654}

id: 567
event: job_finished
data: {"overall": {"text": "The result is Consistent, but only weakly: SynthScan and WeatherCheck fit the claim, though none of them is a strong test on its own. No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion. Consistent means the measurements fit the claimed time and place, though these checks cannot rule out a staged scene or edits that they do not measure.", "weak": true, "reasons": ["SynthScan (Consistent, 65% confidence): No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion.", "WeatherCheck (Consistent, 45% confidence): What the photo shows (a clear sky, hard shadows and daylight) is consistent with the weather record for 2022-06-04 14:14 UTC: 0% cloud cover and 0.0 mm of rain in that hour."], "verdict": "verified", "headline": "The evidence is weakly consistent with this claim.", "confidence": 0.598, "text_source": "template:guard_rejected", "checks_counted": ["synthscan", "weathercheck"]}}

List your checks

GET /v1/checks

Newest first, up to 100 a page. Pass next_cursor back as cursor for the next page. Null means this is the last.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/checks?limit=20" \
  -H "Authorization: Bearer $TRUTHKIT_API_KEY"
Real response200Captured 31 October 2026
GET /v1/checks
{
  "items": [
    {
      "id": "qHJqa1YnaRHg",
      "status": "done",
      "kind": "image",
      "filename": "real-photo.jpg",
      "verdict": "verified",
      "weak": true,
      "headline": "The evidence is weakly consistent with this claim.",
      "shared": false,
      "share_id": null,
      "sample_id": null,
      "has_file": true,
      "created_at": "2026-10-31T07:11:10.911375+00:00",
      "finished_at": "2026-10-31T07:12:10.365644+00:00"
    }
  ],
  "next_cursor": null
}

Delete a check now

DELETE /v1/checks/{id}

Deletes your file, every overlay and plot, the report and the check. This cannot be undone. Otherwise originals are deleted after the retention period. It still counts toward your quota.

curl -X DELETE "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/checks/$CHECK_ID" \
  -H "Authorization: Bearer $TRUTHKIT_API_KEY"

Correct the shadow pairs

POST /v1/checks/{id}/shadow-adjust

Send the pairs as a person corrected them, in coordinates from 0 to 1. ShadowProof solves again from those pairs, the first automatic result is kept, the new result is marked user_adjusted, and the verdict is recalculated.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/checks/$CHECK_ID/shadow-adjust" \
  -H "Authorization: Bearer $TRUTHKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pairs":[{"id":0,"contact":[0.42,0.81],"top":[0.42,0.33],"tip":[0.61,0.84]}]}'

Overlays and plots

GET /v1/checks/{id}/artifacts/{check}/{name}

Results list their files under artifacts: overlays as PNG and plot data as JSON. Their links are signed and expire after a day, so read them from a fresh response and do not store them.

Sharing

Make a check public

POST /v1/checks/{id}/share

Checks are private until you do this. The public page shows the verdict and evidence with the place rounded to the city. Your original file is never shown, and share_original only adds the overlays drawn on it.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/checks/$CHECK_ID/share" \
  -H "Authorization: Bearer $TRUTHKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"share_original":false}'
Real response200Captured 6 November 2026
POST /v1/checks/{id}/share
{
  "shared": true,
  "share_id": "gtkxmwhnWj8",
  "share_original": false,
  "url": "https://truthkit.vercel.app/v/gtkxmwhnWj8",
  "badge_url": "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/public/gtkxmwhnWj8/badge.svg"
}

Make it private again

DELETE /v1/checks/{id}/share

The public page and badge stop working at once. Sharing again creates a new link.

curl -X DELETE "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/checks/$CHECK_ID/share" \
  -H "Authorization: Bearer $TRUTHKIT_API_KEY"
Real response200Captured 6 November 2026
DELETE /v1/checks/{id}/share
{
  "shared": false,
  "share_id": null,
  "share_original": false,
  "url": null,
  "badge_url": null
}

Read a shared check

GET /v1/public/{share_id}

No credentials. Everything passes through a redactor that rounds coordinates to the city and drops file names and personal metadata.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/public/$SHARE_ID"
Real response200Captured 6 November 2026
GET /v1/public/{share_id}
{
  "id": "gtkxmwhnWj8",
  "status": "done",
  "kind": "image",
  "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
  "checks": {
    "eyemirror": {
      "status": "done",
      "result": {
        "check": "eyemirror",
        "summary": "No face was found in this file, so there are no eyes to compare.",
        "verdict": "not_applicable",
        "version": "eyemirror@1.0.0",
        "evidence": {},
        "variants": {
          "runs": 0,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": false,
        "confidence": 0,
        "duration_ms": 752,
        "user_adjusted": false
      }
    },
    "humprint": {
      "status": "done",
      "result": {
        "check": "humprint",
        "summary": "HumPrint reads mains hum in sound, and a photo has none.",
        "verdict": "not_applicable",
        "version": "humprint@1.0.0",
        "evidence": {},
        "variants": {
          "runs": 0,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": false,
        "confidence": 0,
        "duration_ms": 0,
        "user_adjusted": false
      }
    },
    "patternscan": {
      "status": "done",
      "result": {
        "check": "patternscan",
        "summary": "PatternScan found none of the traces it looks for. That does not show the picture is real: cropping, resizing or renaming removes them.",
        "verdict": "inconclusive",
        "version": "patternscan@1.0.0",
        "evidence": {
          "runs": [
            {
              "id": "size",
              "note": "4032 x 3024 is not a size generators output.",
              "unit": null,
              "label": "Output size",
              "value": null,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "filename",
              "note": "The file name is not a generator's default name.",
              "unit": null,
              "label": "File name",
              "value": null,
              "verdict": "inconclusive",
              "confidence": 0
            }
          ],
          "width": 4032,
          "camera": "Apple iPhone 12 Pro Max",
          "height": 3024,
          "reason": "no_trace",
          "signals": [],
          "generator_size": false,
          "filename_generator": null
        },
        "variants": {
          "runs": 2,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": true,
        "confidence": 0,
        "duration_ms": 9,
        "user_adjusted": false
      }
    },
    "provenance": {
      "status": "done",
      "result": {
        "check": "provenance",
        "summary": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.",
        "verdict": "inconclusive",
        "version": "provenance@1.0.0",
        "evidence": {
          "c2pa": {
            "tool": null,
            "valid": false,
            "issues": [],
            "signer": null,
            "actions": [],
            "present": false,
            "trusted": false,
            "signed_at": null,
            "unreadable": null,
            "signer_label": null,
            "validation_codes": [],
            "validation_status": null,
            "digital_source_type": null
          },
          "exif": {
            "lat": 39.5,
            "lon": -86.1,
            "make": "Apple",
            "model": "iPhone 12 Pro Max",
            "software": "15.5",
            "datetime_original": "2022-06-04T10:14:59.983-04:00"
          },
          "runs": [
            {
              "id": "signature",
              "note": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.",
              "unit": null,
              "label": "Credentials and metadata",
              "value": null,
              "verdict": "inconclusive",
              "confidence": 0
            }
          ],
          "reason": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.",
          "synthid": {
            "url": "https://synthid.com",
            "note": "SynthID is Google's invisible watermark. It has no public API, so TruthKit does not scan for it. You can check a file yourself at synthid.com."
          },
          "ai_markers": [],
          "signed_and_valid": false
        },
        "variants": {
          "runs": 1,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": true,
        "confidence": 0,
        "duration_ms": 13,
        "user_adjusted": false
      }
    },
    "pulsecheck": {
      "status": "done",
      "result": {
        "check": "pulsecheck",
        "summary": "PulseCheck measures colour changes over time, so it needs a video. A single photo has none.",
        "verdict": "not_applicable",
        "version": "pulsecheck@1.0.0",
        "evidence": {},
        "variants": {
          "runs": 0,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": false,
        "confidence": 0,
        "duration_ms": 0,
        "user_adjusted": false
      }
    },
    "shadowproof": {
      "status": "done",
      "result": {
        "check": "shadowproof",
        "summary": "The 10 runs and the full analysis do not agree, so the check gives no answer.",
        "verdict": "inconclusive",
        "version": "shadowproof@1.0.0",
        "evidence": {
          "runs": [
            {
              "id": "r1",
              "note": "The measured sun elevation is within 8.5 degrees of the claim.",
              "unit": "deg",
              "label": "Without Person 1, jitter x1",
              "value": 31.631751015051567,
              "verdict": "verified",
              "confidence": 0.165
            },
            {
              "id": "r2",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 2, jitter x0.75",
              "value": 19.51836558084403,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r3",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 3, jitter x1.25",
              "value": 20.586148238799428,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r4",
              "note": "The measured sun elevation is within 7.8 degrees of the claim.",
              "unit": "deg",
              "label": "Without Person 1, jitter x1",
              "value": 32.3741008019022,
              "verdict": "verified",
              "confidence": 0.195
            },
            {
              "id": "r5",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 2, jitter x0.75",
              "value": 19.317814021779046,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r6",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 3, jitter x1.25",
              "value": 20.202704985735988,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r7",
              "note": "The measured sun elevation is within 7.7 degrees of the claim.",
              "unit": "deg",
              "label": "Without Person 1, jitter x1",
              "value": 32.175667091905794,
              "verdict": "verified",
              "confidence": 0.205
            },
            {
              "id": "r8",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 2, jitter x0.75",
              "value": 19.35563487919962,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r9",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 3, jitter x1.25",
              "value": 20.773654290207592,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r10",
              "note": "The measured sun elevation is within 8.0 degrees of the claim.",
              "unit": "deg",
              "label": "Without Person 1, jitter x1",
              "value": 31.73684883770895,
              "verdict": "verified",
              "confidence": 0.19
            }
          ],
          "image": {
            "width": 1600,
            "height": 1200
          },
          "pairs": [
            {
              "id": "a1",
              "tip": [
                0.43312,
                0.80296
              ],
              "top": [
                0.58688,
                0.48514
              ],
              "label": "Person 1",
              "reason": null,
              "source": "auto",
              "usable": true,
              "contact": [
                0.58875,
                0.64083
              ],
              "quality": 1,
              "sigma_deg": 1.6,
              "caster_class": "person",
              "elevation_deg": 17.94
            },
            {
              "id": "a2",
              "tip": [
                0.75949,
                0.75486
              ],
              "top": [
                0.7175,
                0.51417
              ],
              "label": "Person 2",
              "reason": null,
              "source": "auto",
              "usable": true,
              "contact": [
                0.71562,
                0.68
              ],
              "quality": 1,
              "sigma_deg": 3.66,
              "caster_class": "person",
              "elevation_deg": 32.34
            },
            {
              "id": "a3",
              "tip": [
                0.43342,
                0.69252
              ],
              "top": [
                0.44656,
                0.5025
              ],
              "label": "Person 3",
              "reason": null,
              "source": "auto",
              "usable": true,
              "contact": [
                0.44312,
                0.6425
              ],
              "quality": 1,
              "sigma_deg": 4.81,
              "caster_class": "person",
              "elevation_deg": 30.66
            },
            {
              "id": "a4",
              "tip": [
                0.00005,
                0.99292
              ],
              "top": [
                0.25562,
                0.4925
              ],
              "label": "Person 4",
              "reason": "The shadow runs into the image edge, so its end cannot be found.",
              "source": "auto",
              "usable": false,
              "contact": [
                0.29938,
                0.7625
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            },
            {
              "id": "a5",
              "tip": [
                0.30125,
                0.70583
              ],
              "top": [
                0.32125,
                0.47667
              ],
              "label": "Person 5",
              "reason": "No shadow starts at the base of the object.",
              "source": "auto",
              "usable": false,
              "contact": [
                0.30125,
                0.70583
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            },
            {
              "id": "a6",
              "tip": [
                0.08305,
                0.66133
              ],
              "top": [
                0.04688,
                0.50083
              ],
              "label": "Person 6",
              "reason": "The shadow is not bounded on both sides.",
              "source": "auto",
              "usable": false,
              "contact": [
                0.03812,
                0.63833
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            },
            {
              "id": "a7",
              "tip": [
                0.96906,
                0.63232
              ],
              "top": [
                0.905,
                0.50667
              ],
              "label": "Person 7",
              "reason": "The shadow is too faint (contrast 6 against 10 needed).",
              "source": "auto",
              "usable": false,
              "contact": [
                0.89812,
                0.6025
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            },
            {
              "id": "a8",
              "tip": [
                0.93254,
                0.64704
              ],
              "top": [
                0.9825,
                0.51917
              ],
              "label": "Person 8",
              "reason": "The shadow is not bounded on both sides.",
              "source": "auto",
              "usable": false,
              "contact": [
                0.98562,
                0.63583
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            }
          ],
          "camera": {
            "focal_px": 924.5,
            "reliable": true,
            "reported": {
              "focal_px": 0,
              "roll_deg": 1.218,
              "pitch_deg": 2.084
            },
            "roll_deg": 0.31,
            "pitch_deg": 2.02,
            "focal_note": "EXIF FocalLengthIn35mmFilm 20 mm, 925 px here (GeoCalib alone estimated 1015 px).",
            "focal_source": "exif",
            "focal_uncertainty_px": 37,
            "gravity_uncertainty_deg": 2.08
          },
          "ground": {
            "flat": true,
            "note": "Ground around the 3 pairs used is consistent with a flat plane."
          },
          "reason": "The 10 runs and the full analysis do not agree, so the check gives no answer.",
          "daylight": {
            "note": "mean luma 130, 95th percentile 201",
            "looks_daylit": true
          },
          "estimate": {
            "unit": "deg",
            "median": 20.67990126450351,
            "spread": 12.021123949977586
          },
          "delta_deg": -10.03,
          "sigma_deg": 2.2,
          "consistency": {
            "runs": 10,
            "slack": 0.194511,
            "votes": {
              "verified": 0,
              "contradicted": 0,
              "inconclusive": 10
            },
            "reason": "The shadow lines do not meet cleanly, but the 3D light directions agree within measurement noise, so this is not enough to say the shadows disagree.",
            "verdict": "inconclusive",
            "misfit_deg": 30.46,
            "scatter_deg": 10.58,
            "pairs_compared": 3,
            "slack_inflated": 0.171325,
            "noise_scatter_deg": 4.1
          },
          "usable_pairs": 3,
          "user_adjusted": false,
          "candidate_dates": [],
          "candidate_times": [
            "2022-06-04T12:22:21.442786+00:00",
            "2022-06-04T23:03:04.506349+00:00"
          ],
          "detection_notes": [
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped."
          ],
          "allowed_band_deg": 5,
          "sun_below_horizon": false,
          "candidate_time_ranges": [
            {
              "to": "2022-06-04T12:33:59.092995+00:00",
              "from": "2022-06-04T12:10:39.185098+00:00"
            },
            {
              "to": "2022-06-04T23:14:47.288106+00:00",
              "from": "2022-06-04T22:51:26.370020+00:00"
            }
          ],
          "expected_elevation_deg": 31.54,
          "measured_elevation_deg": 21.51
        },
        "variants": {
          "runs": 10,
          "spread": 12.021123949977586,
          "usable": 4,
          "agreement": 1
        },
        "artifacts": [
          {
            "url": "/v1/public/gtkxmwhnWj8/artifacts/shadowproof/sun_curve.json",
            "kind": "plot_json"
          }
        ],
        "applicable": true,
        "confidence": 0,
        "duration_ms": 5600,
        "user_adjusted": false
      }
    },
    "synthscan": {
      "status": "done",
      "result": {
        "check": "synthscan",
        "summary": "No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion.",
        "verdict": "verified",
        "version": "synthscan@1.3.0",
        "evidence": {
          "p_ai": 0.058,
          "runs": [
            {
              "id": "swinv2-full",
              "note": "2% AI over the picture.",
              "unit": "% AI",
              "label": "Detector, whole frame",
              "value": 2,
              "verdict": "verified",
              "confidence": 0.96
            },
            {
              "id": "swinv2-centre",
              "note": "2% AI over the picture.",
              "unit": "% AI",
              "label": "Detector, centre",
              "value": 2,
              "verdict": "verified",
              "confidence": 0.96
            },
            {
              "id": "swinv2-tiles",
              "note": "2% AI over 6 tiles.",
              "unit": "% AI",
              "label": "Detector, fine texture",
              "value": 2,
              "verdict": "verified",
              "confidence": 0.96
            },
            {
              "id": "commfor-full",
              "note": "2% AI over the picture.",
              "unit": "% AI",
              "label": "Community Forensics, whole frame",
              "value": 2,
              "verdict": "verified",
              "confidence": 0.96
            },
            {
              "id": "commfor-tiles",
              "note": "3% AI over 6 tiles.",
              "unit": "% AI",
              "label": "Community Forensics, fine texture",
              "value": 2.8,
              "verdict": "verified",
              "confidence": 0.944
            },
            {
              "id": "photographer",
              "note": "Natural depth of field and lens distortion; Realistic high noon harsh shadows and sensor dynamic range; Genuine photographic grain and micro-contrast",
              "unit": "% AI",
              "label": "Photographer",
              "value": 5,
              "verdict": "verified",
              "confidence": 0.9
            },
            {
              "id": "physicist",
              "note": "Consistent shadows cast by all people and the pole; Accurate perspective lines on the ground and buildings; Correct handling of bright sunlight and overhead string lights",
              "unit": "% AI",
              "label": "Physicist",
              "value": 5,
              "verdict": "verified",
              "confidence": 0.9
            },
            {
              "id": "anatomist",
              "note": "Normal human anatomy and limb proportions; Correct finger counts and hand structures; Natural posture and weight distribution of walking pedestrians",
              "unit": "% AI",
              "label": "Anatomist",
              "value": 5,
              "verdict": "verified",
              "confidence": 0.9
            },
            {
              "id": "detail",
              "note": "Legible text on the green trash bin reading Ray's; Coherent lettering on the trailer signage; Consistent patterns on the string lights and tents",
              "unit": "% AI",
              "label": "Detail inspector",
              "value": 10,
              "verdict": "verified",
              "confidence": 0.8
            },
            {
              "id": "generalist",
              "note": "Natural clutter and imperfect crowd arrangement of a real market; Normal lighting and surface textures without plastic sheen; Authentic everyday snapshot aesthetics",
              "unit": "% AI",
              "label": "Forensic generalist",
              "value": 5,
              "verdict": "verified",
              "confidence": 0.9
            }
          ],
          "frames": 1,
          "reviews": [
            {
              "cues": [
                "Natural depth of field and lens distortion",
                "Realistic high noon harsh shadows and sensor dynamic range",
                "Genuine photographic grain and micro-contrast"
              ],
              "p_ai": 0.05,
              "label": "Photographer"
            },
            {
              "cues": [
                "Consistent shadows cast by all people and the pole",
                "Accurate perspective lines on the ground and buildings",
                "Correct handling of bright sunlight and overhead string lights"
              ],
              "p_ai": 0.05,
              "label": "Physicist"
            },
            {
              "cues": [
                "Normal human anatomy and limb proportions",
                "Correct finger counts and hand structures",
                "Natural posture and weight distribution of walking pedestrians"
              ],
              "p_ai": 0.05,
              "label": "Anatomist"
            },
            {
              "cues": [
                "Legible text on the green trash bin reading Ray's",
                "Coherent lettering on the trailer signage",
                "Consistent patterns on the string lights and tents"
              ],
              "p_ai": 0.1,
              "label": "Detail inspector"
            },
            {
              "cues": [
                "Natural clutter and imperfect crowd arrangement of a real market",
                "Normal lighting and surface textures without plastic sheen",
                "Authentic everyday snapshot aesthetics"
              ],
              "p_ai": 0.05,
              "label": "Forensic generalist"
            }
          ],
          "signals": [],
          "estimate": {
            "unit": "% AI",
            "median": 5.8,
            "spread": null
          },
          "detectors": {
            "swinv2": "haywoodsloan/ai-image-detector-deploy",
            "commfor": "OwensLab/commfor-model-384"
          },
          "lead_cues": [
            "Natural depth of field and lens distortion",
            "Realistic high noon harsh shadows and sensor dynamic range",
            "Genuine photographic grain and micro-contrast"
          ],
          "tiles_p_ai": 0.02,
          "commfor_p_ai": 0.02,
          "persona_p_ai": 0.058,
          "detector_p_ai": 0.02,
          "frame_times_s": [
            0
          ],
          "reviewer_source": "gemini:gemini-3.5-flash-lite",
          "families_disagree": false,
          "commfor_tiles_p_ai": 0.028
        },
        "variants": {
          "runs": 10,
          "spread": 3,
          "usable": 10,
          "agreement": 1
        },
        "artifacts": [],
        "applicable": true,
        "confidence": 0.654,
        "duration_ms": 10587,
        "user_adjusted": false
      }
    },
    "weathercheck": {
      "status": "done",
      "result": {
        "check": "weathercheck",
        "summary": "What the photo shows (a clear sky, hard shadows and daylight) is consistent with the weather record for 2022-06-04 14:14 UTC: 0% cloud cover and 0.0 mm of rain in that hour.",
        "verdict": "verified",
        "version": "weathercheck@1.0.0",
        "evidence": {
          "rule": "consistent",
          "runs": [
            {
              "id": "hour_+0",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed hour",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "hour_-30",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed time -30 min",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "hour_+30",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed time +30 min",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "hour_-60",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed time -60 min",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "hour_+60",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed time +60 min",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "cell_ne",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "North-east grid cell",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "cell_sw",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "South-west grid cell",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "read_1",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Vision read 1",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "read_2",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Vision read 2",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "read_3",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Vision read 3",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            }
          ],
          "scene": {
            "daylight": true,
            "sky_state": "clear",
            "confidence": 1,
            "wet_ground": false,
            "sky_visible": true,
            "hard_shadows": true
          },
          "reason": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
          "weather": {
            "model": "best_match",
            "is_day": 1,
            "source": "archive-api.open-meteo.com",
            "grid_lat": 39.5,
            "grid_lon": -86,
            "cloud_cover_pct": 0,
            "precipitation_mm": 0
          },
          "estimate": {
            "unit": "% cloud cover",
            "median": 0,
            "spread": 0
          },
          "grid_note": "Historical weather comes from a coarse grid of about 10 km (reanalysis), so a single hour can differ from the real sky above one street. This check is weighted lower than ShadowProof for that reason.",
          "neighbours": [
            {
              "cell": "ne",
              "label": "the cell to the north-east",
              "is_day": 1,
              "grid_lat": 39.6,
              "grid_lon": -85.9,
              "cloud_cover_pct": 0
            },
            {
              "cell": "sw",
              "label": "the cell to the south-west",
              "is_day": 1,
              "grid_lat": 39.3,
              "grid_lon": -86.2,
              "cloud_cover_pct": 0
            }
          ],
          "attribution": {
            "url": "https://open-meteo.com/",
            "name": "Open-Meteo.com",
            "license": "CC BY 4.0"
          },
          "scene_reads": [
            {
              "daylight": true,
              "sky_state": "clear",
              "confidence": 1,
              "wet_ground": false,
              "sky_visible": true,
              "hard_shadows": true
            },
            {
              "daylight": true,
              "sky_state": "clear",
              "confidence": 1,
              "wet_ground": false,
              "sky_visible": true,
              "hard_shadows": true
            },
            {
              "daylight": true,
              "sky_state": "clear",
              "confidence": 1,
              "wet_ground": false,
              "sky_visible": true,
              "hard_shadows": true
            }
          ],
          "vision_notes": [],
          "vision_source": "gemini:gemini-3.5-flash-lite",
          "claimed_window": {
            "hours": [
              "2022-06-04T13:00:00+00:00",
              "2022-06-04T14:00:00+00:00",
              "2022-06-04T15:00:00+00:00",
              "2022-06-04T16:00:00+00:00"
            ],
            "end_utc": "2022-06-04T15:14:59+00:00",
            "start_utc": "2022-06-04T13:14:59+00:00"
          }
        },
        "variants": {
          "runs": 10,
          "spread": 0,
          "usable": 10,
          "agreement": 1
        },
        "artifacts": [
          {
            "url": "/v1/public/gtkxmwhnWj8/artifacts/weathercheck/weather_window.json",
            "kind": "plot_json"
          }
        ],
        "applicable": true,
        "confidence": 0.44999999999999996,
        "duration_ms": 4744,
        "user_adjusted": false
      }
    }
  },
  "overall": {
    "text": "The result is Consistent, but only weakly: SynthScan and WeatherCheck fit the claim, though none of them is a strong test on its own. No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion. Consistent means the measurements fit the claimed time and place, though these checks cannot rule out a staged scene or edits that they do not measure.",
    "weak": true,
    "reasons": [
      "SynthScan (Consistent, 65% confidence): No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion.",
      "WeatherCheck (Consistent, 45% confidence): What the photo shows (a clear sky, hard shadows and daylight) is consistent with the weather record for 2022-06-04 14:14 UTC: 0% cloud cover and 0.0 mm of rain in that hour."
    ],
    "verdict": "verified",
    "headline": "The evidence is weakly consistent with this claim.",
    "confidence": 0.598,
    "text_source": "template:guard_rejected",
    "checks_counted": [
      "synthscan",
      "weathercheck"
    ]
  },
  "error": null,
  "claim": {
    "lat": 39.5,
    "lon": -86.1,
    "label": null,
    "tz_name": "America/Indiana/Indianapolis",
    "utc_end": "2022-06-04T15:14:59+00:00",
    "local_iso": "2022-06-04T10:14:59-04:00",
    "time_mode": "plus_minus_1h",
    "utc_start": "2022-06-04T13:14:59+00:00",
    "place_mode": "exact",
    "utc_center": "2022-06-04T14:14:59+00:00",
    "time_source": "claim",
    "country_code": "US",
    "place_source": "claim"
  },
  "media": {
    "fps": null,
    "kind": "image",
    "mime": "image/jpeg",
    "width": 4032,
    "height": 3024,
    "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
    "bitrate": null,
    "has_audio": false,
    "has_video": false,
    "duration_s": null,
    "size_bytes": 6675621,
    "audio_codec": null,
    "video_codec": null,
    "audio_sample_rate": null
  },
  "shared": true,
  "share_id": "gtkxmwhnWj8",
  "share_original": false,
  "sample_id": null,
  "created_at": "2026-11-06T07:22:31.400198+00:00",
  "started_at": "2026-11-06T07:22:32.960184+00:00",
  "finished_at": "2026-11-06T07:22:50.448949+00:00",
  "file_deleted_at": null
}

A live verdict badge

GET /v1/public/{share_id}/badge.svg

An SVG to embed beside an image. It reads the current verdict, so it changes if the check is made private.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/public/$SHARE_ID/badge.svg"
Real response200Captured 6 November 2026
GET /v1/public/{share_id}/badge.svg
<svg xmlns="http://www.w3.org/2000/svg" width="223" height="30" viewBox="0 0 223 30" role="img" aria-label="TruthKit: Consistent (weak)"><title>TruthKit: Consistent (weak)</title><style>text{font-family:ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif}.bg{fill:#F8F6F1;stroke:#D3CEC2}.t{fill:#141412}.t2{fill:#47463F}.ok{fill:#1B6B45}.bad{fill:#B42318}.warn{fill:#855600}.okt{fill:#1B6B45}.badt{fill:#B42318}.warnt{fill:#855600}.rl{fill:#D3CEC2}.ink{stroke:#F8F6F1;fill:none}.inkf{fill:#F8F6F1}.ring{stroke:#47463F;fill:none}@media (prefers-color-scheme: dark){.bg{fill:#181817;stroke:#2C2B28}.t{fill:#EDE9DF}.t2{fill:#B8B4A9}.ok{fill:#52C78E}.bad{fill:#FF7D69}.warn{fill:#EDB23A}.okt{fill:#52C78E}.badt{fill:#FF7D69}.warnt{fill:#EDB23A}.rl{fill:#2C2B28}.ink{stroke:#181817;fill:none}.inkf{fill:#181817}.ring{stroke:#B8B4A9;fill:none}}</style><rect class="bg" x="0.5" y="0.5" width="222" height="29" rx="14.5" stroke-width="1"/><circle class="ok" cx="16" cy="15" r="7.5"/><path class="ink" d="M12.4 15.2l2.5 2.6 4.6-5.2" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/><text class="okt" x="30" y="19.5" font-size="13" font-weight="600">Consistent</text><text class="warnt" x="110.0" y="19.5" font-size="11" font-weight="600">weak</text><rect class="rl" x="147.0" y="8" width="1" height="14"/><text class="t2" x="157.0" y="19.3" font-size="11" letter-spacing=".2">TruthKit</text></svg>

File and place tools

Read a file's own details

POST /v1/inspect

Type, size, dimensions and camera metadata, plus a suggested claim from that metadata saying where each value came from. Nothing about the file is kept.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/inspect" \
  -F 'file=@photo.jpg'
Real response200Captured 6 November 2026
POST /v1/inspect with real-photo.jpg
{
  "media": {
    "kind": "image",
    "mime": "image/jpeg",
    "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
    "size_bytes": 6675621,
    "duration_s": null,
    "width": 4032,
    "height": 3024,
    "fps": null,
    "has_audio": false,
    "has_video": false,
    "video_codec": null,
    "audio_codec": null,
    "audio_sample_rate": null,
    "bitrate": null,
    "tags": {}
  },
  "metadata": {
    "ai_markers": [],
    "sources": {
      "time": "EXIF DateTimeOriginal with UTC offset -04:00",
      "place": "EXIF GPS"
    },
    "make": "Apple",
    "model": "iPhone 12 Pro Max",
    "software": "15.5",
    "lens": "iPhone 12 Pro Max back triple camera 1.54mm f/2.4",
    "datetime_original": "2022-06-04T10:14:59.983-04:00",
    "datetime_modified": "2022-06-04T10:14:59",
    "lat": 39.479653,
    "lon": -86.058639,
    "altitude_m": 216.9
  },
  "claim_prefill": {
    "place": {
      "mode": "exact",
      "lat": 39.479653,
      "lon": -86.058639,
      "label": null
    },
    "time": {
      "mode": "exact",
      "iso": "2022-06-04T10:14:59.983-04:00",
      "tz": "auto"
    },
    "sources": {
      "place": "EXIF GPS",
      "time": "EXIF DateTimeOriginal with UTC offset -04:00"
    }
  },
  "stored": false
}

Find a place

GET /v1/geocode?q=

Turns a place name into coordinates and a timezone, ready for the claim.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/geocode?q=Sharjah"
Real response200Captured 6 November 2026
GET /v1/geocode?q=Sharjah
{
  "label": "Sharjah city, Emirate of Sharjah, United Arab Emirates",
  "lat": 25.3342,
  "lon": 55.41221,
  "country_code": "AE",
  "timezone": "Asia/Dubai"
}

List the sample files

GET /v1/samples

The files on the home page, with their claims, sources and licences, and their most recent run. POST /v1/samples/{id}/run starts a new one.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/samples"
Real response200Captured 6 November 2026
GET /v1/samples
{
  "items": [
    {
      "id": "real-photo",
      "title": "A real street market in strong sun",
      "kind": "image",
      "claim": {
        "place": {
          "mode": "exact",
          "lat": 39.479653,
          "lon": -86.058639,
          "label": "Franklin, Indiana, United States"
        },
        "time": {
          "mode": "plus_minus_1h",
          "iso": "2022-06-04T10:14:59",
          "tz": "auto"
        }
      },
      "source": "https://commons.wikimedia.org/wiki/File:Franklin_Farmers_Market.jpg",
      "licence": "CC BY 4.0",
      "thumbnail_url": "/v1/samples/real-photo/thumbnail",
      "last_run": null
    },
    {
      "id": "ai-street",
      "title": "An AI-generated street-side scene",
      "kind": "image",
      "claim": {
        "place": {
          "mode": "exact",
          "lat": 17.4239,
          "lon": 78.4738,
          "label": "Hyderabad, India"
        },
        "time": {
          "mode": "plus_minus_1h",
          "iso": "2026-01-14T10:30:00",
          "tz": "auto"
        }
      },
      "source": "https://commons.wikimedia.org/wiki/File:%E0%B0%9A%E0%B1%86%E0%B0%B0%E0%B1%81%E0%B0%B5%E0%B1%81%E0%B0%95%E0%B0%9F%E0%B1%8D%E0%B0%9F.png",
      "licence": "Public domain",
      "thumbnail_url": "/v1/samples/ai-street/thumbnail",
      "last_run": {
        "share_id": "nVUHPql4eBw",
        "finished_at": "2026-11-02T18:04:50.389785+00:00",
        "verdict": "contradicted"
      }
    },
    {
      "id": "hum-cut",
      "title": "A recording with a hidden cut",
      "kind": "audio",
      "claim": {
        "place": {
          "mode": "exact",
          "lat": 30.5928,
          "lon": 114.3055,
          "label": "Wuhan, China"
        },
        "time": {
          "mode": "unknown",
          "iso": null,
          "tz": "auto"
        }
      },
      "source": "https://github.com/ghua-ac/ENF-WHU-Dataset (H1/021.wav), edited by TruthKit",
      "licence": "MIT",
      "thumbnail_url": "/v1/samples/hum-cut/thumbnail",
      "last_run": null
    }
  ]
}

Limits and sign-in mode

GET /v1/config

Public settings. Contains no secrets. The quotas on this page come from here.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/config"
Real response200Captured 6 November 2026
GET /v1/config
{
  "version": "1.0.0",
  "auth": "supabase",
  "supabase_url": "https://toaghfgiqutkwqwgdozf.supabase.co",
  "supabase_anon_key": "sb_publishable_OjXRqb-oeBFalCiA8-yczw_mGARIoX4",
  "limits": {
    "max_upload_mb": 50,
    "max_duration_s": 300,
    "retention_days": 7,
    "anon_checks_per_day": 10,
    "free_checks_per_month": 50
  },
  "vision_provider": "gemini"
}

The claim format

A claim is what someone says about a file. Leave out anything you do not know: a check that needs it returns inconclusive and says why. The timezone comes from the coordinates, and all the maths runs in UTC.

place.mode

exact
Send lat and lon.
city
A town or city: send lat, lon and label. Tolerances widen to suit.
unknown
Checks that need a place return inconclusive.

time.mode

exact
Send iso, a local time such as 2026-10-12T14:00:00.
plus_minus_1h
Within an hour either side of iso.
rough_date
The date in iso is about right.
unknown
Checks that need a time return inconclusive.
{
  "place": { "mode": "city", "label": "Sharjah, UAE", "lat": 25.35, "lon": 55.42 },
  "time":  { "mode": "exact", "iso": "2026-10-12T14:00:00", "tz": "auto" }
}

The result contract

Every one of the eight checks returns exactly this shape. Numbers specific to a check are under evidence, which may lack a key when the check was inconclusive, so read it defensively.

check
synthscan, patternscan, shadowproof, weathercheck, pulsecheck, eyemirror, humprint or provenance.
applicable
False when this check cannot run on this kind of file.
verdict
See the table below.
confidence
From 0 to 1.
summary
One sentence built from the check's own numbers, never written by an LLM.
evidence
The measurements, plus runs for the 10 varied runs and reason when inconclusive.
artifacts
Overlays and plots, as signed links.
variants
How the 10 runs agreed: runs, usable, agreement, spread.
user_adjusted
True when a person corrected the input.
duration_ms
How long the check took.
version
Which version of the check produced this, such as shadowproof@1.0.0.
Real response200Captured 6 November 2026
GET /v1/checks/{id}, once finished
{
  "id": "SWO9cpoo8wx3",
  "status": "done",
  "kind": "image",
  "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
  "checks": {
    "eyemirror": {
      "status": "done",
      "result": {
        "check": "eyemirror",
        "summary": "No face was found in this file, so there are no eyes to compare.",
        "verdict": "not_applicable",
        "version": "eyemirror@1.0.0",
        "evidence": {},
        "variants": {
          "runs": 0,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": false,
        "confidence": 0,
        "duration_ms": 752,
        "user_adjusted": false
      }
    },
    "humprint": {
      "status": "done",
      "result": {
        "check": "humprint",
        "summary": "HumPrint reads mains hum in sound, and a photo has none.",
        "verdict": "not_applicable",
        "version": "humprint@1.0.0",
        "evidence": {},
        "variants": {
          "runs": 0,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": false,
        "confidence": 0,
        "duration_ms": 0,
        "user_adjusted": false
      }
    },
    "patternscan": {
      "status": "done",
      "result": {
        "check": "patternscan",
        "summary": "PatternScan found none of the traces it looks for. That does not show the picture is real: cropping, resizing or renaming removes them.",
        "verdict": "inconclusive",
        "version": "patternscan@1.0.0",
        "evidence": {
          "runs": [
            {
              "id": "size",
              "note": "4032 x 3024 is not a size generators output.",
              "unit": null,
              "label": "Output size",
              "value": null,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "filename",
              "note": "The file name is not a generator's default name.",
              "unit": null,
              "label": "File name",
              "value": null,
              "verdict": "inconclusive",
              "confidence": 0
            }
          ],
          "width": 4032,
          "camera": "Apple iPhone 12 Pro Max",
          "height": 3024,
          "reason": "no_trace",
          "signals": [],
          "generator_size": false,
          "filename_generator": null
        },
        "variants": {
          "runs": 2,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": true,
        "confidence": 0,
        "duration_ms": 9,
        "user_adjusted": false
      }
    },
    "provenance": {
      "status": "done",
      "result": {
        "check": "provenance",
        "summary": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.",
        "verdict": "inconclusive",
        "version": "provenance@1.0.0",
        "evidence": {
          "c2pa": {
            "tool": null,
            "valid": false,
            "issues": [],
            "signer": null,
            "actions": [],
            "present": false,
            "trusted": false,
            "signed_at": null,
            "unreadable": null,
            "signer_label": null,
            "validation_codes": [],
            "validation_status": null,
            "digital_source_type": null
          },
          "exif": {
            "lat": 39.479653,
            "lon": -86.058639,
            "make": "Apple",
            "model": "iPhone 12 Pro Max",
            "software": "15.5",
            "datetime_original": "2022-06-04T10:14:59.983-04:00"
          },
          "runs": [
            {
              "id": "signature",
              "note": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.",
              "unit": null,
              "label": "Credentials and metadata",
              "value": null,
              "verdict": "inconclusive",
              "confidence": 0
            }
          ],
          "reason": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.",
          "synthid": {
            "url": "https://synthid.com",
            "note": "SynthID is Google's invisible watermark. It has no public API, so TruthKit does not scan for it. You can check a file yourself at synthid.com."
          },
          "ai_markers": [],
          "signed_and_valid": false
        },
        "variants": {
          "runs": 1,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": true,
        "confidence": 0,
        "duration_ms": 13,
        "user_adjusted": false
      }
    },
    "pulsecheck": {
      "status": "done",
      "result": {
        "check": "pulsecheck",
        "summary": "PulseCheck measures colour changes over time, so it needs a video. A single photo has none.",
        "verdict": "not_applicable",
        "version": "pulsecheck@1.0.0",
        "evidence": {},
        "variants": {
          "runs": 0,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": false,
        "confidence": 0,
        "duration_ms": 0,
        "user_adjusted": false
      }
    },
    "shadowproof": {
      "status": "done",
      "result": {
        "check": "shadowproof",
        "summary": "The 10 runs and the full analysis do not agree, so the check gives no answer.",
        "verdict": "inconclusive",
        "version": "shadowproof@1.0.0",
        "evidence": {
          "runs": [
            {
              "id": "r1",
              "note": "The measured sun elevation is within 8.5 degrees of the claim.",
              "unit": "deg",
              "label": "Without Person 1, jitter x1",
              "value": 31.631751015051567,
              "verdict": "verified",
              "confidence": 0.165
            },
            {
              "id": "r2",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 2, jitter x0.75",
              "value": 19.51836558084403,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r3",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 3, jitter x1.25",
              "value": 20.586148238799428,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r4",
              "note": "The measured sun elevation is within 7.8 degrees of the claim.",
              "unit": "deg",
              "label": "Without Person 1, jitter x1",
              "value": 32.3741008019022,
              "verdict": "verified",
              "confidence": 0.195
            },
            {
              "id": "r5",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 2, jitter x0.75",
              "value": 19.317814021779046,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r6",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 3, jitter x1.25",
              "value": 20.202704985735988,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r7",
              "note": "The measured sun elevation is within 7.7 degrees of the claim.",
              "unit": "deg",
              "label": "Without Person 1, jitter x1",
              "value": 32.175667091905794,
              "verdict": "verified",
              "confidence": 0.205
            },
            {
              "id": "r8",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 2, jitter x0.75",
              "value": 19.35563487919962,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r9",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 3, jitter x1.25",
              "value": 20.773654290207592,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r10",
              "note": "The measured sun elevation is within 8.0 degrees of the claim.",
              "unit": "deg",
              "label": "Without Person 1, jitter x1",
              "value": 31.73684883770895,
              "verdict": "verified",
              "confidence": 0.19
            }
          ],
          "image": {
            "width": 1600,
            "height": 1200
          },
          "pairs": [
            {
              "id": "a1",
              "tip": [
                0.43312,
                0.80296
              ],
              "top": [
                0.58688,
                0.48514
              ],
              "label": "Person 1",
              "reason": null,
              "source": "auto",
              "usable": true,
              "contact": [
                0.58875,
                0.64083
              ],
              "quality": 1,
              "sigma_deg": 1.6,
              "caster_class": "person",
              "elevation_deg": 17.94
            },
            {
              "id": "a2",
              "tip": [
                0.75949,
                0.75486
              ],
              "top": [
                0.7175,
                0.51417
              ],
              "label": "Person 2",
              "reason": null,
              "source": "auto",
              "usable": true,
              "contact": [
                0.71562,
                0.68
              ],
              "quality": 1,
              "sigma_deg": 3.66,
              "caster_class": "person",
              "elevation_deg": 32.34
            },
            {
              "id": "a3",
              "tip": [
                0.43342,
                0.69252
              ],
              "top": [
                0.44656,
                0.5025
              ],
              "label": "Person 3",
              "reason": null,
              "source": "auto",
              "usable": true,
              "contact": [
                0.44312,
                0.6425
              ],
              "quality": 1,
              "sigma_deg": 4.81,
              "caster_class": "person",
              "elevation_deg": 30.66
            },
            {
              "id": "a4",
              "tip": [
                0.00005,
                0.99292
              ],
              "top": [
                0.25562,
                0.4925
              ],
              "label": "Person 4",
              "reason": "The shadow runs into the image edge, so its end cannot be found.",
              "source": "auto",
              "usable": false,
              "contact": [
                0.29938,
                0.7625
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            },
            {
              "id": "a5",
              "tip": [
                0.30125,
                0.70583
              ],
              "top": [
                0.32125,
                0.47667
              ],
              "label": "Person 5",
              "reason": "No shadow starts at the base of the object.",
              "source": "auto",
              "usable": false,
              "contact": [
                0.30125,
                0.70583
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            },
            {
              "id": "a6",
              "tip": [
                0.08305,
                0.66133
              ],
              "top": [
                0.04688,
                0.50083
              ],
              "label": "Person 6",
              "reason": "The shadow is not bounded on both sides.",
              "source": "auto",
              "usable": false,
              "contact": [
                0.03812,
                0.63833
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            },
            {
              "id": "a7",
              "tip": [
                0.96906,
                0.63232
              ],
              "top": [
                0.905,
                0.50667
              ],
              "label": "Person 7",
              "reason": "The shadow is too faint (contrast 6 against 10 needed).",
              "source": "auto",
              "usable": false,
              "contact": [
                0.89812,
                0.6025
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            },
            {
              "id": "a8",
              "tip": [
                0.93254,
                0.64704
              ],
              "top": [
                0.9825,
                0.51917
              ],
              "label": "Person 8",
              "reason": "The shadow is not bounded on both sides.",
              "source": "auto",
              "usable": false,
              "contact": [
                0.98562,
                0.63583
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            }
          ],
          "camera": {
            "focal_px": 924.5,
            "reliable": true,
            "reported": {
              "focal_px": 0,
              "roll_deg": 1.218,
              "pitch_deg": 2.084
            },
            "roll_deg": 0.31,
            "pitch_deg": 2.02,
            "focal_note": "EXIF FocalLengthIn35mmFilm 20 mm, 925 px here (GeoCalib alone estimated 1015 px).",
            "focal_source": "exif",
            "focal_uncertainty_px": 37,
            "gravity_uncertainty_deg": 2.08
          },
          "ground": {
            "flat": true,
            "note": "Ground around the 3 pairs used is consistent with a flat plane."
          },
          "reason": "The 10 runs and the full analysis do not agree, so the check gives no answer.",
          "daylight": {
            "note": "mean luma 130, 95th percentile 201",
            "looks_daylit": true
          },
          "estimate": {
            "unit": "deg",
            "median": 20.67990126450351,
            "spread": 12.021123949977586
          },
          "delta_deg": -10.03,
          "sigma_deg": 2.2,
          "consistency": {
            "runs": 10,
            "slack": 0.194511,
            "votes": {
              "verified": 0,
              "contradicted": 0,
              "inconclusive": 10
            },
            "reason": "The shadow lines do not meet cleanly, but the 3D light directions agree within measurement noise, so this is not enough to say the shadows disagree.",
            "verdict": "inconclusive",
            "misfit_deg": 30.46,
            "scatter_deg": 10.58,
            "pairs_compared": 3,
            "slack_inflated": 0.171325,
            "noise_scatter_deg": 4.1
          },
          "usable_pairs": 3,
          "user_adjusted": false,
          "candidate_dates": [],
          "candidate_times": [
            "2022-06-04T12:22:21.442786+00:00",
            "2022-06-04T23:03:04.506349+00:00"
          ],
          "detection_notes": [
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped."
          ],
          "allowed_band_deg": 5,
          "sun_below_horizon": false,
          "candidate_time_ranges": [
            {
              "to": "2022-06-04T12:33:59.092995+00:00",
              "from": "2022-06-04T12:10:39.185098+00:00"
            },
            {
              "to": "2022-06-04T23:14:47.288106+00:00",
              "from": "2022-06-04T22:51:26.370020+00:00"
            }
          ],
          "expected_elevation_deg": 31.54,
          "measured_elevation_deg": 21.51
        },
        "variants": {
          "runs": 10,
          "spread": 12.021123949977586,
          "usable": 4,
          "agreement": 1
        },
        "artifacts": [
          {
            "url": "/v1/checks/SWO9cpoo8wx3/artifacts/shadowproof/overlay.png?exp=1794036174&sig=[redacted]",
            "kind": "overlay_png"
          },
          {
            "url": "/v1/checks/SWO9cpoo8wx3/artifacts/shadowproof/preview.png?exp=1794036174&sig=[redacted]",
            "kind": "overlay_png"
          },
          {
            "url": "/v1/checks/SWO9cpoo8wx3/artifacts/shadowproof/sun_curve.json?exp=1794036174&sig=[redacted]",
            "kind": "plot_json"
          }
        ],
        "applicable": true,
        "confidence": 0,
        "duration_ms": 5600,
        "user_adjusted": false
      }
    },
    "synthscan": {
      "status": "done",
      "result": {
        "check": "synthscan",
        "summary": "No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion.",
        "verdict": "verified",
        "version": "synthscan@1.3.0",
        "evidence": {
          "p_ai": 0.058,
          "runs": [
            {
              "id": "swinv2-full",
              "note": "2% AI over the picture.",
              "unit": "% AI",
              "label": "Detector, whole frame",
              "value": 2,
              "verdict": "verified",
              "confidence": 0.96
            },
            {
              "id": "swinv2-centre",
              "note": "2% AI over the picture.",
              "unit": "% AI",
              "label": "Detector, centre",
              "value": 2,
              "verdict": "verified",
              "confidence": 0.96
            },
            {
              "id": "swinv2-tiles",
              "note": "2% AI over 6 tiles.",
              "unit": "% AI",
              "label": "Detector, fine texture",
              "value": 2,
              "verdict": "verified",
              "confidence": 0.96
            },
            {
              "id": "commfor-full",
              "note": "2% AI over the picture.",
              "unit": "% AI",
              "label": "Community Forensics, whole frame",
              "value": 2,
              "verdict": "verified",
              "confidence": 0.96
            },
            {
              "id": "commfor-tiles",
              "note": "3% AI over 6 tiles.",
              "unit": "% AI",
              "label": "Community Forensics, fine texture",
              "value": 2.8,
              "verdict": "verified",
              "confidence": 0.944
            },
            {
              "id": "photographer",
              "note": "Natural depth of field and lens distortion; Realistic high noon harsh shadows and sensor dynamic range; Genuine photographic grain and micro-contrast",
              "unit": "% AI",
              "label": "Photographer",
              "value": 5,
              "verdict": "verified",
              "confidence": 0.9
            },
            {
              "id": "physicist",
              "note": "Consistent shadows cast by all people and the pole; Accurate perspective lines on the ground and buildings; Correct handling of bright sunlight and overhead string lights",
              "unit": "% AI",
              "label": "Physicist",
              "value": 5,
              "verdict": "verified",
              "confidence": 0.9
            },
            {
              "id": "anatomist",
              "note": "Normal human anatomy and limb proportions; Correct finger counts and hand structures; Natural posture and weight distribution of walking pedestrians",
              "unit": "% AI",
              "label": "Anatomist",
              "value": 5,
              "verdict": "verified",
              "confidence": 0.9
            },
            {
              "id": "detail",
              "note": "Legible text on the green trash bin reading Ray's; Coherent lettering on the trailer signage; Consistent patterns on the string lights and tents",
              "unit": "% AI",
              "label": "Detail inspector",
              "value": 10,
              "verdict": "verified",
              "confidence": 0.8
            },
            {
              "id": "generalist",
              "note": "Natural clutter and imperfect crowd arrangement of a real market; Normal lighting and surface textures without plastic sheen; Authentic everyday snapshot aesthetics",
              "unit": "% AI",
              "label": "Forensic generalist",
              "value": 5,
              "verdict": "verified",
              "confidence": 0.9
            }
          ],
          "frames": 1,
          "reviews": [
            {
              "cues": [
                "Natural depth of field and lens distortion",
                "Realistic high noon harsh shadows and sensor dynamic range",
                "Genuine photographic grain and micro-contrast"
              ],
              "p_ai": 0.05,
              "label": "Photographer"
            },
            {
              "cues": [
                "Consistent shadows cast by all people and the pole",
                "Accurate perspective lines on the ground and buildings",
                "Correct handling of bright sunlight and overhead string lights"
              ],
              "p_ai": 0.05,
              "label": "Physicist"
            },
            {
              "cues": [
                "Normal human anatomy and limb proportions",
                "Correct finger counts and hand structures",
                "Natural posture and weight distribution of walking pedestrians"
              ],
              "p_ai": 0.05,
              "label": "Anatomist"
            },
            {
              "cues": [
                "Legible text on the green trash bin reading Ray's",
                "Coherent lettering on the trailer signage",
                "Consistent patterns on the string lights and tents"
              ],
              "p_ai": 0.1,
              "label": "Detail inspector"
            },
            {
              "cues": [
                "Natural clutter and imperfect crowd arrangement of a real market",
                "Normal lighting and surface textures without plastic sheen",
                "Authentic everyday snapshot aesthetics"
              ],
              "p_ai": 0.05,
              "label": "Forensic generalist"
            }
          ],
          "signals": [],
          "estimate": {
            "unit": "% AI",
            "median": 5.8,
            "spread": null
          },
          "detectors": {
            "swinv2": "haywoodsloan/ai-image-detector-deploy",
            "commfor": "OwensLab/commfor-model-384"
          },
          "lead_cues": [
            "Natural depth of field and lens distortion",
            "Realistic high noon harsh shadows and sensor dynamic range",
            "Genuine photographic grain and micro-contrast"
          ],
          "tiles_p_ai": 0.02,
          "commfor_p_ai": 0.02,
          "persona_p_ai": 0.058,
          "detector_p_ai": 0.02,
          "frame_times_s": [
            0
          ],
          "reviewer_source": "gemini:gemini-3.5-flash-lite",
          "families_disagree": false,
          "commfor_tiles_p_ai": 0.028
        },
        "variants": {
          "runs": 10,
          "spread": 3,
          "usable": 10,
          "agreement": 1
        },
        "artifacts": [],
        "applicable": true,
        "confidence": 0.654,
        "duration_ms": 10587,
        "user_adjusted": false
      }
    },
    "weathercheck": {
      "status": "done",
      "result": {
        "check": "weathercheck",
        "summary": "What the photo shows (a clear sky, hard shadows and daylight) is consistent with the weather record for 2022-06-04 14:14 UTC: 0% cloud cover and 0.0 mm of rain in that hour.",
        "verdict": "verified",
        "version": "weathercheck@1.0.0",
        "evidence": {
          "rule": "consistent",
          "runs": [
            {
              "id": "hour_+0",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed hour",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "hour_-30",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed time -30 min",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "hour_+30",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed time +30 min",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "hour_-60",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed time -60 min",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "hour_+60",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed time +60 min",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "cell_ne",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "North-east grid cell",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "cell_sw",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "South-west grid cell",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "read_1",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Vision read 1",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "read_2",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Vision read 2",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "read_3",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Vision read 3",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            }
          ],
          "scene": {
            "daylight": true,
            "sky_state": "clear",
            "confidence": 1,
            "wet_ground": false,
            "sky_visible": true,
            "hard_shadows": true
          },
          "reason": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
          "weather": {
            "model": "best_match",
            "is_day": 1,
            "source": "archive-api.open-meteo.com",
            "grid_lat": 39.5,
            "grid_lon": -86,
            "cloud_cover_pct": 0,
            "precipitation_mm": 0
          },
          "estimate": {
            "unit": "% cloud cover",
            "median": 0,
            "spread": 0
          },
          "grid_note": "Historical weather comes from a coarse grid of about 10 km (reanalysis), so a single hour can differ from the real sky above one street. This check is weighted lower than ShadowProof for that reason.",
          "neighbours": [
            {
              "cell": "ne",
              "label": "the cell to the north-east",
              "is_day": 1,
              "grid_lat": 39.6,
              "grid_lon": -85.9,
              "cloud_cover_pct": 0
            },
            {
              "cell": "sw",
              "label": "the cell to the south-west",
              "is_day": 1,
              "grid_lat": 39.3,
              "grid_lon": -86.2,
              "cloud_cover_pct": 0
            }
          ],
          "attribution": {
            "url": "https://open-meteo.com/",
            "name": "Open-Meteo.com",
            "license": "CC BY 4.0"
          },
          "scene_reads": [
            {
              "daylight": true,
              "sky_state": "clear",
              "confidence": 1,
              "wet_ground": false,
              "sky_visible": true,
              "hard_shadows": true
            },
            {
              "daylight": true,
              "sky_state": "clear",
              "confidence": 1,
              "wet_ground": false,
              "sky_visible": true,
              "hard_shadows": true
            },
            {
              "daylight": true,
              "sky_state": "clear",
              "confidence": 1,
              "wet_ground": false,
              "sky_visible": true,
              "hard_shadows": true
            }
          ],
          "vision_notes": [],
          "vision_source": "gemini:gemini-3.5-flash-lite",
          "claimed_window": {
            "hours": [
              "2022-06-04T13:00:00+00:00",
              "2022-06-04T14:00:00+00:00",
              "2022-06-04T15:00:00+00:00",
              "2022-06-04T16:00:00+00:00"
            ],
            "end_utc": "2022-06-04T15:14:59+00:00",
            "start_utc": "2022-06-04T13:14:59+00:00"
          }
        },
        "variants": {
          "runs": 10,
          "spread": 0,
          "usable": 10,
          "agreement": 1
        },
        "artifacts": [
          {
            "url": "/v1/checks/SWO9cpoo8wx3/artifacts/weathercheck/weather_window.json?exp=1794036174&sig=[redacted]",
            "kind": "plot_json"
          }
        ],
        "applicable": true,
        "confidence": 0.44999999999999996,
        "duration_ms": 4744,
        "user_adjusted": false
      }
    }
  },
  "overall": {
    "text": "The result is Consistent, but only weakly: SynthScan and WeatherCheck fit the claim, though none of them is a strong test on its own. No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion. Consistent means the measurements fit the claimed time and place, though these checks cannot rule out a staged scene or edits that they do not measure.",
    "weak": true,
    "reasons": [
      "SynthScan (Consistent, 65% confidence): No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion.",
      "WeatherCheck (Consistent, 45% confidence): What the photo shows (a clear sky, hard shadows and daylight) is consistent with the weather record for 2022-06-04 14:14 UTC: 0% cloud cover and 0.0 mm of rain in that hour."
    ],
    "verdict": "verified",
    "headline": "The evidence is weakly consistent with this claim.",
    "confidence": 0.598,
    "text_source": "template:guard_rejected",
    "checks_counted": [
      "synthscan",
      "weathercheck"
    ]
  },
  "error": null,
  "claim": {
    "lat": 39.479653,
    "lon": -86.058639,
    "label": "Franklin, Indiana, United States",
    "tz_name": "America/Indiana/Indianapolis",
    "utc_end": "2022-06-04T15:14:59+00:00",
    "local_iso": "2022-06-04T10:14:59-04:00",
    "time_mode": "plus_minus_1h",
    "utc_start": "2022-06-04T13:14:59+00:00",
    "place_mode": "exact",
    "utc_center": "2022-06-04T14:14:59+00:00",
    "time_source": "claim",
    "country_code": "US",
    "place_source": "claim"
  },
  "media": {
    "fps": null,
    "kind": "image",
    "mime": "image/jpeg",
    "tags": {},
    "width": 4032,
    "height": 3024,
    "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
    "bitrate": null,
    "has_audio": false,
    "has_video": false,
    "duration_s": null,
    "size_bytes": 6675621,
    "audio_codec": null,
    "video_codec": null,
    "audio_sample_rate": null
  },
  "shared": false,
  "share_id": null,
  "share_original": false,
  "sample_id": null,
  "created_at": "2026-11-06T07:22:31.400198+00:00",
  "started_at": "2026-11-06T07:22:32.960184+00:00",
  "finished_at": "2026-11-06T07:22:50.448949+00:00",
  "file_deleted_at": null,
  "filename": "real-photo.jpg"
}

What the verdicts mean

verified ConsistentThe evidence matches the claim. It does not mean the file is genuine.
contradicted ContradictedThe evidence conflicts with the claim.
inconclusive InconclusiveNot enough signal to say. This is a good and honest answer, and it never counts as evidence.
not_applicable Not applicableThis check cannot run on this file, and the reason is given.

The overall verdict adds weak, which is true when only weak passes support it, along with reasons, text and the checks it counted.

synthscanSynthScanWas this made by an AI generator?
patternscanPatternScanDoes it keep a generator's size or file name?
shadowproofShadowProofDo the shadows match the sun?
weathercheckWeatherCheckWas the sky like that?
pulsecheckPulseCheckIs there a pulse in the face?
eyemirrorEyeMirrorDo both eyes reflect the same light?
humprintHumPrintDoes the power-grid hum hold together?
provenanceProvenanceWhat does the file say about itself?

Errors and rate limits

Every error has the same shape: error is a plain sentence you can show to a person, code is stable for your program, and request_id is what to quote when you ask for help.

Rate limits

With a key, the free tier is 50 checks a month, counted per key, per calendar month in UTC. Without a key, it is 10 checks a day per address. Files can be up to 50 MB and 5 minutes. These numbers come from GET /v1/config.

Over a limit, you get a 429 with a Retry-After header in seconds. The body repeats it as retry_after_s, and a quota adds resets_at, limit and scope. Wait that long before trying again. A rejected upload does not use any of your allowance, and a successful start returns X-RateLimit-Remaining.

Real response429Captured 31 October 2026
POST /v1/auth/login, too many times
Retry-After: 39
{
  "error": "Too many sign-in attempts for this account in a short time. Please wait 39 seconds and try again.",
  "code": "rate_limited",
  "retry_after_s": 39,
  "scope": "burst",
  "request_id": "578ec318259b"
}
Real response404Captured 6 November 2026
GET /v1/checks/nosuchcheck
{
  "error": "No such check, or you do not have access to it.",
  "code": "not_found",
  "request_id": "3ec51c5b081d"
}
Real response422Captured 6 November 2026
POST /v1/checks with neither a file nor a url
{
  "error": "Send a file, or a url to download one.",
  "code": "invalid_request",
  "fields": [
    {
      "field": "file",
      "message": "Send a file, or a url."
    }
  ],
  "request_id": "07a906bfe1fa"
}
Real response401Captured 6 November 2026
GET /v1/usage with a key that does not exist
{
  "error": "That API key is not valid.",
  "code": "unauthorized",
  "request_id": "e0d6122808cc"
}
Real response415Captured 6 November 2026
POST /v1/checks with a text file
{
  "error": "Unsupported file type. Use a photo (JPEG, PNG, WebP, HEIC), video (MP4, MOV, WebM) or audio file.",
  "code": "unsupported_media",
  "request_id": "dfe18c1a09c0"
}
StatuscodeWhen
400bad_requestThe request could not be understood.
401unauthorizedA credential was sent but is not valid, or signing in is needed.
403forbiddenNot allowed, such as an expired link or a key trying to manage keys.
404not_foundNo such item, or you do not have access to it.
409conflictNot possible yet, such as sharing a check that is still running.
413file_too_largeThe file is over the limit.
415unsupported_mediaNot a supported photo, video or audio file.
422invalid_requestAn input is missing or wrong. fields says which.
429rate_limitedToo many requests, or the quota is used up. See Retry-After.

Webhooks

Pass webhook_url when you start a check. When it finishes, the engine sends a POST with the finished check. The body has id, type (check.completed or check.failed), created and data, which is the same shape as reading the check.

The 202 response includes webhook_secret. With an API key it is the same for every check on that key, and it also comes back when you create the key.

Each delivery carries TruthKit-Signature: t=<unix time>,v1=<hex>, where v1 is the HMAC-SHA256 of the time, a dot and the raw body. Check it against the raw bytes, before any JSON parsing.

Answer with a 2xx quickly. A failed delivery is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours and 24 hours, then given up. A redirect counts as a failure. The same id can arrive twice, so ignore repeats.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/checks" \
  -H "Authorization: Bearer $TRUTHKIT_API_KEY" \
  -F 'file=@photo.jpg' \
  -F 'claim={"place":{"mode":"city","label":"Sharjah, UAE","lat":25.35,"lon":55.42},"time":{"mode":"exact","iso":"2026-10-12T14:00:00","tz":"auto"}}' \
  -F 'webhook_url=https://example.com/hooks/truthkit'
import hashlib, hmac, time

def verify_truthkit(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:
    """secret: the webhook_secret from the 202 response. header: the TruthKit-Signature header. raw_body: the bytes as received."""
    parts = [p.split("=", 1) for p in header.split(",")]
    timestamp = next(v for k, v in parts if k == "t")
    if abs(time.time() - int(timestamp)) > tolerance:
        return False  # too old: a replay
    expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, v) for k, v in parts if k == "v1")

Signed reports

When a check finishes, the engine writes a report holding the file's SHA-256, every check's evidence and any adjustments by a person, and signs it with Ed25519. Anyone can verify it later, without trusting this website.

{
  "payload": { ... },
  "signature": { "alg": "Ed25519", "kid": "...", "canonicalization": "RFC8785", "value": "<base64url>" }
}

Get a report

GET /v1/reports/{id}

A check's id with its token returns the full report. A share id returns the public report, with the place rounded to the city. Add format=png for the verdict card.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/reports/$CHECK_ID" \
  -H "X-TruthKit-Job-Token: $CHECK_TOKEN"
Real response200Captured 6 November 2026
GET /v1/reports/{id}
{
  "payload": {
    "card": {
      "width": 1200,
      "height": 630,
      "sha256": "d11f527e9acc6dbd9b8d294970428d47960246df75959ebec63eb4098aa7930b"
    },
    "file": {
      "kind": "image",
      "mime": "image/jpeg",
      "name": "real-photo.jpg",
      "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
      "size_bytes": 6675621
    },
    "claim": {
      "lat": 39.479653,
      "lon": -86.058639,
      "label": "Franklin, Indiana, United States",
      "tz_name": "America/Indiana/Indianapolis",
      "utc_end": "2022-06-04T15:14:59+00:00",
      "local_iso": "2022-06-04T10:14:59-04:00",
      "time_mode": "plus_minus_1h",
      "utc_start": "2022-06-04T13:14:59+00:00",
      "place_mode": "exact",
      "utc_center": "2022-06-04T14:14:59+00:00",
      "time_source": "claim",
      "country_code": "US",
      "place_source": "claim"
    },
    "checks": {
      "humprint": {
        "check": "humprint",
        "summary": "HumPrint reads mains hum in sound, and a photo has none.",
        "verdict": "not_applicable",
        "version": "humprint@1.0.0",
        "evidence": {},
        "variants": {
          "runs": 0,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": false,
        "confidence": 0,
        "duration_ms": 0,
        "user_adjusted": false
      },
      "eyemirror": {
        "check": "eyemirror",
        "summary": "No face was found in this file, so there are no eyes to compare.",
        "verdict": "not_applicable",
        "version": "eyemirror@1.0.0",
        "evidence": {},
        "variants": {
          "runs": 0,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": false,
        "confidence": 0,
        "duration_ms": 752,
        "user_adjusted": false
      },
      "synthscan": {
        "check": "synthscan",
        "summary": "No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion.",
        "verdict": "verified",
        "version": "synthscan@1.3.0",
        "evidence": {
          "p_ai": 0.058,
          "runs": [
            {
              "id": "swinv2-full",
              "note": "2% AI over the picture.",
              "unit": "% AI",
              "label": "Detector, whole frame",
              "value": 2,
              "verdict": "verified",
              "confidence": 0.96
            },
            {
              "id": "swinv2-centre",
              "note": "2% AI over the picture.",
              "unit": "% AI",
              "label": "Detector, centre",
              "value": 2,
              "verdict": "verified",
              "confidence": 0.96
            },
            {
              "id": "swinv2-tiles",
              "note": "2% AI over 6 tiles.",
              "unit": "% AI",
              "label": "Detector, fine texture",
              "value": 2,
              "verdict": "verified",
              "confidence": 0.96
            },
            {
              "id": "commfor-full",
              "note": "2% AI over the picture.",
              "unit": "% AI",
              "label": "Community Forensics, whole frame",
              "value": 2,
              "verdict": "verified",
              "confidence": 0.96
            },
            {
              "id": "commfor-tiles",
              "note": "3% AI over 6 tiles.",
              "unit": "% AI",
              "label": "Community Forensics, fine texture",
              "value": 2.8,
              "verdict": "verified",
              "confidence": 0.944
            },
            {
              "id": "photographer",
              "note": "Natural depth of field and lens distortion; Realistic high noon harsh shadows and sensor dynamic range; Genuine photographic grain and micro-contrast",
              "unit": "% AI",
              "label": "Photographer",
              "value": 5,
              "verdict": "verified",
              "confidence": 0.9
            },
            {
              "id": "physicist",
              "note": "Consistent shadows cast by all people and the pole; Accurate perspective lines on the ground and buildings; Correct handling of bright sunlight and overhead string lights",
              "unit": "% AI",
              "label": "Physicist",
              "value": 5,
              "verdict": "verified",
              "confidence": 0.9
            },
            {
              "id": "anatomist",
              "note": "Normal human anatomy and limb proportions; Correct finger counts and hand structures; Natural posture and weight distribution of walking pedestrians",
              "unit": "% AI",
              "label": "Anatomist",
              "value": 5,
              "verdict": "verified",
              "confidence": 0.9
            },
            {
              "id": "detail",
              "note": "Legible text on the green trash bin reading Ray's; Coherent lettering on the trailer signage; Consistent patterns on the string lights and tents",
              "unit": "% AI",
              "label": "Detail inspector",
              "value": 10,
              "verdict": "verified",
              "confidence": 0.8
            },
            {
              "id": "generalist",
              "note": "Natural clutter and imperfect crowd arrangement of a real market; Normal lighting and surface textures without plastic sheen; Authentic everyday snapshot aesthetics",
              "unit": "% AI",
              "label": "Forensic generalist",
              "value": 5,
              "verdict": "verified",
              "confidence": 0.9
            }
          ],
          "frames": 1,
          "reviews": [
            {
              "cues": [
                "Natural depth of field and lens distortion",
                "Realistic high noon harsh shadows and sensor dynamic range",
                "Genuine photographic grain and micro-contrast"
              ],
              "p_ai": 0.05,
              "label": "Photographer"
            },
            {
              "cues": [
                "Consistent shadows cast by all people and the pole",
                "Accurate perspective lines on the ground and buildings",
                "Correct handling of bright sunlight and overhead string lights"
              ],
              "p_ai": 0.05,
              "label": "Physicist"
            },
            {
              "cues": [
                "Normal human anatomy and limb proportions",
                "Correct finger counts and hand structures",
                "Natural posture and weight distribution of walking pedestrians"
              ],
              "p_ai": 0.05,
              "label": "Anatomist"
            },
            {
              "cues": [
                "Legible text on the green trash bin reading Ray's",
                "Coherent lettering on the trailer signage",
                "Consistent patterns on the string lights and tents"
              ],
              "p_ai": 0.1,
              "label": "Detail inspector"
            },
            {
              "cues": [
                "Natural clutter and imperfect crowd arrangement of a real market",
                "Normal lighting and surface textures without plastic sheen",
                "Authentic everyday snapshot aesthetics"
              ],
              "p_ai": 0.05,
              "label": "Forensic generalist"
            }
          ],
          "signals": [],
          "estimate": {
            "unit": "% AI",
            "median": 5.8,
            "spread": null
          },
          "detectors": {
            "swinv2": "haywoodsloan/ai-image-detector-deploy",
            "commfor": "OwensLab/commfor-model-384"
          },
          "lead_cues": [
            "Natural depth of field and lens distortion",
            "Realistic high noon harsh shadows and sensor dynamic range",
            "Genuine photographic grain and micro-contrast"
          ],
          "tiles_p_ai": 0.02,
          "commfor_p_ai": 0.02,
          "persona_p_ai": 0.058,
          "detector_p_ai": 0.02,
          "frame_times_s": [
            0
          ],
          "reviewer_source": "gemini:gemini-3.5-flash-lite",
          "families_disagree": false,
          "commfor_tiles_p_ai": 0.028
        },
        "variants": {
          "runs": 10,
          "spread": 3,
          "usable": 10,
          "agreement": 1
        },
        "artifacts": [],
        "applicable": true,
        "confidence": 0.654,
        "duration_ms": 10587,
        "user_adjusted": false
      },
      "provenance": {
        "check": "provenance",
        "summary": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.",
        "verdict": "inconclusive",
        "version": "provenance@1.0.0",
        "evidence": {
          "c2pa": {
            "tool": null,
            "valid": false,
            "issues": [],
            "signer": null,
            "actions": [],
            "present": false,
            "trusted": false,
            "signed_at": null,
            "unreadable": null,
            "signer_label": null,
            "validation_codes": [],
            "validation_status": null,
            "digital_source_type": null
          },
          "exif": {
            "lat": 39.479653,
            "lon": -86.058639,
            "make": "Apple",
            "model": "iPhone 12 Pro Max",
            "software": "15.5",
            "datetime_original": "2022-06-04T10:14:59.983-04:00"
          },
          "runs": [
            {
              "id": "signature",
              "note": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.",
              "unit": null,
              "label": "Credentials and metadata",
              "value": null,
              "verdict": "inconclusive",
              "confidence": 0
            }
          ],
          "reason": "No content credentials found, which proves nothing either way. Most platforms and messaging apps strip them.",
          "synthid": {
            "url": "https://synthid.com",
            "note": "SynthID is Google's invisible watermark. It has no public API, so TruthKit does not scan for it. You can check a file yourself at synthid.com."
          },
          "ai_markers": [],
          "signed_and_valid": false
        },
        "variants": {
          "runs": 1,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": true,
        "confidence": 0,
        "duration_ms": 13,
        "user_adjusted": false
      },
      "pulsecheck": {
        "check": "pulsecheck",
        "summary": "PulseCheck measures colour changes over time, so it needs a video. A single photo has none.",
        "verdict": "not_applicable",
        "version": "pulsecheck@1.0.0",
        "evidence": {},
        "variants": {
          "runs": 0,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": false,
        "confidence": 0,
        "duration_ms": 0,
        "user_adjusted": false
      },
      "patternscan": {
        "check": "patternscan",
        "summary": "PatternScan found none of the traces it looks for. That does not show the picture is real: cropping, resizing or renaming removes them.",
        "verdict": "inconclusive",
        "version": "patternscan@1.0.0",
        "evidence": {
          "runs": [
            {
              "id": "size",
              "note": "4032 x 3024 is not a size generators output.",
              "unit": null,
              "label": "Output size",
              "value": null,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "filename",
              "note": "The file name is not a generator's default name.",
              "unit": null,
              "label": "File name",
              "value": null,
              "verdict": "inconclusive",
              "confidence": 0
            }
          ],
          "width": 4032,
          "camera": "Apple iPhone 12 Pro Max",
          "height": 3024,
          "reason": "no_trace",
          "signals": [],
          "generator_size": false,
          "filename_generator": null
        },
        "variants": {
          "runs": 2,
          "spread": null,
          "usable": 0,
          "agreement": 0
        },
        "artifacts": [],
        "applicable": true,
        "confidence": 0,
        "duration_ms": 9,
        "user_adjusted": false
      },
      "shadowproof": {
        "check": "shadowproof",
        "summary": "The 10 runs and the full analysis do not agree, so the check gives no answer.",
        "verdict": "inconclusive",
        "version": "shadowproof@1.0.0",
        "evidence": {
          "runs": [
            {
              "id": "r1",
              "note": "The measured sun elevation is within 8.5 degrees of the claim.",
              "unit": "deg",
              "label": "Without Person 1, jitter x1",
              "value": 31.631751015051567,
              "verdict": "verified",
              "confidence": 0.165
            },
            {
              "id": "r2",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 2, jitter x0.75",
              "value": 19.51836558084403,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r3",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 3, jitter x1.25",
              "value": 20.586148238799428,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r4",
              "note": "The measured sun elevation is within 7.8 degrees of the claim.",
              "unit": "deg",
              "label": "Without Person 1, jitter x1",
              "value": 32.3741008019022,
              "verdict": "verified",
              "confidence": 0.195
            },
            {
              "id": "r5",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 2, jitter x0.75",
              "value": 19.317814021779046,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r6",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 3, jitter x1.25",
              "value": 20.202704985735988,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r7",
              "note": "The measured sun elevation is within 7.7 degrees of the claim.",
              "unit": "deg",
              "label": "Without Person 1, jitter x1",
              "value": 32.175667091905794,
              "verdict": "verified",
              "confidence": 0.205
            },
            {
              "id": "r8",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 2, jitter x0.75",
              "value": 19.35563487919962,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r9",
              "note": "The measured sun does not match the claim, but only automatic pairs support that. Confirm the shadows on the shadow screen before relying on it.",
              "unit": "deg",
              "label": "Without Person 3, jitter x1.25",
              "value": 20.773654290207592,
              "verdict": "inconclusive",
              "confidence": 0
            },
            {
              "id": "r10",
              "note": "The measured sun elevation is within 8.0 degrees of the claim.",
              "unit": "deg",
              "label": "Without Person 1, jitter x1",
              "value": 31.73684883770895,
              "verdict": "verified",
              "confidence": 0.19
            }
          ],
          "image": {
            "width": 1600,
            "height": 1200
          },
          "pairs": [
            {
              "id": "a1",
              "tip": [
                0.43312,
                0.80296
              ],
              "top": [
                0.58688,
                0.48514
              ],
              "label": "Person 1",
              "reason": null,
              "source": "auto",
              "usable": true,
              "contact": [
                0.58875,
                0.64083
              ],
              "quality": 1,
              "sigma_deg": 1.6,
              "caster_class": "person",
              "elevation_deg": 17.94
            },
            {
              "id": "a2",
              "tip": [
                0.75949,
                0.75486
              ],
              "top": [
                0.7175,
                0.51417
              ],
              "label": "Person 2",
              "reason": null,
              "source": "auto",
              "usable": true,
              "contact": [
                0.71562,
                0.68
              ],
              "quality": 1,
              "sigma_deg": 3.66,
              "caster_class": "person",
              "elevation_deg": 32.34
            },
            {
              "id": "a3",
              "tip": [
                0.43342,
                0.69252
              ],
              "top": [
                0.44656,
                0.5025
              ],
              "label": "Person 3",
              "reason": null,
              "source": "auto",
              "usable": true,
              "contact": [
                0.44312,
                0.6425
              ],
              "quality": 1,
              "sigma_deg": 4.81,
              "caster_class": "person",
              "elevation_deg": 30.66
            },
            {
              "id": "a4",
              "tip": [
                0.00005,
                0.99292
              ],
              "top": [
                0.25562,
                0.4925
              ],
              "label": "Person 4",
              "reason": "The shadow runs into the image edge, so its end cannot be found.",
              "source": "auto",
              "usable": false,
              "contact": [
                0.29938,
                0.7625
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            },
            {
              "id": "a5",
              "tip": [
                0.30125,
                0.70583
              ],
              "top": [
                0.32125,
                0.47667
              ],
              "label": "Person 5",
              "reason": "No shadow starts at the base of the object.",
              "source": "auto",
              "usable": false,
              "contact": [
                0.30125,
                0.70583
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            },
            {
              "id": "a6",
              "tip": [
                0.08305,
                0.66133
              ],
              "top": [
                0.04688,
                0.50083
              ],
              "label": "Person 6",
              "reason": "The shadow is not bounded on both sides.",
              "source": "auto",
              "usable": false,
              "contact": [
                0.03812,
                0.63833
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            },
            {
              "id": "a7",
              "tip": [
                0.96906,
                0.63232
              ],
              "top": [
                0.905,
                0.50667
              ],
              "label": "Person 7",
              "reason": "The shadow is too faint (contrast 6 against 10 needed).",
              "source": "auto",
              "usable": false,
              "contact": [
                0.89812,
                0.6025
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            },
            {
              "id": "a8",
              "tip": [
                0.93254,
                0.64704
              ],
              "top": [
                0.9825,
                0.51917
              ],
              "label": "Person 8",
              "reason": "The shadow is not bounded on both sides.",
              "source": "auto",
              "usable": false,
              "contact": [
                0.98562,
                0.63583
              ],
              "quality": 0,
              "sigma_deg": null,
              "caster_class": "person",
              "elevation_deg": null
            }
          ],
          "camera": {
            "focal_px": 924.5,
            "reliable": true,
            "reported": {
              "focal_px": 0,
              "roll_deg": 1.218,
              "pitch_deg": 2.084
            },
            "roll_deg": 0.31,
            "pitch_deg": 2.02,
            "focal_note": "EXIF FocalLengthIn35mmFilm 20 mm, 925 px here (GeoCalib alone estimated 1015 px).",
            "focal_source": "exif",
            "focal_uncertainty_px": 37,
            "gravity_uncertainty_deg": 2.08
          },
          "ground": {
            "flat": true,
            "note": "Ground around the 3 pairs used is consistent with a flat plane."
          },
          "reason": "The 10 runs and the full analysis do not agree, so the check gives no answer.",
          "daylight": {
            "note": "mean luma 130, 95th percentile 201",
            "looks_daylit": true
          },
          "estimate": {
            "unit": "deg",
            "median": 20.67990126450351,
            "spread": 12.021123949977586
          },
          "delta_deg": -10.03,
          "sigma_deg": 2.2,
          "consistency": {
            "runs": 10,
            "slack": 0.194511,
            "votes": {
              "verified": 0,
              "contradicted": 0,
              "inconclusive": 10
            },
            "reason": "The shadow lines do not meet cleanly, but the 3D light directions agree within measurement noise, so this is not enough to say the shadows disagree.",
            "verdict": "inconclusive",
            "misfit_deg": 30.46,
            "scatter_deg": 10.58,
            "pairs_compared": 3,
            "slack_inflated": 0.171325,
            "noise_scatter_deg": 4.1
          },
          "usable_pairs": 3,
          "user_adjusted": false,
          "candidate_dates": [],
          "candidate_times": [
            "2022-06-04T12:22:21.442786+00:00",
            "2022-06-04T23:03:04.506349+00:00"
          ],
          "detection_notes": [
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped.",
            "A vertical edge continues beyond where its top could be found, so it was skipped."
          ],
          "allowed_band_deg": 5,
          "sun_below_horizon": false,
          "candidate_time_ranges": [
            {
              "to": "2022-06-04T12:33:59.092995+00:00",
              "from": "2022-06-04T12:10:39.185098+00:00"
            },
            {
              "to": "2022-06-04T23:14:47.288106+00:00",
              "from": "2022-06-04T22:51:26.370020+00:00"
            }
          ],
          "expected_elevation_deg": 31.54,
          "measured_elevation_deg": 21.51
        },
        "variants": {
          "runs": 10,
          "spread": 12.021123949977586,
          "usable": 4,
          "agreement": 1
        },
        "artifacts": [
          {
            "url": "/v1/checks/SWO9cpoo8wx3/artifacts/shadowproof/overlay.png",
            "kind": "overlay_png"
          },
          {
            "url": "/v1/checks/SWO9cpoo8wx3/artifacts/shadowproof/preview.png",
            "kind": "overlay_png"
          },
          {
            "url": "/v1/checks/SWO9cpoo8wx3/artifacts/shadowproof/sun_curve.json",
            "kind": "plot_json"
          }
        ],
        "applicable": true,
        "confidence": 0,
        "duration_ms": 5600,
        "user_adjusted": false
      },
      "weathercheck": {
        "check": "weathercheck",
        "summary": "What the photo shows (a clear sky, hard shadows and daylight) is consistent with the weather record for 2022-06-04 14:14 UTC: 0% cloud cover and 0.0 mm of rain in that hour.",
        "verdict": "verified",
        "version": "weathercheck@1.0.0",
        "evidence": {
          "rule": "consistent",
          "runs": [
            {
              "id": "hour_+0",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed hour",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "hour_-30",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed time -30 min",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "hour_+30",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed time +30 min",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "hour_-60",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed time -60 min",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "hour_+60",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Claimed time +60 min",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "cell_ne",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "North-east grid cell",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "cell_sw",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "South-west grid cell",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "read_1",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Vision read 1",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "read_2",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Vision read 2",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            },
            {
              "id": "read_3",
              "note": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
              "unit": "% cloud cover",
              "label": "Vision read 3",
              "value": 0,
              "verdict": "verified",
              "confidence": 0.45
            }
          ],
          "scene": {
            "daylight": true,
            "sky_state": "clear",
            "confidence": 1,
            "wet_ground": false,
            "sky_visible": true,
            "hard_shadows": true
          },
          "reason": "The daylight and clear sky and hard shadows in the photo are consistent with the weather record.",
          "weather": {
            "model": "best_match",
            "is_day": 1,
            "source": "archive-api.open-meteo.com",
            "grid_lat": 39.5,
            "grid_lon": -86,
            "cloud_cover_pct": 0,
            "precipitation_mm": 0
          },
          "estimate": {
            "unit": "% cloud cover",
            "median": 0,
            "spread": 0
          },
          "grid_note": "Historical weather comes from a coarse grid of about 10 km (reanalysis), so a single hour can differ from the real sky above one street. This check is weighted lower than ShadowProof for that reason.",
          "neighbours": [
            {
              "cell": "ne",
              "label": "the cell to the north-east",
              "is_day": 1,
              "grid_lat": 39.6,
              "grid_lon": -85.9,
              "cloud_cover_pct": 0
            },
            {
              "cell": "sw",
              "label": "the cell to the south-west",
              "is_day": 1,
              "grid_lat": 39.3,
              "grid_lon": -86.2,
              "cloud_cover_pct": 0
            }
          ],
          "attribution": {
            "url": "https://open-meteo.com/",
            "name": "Open-Meteo.com",
            "license": "CC BY 4.0"
          },
          "scene_reads": [
            {
              "daylight": true,
              "sky_state": "clear",
              "confidence": 1,
              "wet_ground": false,
              "sky_visible": true,
              "hard_shadows": true
            },
            {
              "daylight": true,
              "sky_state": "clear",
              "confidence": 1,
              "wet_ground": false,
              "sky_visible": true,
              "hard_shadows": true
            },
            {
              "daylight": true,
              "sky_state": "clear",
              "confidence": 1,
              "wet_ground": false,
              "sky_visible": true,
              "hard_shadows": true
            }
          ],
          "vision_notes": [],
          "vision_source": "gemini:gemini-3.5-flash-lite",
          "claimed_window": {
            "hours": [
              "2022-06-04T13:00:00+00:00",
              "2022-06-04T14:00:00+00:00",
              "2022-06-04T15:00:00+00:00",
              "2022-06-04T16:00:00+00:00"
            ],
            "end_utc": "2022-06-04T15:14:59+00:00",
            "start_utc": "2022-06-04T13:14:59+00:00"
          }
        },
        "variants": {
          "runs": 10,
          "spread": 0,
          "usable": 10,
          "agreement": 1
        },
        "artifacts": [
          {
            "url": "/v1/checks/SWO9cpoo8wx3/artifacts/weathercheck/weather_window.json",
            "kind": "plot_json"
          }
        ],
        "applicable": true,
        "confidence": 0.44999999999999996,
        "duration_ms": 4744,
        "user_adjusted": false
      }
    },
    "engine": {
      "checks": {
        "humprint": "humprint@1.0.0",
        "eyemirror": "eyemirror@1.0.0",
        "synthscan": "synthscan@1.3.0",
        "provenance": "provenance@1.0.0",
        "pulsecheck": "pulsecheck@1.0.0",
        "patternscan": "patternscan@1.0.0",
        "shadowproof": "shadowproof@1.0.0",
        "weathercheck": "weathercheck@1.0.0"
      },
      "version": "1.0.0"
    },
    "issuer": {
      "name": "TruthKit",
      "keys_url": "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/.well-known/truthkit-keys.json"
    },
    "job_id": "SWO9cpoo8wx3",
    "schema": "truthkit.report/1",
    "overall": {
      "text": "The result is Consistent, but only weakly: SynthScan and WeatherCheck fit the claim, though none of them is a strong test on its own. No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion. Consistent means the measurements fit the claimed time and place, though these checks cannot rule out a staged scene or edits that they do not measure.",
      "weak": true,
      "reasons": [
        "SynthScan (Consistent, 65% confidence): No signs of AI generation were found in this picture. Most telling: Natural depth of field and lens distortion.",
        "WeatherCheck (Consistent, 45% confidence): What the photo shows (a clear sky, hard shadows and daylight) is consistent with the weather record for 2022-06-04 14:14 UTC: 0% cloud cover and 0.0 mm of rain in that hour."
      ],
      "verdict": "verified",
      "headline": "The evidence is weakly consistent with this claim.",
      "confidence": 0.598,
      "text_source": "template:guard_rejected",
      "checks_counted": [
        "synthscan",
        "weathercheck"
      ]
    },
    "variant": "full",
    "revision": 1,
    "report_id": "73661bcf2307ccde",
    "created_at": "2026-11-06T07:22:49+00:00",
    "adjustments": [],
    "c2pa_signed": true,
    "attributions": [
      {
        "url": "https://open-meteo.com/",
        "name": "Weather data by Open-Meteo.com",
        "licence": "CC BY 4.0",
        "licence_url": "https://creativecommons.org/licenses/by/4.0/"
      }
    ],
    "share_original": false
  },
  "signature": {
    "alg": "Ed25519",
    "kid": "6c35b42799f2b48f",
    "value": "-eRrcQBsioxFdokmrwYFdcfxr7-cg-u4_ET9I40YjAxaT0H9Q32XHy-mLtKTVJedgvMsJV7Mw4M6fzBDRbQRCw",
    "canonicalization": "RFC8785"
  }
}

Ask the server

POST /v1/reports/verify

POST the report back. It always answers with valid, kid, reason, sha256, report_id and created_at, whatever you send.

Or drop a report on the verify page

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/reports/$CHECK_ID" -H "X-TruthKit-Job-Token: $CHECK_TOKEN" -o report.json

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/reports/verify" \
  -H "Content-Type: application/json" \
  --data-binary @report.json
Real response200Captured 6 November 2026
POST /v1/reports/verify with that report
{
  "valid": true,
  "kid": "6c35b42799f2b48f",
  "reason": "The signature matches. This report is exactly as TruthKit signed it.",
  "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
  "report_id": "73661bcf2307ccde",
  "created_at": "2026-11-06T07:22:49+00:00"
}
Real response200Captured 6 November 2026
POST /v1/reports/verify after changing one field
{
  "valid": false,
  "kid": "6c35b42799f2b48f",
  "reason": "The signature does not match this payload. The report was changed after signing, or TruthKit did not sign it.",
  "sha256": "a19cef68ca8657383eaa3e48d4e1d0b32823a3e016576063e99156fda18864e0",
  "report_id": "73661bcf2307ccde",
  "created_at": "2026-11-06T07:22:49+00:00"
}

Check it yourself

GET /.well-known/truthkit-keys.json

Fetch the public keys, canonicalize the payload with RFC 8785, and verify the signature. Old keys stay listed, so old reports stay verifiable.

# Signature checks need a JSON canonicalizer, so use Python or JavaScript for this one.
curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/.well-known/truthkit-keys.json"
Real response200Captured 6 November 2026
GET /.well-known/truthkit-keys.json
{
  "keys": [
    {
      "kty": "OKP",
      "crv": "Ed25519",
      "x": "Fatj7gUA4ZqcszUxWHwheUQILfh_imcXjAH7f7ZgRN8",
      "kid": "6c35b42799f2b48f",
      "alg": "EdDSA",
      "use": "sig"
    }
  ]
}

Account endpoints

Create a key

POST /v1/keys

Needs a signed-in session. The full key comes back once. Up to 20 active keys.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/keys" \
  -H "Authorization: Bearer $TRUTHKIT_SESSION" \
  -H "Content-Type: application/json" \
  -d '{"name":"Claims team, production"}'
Real response201Captured 31 October 2026
POST /v1/keys
{
  "id": "ea2ac8ed-9bb3-452d-a6d6-bf9d0ff1466d",
  "name": "Documentation capture",
  "key": "tk_live_inhS[redacted on this page]",
  "prefix": "tk_live_inhS",
  "monthly_quota": 50,
  "webhook_secret": "whsec_9c3a1a[redacted on this page]",
  "created_at": "2026-10-31T07:11:08.905277+00:00"
}

List your keys

GET /v1/keys

Name, prefix, this month's use and when each was last used. The key itself is never shown again.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/keys" \
  -H "Authorization: Bearer $TRUTHKIT_SESSION"
Real response200Captured 31 October 2026
GET /v1/keys
{
  "keys": [
    {
      "id": "ea2ac8ed-9bb3-452d-a6d6-bf9d0ff1466d",
      "name": "Documentation capture",
      "prefix": "tk_live_inhS",
      "monthly_quota": 50,
      "used_this_month": 1,
      "created_at": "2026-10-31T07:11:08.905277+00:00",
      "last_used_at": "2026-10-31T07:12:11.840330+00:00",
      "revoked_at": null
    }
  ]
}

Revoke a key

DELETE /v1/keys/{id}

It stops working immediately. Checks already made with it are kept. Revoking twice is fine.

curl -X DELETE "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/keys/$KEY_ID" \
  -H "Authorization: Bearer $TRUTHKIT_SESSION"
Real response200Captured 31 October 2026
DELETE /v1/keys/{id}
{
  "id": "ea2ac8ed-9bb3-452d-a6d6-bf9d0ff1466d",
  "name": "Documentation capture",
  "prefix": "tk_live_inhS",
  "monthly_quota": 50,
  "used_this_month": 1,
  "created_at": "2026-10-31T07:11:08.905277+00:00",
  "last_used_at": "2026-10-31T07:12:11.840330+00:00",
  "revoked_at": "2026-10-31T07:12:11.997971+00:00"
}

Usage

GET /v1/usage

This month's count, what is left, when it resets and one count per day. With a key it is that key. With a session it is all your keys, with a breakdown. Pass period=2026-10 or key_id to narrow it.

curl "https://aadam-2012legend--truthkit-engine-web.us-east.modal.run/v1/usage" \
  -H "Authorization: Bearer $TRUTHKIT_API_KEY"
Real response200Captured 31 October 2026
GET /v1/usage
{
  "principal": "api_key",
  "period": "2026-10",
  "starts_at": "2026-10-01T00:00:00+00:00",
  "resets_at": "2026-11-01T00:00:00+00:00",
  "used": 1,
  "limit": 50,
  "remaining": 49,
  "daily": [
    {
      "date": "2026-10-01",
      "count": 0
    },
    {
      "date": "2026-10-02",
      "count": 0
    },
    {
      "date": "2026-10-03",
      "count": 0
    },
    {
      "date": "2026-10-04",
      "count": 0
    },
    {
      "date": "2026-10-05",
      "count": 0
    },
    {
      "date": "2026-10-06",
      "count": 0
    },
    {
      "date": "2026-10-07",
      "count": 0
    },
    {
      "date": "2026-10-08",
      "count": 0
    },
    {
      "date": "2026-10-09",
      "count": 0
    },
    {
      "date": "2026-10-10",
      "count": 0
    },
    {
      "date": "2026-10-11",
      "count": 0
    },
    {
      "date": "2026-10-12",
      "count": 0
    },
    {
      "date": "2026-10-13",
      "count": 0
    },
    {
      "date": "2026-10-14",
      "count": 0
    },
    {
      "date": "2026-10-15",
      "count": 0
    },
    {
      "date": "2026-10-16",
      "count": 0
    },
    {
      "date": "2026-10-17",
      "count": 0
    },
    {
      "date": "2026-10-18",
      "count": 0
    },
    {
      "date": "2026-10-19",
      "count": 0
    },
    {
      "date": "2026-10-20",
      "count": 0
    },
    {
      "date": "2026-10-21",
      "count": 0
    },
    {
      "date": "2026-10-22",
      "count": 0
    },
    {
      "date": "2026-10-23",
      "count": 0
    },
    {
      "date": "2026-10-24",
      "count": 0
    },
    {
      "date": "2026-10-25",
      "count": 0
    },
    {
      "date": "2026-10-26",
      "count": 0
    },
    {
      "date": "2026-10-27",
      "count": 0
    },
    {
      "date": "2026-10-28",
      "count": 0
    },
    {
      "date": "2026-10-29",
      "count": 0
    },
    {
      "date": "2026-10-30",
      "count": 0
    },
    {
      "date": "2026-10-31",
      "count": 1
    }
  ],
  "keys": null
}

Local sign-in

POST /v1/auth/signup, /v1/auth/login

Servers without Supabase sign people in themselves, through POST /v1/auth/signup, POST /v1/auth/login and GET /v1/auth/me. Check auth in /v1/config to see which mode a server uses. DELETE /v1/account deletes your checks, keys and the account.

Real response201Captured 31 October 2026
POST /v1/auth/signup
{
  "token": "eyJhbGciOiJI[redacted on this page]",
  "expires_at": "2026-11-30T07:11:07.758142+00:00",
  "user": {
    "id": "5517f355-31b1-4dfa-bf3d-c41e63b5800f",
    "email": "docs-capture-e19c13a9@example.test"
  }
}

SDK, CLI and MCP

If you would rather not write HTTP calls, packages/ in this repository holds three tools built on this API. All use the same engine and the same keys. A verdict says whether a file is consistent with the claimed time and place, never that it is real or fake, and inconclusive is a common and honest answer.

The hosted engine sleeps when idle, so the first request after a quiet spell can take about 30 seconds. The SDK waits for it.

Python SDK

pip install truthkit, for Python 3.10 or newer. The .word property gives the words this site uses. Add the verify extra for offline report verification.

from truthkit import TruthKit

tk = TruthKit()  # reads TRUTHKIT_API_KEY
result = tk.check("photo.jpg", place="Sharjah, UAE", time="2026-10-12T14:00")
print(result.word, "-", result.headline)

Command line

The SDK installs a truthkit command. Its exit code carries the verdict, so it fits a script.

truthkit check photo.jpg --place "Sharjah, UAE" --time 2026-10-12T14:00
# exit 0 consistent, 1 contradicted, 2 inconclusive, 3 error

MCP server

pip install truthkit-mcp gives Claude and other assistants three tools: check_file, get_check and verify_report. Their descriptions tell the assistant what a verdict does and does not mean. It only ever uploads media files, and TRUTHKIT_MCP_ALLOWED_DIRS can fence it to chosen folders.

{
  "mcpServers": {
    "truthkit": {
      "command": "truthkit-mcp",
      "env": { "TRUTHKIT_API_KEY": "tk_live_..." }
    }
  }
}

Full instructions are in packages/README.md, with one README per package.