unlob Docs
Browse documentation

Evidence changes: what appeared since, and what you lost

The evidence for an objective now, diffed against the evidence as of an earlier instant — plus whether the URLs you relied on are still there.

The question it answers

“What changed?” is the question a monitoring agent asks, and a scheduled search answers it badly: the same query returns the same links plus a few new ones, and the agent has to work out which are new, which are copies, and whether anything that mattered went away.

changes answers it as evidence. It builds the ground set for an objective twice — once over everything the index holds, once over only what had been crawled by since — and diffs the two. Then it answers known_urls the way why_not would.

curl -sG -H "x-api-key: $UNLOB_API_KEY" https://api.unlob.com/changes \
  --data-urlencode 'objective=acme zed acquisition' -d since=7d \
  --data-urlencode 'known_urls=https://example.com/a,https://example.org/b'

A call costs 10 credits, however many stories the two reconstructions expanded, and a call that runs and fails costs 1.

The request

ParameterMeaning
objectiveWhat you need evidence for. q is accepted as an alias.
sinceAn absolute unix timestamp, a bare number of seconds ago, or a duration ago: 1d, 12h, 2w. Must be in the past.
known_urlsComma list of URLs you relied on before.
claimExperimental: also report evidence that newly contradicts this claim.
token_budget, mode, vertical, lang, safeAs on ground.

The response

{
  "status": "changed",
  "since": 1757200000,
  "new_evidence": [ { "hit": { "…": "…" }, "source_role": "official", "origin_type": "original" } ],
  "origins_gained": [ { "id": "o:…", "kind": "original", "owner": "fda.gov" } ],
  "roles_gained": ["official"],
  "independent_origins": { "before": 1, "after": 2 },
  "contradictions_gained": [],
  "known_urls": [
    { "url": "https://example.com/a", "status": "present" },
    { "url": "https://example.org/b", "status": "removed", "reason": "superseded", "superseded_by": "…:0", "removed_at": 1757400000 }
  ],
  "coverage": { "…": "the receipt for now" },
  "coverage_before": { "…": "the receipt as of since" },
  "next_actions": [
    { "action": "get_document", "target": "…:0", "why": "https://example.org/b was superseded; this is its replacement" }
  ],
  "provenance": {
    "method": "deterministic",
    "experimental": ["evidence_changes"],
    "limitations": [
      "passages removed after `since` are not reconstructable, so the before-set and its counts are lower bounds",
      "as of `since` means crawled by then (fetched_at), not published by then",
      "origins are matched by id, which is stable while a story's cluster is; a re-clustered story can show as gained"
    ]
  }
}
FieldMeaning
statuschanged, unchanged, or partial when either reconstruction was incomplete.
new_evidenceEvidence items that did not exist as of since.
origins_gainedOrigins present now that were not present then.
roles_gainedSource roles present now that were missing then — an official source appearing is the case that matters most.
independent_originsBefore and after. A claim that gained its second independent origin shows here.
contradictions_gainedWith claim: evidence ids that contradict it now and did not then.
known_urlsEach URL: present, removed with the reason and its replacement, unknown, or error when the lookup failed.
coverage, coverage_beforeBoth receipts.

What it cannot know

The limitations are in the response because they are real. The as-of set is built from what the index holds today, filtered by crawl time, so passages removed after since are missing from both sides: the before-counts are lower bounds. “As of” means crawled by then, not published by then — a page published before since and crawled after it counts as new. And origins are matched by id, which is stable while a story’s cluster is; a story that was re-clustered in between can show as gained.

known_urls closes the first gap for the URLs you care about: whether each is still present, and if it was removed, why and what replaced it.

Using it in a loop

Keep the ids and URLs of the evidence you acted on. On the next pass, call changes with since set to the previous pass and known_urls set to those URLs. status: unchanged means nothing to do. roles_gained containing official or new_evidence with a primary item is the case worth re-reading, and next_actions points at it.

Next