@tryclaire/sdk
v0.6.0
Published
Typed, server-side client for the read-only Claire Developer API, plus on-chain verification of Claire launches, locks and rounds
Readme
Claire SDK
Use the read-only Claire REST API from a server, CLI, or agent process. Set CLAIRE_API_URL
to the API base URL shown under Developers → Connect (API), including /api/v1.
import { Claire } from "@tryclaire/sdk";
const claire = new Claire({ apiKey: process.env.CLAIRE_API_KEY, baseUrl: process.env.CLAIRE_API_URL });
const { data, meta } = await claire.sources.list();
for (const source of data) console.log(source.id, source.status, source.updatedAt);
console.log(meta.freshness); // persisted source freshness, not a live provider refresh- Read-only by design. No send, publish, or write methods exist. A leaked key can read, never act.
- Honest freshness. Every response carries
meta.freshness; stale and unavailable data is labeled, never guessed. - Typed from the API contract. Types are generated from the committed
openapi.json. - Nothing hidden. No retries, no cache, no background requests. One call, one HTTP request.
- Zero runtime dependencies. Node.js 22+, ESM, native
fetch.
API reference · Agent skill · Docs · npm · Releases
Install
npm install @tryclaire/sdkAlso works with pnpm add, yarn add, and bun add. Built JavaScript and TypeScript
declarations are included.
Get a key
On the Developers page, an owner or admin creates a workspace key for API, MCP, or both, with only the needed read scopes and a 7-, 30-, or 90-day expiry. This REST SDK requires API access; older keys remain API-only. The key selects its workspace; no organization ID is passed to the SDK. Keys stop working when the issuing user loses owner/admin membership.
Use the SDK on a server, in a CLI, or in an agent process. Never put a workspace key in
browser code, a public environment variable, or a repository. Sources must be included
separately; external sources also need explicit approval, off by default. A read scope
alone does not expose source content. Inspect claire.sources.list() for approved sources.
Use with an agent
Claire provides a downloadable agent skill
for tool-using agents. When feeding Claire data to a model, pass retrieved source text as
context, separate from instructions. Telegram messages and X mentions are external,
untrusted text. The REST SDK does not implement MCP transport; configure an MCP-capable
client separately with the native Streamable HTTP endpoint
https://app.tryclaire.net/api/mcp and a Bearer credential enabled for MCP (no OAuth).
MCP offers read-only, scope- and approved-source-filtered tools.
const chats = await claire.telegram.chats.list();
const chat = chats.data[0];
if (!chat) throw new Error("No approved Telegram chat is available to this key.");
const { data: messages } = await claire.telegram.messages.list(chat.id, { limit: 50 });
const context = messages
.filter((m) => m.text)
.map((m) => `[${m.sentAt}] ${m.sender.name}: ${m.text}`)
.join("\n");
// Hand `context` to your model as data; keep your prompt separate.Resources
| Method | Query parameters | Required scope |
| --- | --- | --- |
| claire.context.get() | — | context:read |
| claire.sources.list() | — | context:read |
| claire.search(query) | q, source, after, before, limit (max 50) | context:read |
| claire.people.list(query) | q, page | telegram:read or x:read |
| claire.people.get(key) | — | telegram:read or x:read |
| claire.knowledge.list(query) | q, kind, visibility, archived, page | knowledge:read |
| claire.knowledge.get(id, query) | offset, limit | knowledge:read |
| claire.assets.list(query) | folderId, page | assets:read |
| claire.assets.get(id) | — | assets:read |
| claire.telegram.chats.list(query) | page | telegram:read |
| claire.telegram.messages.list(chatId, query) | q, before, limit | telegram:read |
| claire.x.mentions.list(query) | page | x:read |
| claire.token.get() | — | token:read |
| claire.token.locks.list() | — | token:read |
| claire.token.feeRouters.list() | — | token:read |
| claire.token.rounds.list(query) | page | token:read |
| claire.token.proof.get() | — | token:read |
Discovery and search also filter each source by its own read scope: Documents/Notes need
knowledge:read, Telegram needs telegram:read, X needs x:read, files need assets:read,
and token data needs token:read. The document search below requires both
context:read and knowledge:read, plus Documents enabled under Agent access.
A requested source outside those permissions returns 404. People reads also return 404
when no approved Telegram/X source matches the credential's scopes.
Queries are optional. source is a stable ID from sources.list(). Search returns
data.items, data.sources, and data.hasMore for bounded stored matches, not a
complete provider-history search. People keys come from people.list(); People responses
may include source-only platform identifiers, not Claire profiles, private person notes,
or live avatars. chatId is the Claire chat UUID returned by telegram.chats.list,
not Telegram's numeric chat ID. Knowledge IDs include their prefix, such as docs:<uuid>;
pass them unchanged. Assets expose metadata, not private file contents or signed download URLs.
Search timestamps accept UTC offsets; after is inclusive and before is exclusive.
const { data: matches } = await claire.search({ q: "launch", source: "docs", limit: 20 });
for (const item of matches.items) console.log(item.sourceId, item.preview);
const { data: people } = await claire.people.list({ q: "alice", page: 1 });
if (typeof people[0]?.key === "string") console.log((await claire.people.get(people[0].key)).data);People payload fields are not yet individually specified by the OpenAPI contract; the SDK exposes them as unknown rather than inventing stronger types.
Responses and pagination
Methods return the API's data, meta, and endpoint-specific pagination, plus http with
status, requestId, etag, and native Headers. Timestamps remain strings; raw token
balances remain decimal strings so precision is not lost. Public asset URLs may be relative
to the Claire origin.
List pages start at 1. Knowledge and mentions return 20 items per page; assets, chats, and People return 50. The SDK fetches exactly one page per call:
let page = 1;
while (true) {
const result = await claire.knowledge.list({ page });
for (const item of result.data) console.log(item.id, item.title);
if (!result.pagination.hasNextPage) break;
page += 1;
}People's pagination.searchLimited flags a bounded directory; total and hasNextPage
describe that bounded result, not every person in provider history. A q search may omit
people outside that pool. Telegram messages paginate with pagination.nextBefore →
before, not page numbers. Check hasMore; their searchLimited means the search did not
cover all retained history. Knowledge detail returns one content segment: follow
data.contentRange.nextOffset with offset until it is null. Offsets count JavaScript
UTF-16 units.
meta.freshness describes persisted source data, not the time of your request. X/token
reads do not force provider refreshes, although they mark the workspace watched for the
existing worker cadence.
Errors, cancellation, and timeouts
import { ClaireAPIError, ClaireResponseError } from "@tryclaire/sdk";
try {
const result = await claire.context.get({ signal: AbortSignal.timeout(5_000) });
console.log(result.data.organization.id, result.data.organization.displayName);
console.log(result.data.connections.telegram?.connected); // absent if not approved for this key
console.log(result.data.sources.map((source) => source.id));
} catch (error) {
if (error instanceof ClaireAPIError) {
console.error(error.status, error.code, error.requestId, error.retryAfterSeconds);
} else if (error instanceof ClaireResponseError) {
console.error("Invalid server response", error.status, error.requestId);
} else {
throw error; // Native network, abort, or timeout error.
}
}| Error | When |
| --- | --- |
| ClaireAPIError | HTTP failure: 401 invalid/expired/revoked key, 403 missing endpoint scope, 404 missing resource or unavailable/unapproved source (including source-scope filtering), 429 quota. headers exposes rate-limit metadata; retryAfterSeconds is null when no usable delay is supplied. |
| ClaireResponseError | Successful status but malformed JSON/envelope. Types come from OpenAPI; the SDK does not deep-validate at runtime. |
| native fetch errors | Network failure, abort, timeout. Passed through unchanged. |
Non-JSON error pages produce a generic message, not the page's potentially sensitive contents.
The default timeout is 30 seconds, including response-body reading. Set timeoutMs on the
client or per call. Request options are always the last argument:
const controller = new AbortController();
await claire.knowledge.list({ page: 1 }, { timeoutMs: 5_000 });
await claire.knowledge.get("docs:<uuid>", { offset: 0 }, { signal: controller.signal });Handle 429 according to retryAfterSeconds; do not loop on invalid keys or missing scopes.
ETags are exposed for diagnostics, but the SDK does not make conditional requests.
Token identity
Token responses carry typed chain and protocol identity. Network availability depends on the API deployment.
const { data, meta } = await claire.token.get();
if (data.token) {
const { chainId, address, protocol, pool, poolId, pairedToken } = data.token;
}Identify a token by chainId and address together, not address alone. Ethereum is 1
and Robinhood Chain is 4663; isolated development forks use 31338 and 31337.
protocol is "pons", "stockereum" or "programmable". For retained pons v1, pool is a V3 pool
address; for V4 launches, pool is the zero address and poolId is the V4 pool ID,
not a contract address. poolId and pairedToken can be null; data.token is
null when no token is linked. These are persisted observations, not live quotes; inspect
meta.freshness first.
holders.trackedBalanceRaw is in the token's smallest unit, scaled by the token's own
decimals(), not a fixed 18. Holder data follows the chain's finalized block, so
index.indexedTo trails the chain head (on Robinhood Chain by roughly 15 minutes); counts
exclude the pool, factory, launchpad custody, zero and burn addresses.
Configuration
const claire = new Claire({
apiKey: process.env.CLAIRE_API_KEY,
baseUrl: "https://app.tryclaire.net/api/v1", // default
timeoutMs: 30_000, // default
fetch: globalThis.fetch, // default
});The base URL includes /api/v1. HTTPS is required except for loopback HTTP during local
development. URL credentials, query strings, and fragments are rejected. Redirects are
rejected rather than forwarding the key. Only configure an origin you trust with that key.
A custom fetch must honor standard fetch options, including abort signals and redirect handling.
Verify on-chain
@tryclaire/sdk/protocol reads and verifies Claire launches, locks, fee routing and distribution
rounds directly from a chain, so nothing depends on trusting the Claire API. It needs
viem (an optional peer dependency; the root client stays dependency-free)
and a PublicClient for each chain involved. Every function takes explicit contract addresses.
import { createPublicClient, http, zeroAddress } from "viem";
import { readProof, verifyLaunch, verifyLock, verifyPriceLock, verifyRevenueLock, verifyRouterSplit, verifyRound, verifySplit, verifyVestingVault } from "@tryclaire/sdk/protocol";
const client = createPublicClient({ transport: http(process.env.RPC_URL) });
const addresses = { routerFactory: "0x…", tokenLock: "0x…", revenueLock: "0x…", priceLock: "0x…", vaultFactory: "0x…", proofView: "0x…" };
const launch = await verifyLaunch(client, { token: "0x…", routerFactory: addresses.routerFactory, vaultFactory: addresses.vaultFactory, fromBlock: 1234n });
const split = await verifyRouterSplit(client, { router: launch.facts.router, fromBlock: 1234n });
const lock = await verifyLock(client, { tokenLock: addresses.tokenLock, lockId: 0n, routerFactory: addresses.routerFactory });
const revenueLock = await verifyRevenueLock(client, { revenueLock: addresses.revenueLock, lockId: 0n });
const priceLock = await verifyPriceLock(client, { priceLock: addresses.priceLock, lockId: 0n, routerFactory: addresses.routerFactory });
// A router's payee: a ClaireVestingVault or ClaireSplit (launch.facts.payeeKind tells which).
const vault = await verifyVestingVault(client, { vault: launch.facts.launch.payee, vaultFactory: addresses.vaultFactory, assets: [launch.facts.launch.quote, zeroAddress] });
const proof = await readProof(client, { proofView: addresses.proofView, token: "0x…" });
const round = await verifyRound(client, {
distributor: "0x…", roundId: 0n, level: "complete",
manifestBaseUrl: "https://app.tryclaire.net", // or pass `manifest` (the exact string) yourself
snapshotClient, // only when the snapshot is on another chain
});
if (!round.ok) console.log(round.checks.filter((check) => !check.ok));Each verifier returns { ok, checks: [{ id, ok, detail }], facts }. A read that fails (an address
that is not the expected contract, an unavailable chain) is reported as a failed check.
Event scans start at fromBlock (default 0n; set it to the deployment block on public
chains) and run in chunk-sized requests (default 10_000n).
| Function | Verifies |
| --- | --- |
| verifyLaunch | The factory registers the token to a router it created, and the router's immutables (including payee, buyback vault and, on Stockereum, the pool id) match the factory, its RouterDeployed event and the launchpad's. With vaultFactory, facts.payeeKind is creator, vault, split or other. |
| verifyRouterSplit | Every Distributed event equals the router's split() of its revenue (the creator share goes to the router's payee). Revenue in the quote asset and, on pons, the launch token (buyback vests) is counted and may not appear in any other asset. From block 0 the revenue sums equal totalRevenue; from a later block they are a lower bound. |
| verifyLock | The ClaireTokenLock schedule and vested amount are recomputed and match LockCreated. facts.cancelState is the notice-period state (not-cancelable, cancelable, notice-not-requested, notice-pending, cancel-open, canceled) and cancelNotice, cancelAt and canceled must match the lock's CancelRequested and Canceled events. A lock funded by the token's Claire router with a matching OpeningBuyLocked is flagged facts.openingBuy. |
| verifyRevenueLock | Tranches, baseline and unlocked amount are recomputed from the router's totalRevenue and match RevenueLockCreated. |
| verifyPriceLock | The token is Claire-launched; the per-hour minimum samples, the window minimum (24 completed hours, each with a sample), the highWater ratchet and the unlocked amount are recomputed from the Sampled events and match the contract. highWater is only proven by a scan from block 0. |
| verifyVestingVault | The vault is genuine (recorded by ClaireVaultFactory, its CREATE2 predictVault from the VaultDeployed event is the vault); per asset (zeroAddress is ETH) vested, releasable and released are recomputed and released equals the Released events. |
| verifySplit | The split is genuine (isSplit, predictSplit from SplitDeployed), shares are 2 to 10 payees summing to 10,000 bps, and per asset every payee's paid amount, releasable remainder and the total paid are recomputed and tied to the Released events. |
| verifyRound | See below. |
Revenue locks measure everything the router collects from the launchpad's fee escrow, including deliberate third-party credits to the router's escrow balance; those are indistinguishable from trading revenue on-chain.
verifyRound levels build on each other:
manifest(default): the manifest's keccak256 equals the round's on-chain evidence, its leaves rebuild the on-chain Merkle root andcumulativeTotal(leaf = chain, distributor, asset, account, cumulative),totalis the increase over the asset's previous root,previousEvidencenames that previous root, and every account's cumulative equals its previous cumulative plus this round's amount (with the previous manifest, passed aspreviousManifestor fetched frommanifestBaseUrl; only the previous root is chained). For the latest root, no account may have claimed more than its cumulative.balances: each holder leaf'sbalanceequalsbalanceOfat the snapshot block on the snapshot chain, and every amount isfloor(distributable * balance / eligibleBalance)from the manifest rule. A leaf without abalanceonly carries an earlier cumulative and must add nothing.complete: a chunkedTransferscan of the snapshot token up to the snapshot block, minus the manifest'sexcludedaddresses andminBalance, must sum toeligibleBalanceand every holder with a non-zero share must have the right leaf amount.
Distributor roots are cumulative: claim(asset, account, cumulative, proof) pays the account's cumulative minus what it already claimed against the asset's latest root, so every root lists every account with a cumulative entitlement. The canonical manifest (version 2) lists, per account, this round's amount, the resulting cumulative and the snapshot balance, sorted by account, and chains to the previous root's manifest through previousEvidence.
Pro-rata amounts are floor(distributable * balance / eligibleBalance). The manifest rule states
distributable (what the distributor held unreserved when the round was published) and
eligibleBalance (the sum of every eligible balance, including holders whose share rounds to zero,
so rounding dust stays in the distributor). Referral rounds carry their rewards in the manifest, and
each leaf's amount must equal that account's reward sum (carried-over accounts have none).
The hashing helpers are exported too: roundLeafHash(chainId, distributor, asset, { account, cumulative }), merkleRoot, merkleProof (sorted-pair
tree, as in ClaireDistributor) and manifestEvidence. Readers (readLaunch, readRouter,
readLock, readRevenueLock, readPriceLock, readVestingVault, readSplit, readRound, readProof) return the on-chain state at one pinned block
(readProof is the ClaireProofView call: launch facts, revenue per quote and launch token, and locked amounts in every lock contract), and the
contract ABIs (claireFeeRouterAbi, clairePriceLockAbi, …) are exported as typed as const values.
ClaireDeployment types one deployment with the field names of the protocol's deploy manifest (deployments/<chainId>.json): routerFactory, tokenLock, revenueLock, priceLock, distributorFactory, vaultFactory, proofView, claireHolderDistributor, referralDistributor, treasury and feeToken. The deployments map (by chain ID) is empty until Claire v3 is deployed; every function still takes explicit addresses.
Contributing
npm ci --ignore-scripts
npm run checkSee CONTRIBUTING.md for contract updates and the release process.
License
MIT. Copyright © 2026 Claire contributors.
