TRUST LAYER
API v1

Reputation over HTTP

JSON over HTTPS. Every per-address endpoint behaves the same way: looking an address up is a request to index it, and it answers 202 until its history has been read. Nothing is invented for an address without data. A typed TypeScript SDK wraps all of it.

Quick start

curl -H "Authorization: Bearer $TRUST_LAYER_API_KEY" \
  $TRUST_LAYER_API/v1/risk/0x…

# 202 while the address is indexed:
{ "subject": "0x…", "indexing": { "status": "backfilling", … } }

# then 200:
{
  "status": "scored",
  "trust_score": 746,
  "band": "strong",
  "risk_level": "low",
  "confidence": 0.71,
  "flags": [],
  "score_model_version": "v0.3",
  "aleph": null
}
const res = await fetch(`${API}/v1/risk/${address}`, {
  headers: { Authorization: `Bearer ${process.env.TRUST_LAYER_API_KEY}` },
});
if (res.status === 202) return "pending"; // not rated yet: retry shortly
const risk = await res.json();

if (risk.status !== "insufficient_data" && risk.trust_score < 300) {
  warn(risk.flags);   // each flag links to its evidence
}

API keys

Send your key as a bearer token: Authorization: Bearer tl_live_…. A key is a secret: call the API from your server, never from a browser or a mobile app. A deployment may also answer requests without a key, at a low per-IP rate, or refuse them.

StatusCodeMeaning
401UNAUTHORIZEDThe key is missing where one is required, unknown or revoked.
403FORBIDDENThe key is valid but not allowed to do this, for example an endpoint outside its scopes.
429RATE_LIMITEDToo many requests. Wait for Retry-After seconds, then try again.

The developer dashboard shows a key's limits, today's usage against its quota, the last 30 days and the health of its webhooks.

Rate limits

  • Each key has a per-minute limit, and may have a daily quota. Responses carry the current window in RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the window resets).
  • Over the limit, the API answers 429 RATE_LIMITED with Retry-After in seconds. Wait at least that long; the SDK does this for you.
  • GET /v1/usage returns the calling key's limits, today's requests, errors, rate-limited requests and quota left, and the last 30 UTC days.
  • Batch requests, pagination and webhooks keep request counts low; prefer them to polling many addresses one by one.
HTTP/1.1 429 Too Many Requests
Retry-After: 12
RateLimit-Limit: 120
RateLimit-Remaining: 0
RateLimit-Reset: 12

{ "error": { "code": "RATE_LIMITED", "message": "…" } }

Decisions

GET /v1/risk/:addressCompact answer for integrations: status, score, band, risk level, confidence, medium-or-worse flags, and the latest Aleph assessment as a separate labelled block.
GET /v1/trust/:addressThe full TRUST RECORD: score, band, risk level, confidence, subscores and the explanation behind every point, caps and confidence factors.
GET /v1/trust/:address/historyAppend-only score snapshots with why each one changed. ?limit=
POST /v1/batchUp to 100 addresses per request, answered as /v1/risk or /v1/trust. See Batch below.

Evidence

GET /v1/rap-sheet/:addressRisk items, newest first. Filters: severity (minimum), classification, type, rule, related, since, until; limit + cursor pagination.
GET /v1/record/:addressRecord summary (stats, positive lines, risk counts, coverage) plus positive and neutral items, with the same filters.
GET /v1/record/:address/positivePositive lines and positive items only.
GET /v1/items/:idOne item by id (evt_…), with its evidence, rule and related entities.
GET /v1/token/:addressToken facts: metadata, creation, top holders, pools and liquidity flows, transfer activity.
GET /v1/developer/:addressA deploying wallet: everything it launched, how each launch's own record reads, and who funded it.

Graph & inference

GET /v1/graph/:addressNeighbourhood over control-like links, funding cluster, and association kept apart from direct evidence. ?depth=1|2&limit=10..300
GET /v1/entities/:address/relationshipsEvery aggregated relationship with its evidence trail. ?direction=in|out|all&type=FUNDED&limit=
GET /v1/aleph/:addressLatest Aleph assessment (ALEPH_INFERENCE) with reasons, resolved evidence refs and confidence factors. Reading it queues a fresh assessment when the evidence changed.
GET /v1/aleph/:address/historyEvery Aleph run, including rejected ones with the validator's errors.

Discovery & metadata

GET /v1/search?q=Known addresses by full address, hex prefix, token symbol, token name or label. Never queues.
GET /v1/entities/:addressEntity and indexing status. 200 indexed, 202 queued or backfilling, 422 for system addresses.
GET /v1/rulesActive score model, every detector rule with thresholds, the verified event catalog, the Aleph model and the forbidden-terms list.
GET /v1/metaEntity types, classification vocabulary, score weights and bands.
GET /v1/statusChain connectivity, database, indexer summary, active model versions.

Keys, webhooks & disputes

