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.
| Environment | URL |
|---|---|
| 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).
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:
curl https://lien-api-production.up.railway.app/v1/report/FAQXa8Sv7foH53gV78u1Rbs1fwaCKrg8oxesCEyeNbEhThen gate a tab from the SDK:
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.
# 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:
| Source | What it provides | agent_id is | verified_8004 |
|---|---|---|---|
8004 registry | Identity, reputation, account age (Solana Agent Registry). | the 8004 account address | true |
Payment wallet / x402 | Real settlement behavior: volume, on-time, diversity, defaults. | the wallet address | false |
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.
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) | Measures | Weight |
|---|---|---|
on_time_rate | Share of obligations closed on time. | 0.30 |
volume | Total settled value in the window. | 0.25 |
account_age | Days the identity has been active. | 0.15 |
counterparty_diversity | Distinct counterparties. | 0.15 |
defaults | Count 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
| Field | Type | Description |
|---|---|---|
| object | "credit_score" | Object type. |
| agent_id | string | The agent's identifier (8004 address or wallet). |
| score | integer | 300–850. |
| band | enum | poor | fair | good | very_good | excellent. |
| status | enum | good_standing | on_watch | defaulted. |
| limit | limit | null | Recommended post-paid ceiling; null if not eligible. |
| attested | boolean | Whether the score is written to the agent's 8004 record. |
| updated_at | string | Last recomputation time. |
{
"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
| Field | Type | Description |
|---|---|---|
| amount | integer | Ceiling in minor units (500000000 = 500 USDC). |
| currency | string | Settlement asset (USDC). |
| period | enum | day | week | month. |
The factor object
| Field | Type | Description |
|---|---|---|
| key | enum | on_time_rate | volume | account_age | counterparty_diversity | defaults. |
| value | number | Raw measured value (e.g. 0.99 for a rate, 480000 for volume). |
| weight | number | Factor weight in the model (0–1). |
| contribution | number | Weighted share this factor added to the final score (0–1). |
| normalized | number | value mapped to 0–1 before weighting. |
| bootstrapped | boolean | true if inferred from 8004 reputation (no LIEN ledger yet). |
The report object
Everything in credit_score, plus:
| Field | Type | Description |
|---|---|---|
| identity | object | { name, image, verified_8004 } — verified_8004=false for wallet-only agents. |
| factors | factor[] | Per-factor breakdown. |
| recent_settlements | settlement[] | Most recent settlements (max 50). |
The settlement object
| Field | Type | Description |
|---|---|---|
| object | "settlement" | Object type. |
| id | string | Settlement id. |
| agent_id | string | The agent that settled. |
| tab_id | string | null | The post-paid tab this closed, if any. |
| counterparty | string | Counterparty address. |
| amount | integer | Minor units. |
| currency | string | Asset. |
| status | enum | settled | late | defaulted. |
| occurred_at | string | Timestamp. |
The list envelope
{
"object": "list",
"data": [ /* ... */ ],
"has_more": true,
"next_cursor": "FAQXa8Sv7foH53gV78u1Rbs1fwaCKrg8oxesCEyeNbEh"
}Endpoints
| Method | Path | Auth | Returns |
|---|---|---|---|
| GET | /score/:agent_id | public | credit_score |
| GET | /report/:agent_id | public | report |
| GET | /registry | public | list<credit_score> |
| POST | /settlements | Bearer | settlement |
| POST | /attest/:agent_id | Bearer | credit_score |
| POST | /agents/:agent_id/link | Bearer | link |
Retrieve a score
GET /score/:agent_idReturns credit_score. Errors: agent_not_registered, not_found.
curl https://lien-api-production.up.railway.app/v1/score/FAQXa8Sv7foH53gV78u1Rbs1fwaCKrg8oxesCEyeNbEhRetrieve a full report
GET /report/:agent_idReturns the report object — score + identity + factors + recent settlements. This is what the public profile page renders.
List the registry
GET /registry| Query param | Type | Description |
|---|---|---|
| sort | enum | score (default, desc) | volume | recent. |
| status | enum | Filter by good_standing | on_watch | defaulted. |
| limit | integer | 1–100. |
| starting_after | string | Cursor — agent_id of the last item. |
Returns a paginated list of credit_score.
Report a settlement outcome
POST /settlementsCall 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 field | Type | Required | Description |
|---|---|---|---|
| agent_id | string | yes | The agent (8004 id or wallet). |
| tab_id | string | yes | The tab being settled. |
| amount | integer | yes | Minor units settled. |
| on_time | boolean | yes | Whether it settled within the period. |
| counterparty | string | no | Provider / counterparty address. |
| tx | string | no | Settlement transaction signature. |
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_idWrites the current score to the agent's 8004 reputation record. Requires the agent's signed feedback_auth; without it, returns 403 authorization_required.
attested flips to true once the writer is live.Link a wallet to an 8004 agent
POST /agents/:agent_id/linkMerges 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 field | Type | Required | Description |
|---|---|---|---|
| wallet | string | yes | Wallet address to link. |
| wallet_signature | string | yes | ed25519 signature by the wallet. |
| owner_signature | string | yes | ed25519 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
{
"error": {
"type": "agent_not_registered",
"message": "No 8004 identity and no settlement history for this agent",
"param": "agent_id"
}
}| HTTP | type | When |
|---|---|---|
| 400 | invalid_request | Malformed parameter or body. |
| 401 | authentication_error | Missing or invalid API key. |
| 403 | authorization_required | Write requires the agent's signed authorization. |
| 404 | not_found | No such resource. |
| 404 | agent_not_registered | No 8004 identity and no settlement history yet. |
| 409 | idempotency_conflict | Idempotency key reused with a different body. |
| 429 | rate_limited | Too many requests. |
| 5xx | api_error | Something 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.
TypeScript SDK
npm install @lien/sdkimport { Lien } from "@lien/sdk";
const lien = new Lien({
apiKey: process.env.LIEN_API_KEY, // required only for writes
network: "mainnet"
});| Method | Returns | Description |
|---|---|---|
lien.check(agentId) | CreditScore | Compact score (GET /score). |
lien.report(agentId) | Report | Full report (GET /report). |
lien.registry(params?) | Page<CreditScore> | Paginated registry. |
lien.settlements.create(body, { idempotencyKey }) | Settlement | Report a tab outcome. |
lien.x402.authorize(payerWallet) | { creditworthy, limit, score } | Credit decision for an x402 payer; unknown → require prepay. |
lien.x402.reportPayment(body) | Settlement | Report an x402 payment (payer wallet = agent_id). |
lien.link(agentId, sigs) | Link | Link a wallet to an 8004 agent. |
lien.attest(agentId, { feedbackAuth }) | CreditScore | Write attestation. |
Lien.webhooks.constructEvent(body, sig, secret) | Event | Verify + parse a webhook. |
Errors throw a typed LienError carrying the HTTP status and error type:
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:
- On first access,
lien.check(agentId). Ifdefaultedor nolimit, require prepay and stop. - Open a tab in your billing system using
limit.amount/limit.period. - Meter the agent's usage against the tab.
- At period end, settle net via x402 (or your rail).
- Call
lien.settlements.create(...)so the outcome feeds the next score. - Subscribe to
agent.defaultedto close tabs mid-period if standing drops.
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.
// 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:
{
"id": "evt_2a9...",
"type": "score.updated",
"created": "2026-06-21T09:12:00Z",
"data": { /* the affected credit_score or settlement */ }
}| Event type | Fires when |
|---|---|
score.updated | An agent's score, band, or status changes. |
agent.defaulted | An agent enters defaulted. |
agent.recovered | A defaulted agent returns to on_watch/good_standing. |
attestation.written | A 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.
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.
