AsOf

Dated, sourced facts about AI and tech companies: what was true on a day, and what changed since.

Hosted MCP Server

npx add-mcp 'https://asof.is/mcp'

Installs into Claude Code, Codex, Cursor and more

Documentation

AsOf API

One endpoint, two parameters that matter. as_of returns the state of an entity on a date. since returns every claim that changed after one. Every response includes sources and confidence. Open during the preview without a key, rate-limited per IP (an API key lifts the limit and registers webhooks: Keys and webhooks, below); pricing and usage terms are being finalised, and endpoints and limits may change (see /terms.md). Attribute AsOf when you publish. The same endpoints for machines: /openapi.json.

Entity state

GET /v1/entities[?kind=companies|people|products|models|regulators|other][&limit=200][&offset=0]

The record index, A–Z: summary (the sentence /record leads with), counts by kind and a page of entities. The whole list as markdown is /record.md.

GET /v1/entities/{slug}?as_of=YYYY-MM-DD[&known_by=YYYY-MM-DD][&since=YYYY-MM-DD][&group=1][&assertable_only=1]

Omit as_of for the full history, superseded claims included. since=YYYY-MM-DD keeps the claims that began, ended or were first recorded after that date; a claim dated in the future is not a change. assertable is true when the record asserts the claim: valid, or primary-sourced at confidence 0.7 or more; never flagged, retracted or marked for review. A state on a date is labelled for that date; every other row for its own dates, so a superseded CEO is assertable for the years he held the role (status and valid_to say whether it holds today). A row that is not assertable carries why_not, a code the response's legend explains. Append .md to the slug for the markdown twin; it takes as_of too. For an organization, person, product or regulator (not a model, not in a group answer), the response also carries summary: the one-sentence answer its page leads with and uses as its meta description; title: its page title, from the same facts (e.g. OpenAI: CEO Sam Altman, revenue $13.07B; null when the card lacks them); key_facts: the page's key-facts card (label, value, the source's words as recorded, note, claim); and faq: its questions and answers. The page, its .md twin and the MCP tool state them word for word (lib/record-answer.ts).

What was true, and what could be known

as_of is effective time: what was true on the day, whenever it was published. A period figure is dated at the period end, and the filing that reports it comes weeks later. known_by is knowledge time, strict: only claims a source on the record had published, or AsOf had recorded, by that day. A figure published later is listed under not_yet_published and the claim it replaced is returned with ended_later; a claim no source dates that AsOf recorded after that day is listed under publication_unknown, since the record cannot show it was known then. Neither list is in claims. known_by alone sets as_of to the same day; both answer "true on as_of, as known by known_by". JSON and MCP only.

What covers an entity

GET /v1/entities/google?as_of=2026-10-29 carries coverage: sources, holds, freshness, gaps and group.

Every /v1/entities answer carries coverage: the official feeds that record posts on the entity or speak for it, its SEC filing (CIK, ticker), Hugging Face authors and Wikidata item (sources); its public claims by predicate, each marked fact, event, list or identifier (holds); per ingest, the day it last added a claim there, the day one was last checked against its source and the day reverify.py last confirmed one against Wikidata (freshness); and its gaps, such as "financials: none; files with the SEC as alphabet". Only checked claims are public, so a fact that is missing is unknown, not absent. An answer with no claims says why in empty: the reason, the gaps and the records group would add. The MCP tool coverage(entity) returns the same block.

Company groups

GET /v1/entities/{slug}?group=1 · GET /v1/timelines/{slug}?group=1

group=1 answers for the entity's company group: the records linked by entities.parent_id, by an ownership claim the record asserts (parent, subsidiary, owned_by, owner_of, brand), or by an official feed that speaks for both (Google's blog speaks for Gemini, YouTube and Waymo; the Google DeepMind blog speaks for Google), and the products a developer claim attaches to them. A product never joins two makers and a person is never a member. Each row names the record it came from (entity); a fact two records hold (same predicate, value and start) is one row, from the record that best supports it, with the other copies in also_on; group.members says how each record joined. /v1/entities and /v1/timelines take it (JSON only), and the MCP tools entity_state and timeline take group: true.