GET /v1/usageThe calling key: name, prefix, scopes, per-minute limit and daily quota, today's usage and quota left, and the last 30 days.
POST /v1/webhooksSubscribe a URL: { url, events[], subjects? }. Answers 201 with the subscription and its signing secret, shown once.
GET /v1/webhooksYour subscriptions (without secrets): events, subjects, active, consecutive failures, disabled reason.
DELETE /v1/webhooks/:idRemove a subscription.
POST /v1/webhooks/:id/testSend a signed ping delivery to the subscription's URL.
GET /v1/webhooks/:id/deliveriesRecent deliveries: event, status, attempts, your endpoint's last HTTP status and error.
POST /v1/disputesReport a false positive or incorrect data: { subject, event_id?, kind, reason, contact? } → { id, status }.
GET /v1/disputes/:idWhere a dispute stands: open, upheld, rejected or withdrawn.

Batch

POST /v1/batch takes 1 to 100 addresses and a view of risk (the default) or trust. Every address gets its own entry, in request order, with the status the single endpoint would have answered: 200 with the same data as GET /v1/risk or GET /v1/trust, 202 while it is indexed, or 400, 422, 503 with an error. One bad address never fails the others. The SDK splits longer lists into consecutive requests.

POST /v1/batch
Authorization: Bearer tl_live_…
Content-Type: application/json

{
  "addresses": ["0xAb12…", "0x9f8E…", "0xnot-an-address"],
  "view": "risk"
}
{
  "results": [
    { "address": "0xAb12…", "status": 200,
      "data": { "status": "scored", "trust_score": 746, … } },
    { "address": "0x9f8E…", "status": 202,
      "data": { "indexing": { "status": "queued", … } } },
    { "address": "0xnot-an-address", "status": 400,
      "error": { "code": "INVALID_ADDRESS", "message": "…" } }
  ]
}

Webhooks

Subscribe a URL to events, for every address or only for the addresses in subjects. Creating a subscription returns its signing secret once: store it.

EventSent when
score.updatedThe subject's Trust Score changed (a new snapshot in its score history).
risk.detectedA risk was detected for the subject. data carries the item with its classification.
rap_sheet.createdA RAP SHEET item was created for the subject.
wallet.cluster_changedThe wallet's funding cluster changed. A grouping by funding links, not a claim of common control.
aleph.assessment.updatedA new Aleph assessment. Always ALEPH_INFERENCE: an interpretation of the evidence, never a fact.
pingSent by POST /v1/webhooks/:id/test, to check your endpoint.

Delivery

POST /your/webhook/endpoint
Content-Type: application/json
X-TrustLayer-Event: score.updated
X-TrustLayer-Delivery: whd_01JA…
X-TrustLayer-Signature: t=1791633600,v1=5f2b…c9e1

{
  "id": "whe_01JA…",
  "type": "score.updated",
  "created_at": "2026-10-10T12:00:00.000Z",
  "network": "robinhood",
  "subject": "0x7F2c…A91b",
  "data": { … }
}
  • X-TrustLayer-Signature is t=<unix seconds>,v1=<hex>, where the hex is HMAC-SHA256 with your secret over <t>.<raw body>. More than one v1 may appear; accept the delivery when any one matches.
  • Verify against the raw body exactly as received, compare in constant time, and reject a timestamp more than 300 seconds from your clock. That stops forged and replayed deliveries.
  • Answer with a 2xx status quickly and do slow work afterwards. Other answers and timeouts count as failures and are retried with backoff, so de-duplicate on the event id. A subscription that keeps failing is disabled; GET /v1/webhooks shows consecutive_failures and disabled_reason.
  • Treat data as a notification and read the subject again for its current state. An aleph.assessment.updated event is an inference: route it to review, never treat it as a fact.

Verifying a delivery

import { verifyWebhook, WebhookVerificationError } from "@trust-layer/sdk";

