IEN
Agent credit bureau
Developers

LIEN API reference

REST API and TypeScript SDK for the agent credit bureau. Reads are public; writes use a Bearer key.

Basehttps://lien-api-production.up.railway.app/v1

Overview

LIEN is the credit bureau for autonomous agents. It scores payment behavior and exposes the result over a REST API and a TypeScript SDK so providers can decide who to extend post-paid terms to and react when standing changes.

EnvironmentURL
Live (mainnet)https://lien-api-production.up.railway.app/v1

All requests are HTTPS. All responses are JSON. Timestamps are RFC 3339 (UTC). Monetary amounts are integer USDC minor units (6 decimals — 1_000_000 = 1 USDC).

Status values are lowercase: good_standing, on_watch, defaulted. Reads are public — no key. Writes (settlements, attestation, link) require a Bearer key. Attestation validates end-to-end today, but the on-chain write to 8004 is pending the mainnet signer deploy — attested stays false until then.

Quickstart

Reads are public, so you can hit the API with nothing but curl:

bash
curl https://lien-api-production.up.railway.app/v1/report/FAQXa8Sv7foH53gV78u1Rbs1fwaCKrg8oxesCEyeNbEh

Then gate a tab from the SDK:

ts
import { Lien } from "@lien/sdk";

const lien = new Lien({ apiKey: process.env.LIEN_API_KEY, network: "mainnet" });

const credit = await lien.check(agentId);
if (credit.status !== "defaulted" && credit.limit) {
  openTab(agentId, credit.limit);
} else {
  requirePrepay(agentId);
}

When the period closes, report the outcome with lien.settlements.create so it feeds the next score.

Authentication

Reads are public. GET /score, GET /report, and GET /registry need no credentials.

Writes require a key. POST /settlements, POST /attest/:agent_id, and POST /agents/:agent_id/link require:

Authorization: Bearer sk_live_2pQ...

Keys are secret — use them server-side only. Missing or invalid keys on a write return 401 authentication_error.

bash
# read — no key needed
curl https://lien-api-production.up.railway.app/v1/score/FAQXa8Sv7foH53gV78u1Rbs1fwaCKrg8oxesCEyeNbEh

# write — key required
curl -X POST https://lien-api-production.up.railway.app/v1/settlements \
  -H "Authorization: Bearer $LIEN_API_KEY" -H "content-type: application/json" \
  -d '{"agent_id":"<id>","tab_id":"tab_91","amount":120000000,"on_time":true}'

Identity model

LIEN's canonical subject is an entry in LIEN's own registry, keyed by agent_id and resolved through one or more identity sources:

SourceWhat it providesagent_id isverified_8004
8004 registryIdentity, reputation, account age (Solana Agent Registry).the 8004 account addresstrue
Payment wallet / x402Real settlement behavior: volume, on-time, diversity, defaults.the wallet addressfalse

A score blends both sources when both are present; double-counting is avoided. Agents with no 8004 entry are scored purely from their payment behavior, so LIEN reaches far beyond the agents registered on 8004.

LIEN never mints identities on an agent's behalf — it observes existing identities and scores behavior. There is no on-boarding, no KYC, no registration step.

To merge a payment wallet into an existing 8004 agent's credit file, use POST /agents/:agent_id/link. Both the wallet and the 8004 owner must ed25519-sign the message lien:link:v1:<agent_id>:<wallet>.

Scoring model

Deterministic, computed over a trailing 90-day window from two input sources: the agent's 8004 reputation (identity age, feedback, counterparties) and LIEN's settlement ledger (outcomes reported via POST /settlements). Same inputs, same score. Agents with no ledger yet are bootstrapped from 8004 reputation alone (bootstrapped: true on the factor).

Factor (key)MeasuresWeight
on_time_rateShare of obligations closed on time.0.30
volumeTotal settled value in the window.0.25
account_ageDays the identity has been active.0.15
counterparty_diversityDistinct counterparties.0.15
defaultsCount of unsettled obligations (penalty).0.15

The weighted result maps to 300–850, bucketed into bands: poor <580, fair 580–669, good 670–739, very_good 740–799, excellent 800+. The recommended limit scales with the score and the agent's typical per-period volume, so a high score on a low-volume agent still yields a conservative ceiling.

Objects

The credit_score object

FieldTypeDescription
object"credit_score"Object type.
agent_idstringThe agent's identifier (8004 address or wallet).
scoreinteger300–850.
bandenumpoor | fair | good | very_good | excellent.
statusenumgood_standing | on_watch | defaulted.
limitlimit | nullRecommended post-paid ceiling; null if not eligible.
attestedbooleanWhether the score is written to the agent's 8004 record.
updated_atstringLast recomputation time.
json
{
  "object": "credit_score",
  "agent_id": "FAQXa8Sv7foH53gV78u1Rbs1fwaCKrg8oxesCEyeNbEh",
  "score": 782,
  "band": "very_good",
  "status": "good_standing",
  "limit": { "amount": 500000000, "currency": "USDC", "period": "week" },
  "attested": false,
  "updated_at": "2026-06-21T09:12:00Z"
}