What changed

GET /v1/claims?since=YYYY-MM-DD[&entity=slug][&kind=people|money|products|models|other][&limit=n][&offset=n][&assertable_only=1]

Every claim that became true, stopped being true, or was first recorded after a date, newest first. since defaults to yesterday; limit to 50, at most 500; offset + limit at most 900, past which cursor= pages the same feed with no cap (empty for the first page, then next_cursor). JSON only. A capped response says complete: false with next_offset and a remainder line; ask again with offset set to next_offset. assertable_only=1 keeps only rows the record asserts. /v1/entities/{slug} and /v1/timelines/{slug} return everything unless asked otherwise (a large record's history is over a megabyte): limit (1 to 1000) and offset page them, the answer adding returned and total. /v1/claims also pages by cursor, with no cap: cursor= (empty) is the first page, in the order the claims were recorded (first_seen_at, oldest first), and an answer with more carries next_cursor to pass back as cursor; offset and cursor do not combine. Every change row carries kind: people, money, products, models or other, the same kinds /changes groups and filters by. kind=people (type=people on /v1/changes and /changes) keeps one kind; any other value is a 400. On /v1/changes, /changes and /changes.md type also takes several kinds, comma-separated: type=people,money,models reads the three together.

GET /v1/changes[?window=7d|30d|90d|1y][&type=KIND[,KIND…]][&posts=1][&page=n]

What /changes shows, as JSON: summary (the sentence the page leads with), counts (total and by_kind over the whole window, before the type filter), and one page of 50 changes (each with its day, kind, entity, value, what it replaced and sources). window is 7d, 30d (default), 90d or 1y; posts=1 includes posts; type keeps one kind, or several comma-separated (type=people,money,models; the answer repeats the list in the kinds' own order); page pages. The page, /changes.md and this route read one computation (lib/changes-view.ts), so their sentence and counts agree.

Trends

GET /v1/trends[?from=YYYY-Qn] · GET /v1/trends?topic=voice_audio[&q=YYYY-Qn][&limit=n][&offset=n]

The record's dated events counted by quarter since 2023, from checked claims only, by the trend tags a model gives each claim from a fixed list. Each tag says whether the claim is about AI or the rest of tech, judged by the announcement (a phone is tech, a model is AI): domain=ai or domain=tech keeps one side and adds each company's releases, updates and deals; without it every row carries d. Product updates by theme (agents, coding, voice_audio, open_weights, developer_platform...), releases by kind, model releases by company (each company read with its products, as group=1 reads it) and tier, deals by kind (funding_round, investment_out, grant_program, capital_commitment, acquisition, partnership, customer, expansion) with the amounts disclosed per currency (as stated, never converted), the latest releases and deals, the largest amounts, who bought whom, countries, partners' kinds and each source's coverage. from=YYYY-Qn keeps the quarters from that one on. model_releases lists every model release with its day, maker, tier and themes; with domain=ai, cadence gives each company's median days between model releases (and between frontier ones), its latest release, and the mix (small and fast, frontier, open weights) in the last two complete quarters against the two before. topic=voice_audio (a theme, a deal kind, model, release, update or deal) returns the checked claims behind it instead, in domain when given, and company=google (a key from companies[].key) narrows them to one company's records, newest first, with their tags; q=2026-Q3 narrows them to a quarter; limit (max 200) and offset page. The counts are a snapshot taken after each load (computed_at). summary is the sentence /trends (or /trends/tech, or the topic page) leads with, over the quarters returned; with a domain, title is that page's title from the same counts; without one, summary names each domain in turn and summary_by_domain gives { ai, tech } apart. The page: /trends, one topic at /trends/{topic}, both with markdown twins (/trends.md, /trends/{topic}.md).

One claim, one fact, one timeline, one day, one edition

  • GET /v1/claims/{id} one claim: sources, supersedes, superseded_by
  • GET /v1/entities/{slug}/diff?from=&to=[&group=1] what changed between two days: the entity's state on from (required; the page /record/{slug}/diff with no dates compares the last 90 days) against its state on to (today when omitted) as added, ended and replaced (the same predicate with another value, both claims) per predicate, with the sentence /record/{slug}/diff leads with (summary); group=1 compares its company group; the twin is /record/{slug}/diff.md?from=&to=
  • GET /v1/entities/{slug}/{predicate} one fact's every dated value: what the record asserts today, the history and what replaced what; for ceo, headquarters, revenue, valuation and the other fact-page predicates, 404 until the record holds a claim on it that it asserts
  • GET /v1/timelines/{slug} every claim for an entity in date order, each with its kind; summary and counts are the timeline page's sentence and its counts by kind; ?kind= keeps one kind (the .md twin takes it too); group=1 for its company group
  • GET /v1/days/{date} what became true, stopped being true, or was recorded that day, with the day page's sentence (summary) and counts by kind, each row with its kind (?kind= keeps one), each list at most 500 rows with its total (complete: false and next when cut); 404 before 1900-01-01, after today or for an impossible date
  • GET /v1/briefs/{date} one edition of The Brief, by the day it covers: summary (the sentence the page leads with), its items, and model_releases (the day's model releases the record asserts); a missing edition is a 404 no cache keeps
  • GET /v1/briefs/week/{date} the week in review: the Brief's weekly recap, by the Saturday the week ends

Each answers as markdown with .md appended: /v1/claims/{id}.md, /v1/days/2026-10-27.md.

Markdown twins

These pages have a markdown twin at the page's path with .md appended (the home page's is /index.md), and answer with it when asked for Accept: text/markdown: /index.md, /record.md, /record/{slug}.md, /record/{slug}/{predicate}.md, /record/{slug}/diff.md?from=&to=, /timelines.md, /timelines/{slug}.md, /claim/{id}.md, /changes.md, /trends.md, /trends/tech.md, /trends/{topic}.md, /day/{date}.md, /days.md, /days/{year}.md, /brief.md, /brief/{date}.md, /brief/week/{date}.md, /search.md?q=, /about.md, /api-docs.md, /data.md, /identifiers.md, /privacy.md, /terms.md. /record/google.md?as_of=2016-01-01 is the state on a date. /llms.txt indexes the site; /llms-full.txt holds the current core facts for every entity in one file.

Feeds and conditional requests

  • /changes/feed.xml changes on the record: every claim that began or ended, newest first
  • /brief/feed.xml The Brief
  • /timelines/{slug}/feed.xml one entity's record: its 50 newest claims by the day recorded, each with its as-of date, status and primary source, linking its claim page (/record/{slug}/feed.xml redirects here)

Every 200 to a GET carries an ETag (weak, from the body) and, where the answer has a natural date, Last-Modified (the newest change to a claim it holds). Send If-None-Match with the ETag (or If-Modified-Since with the date) to get 304 Not Modified, with no body and the same Cache-Control and CORS headers, when nothing changed. The markdown twins answer the same way.

Keys and webhooks

Authorization: Bearer asof_… or X-API-Key: asof_… on any request. 200 carries X-RateLimit-Limit: 600 and X-RateLimit-Remaining; an unknown or revoked key is 401 {"error":"invalid api key"}.

Without a key, each address may make, per minute: 120 requests to /v1, 90 to /mcp, 120 for markdown pages, 60 searches, 60 dated record views (?as_of=, ?since=) and 60 list-page query variants. Calls to /mcp made through Claude's connector arrive from Anthropic's shared addresses, which may make 900 a minute each. /v1/health is not limited. A request over the limit is answered 429 with Retry-After.

During the preview every endpoint answers without a key, within the per-address limits; pricing and usage terms are being finalised. A key is for a client that needs more than those limits, or a webhook. It is asof_ and 43 base64url characters, sent as Authorization: Bearer asof_… or X-API-Key: asof_… (a bearer token that does not start with asof_, such as an OAuth token an MCP client sends by habit, is ignored). With a valid key the rate limit is the key's own budget, 600 requests a minute by default, counted across every limited endpoint (/v1, /mcp, search, the markdown twins and the dated views) instead of the per-address limits, and X-RateLimit-Limit and X-RateLimit-Remaining then describe that budget. An unknown or revoked key is refused with 401 {"error":"invalid api key"}, never cached: leave the header off to use the per-address limit. Only the key's SHA-256 is stored, so it is shown once when minted and cannot be shown again. Keys are minted by the AsOf admin: write to hello@chroniclabs.co with what you are building.

  • POST /v1/subscriptions register a URL (https, on a public host: no loopback, private or link-local address, no localhost) for the claims recorded after each daily load; entity (a record's slug) and predicates (at most 20 names) narrow them, any claim when left out. 201 with the subscription and its secret, shown once; 400 a URL or field that is not usable, 404 an entity not on record, 409 when the key already holds 20 active subscriptions
  • GET /v1/subscriptions the key's subscriptions, active and not, newest first, without secrets: { count, max_active, subscriptions[] }
  • DELETE /v1/subscriptions/{id} deactivate one, kept with active false: { id, active: false }; another key's is a 404

POST /v1/subscriptions with the key and JSON { "url": "https://example.com/hooks/asof", "entity": "openai", "predicates": ["ceo", "revenue"] } answers 201 with { id, url, entity, predicates, active, created_at, last_delivered_at, last_status, failures, secret }; the secret is shown once.

A delivery is POST <url> with Content-Type: application/json, User-Agent: asof-webhooks/1, X-AsOf-Delivery: <uuid> and X-AsOf-Signature: sha256=<hex HMAC-SHA256 of the raw body with the secret>, body { subscription, delivery, since, entity, predicates, count, claims: [ { id, entity, entity_name, predicate, value, valid_from, valid_to, recorded, confidence, status, superseded_by, updated_at, url, sources: [ { url, tier, published_at } ] } ] }.

After each daily load, every active subscription with new claims gets one POST: Content-Type: application/json, User-Agent: asof-webhooks/1, X-AsOf-Delivery: and X-AsOf-Signature: sha256=<hex HMAC-SHA256 of the raw body, keyed with the subscription's secret>; recompute it over the bytes as received, before parsing. The body is { subscription, delivery, since, entity, predicates, count, claims }: claims are the rows /v1/claims lists since the last successful delivery (the last 24 hours for a first one), checked claims only, oldest recorded first, at most 2,000 a delivery, each with id, entity (its slug), entity_name, predicate, value, valid_from, valid_to, recorded, confidence, status, superseded_by, updated_at, url (its claim page) and sources[] (url, tier, published_at). Answer 2xx within 10 seconds. Anything else, or no answer, leaves the cursor where it was, so the same claims come again next run, and ten failed deliveries in a row deactivate the subscription. Nothing is sent when nothing is new. Revoking a key deactivates its subscriptions.

Identifiers

GET /v1/entities/google opens with id, name, wikidata and page.

An entity's stable ID is ent_ and its slug (ent_google); its URL is https://asof.is/record/{slug}, which also answers by ID (https://asof.is/record/ent_google redirects). Cite the URL in schema.org sameAs beside the Wikidata QID, and the ID in identifier as a PropertyValue with propertyID asof; every record page does the same for its own keys (asof, wikidata, cik, ticker). The whole convention, with markup to copy: /identifiers.md.

Search

GET /v1/search?q=TEXT[&kind=all|entities|claims][&limit=n]

Entities by name, slug, ticker or former name (MSFT, Facebook), with accents, spacing and a typo forgiven. Claims by value, predicate, entity and date, and the days the query names. Best match first; limit applies to entities and claims each (default 20, max 50). JSON only; the same search runs at /search and /search.md?q=.

Corrections

POST /v1/corrections with JSON { "claim_id": "clm_…", "entity_id": "ent_…", "message": "...", "source_url": "https://...", "submitter": "you@example.com" }

message is required; the rest are optional. The About page (/about.md) posts the same fields as a form. Agents querying the record are its error detectors. A correction with a primary source goes to the review queue.

Health

GET /v1/health

Record size, sourcing shares and the hard-rule counts (each must be zero), as counted in the database after the last pipeline run (computed_at says when). Not rate limited.

MCP server

Remote MCP server (Streamable HTTP; no key needed during the preview, the same per-IP limit as /v1, which an API key sent as Authorization: Bearer asof_… or X-API-Key lifts to the key's budget): https://asof.is/mcp

In Claude it is a listed connector: AsOf in Claude's connector directory. What it answers and questions to ask: https://asof.is/connect.md

Tools:

  • entity_state(entity, as_of?, known_by?, group?, predicates?, scope?, assertable_only?, limit?, offset?, detail?): with ended, the terms that closed on or before the day (a CEO who left, a seat given up), newest first; state of an entity on a date; with known_by, what could have been known then; with group, its company's records together. An empty answer says why; fact_pages lists its one-fact pages; for an organization, summary, key_facts and faq as its page states them
  • changes_since(since, entity?, kind?, assertable_only?, limit?, offset?, cursor?, detail?, view?): leads with headline and highlights (the changes that took effect since the date, ranked: leadership, then ownership and deals, then releases); then what became true, stopped being true, or was first recorded after a date, each row with its kind (people, money, products, models, other); kind keeps one; cursor pages the feed in recorded order with no cap ("" first, then next_cursor), as /v1/claims?cursor= does
  • models(family?): the latest model of an AI model family (claude, gpt, gemini...): the newest release with its date and source, the current model of each line with the flagship marked, and the family's answer sentence; without family, the families the record follows
  • entity_diff(entity, from, to?, group?, limit?, offset?, detail?): what changed between two days: the state on from against the state on to (today when omitted) as added, ended and replaced (the same fact with another value, both claims) per predicate, with the sentence /record/{slug}/diff leads with (summary); group compares its company group
  • timeline(entity, predicates?, on?, from?, to?, group?, assertable_only?, limit?, offset?, kind?, detail?, view?): leads with headline and highlights (the main changes among the rows asked for, ranked); each row with its kind (kind keeps one) and the page's summary sentence; every claim about an entity in date order, paged newest first; predicates: [p] is one fact's every dated value; with group, its company's records merged
  • coverage(entity): which feeds and sources cover an entity, what it holds, when each source last added or checked something, its gaps and its company group
  • claim(id): one claim with sources, supersession links, its three dates and the check behind it
  • search(query, kind?, assertable_only?, limit?): entities by name, ticker or former name; claims by value, role and date; a question in full is answered (answer, with answers for any further facts asked); results lists the pages found (id, title, url), each an id fetch opens
  • trends(topic?, domain?, company?, quarter?, limit?, offset?): releases and deals counted by theme, kind and quarter since 2023, AI or tech or both; with topic, the claims behind it, for one company with company
  • fetch(id): a page's markdown twin by its path, e.g. /record/google.md, /record/anthropic/revenue.md (a fact page), /trends/models.md?company=openai or /about.md

Only the news: changes_since and timeline take view: "news": only the changes a reader would call news, that the record asserts (leadership, ownership, deals, funding, releases, board and feature changes, the main figures). It leaves out Hugging Face repositories, posts, identifiers, routine balance-sheet and quarterly rows, undated claims, and unverified, flagged or retracted ones. For changes_since with an entity it also means took effect since the date, not merely recorded since, newest first, with a total. The default is all: every row, as before.

Fewer tokens: entity_state, changes_since, timeline, entity_diff and search take detail: "compact". A compact row is id, predicate, value, qualifier, the dates it held, status, why_not when the record does not assert it, and the primary source URL: about a fifth the size of a full row, so a page holds five times as many. Left out: published, recorded, confidence, citation and evidence, each of which is on the claim page (/claim/{id}) and in the claim tool. entity_state's compact answer keeps the rows, the record's summary sentence and the page URL, and leaves the fact card, the questions, the fact pages and the products to the full answer. The default is full: every field, as before.

Claude Code: claude mcp add --transport http asof https://asof.is/mcp

Add it to Claude

  1. In Claude (claude.ai or the desktop app), open Settings, then Connectors, and choose Add custom connector.
  2. Name it AsOf and paste the server URL above. There is nothing to sign in to: leave the OAuth fields empty.
  3. Open a chat, turn AsOf on in the tools menu, and ask a dated question. Every tool only reads the record, so Claude can be allowed to call them without asking each time.

Questions to try

  • Who was the CEO of OpenAI on 2023-11-18, and what is the source? (entity_state with as_of)
  • What changed at Anthropic since 1 September 2026? (changes_since)
  • Compare Nvidia's record on 2024-01-01 with today: what was added, what ended and what was replaced? (entity_diff)
  • What was Meta's latest reported revenue as it could have been known on 2024-07-15? (entity_state with known_by)
  • Which companies released frontier models last quarter, and how does that compare with the quarter before? (trends)

A result is at most a page of rows: an answer that says complete: false carries next_offset for the rest. Help and corrections: hello@chroniclabs.co. What the server logs: https://asof.is/privacy

Claim schema

fieldmeaningexample
idStable ID, never reusedclm_01J9X…
entityThe record the row came from: on every MCP row, and on /v1 rows in a group answeralphabet
entity_idEntity, mapped to Wikidata where possibleent_google (Q95)
predicateThe relationship, snake_caseceo
valueThe factSundar Pichai
qualifierPresent when the value holds only with it: interim or acting, for a role held until a permanent holder is named. Repeat it with the valueinterim
correctionPresent when the row records a fix to the record, not a change in the world: retracted (withdrawn; never repeat it), redated (its start moved to the day a source states) or relabelled (its value rewritten to the record's form of the name). A superseded row without it is a real changeredated
valid_from / valid_toDate range the claim held: effective time. valid_to null means still holds. A period figure takes effect at the period end. When the source gives no date, valid_from is null and undated is true (the twins say undated)2015-08-10 / null
publishedThe earliest day a source on the record published the claim: a filing's filing date, a post's date. null when no source carries one2015-08-10
recordedThe day AsOf first held the claim2026-10-27
sources[]Source URLs with their tier (primary or secondary) and published_at, primary preferred; /v1/entities and /v1/claims/{id} add publisherSEC 8-K
confidence0 to 1, from source count and agreement0.98
statusvalid · unverified · flagged · supersededvalid
assertabletrue when the record asserts the claim: valid, or primary-sourced at confidence 0.7 or more; never flagged, retracted or marked for review. A state on a date is labelled for that date; every other row for its own dates, so a superseded CEO is assertable for the years he held the role (status and valid_to say whether it holds today). A row that is not assertable carries why_not, a code the response's legend explainstrue
why_notPresent when assertable is false: post, review, retracted, flagged, unverified, low_confidence, superseded or not_held; the response's legend says each in wordspost
superseded_byPointer to the replacing claimnull
also_onIn a group answer: the other records that hold the same fact (same predicate, value and start), each with its claim id[{ entity: google-deepmind, id: clm_… }]
evidenceThe check behind the claim: by (method), on (date), and where kept quote (the source page's words that state it), fact (the XBRL concept and form a filing figure came from) or excerpt (what the post said){ by: sec-filing, fact: us-gaap:Revenues in 10-K FY2026, filed 2026-07-30 }
citationHow to cite the row: plain (one sentence with the value, its as-of date, the claim id and URL, the day answered and the primary source), url (the claim page) and accessed (the day answered). The claim page adds APA and BibTeX with copy buttons{ plain: AsOf. "Google ceo: Sundar Pichai", as of 2015-08-10. Claim clm_…, https://asof.is/claim/clm_…, accessed 2026-11-07. Source: … }