// Next.js route handler (any Fetch-API runtime)
export async function POST(request: Request) {
  const payload = await request.text(); // the raw body, unparsed
  try {
    const event = await verifyWebhook({
      payload,
      header: request.headers.get("x-trustlayer-signature"),
      secret: process.env.TRUST_LAYER_WEBHOOK_SECRET!,
      // toleranceSeconds: 300 (default)
    });
    await enqueue(event); // de-duplicate on event.id
  } catch (error) {
    if (error instanceof WebhookVerificationError) {
      return new Response(null, { status: 400 });
    }
    throw error;
  }
  return new Response(null, { status: 204 });
}
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the exact bytes received. header: X-TrustLayer-Signature.
export function isAuthentic(rawBody: Buffer, header: string, secret: string) {
  const fields = header.split(",").map((part) => part.trim().split("="));
  const t = fields.find(([key]) => key === "t")?.[1];
  if (!t || !/^\d+$/.test(t)) return false;
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${t}.`)
    .update(rawBody)
    .digest();
  return fields.some(([key, value]) =>
    key === "v1" && /^[0-9a-f]{64}$/i.test(value ?? "") &&
    timingSafeEqual(Buffer.from(value!, "hex"), expected));
}

TypeScript SDK

@trust-layer/sdk has no runtime dependencies (fetch and Web Crypto) and runs on Node ≥ 20, Deno, Bun and edge runtimes. Every response is typed with the API's own field names. It retries 429s after Retry-After and 5xx or network errors with backoff (never repeating a create), validates addresses before sending, and returns a typed pending result for addresses still being indexed, or waits for them with waitForReady. baseUrl points it at any deployment.

# from this repository, until the first npm release
pnpm --filter @trust-layer/sdk build

# in a workspace package.json
"@trust-layer/sdk": "workspace:*"

# once published
npm install @trust-layer/sdk
import { TrustLayer, isPending } from "@trust-layer/sdk";

const tl = new TrustLayer({
  apiKey: process.env.TRUST_LAYER_API_KEY,   // server-side only
  baseUrl: process.env.TRUST_LAYER_BASE_URL, // default https://api.trustlayer.xyz
});

const risk = await tl.risk("0x7F2c…A91b");   // RiskResponse | Pending
if (isPending(risk)) {
  console.log("not rated yet:", risk.indexing?.status);
} else {
  console.log(risk.trust_score, risk.band, risk.flags);
}

// Or wait until the address is indexed (polls, then returns data):
const ready = await tl.trust("0x7F2c…A91b", { waitForReady: { timeoutMs: 30_000 } });

Methods

wallet, token, contract (the TRUST RECORD with a type check), trust, risk, trustHistory, rapSheet and record with page and item iterators, item, graph, aleph, alephHistory, tokenProfile, developer, search, batch, usage, webhooks.*, dispute, and verifyWebhook for receivers.

Decision policies

import { policy } from "@trust-layer/sdk";

const guard = policy({
  minTrustScore: 400,           // block below
  warnBelowTrustScore: 550,     // warn below
  maxRiskLevel: "elevated",     // block on high / severe
  requireScored: false,         // true: unscored and provisional block
  treatPendingAs: "pending",    // or "allow" | "warn" | "block"
  blockOnFlagSeverity: "high",  // VERIFIED / SIGNAL flags at or above block
  aleph: { warnAt: "medium" },  // an inference warns; blockAt is opt-in
});

const { decision, reasons } = guard.evaluate(await tl.risk(recipient));
// decision: "allow" | "warn" | "block" | "pending"
// reasons[]: { code, effect, message, classification?, inference, evidence }
  • TRUST LAYER provides the evidence; your application makes the call. A policy turns a /v1/risk answer into allow, warn, block or pending, and every reason cites the score, the flag (with its class and event id) or the assessment behind it.
  • Only the score thresholds and VERIFIED or SIGNAL flags block by default.
  • An ALEPH INFERENCE adds at most a warning, marked as inference. It blocks only when your policy sets aleph.blockAt, and never changes the score.
  • An address that is still being indexed, or has too little history to score, is not evidence of risk: the reason says so.

Integration examples

Runnable TypeScript in packages/sdk/examples. They read TRUST_LAYER_BASE_URL and TRUST_LAYER_API_KEY from the environment.

Wallet: before signing

Checks the recipient before the user signs. Shows each flag with its class and the Aleph assessment apart from the score.

examples/wallet-presign-check.ts

Recipient Trust Score: 211 · HIGH RISK
Band high_risk · confidence 82% · score model v0.3

RAP SHEET (medium or worse):
  HIGH  Deterministic signal  A token this wallet deployed lost…

Aleph inference (not a verified fact): high risk, confidence 62%

Decision: BLOCK (ask the user to confirm, or stop the transfer)

Launchpad: before launching

Reads the deploying wallet with developer(): every earlier launch and how its own record reads.

examples/launchpad-deployer-check.ts

Deployer Trust Score: 834 · Strong
Historical Launches: 7 (5 tokens)
Launches with risk items (medium or worse): 0
High-Risk Events: 0

AI agent: before sending funds

A payment guard. Several payees are screened with one batch call; anything still being indexed waits.

examples/agent-payment-policy.ts

const risk = await tl.risk(payee, { waitForReady: { timeoutMs: 20_000 } });
if (risk.trust_score !== null &&
    risk.trust_score < PAYMENT_POLICY.minimumTrustScore) {
  return reject();
}
const { decision, reasons } = guard.evaluate(risk);

Webhook receiver

A node:http endpoint that verifies every delivery on the raw body, de-duplicates retries and handles each event type.

examples/webhook-receiver.ts

pnpm --filter @trust-layer/sdk example:webhooks --self-test
signed ping   → HTTP 200
tampered ping → HTTP 400
unsigned ping → HTTP 400

Conventions

  • Addresses are accepted in lowercase or valid EIP-55 checksum form and returned checksummed.
  • Errors always look like { "error": { "code": "…", "message": "…" } }.
  • Timestamps are ISO-8601 UTC. Token amounts and wei are decimal strings.
  • Every item states its class (VERIFIED, SIGNAL, ALEPH_INFERENCE) and the rule, catalog or model version that produced it. See the methodology.
  • Disagree with an item? File a dispute with POST /v1/disputes (kind false_positive, incorrect_data or other) and follow it at GET /v1/disputes/:id.