The limit object

FieldTypeDescription
amountintegerCeiling in minor units (500000000 = 500 USDC).
currencystringSettlement asset (USDC).
periodenumday | week | month.

The factor object

FieldTypeDescription
keyenumon_time_rate | volume | account_age | counterparty_diversity | defaults.
valuenumberRaw measured value (e.g. 0.99 for a rate, 480000 for volume).
weightnumberFactor weight in the model (0–1).
contributionnumberWeighted share this factor added to the final score (0–1).
normalizednumbervalue mapped to 0–1 before weighting.
bootstrappedbooleantrue if inferred from 8004 reputation (no LIEN ledger yet).

The report object

Everything in credit_score, plus:

FieldTypeDescription
identityobject{ name, image, verified_8004 } — verified_8004=false for wallet-only agents.
factorsfactor[]Per-factor breakdown.
recent_settlementssettlement[]Most recent settlements (max 50).

The settlement object

FieldTypeDescription
object"settlement"Object type.
idstringSettlement id.
agent_idstringThe agent that settled.
tab_idstring | nullThe post-paid tab this closed, if any.
counterpartystringCounterparty address.
amountintegerMinor units.
currencystringAsset.
statusenumsettled | late | defaulted.
occurred_atstringTimestamp.

The list envelope

json
{
  "object": "list",
  "data": [ /* ... */ ],
  "has_more": true,
  "next_cursor": "FAQXa8Sv7foH53gV78u1Rbs1fwaCKrg8oxesCEyeNbEh"
}

Endpoints

MethodPathAuthReturns
GET/score/:agent_idpubliccredit_score
GET/report/:agent_idpublicreport
GET/registrypubliclist<credit_score>
POST/settlementsBearersettlement
POST/attest/:agent_idBearercredit_score
POST/agents/:agent_id/linkBearerlink

Retrieve a score

GET /score/:agent_id

Returns credit_score. Errors: agent_not_registered, not_found.

bash
curl https://lien-api-production.up.railway.app/v1/score/FAQXa8Sv7foH53gV78u1Rbs1fwaCKrg8oxesCEyeNbEh

Retrieve a full report

GET /report/:agent_id

Returns the report object — score + identity + factors + recent settlements. This is what the public profile page renders.

List the registry

GET /registry
Query paramTypeDescription
sortenumscore (default, desc) | volume | recent.
statusenumFilter by good_standing | on_watch | defaulted.
limitinteger1–100.
starting_afterstringCursor — agent_id of the last item.

Returns a paginated list of credit_score.

Report a settlement outcome

POST /settlements

Call this after an agent settles (or misses) a tab. Idempotent — pass an Idempotency-Key header; replays with the same key + body return the original result, a different body returns 409.

Body fieldTypeRequiredDescription
agent_idstringyesThe agent (8004 id or wallet).
tab_idstringyesThe tab being settled.
amountintegeryesMinor units settled.
on_timebooleanyesWhether it settled within the period.
counterpartystringnoProvider / counterparty address.
txstringnoSettlement transaction signature.
bash
curl -X POST https://lien-api-production.up.railway.app/v1/settlements \
  -H "Authorization: Bearer $LIEN_API_KEY" \
  -H "Idempotency-Key: stl_8f21a" \
  -d '{"agent_id":"<id>","tab_id":"tab_91","amount":120000000,"on_time":true}'

Returns the created settlement. A defaulted result transitions the agent to defaulted and fires agent.defaulted.

Attest a score on-chain

POST /attest/:agent_id

Writes the current score to the agent's 8004 reputation record. Requires the agent's signed feedback_auth; without it, returns 403 authorization_required.

The on-chain write is pending the mainnet signer/program deploy. Today this validates the authorization and returns the score; attested flips to true once the writer is live.

Link a wallet to an 8004 agent

POST /agents/:agent_id/link

Merges a payment wallet into an existing 8004 agent so they share one credit file. Requires ed25519 signatures from BOTH the wallet and the 8004 owner over the canonical message lien:link:v1:<agent_id>:<wallet>.

Body fieldTypeRequiredDescription
walletstringyesWallet address to link.
wallet_signaturestringyesed25519 signature by the wallet.
owner_signaturestringyesed25519 signature by the 8004 owner.

Pagination

List endpoints are cursor-paginated. Pass limit (1–100, default 25) and starting_after (the agent_id of the last item from the previous page).

Errors

json
{
  "error": {
    "type": "agent_not_registered",
    "message": "No 8004 identity and no settlement history for this agent",
    "param": "agent_id"
  }
}
HTTPtypeWhen
400invalid_requestMalformed parameter or body.
401authentication_errorMissing or invalid API key.
403authorization_requiredWrite requires the agent's signed authorization.
404not_foundNo such resource.
404agent_not_registeredNo 8004 identity and no settlement history yet.
409idempotency_conflictIdempotency key reused with a different body.
429rate_limitedToo many requests.
5xxapi_errorSomething broke on our side; safe to retry.

Rate limits

Per client IP, default 120 requests / minute. Every response carries x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset. Exceeding returns 429 rate_limited with retry-after. Back off and retry.

Versioning

The API is versioned in the path (/v1). Additive changes (new fields, new endpoints, new enum members) are non-breaking and may ship at any time — write clients that ignore unknown fields. Breaking changes ship under a new path version.

Try it

Two live calls against the production API. These are public reads — no key required.

GET /registry?limit=10
Loading…
GET /score/{agent_id}
Enter an agent id and press Fetch.

TypeScript SDK

bash
npm install @lien/sdk
ts
import { Lien } from "@lien/sdk";

const lien = new Lien({
  apiKey: process.env.LIEN_API_KEY,   // required only for writes
  network: "mainnet"
});
MethodReturnsDescription
lien.check(agentId)CreditScoreCompact score (GET /score).
lien.report(agentId)ReportFull report (GET /report).
lien.registry(params?)Page<CreditScore>Paginated registry.
lien.settlements.create(body, { idempotencyKey })SettlementReport a tab outcome.
lien.x402.authorize(payerWallet){ creditworthy, limit, score }Credit decision for an x402 payer; unknown → require prepay.
lien.x402.reportPayment(body)SettlementReport an x402 payment (payer wallet = agent_id).
lien.link(agentId, sigs)LinkLink a wallet to an 8004 agent.
lien.attest(agentId, { feedbackAuth })CreditScoreWrite attestation.
Lien.webhooks.constructEvent(body, sig, secret)EventVerify + parse a webhook.

Errors throw a typed LienError carrying the HTTP status and error type:

ts
import { Lien, LienError } from "@lien/sdk";

try {
  const credit = await lien.check(agentId);
  if (credit.status !== "defaulted" && credit.limit) openTab(agentId, credit.limit);
} catch (e) {
  if (e instanceof LienError && e.type === "agent_not_registered") {
    requirePrepay(agentId);
  } else {
    throw e;
  }
}

Post-paid integration

The provider-side lifecycle:

  1. On first access, lien.check(agentId). If defaulted or no limit, require prepay and stop.
  2. Open a tab in your billing system using limit.amount / limit.period.
  3. Meter the agent's usage against the tab.
  4. At period end, settle net via x402 (or your rail).
  5. Call lien.settlements.create(...) so the outcome feeds the next score.
  6. Subscribe to agent.defaulted to close tabs mid-period if standing drops.
ts
const credit = await lien.check(agentId);
if (credit.status === "defaulted" || !credit.limit) return requirePrepay(agentId);

const tab = billing.openTab(agentId, credit.limit);
// ... usage accrues over the period ...
const result = await billing.settle(tab);            // net settlement

await lien.settlements.create(
  { agent_id: agentId, tab_id: tab.id, amount: result.amount, on_time: result.onTime },
  { idempotencyKey: result.id }
);

x402 payers

For x402 specifically, every payment — prepaid or post-paid — can be reported via lien.x402.reportPayment to build the payer wallet's credit file. The wallet is the agent_id.

ts
// Before serving a paid resource
const decision = await lien.x402.authorize(payerWallet);
if (!decision.creditworthy) return require402(payerWallet);

// After the payment settles (prepaid or net)
await lien.x402.reportPayment({
  payer: payerWallet,
  amount: 120000000,
  resource: "/v1/translate",
  tx: txSig,
  onTime: true,
});

Webhooks

Register an endpoint and receive an event object instead of polling:

json
{
  "id": "evt_2a9...",
  "type": "score.updated",
  "created": "2026-06-21T09:12:00Z",
  "data": { /* the affected credit_score or settlement */ }
}
Event typeFires when
score.updatedAn agent's score, band, or status changes.
agent.defaultedAn agent enters defaulted.
agent.recoveredA defaulted agent returns to on_watch/good_standing.
attestation.writtenA score was written on-chain to an agent's 8004 record.
tab.settlement_due(planned) A tab's settlement period is closing.

Verify signatures. Each delivery includes a LIEN-Signature header — HMAC SHA-256 of the raw body keyed with your webhook secret. Reject anything that doesn't match.

ts
import { Lien } from "@lien/sdk";

const event = Lien.webhooks.constructEvent(rawBody, sig, webhookSecret);
if (event.type === "agent.defaulted") closeTab(event.data.agent_id);

Non-2xx deliveries are retried with exponential backoff for 24h.

FAQ

Can LIEN score any agent?

Yes. Reads are permissionless. An agent is recognized by its 8004 registry entry or simply by its payment wallet — no registration required. Writing a score onto an agent's 8004 record needs the agent's opt-in feedback_auth.

Is the score deterministic?

Yes. Same inputs, same score.

What happens on default?

The agent is marked defaulted and drops to prepay-only across providers that read LIEN. There is no staking and no collateral to slash — the consequence is loss of post-paid terms.

What chains?

Solana first, live on mainnet. ERC-8004 is cross-chain by design, so records are portable as the ecosystem expands.