Borrowing
Open Loan
Pledge security tokens as collateral and borrow the settlement asset
POST
/
lending-external-securities-v2
/
api
/
positions
/
{marketId}
/
open-loan
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/positions/{marketId}/open-loan" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"collateralAmount": "1000",
"borrowAmount": "50000",
"borrowerAddress": "0xabc7f1093d5e26b804a1c3f78de025916b47c0d3",
"clientReference": "CL-000417"
}'
const url = `https://api.trusset.org/lending-external-securities-v2/api/positions/${marketId}/open-loan`;
const headers = {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
};
const res = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({
collateralAmount: '1000',
borrowAmount: '50000',
borrowerAddress: await wallet.getAddress(),
clientReference: 'CL-000417',
signedPrice: {
price: signed.price,
timestamp: signed.timestamp,
validUntil: signed.validUntil,
signature: signed.signature
}
})
});
const { data } = await res.json();
const hashes: string[] = [];
for (const step of data.steps) {
const tx = await wallet.sendTransaction(step.transaction);
await tx.wait();
hashes.push(tx.hash);
}
const confirmed = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ txHash: hashes[data.confirmStepIndex], clientReference: 'CL-000417' })
});
const { data: loan } = await confirmed.json();
{
"success": true,
"data": {
"txHash": "0x9f2c41d8b7e05a3164c2870fbd935e1a4c7802db6135ea9048f7c21b5d3ea41b",
"loanId": "5",
"positionId": "cmswb4n1e0021cghx9k2t6pdm",
"collateralAmount": "1000.0",
"borrowAmount": "50000.0",
"payeeAddress": null,
"paidTo": "0xabc7f1093d5e26b804a1c3f78de025916b47c0d3",
"customerId": "cmt0k3b9x0004cghxw7r2d5ya",
"clientReference": "CL-000417",
"attribution": {
"customerId": "cmt0k3b9x0004cghxw7r2d5ya",
"attributedAt": "2026-09-26T10:14:07.000Z",
"changed": true,
"previousCustomerId": null
},
"vaultDraws": [
{
"vault": "0x4d0cfe39a3431b145d1a1393d901a36d459a1b13",
"amount": "12500.0"
}
]
}
}
{
"success": true,
"data": {
"action": "SIGN_TRANSACTIONS",
"steps": [
{
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x9f8c1d4b2e7a3056c1b8f4d29e0a7c3518b6d24f",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "approve",
"description": "Approve the custody adapter to pull collateral"
},
{
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "openLoan"
}
],
"mode": "CUSTODY",
"confirmStepIndex": 1,
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/positions/cmssh7p1k0001cghxq2m4v8rz/open-loan",
"field": "txHash",
"body": { "clientReference": "CL-000417", "txHash": null }
},
"payeeAddress": null,
"payee": null,
"offering": {
"approximateDueAt": "1797004800",
"termGracePeriodSeconds": "259200",
"termPenaltyBps": 200,
"stampedAtSigning": ["dueAt", "termGracePeriod", "termPenalty"],
"termStamping": "PER_LOAN",
"appliesToOpenLoans": []
},
"transactionFee": {
"kind": "OPEN",
"fee": "2.0",
"feeRaw": "2000000",
"borrowed": "50000.0",
"netDisbursed": "49998.0",
"netDisbursedRaw": "49998000000",
"message": "This market charges a loan opening fee of 2.0 USDC on this draw, withheld from the amount paid out: the debt grows by 50000.0 USDC and the borrower receives 49998.0 USDC."
},
"warnings": [
"This market charges a loan opening fee of 2.0 USDC on this draw, withheld from the amount paid out: the debt grows by 50000.0 USDC and the borrower receives 49998.0 USDC."
],
"clientReference": "CL-000417",
"customerId": "cmt0k3b9x0004cghxw7r2d5ya",
"confirmWithClientReference": true
}
}
{
"success": true,
"data": {
"action": "SIGN_TRANSACTIONS",
"steps": [
{
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "openLoan"
}
],
"mode": "FREEZE",
"confirmStepIndex": 0,
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/positions/cmssh7p1k0001cghxq2m4v8rz/open-loan",
"field": "txHash"
},
"payeeAddress": null,
"payee": null
}
}
{
"success": true,
"data": {
"action": "SIGN_TRANSACTIONS",
"steps": [
{
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "openLoan",
"description": "Open the loan and pay the proceeds to 0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9"
}
],
"mode": "FREEZE",
"confirmStepIndex": 0,
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/positions/cmssh7p1k0001cghxq2m4v8rz/open-loan",
"field": "txHash",
"body": { "clientReference": "CL-000417", "txHash": null }
},
"payeeAddress": "0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9",
"payee": {
"address": "0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9",
"borrowerOfRecord": "0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3",
"receives": "borrowAmount minus the open fee",
"netDisbursed": "50000.0",
"fixedForLife": true,
"receivesBorrowMore": true
},
"warnings": [
"The proceeds are paid to 0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9, not to the signing wallet: it receives the full 50000.0 USDC. 0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3 signs, provides the collateral and stays the borrower of record: every collateral return, any liquidation surplus and every surplus or escrow claim go back to it. The payee is fixed for the life of the loan and also receives every later borrowMore draw."
],
"clientReference": "CL-000417",
"customerId": "cmt0k3b9x0004cghxw7r2d5ya",
"confirmWithClientReference": true
}
}
{
"success": false,
"error": {
"code": "CLIENT_NOT_ELIGIBLE",
"message": "The client CL-000417 is recorded as rejected on this instance, so no loan is booked against it.",
"details": { "reason": "REJECTED", "clientReference": "CL-000417" }
}
}
{
"success": false,
"error": {
"code": "PAYEE_NOT_VERIFIED",
"message": "The payee 0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9 is not admitted to the Professional eligibility register (0x4f2a9c81d06e3b75a1c8e24d9b07f56e3a1d8c90), which this market applies to the payee as well as the borrower, so the market would refuse the payout (PayeeNotVerified). Onboard the payee wallet under the Professional profile and admit it, or omit payToAddress to pay the borrower.",
"details": {
"payee": "0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9",
"register": "0x4f2a9c81d06e3b75a1c8e24d9b07f56e3a1d8c90",
"requiredProfile": {
"key": "PROFESSIONAL",
"name": "Professional",
"entityType": "INDIVIDUAL",
"professionalOnly": true,
"requirementsHash": "0x8919ea37bac1f44ea0e8a37529be970cb49e9779ebd4c4e98490cfd754e9d390"
},
"accessList": null
}
}
}
{
"success": false,
"error": {
"code": "COLLATERAL_ADAPTER_NOT_AUTHORIZED",
"message": "The custody adapter is not allow-listed on the collateral token yet. Complete market configuration (authorizeAdapterOnToken) before opening loans."
}
}
{
"success": false,
"error": {
"code": "EXCEEDS_MAX_BORROW",
"message": "That collateral supports at most 48250.0 USDC of borrowing."
}
}
{
"success": false,
"error": {
"code": "MARKET_PENDING_CURATOR",
"message": "This market has no lender of record yet, so opening a loan is not possible. A nominated candidate must take the role before the market can hold liquidity or collateral."
}
}
{
"success": false,
"error": {
"code": "PROFESSIONAL_BORROWER_GATE_REQUIRED",
"message": "This market prices its collateral from a live market price, which carries the free-hand realization path under § 1259 BGB and is only available against professional borrowers. The borrower register 0x9e3c8a15f4d70b2681c5a3f9027d4be8115c7d21 admits wallets for a verification profile of another instance, which is not limited to professional clients. Choose the register of a profile limited to professional clients as the borrower gate. Borrowing stays closed until the market admin sets that borrower gate under Gates on the operator desk.",
"details": { "field": "borrowerRegistry" }
}
}
Opens an overcollateralized loan. The borrower pledges security tokens and receives the market’s borrow asset, and the borrower’s wallet signs every step. How the collateral is secured depends on the market’s mode:
The opening fee is the other cost fixed at signing. A market may carry a flat opening fee, stamped when it was deployed and never changeable afterwards. Read it from
The build call refuses a payee the transaction could not pay:
Confirming with
FREEZE locks the tokens in the borrower’s own wallet, CUSTODY escrows them in the adapter.
The maximum borrow is the collateral value multiplied by the market’s collateral factor. Quote it first with Get Max Borrow. On a market that charges an opening fee, the borrower receives the borrow amount less the fee and owes the full borrow amount.
Path Parameters
string
required
Body Parameters
string
Security tokens to pledge, as a positive decimal string. Parsed at the collateral token’s decimals. Required when building calldata. Not needed on the confirm call, where the pledge is read from the mined transaction. On a token whose ERC-1400
granularity() is above one base unit, it must be a whole multiple of the granularity.string
Amount to borrow, as a positive decimal string. Parsed at the borrow asset’s decimals. Must be at least the market’s
minBorrowAmount. This is the debt the loan records, before any opening fee is withheld from the payout. Required when building calldata, not needed on the confirm call.string
Wallet that will sign the loan and hold the collateral. Required when building calldata. Every collateral, identity and supply-cap check runs against this address, and there is no loan to resolve it from yet. Omitting it returns
BORROWER_ADDRESS_REQUIRED. Not needed on the confirm call, where the borrower is taken from the mined transaction.object
EIP-712 signed price from Sign Price, applied atomically with the loan. Omit to price against the stored oracle value, which must not be stale. A market that prices from an external feed refuses a signed price with
EXTERNAL_FEED_ACTIVE, and a market with a fixed price refuses one with FIXED_PRICE_ACTIVE, because its oracle accepts none.string
The client this loan is for, as the customer’s
referenceKey, its externalId or its customer ID. Up to 120 characters, matched against the customers of your instance that are not archived. Create the record first with Create Customer.Send it on the build call and again on the confirm call. The build call checks the client before anything is signed and refuses an ineligible one with 409 CLIENT_NOT_ELIGIBLE, see Client of record. The confirm call checks it again and records the attribution. A loan built without it can be signed and mined for a client the confirm call then refuses, and such a loan can only be recorded without a client. The fields customerId, clientId, clientReferenceKey and externalId are refused with CLIENT_REFERENCE_FIELD, and onBehalfOf with KEY_MATERIAL_REJECTED.string
Wallet that receives the borrowed amount instead of the signing wallet. The signer stays the borrower of record. See Pay the proceeds to another wallet. Omitted,
null, an empty string or the borrower’s own address pays the borrower. Build call only: the confirm call reads the payee from the mined transaction.boolean
Set
true on the confirm call to move a loan that is already attributed to another client of your instance. Without it that confirm is refused with CLIENT_REFERENCE_CONFLICT. The change is recorded against both clients with the time it was made.string
Hash of the transaction you broadcast for this operation. Send it to confirm the transaction and record the result. Omit it to receive the calldata.
Transaction Shape by Mode
WhentxHash is omitted the response is always SIGN_TRANSACTIONS with a mode field, and the step count depends on the market.
CUSTODY returns two steps: approve on the collateral token for the adapter, then openLoan. Broadcast in order.
FREEZE returns one step: openLoan. No approval exists because the tokens never leave the borrower’s wallet.
Both shapes carry confirmStepIndex, the index of the openLoan step whose hash confirms the loan, and confirmWith, which names this route and the body to confirm with.
What the loan will cost and when it is due
The calldata response carries anoffering block whenever the market runs loan terms or stamps a fixed rate. A market on the default curve with no term returns no offering at all. Show it before the borrower signs: it is the only place the due date and the stamped rate appear ahead of the loan existing.
object
Show child attributes
Show child attributes
string
Unix seconds when this loan would fall due, computed at build time from the market’s term config. Present only when the market runs terms. It is approximate because the clock starts when the transaction mines, not when the payload was built.
string
Seconds after the due date before the term penalty applies. Present only when the market runs terms.
integer
The term penalty in basis points. Present only when the market runs terms.
integer
The rate this loan would be stamped with, in basis points. Present only when the market’s interest model stamps a fixed rate. A stamped rate does not move with utilization for the life of the loan.
array
The figures stamped onto the loan when the transaction mines. Always
dueAt, plus fixedRate when a fixed rate is stamped. A market that stamps terms per loan adds termGracePeriod and termPenalty.string
PER_LOAN when the market stamps the grace period and term penalty onto each loan at signing. MARKET_CONFIG when open loans follow the live term configuration instead. UNKNOWN when that could not be read.array
Term settings a later configuration change would still reach on this loan. Empty on a
PER_LOAN market, termGracePeriod and termPenalty otherwise.transactionFees on Get Fee Split. Each charge is the flat fee or 0.5 percent of the amount borrowed, whichever is smaller. When it is not zero, the calldata response carries a transactionFee block.
object
Show child attributes
Show child attributes
string
OPEN.string
The fee this loan would pay, in borrow asset units.
string
The same fee in the borrow asset’s smallest unit.
string
The debt the loan records, equal to
borrowAmount.string
What the borrower receives:
borrowed less fee.string
The same amount in the smallest unit.
string
Plain-language statement of the fee, the debt and the payout.
Warnings
The calldata response carries awarnings array when a preflight found something the borrower should see but that does not block the loan. The array is absent when there is nothing to say. Object entries carry kind, enforcement, message and note.
kind | What it means |
|---|---|
TERMS_NOT_PUBLISHED | The lender of record has published no terms of use, so the loan is governed by none |
MATURITY_APPROACHING | The collateral instrument’s recorded maturity falls inside the warning window. Carries maturityDate, daysRemaining, windowDays and assertedBy |
MATURITY_RECORDED | The instrument has a recorded maturity further out. Carries the same fields |
MATURITY_PASSED_FOREIGN_RECORD | The recorded maturity has passed, but it was asserted by an instance that is not a party to this market, so the loan is not refused on it |
MATURITY_UNCHECKED | The recorded maturity could not be read, so this preflight did not check it |
enforcement is OFF_CHAIN on every one of these. The contract does not know about them, so acting on a warning is the caller’s decision. When the market charges an opening fee, the array also carries transactionFee.message as a plain string rather than an object. With a payToAddress, that string is the statement of who receives the proceeds instead, and transactionFee.message names the payee.
Professional borrowers
On aMARKET-priced market the API builds no loan until the market’s borrower register qualifies as a professional gate, and refuses with 409 PROFESSIONAL_BORROWER_GATE_REQUIRED, details.field: "borrowerRegistry". The register qualifies when at least one verification profile admits wallets to it, and every profile that does, on any instance of the market’s network and environment, is limited to professional clients and has its admissions signed by its own instance. See Professional borrowers.
The check runs right after the lender-of-record check, before the adapter, pause and term checks. It reads the market’s price source and borrower register from the chain, and the profiles from Trusset’s records. When either read fails the call answers 503, MARKET_STATE_UNAVAILABLE or STATE_UNAVAILABLE, and builds nothing. The market contract does not apply this rule, so it holds only for transactions built through this API.
Client of record
AclientReference is checked on the build call and on the confirm call, before anything else: before the market’s own checks on the build call, and before the transaction is read on the confirm call. Send it on both. The build call is the one that refuses an ineligible client before anything is signed. The loan is refused with 409 CLIENT_NOT_ELIGIBLE when the client:
- is recorded as
rejectedorrevoked(details.reasonisREJECTEDorREVOKED); - is recorded in a country that the verification profile it was onboarded under blocks, or that a profile behind the market’s borrower register blocks (
COUNTRY_BLOCKED). A profile in theEXTERNALmode is not counted; - is not a professional client, on a
MARKET-priced market (NOT_PROFESSIONAL, orNOT_VERIFIEDfor a client typed professional whose verification on your instance is not complete).
verified and carries the verifiedAt of a verification completed here (a verify, an ID link or a registration confirmed on this instance), with no revocation since. A verified status set through Update Customer marks the record only and does not count. An admission recorded by another instance does not count either, even to this market’s own borrower register. details.clientReference names the client.
The borrower register is read from the market at call time, so a register the market admin changed after the market was recorded is the one applied. When it cannot be read the call answers 503 MARKET_STATE_UNAVAILABLE and builds nothing.
Pay the proceeds to another wallet
payToAddress sends the borrowed amount, less any opening fee, to a wallet other than the one that signs. The signer stays the borrower of record: it provides the collateral, owes the debt, and receives every collateral return, any liquidation surplus and every escrow or surplus claim. The payee is fixed for the life of the loan and receives every later Borrow More draw as well. The calldata calls the openLoan overload that takes the payee.
The market examines the payee against its borrower register, exactly as it examines the borrower, at opening and at every later draw. A payee the register no longer admits blocks every further draw on the loan until it is admitted again, because the payee cannot be changed. Repaying, adding collateral and closing stay open.
- A market whose implementation predates pay-to answers
409 PAY_TO_UNSUPPORTED. Its admin upgrades it with Upgrade Market.payToSupportedon List Markets shows which markets take a payee. - The payee may not be one of the market’s own contracts or a contract it is wired to: the market, its liquidation router, its collateral adapter, its auction venue, its insurance fund, the settlement token (stablecoin), the collateral token, the oracle, the interest rate model or a vault registered on the market. That is refused with
400 PAY_TO_NOT_ALLOWED, anddetails.reasonnames which. The market itself refuses the first five. It would pay the others, and the proceeds would be lost to the borrower while the full debt stays owed. - A payee that the borrower register does not admit is refused with
400 PAYEE_NOT_VERIFIED.detailscarriespayee,register, andrequiredProfileoraccessListwhen the register is one of your profiles’ eligibility registers or a whitelist. A payee whose removal from that register is pending is refused with409 ELIGIBILITY_REMOVAL_PENDING. When that register is the previous Trusset ID Register, a payee it does not admit is refused with409 IDENTITY_GATE_RETIREDinstead, withdetails.gateborroweranddetails.payee, because that register takes no new identities.
payeeAddress and a payee block, and its warnings state who receives what. The confirm call returns the payee the market recorded for the loan, read from its LoanPayeeSet event, and the ledger books the draw and the opening fee against the payee.
Vault draws
Some markets draw on vault liquidity, shown asuseVaultLiquidity on Get Market Metrics. There, a borrow larger than the pool’s borrowable liquidity pulls the shortfall from the registered vaults inside the borrower’s own transaction. The draw is the smallest amount that covers the shortfall with the reserve earmark intact. The confirm response lists each draw in vaultDraws.
The API refuses such a loan only when no draw could fund it. INSUFFICIENT_LIQUIDITY then names what the connected vaults can draw right now. A market whose implementation still holds a draw into an empty pool to the first-deposit floor refuses a smaller draw with FIRST_DRAW_BELOW_FLOOR, and the message names the remedies.
Response Fields
Confirming returns201.
object
Show child attributes
Show child attributes
string
SIGN_TRANSACTIONS. Returned when txHash is omitted.string
FREEZE or CUSTODY. Returned when txHash is omitted.array
Ordered transactions to broadcast. Returned when
txHash is omitted.integer
Index of the
openLoan step. 1 in CUSTODY, 0 in FREEZE.object
{ endpoint, field, body }: this route, txHash as the field, and a body carrying clientReference with txHash: null when the build call named a client. Confirm with the hash of steps[confirmStepIndex]. Returned when txHash is omitted.string
The payee, checksummed, when
payToAddress names a wallet other than the borrower. null otherwise. Returned when txHash is omitted.object
{ address, borrowerOfRecord, receives, netDisbursed, fixedForLife, receivesBorrowMore }: who receives the proceeds and how much, net of any opening fee. null when the borrower is paid. Returned when txHash is omitted.object
object
The opening fee, present only when it is not zero.
boolean
true when clientReference passed the check on the build call. Send it again on the confirm call.string
Transaction hash. Returned when confirming.
string
On-chain loan ID, parsed from the
LoanOpened event. Use this on every subsequent loan operation. Returned when confirming.string
Trusset position record ID, or
null if the record could not be written. Returned when confirming.string
Collateral the loan holds, read from the loan after the transaction mined. Returned when confirming.
string
The loan’s principal, read after the transaction mined. It is the full debt, including any opening fee withheld from the payout. Returned when confirming.
string
The payee the market recorded for the loan, lowercased, or
null when the borrower was paid. Returned when confirming.string
The wallet that received the proceeds: the payee, or the borrower when there is none. Returned when confirming.
string
Customer the loan is attributed to, or
null. On the build call, the customer clientReference resolved to.string
The resolved client’s
externalId, else its referenceKey, else its customer ID. null on a confirm call that carried none.object
Returned when confirming.
customerId, attributedAt, changed (whether this confirm attributed or moved the loan) and previousCustomerId (the client it moved from, when it moved).array
Present when the transaction drew on vault liquidity. Each entry carries
vault (the vault address) and amount (in borrow asset units).string
receipt-log when the transaction reached the market through another contract and was verified from the market’s LoanOpened event. Carries outerTarget and outerSender beside it. Absent otherwise.Loans cannot be opened until the market has a lender of record and its collateral adapter is authorized on the token. A market still seeking a lender of record is refused on that ground first, with
MARKET_PENDING_CURATOR. The token-side authorizations are the register provider’s to arrange, so naming them as the obstacle would send the borrower to the wrong party. Once the role is taken, an unauthorized adapter rejects with COLLATERAL_ADAPTER_NOT_AUTHORIZED or FREEZE_MODE_UNSUPPORTED_TOKEN. See Get Setup Steps.Minimum and maximum borrow are checked when the calldata is built, not when you confirm. Broadcasting an out-of-range amount reverts on-chain rather than returning
BELOW_MIN_BORROW or EXCEEDS_MAX_BORROW. With a signedPrice, the maximum borrow is computed at the signed price, because the contract applies it before it prices the collateral.curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/positions/{marketId}/open-loan" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"collateralAmount": "1000",
"borrowAmount": "50000",
"borrowerAddress": "0xabc7f1093d5e26b804a1c3f78de025916b47c0d3",
"clientReference": "CL-000417"
}'
const url = `https://api.trusset.org/lending-external-securities-v2/api/positions/${marketId}/open-loan`;
const headers = {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
};
const res = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({
collateralAmount: '1000',
borrowAmount: '50000',
borrowerAddress: await wallet.getAddress(),
clientReference: 'CL-000417',
signedPrice: {
price: signed.price,
timestamp: signed.timestamp,
validUntil: signed.validUntil,
signature: signed.signature
}
})
});
const { data } = await res.json();
const hashes: string[] = [];
for (const step of data.steps) {
const tx = await wallet.sendTransaction(step.transaction);
await tx.wait();
hashes.push(tx.hash);
}
const confirmed = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ txHash: hashes[data.confirmStepIndex], clientReference: 'CL-000417' })
});
const { data: loan } = await confirmed.json();
{
"success": true,
"data": {
"txHash": "0x9f2c41d8b7e05a3164c2870fbd935e1a4c7802db6135ea9048f7c21b5d3ea41b",
"loanId": "5",
"positionId": "cmswb4n1e0021cghx9k2t6pdm",
"collateralAmount": "1000.0",
"borrowAmount": "50000.0",
"payeeAddress": null,
"paidTo": "0xabc7f1093d5e26b804a1c3f78de025916b47c0d3",
"customerId": "cmt0k3b9x0004cghxw7r2d5ya",
"clientReference": "CL-000417",
"attribution": {
"customerId": "cmt0k3b9x0004cghxw7r2d5ya",
"attributedAt": "2026-09-26T10:14:07.000Z",
"changed": true,
"previousCustomerId": null
},
"vaultDraws": [
{
"vault": "0x4d0cfe39a3431b145d1a1393d901a36d459a1b13",
"amount": "12500.0"
}
]
}
}
{
"success": true,
"data": {
"action": "SIGN_TRANSACTIONS",
"steps": [
{
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x9f8c1d4b2e7a3056c1b8f4d29e0a7c3518b6d24f",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "approve",
"description": "Approve the custody adapter to pull collateral"
},
{
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "openLoan"
}
],
"mode": "CUSTODY",
"confirmStepIndex": 1,
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/positions/cmssh7p1k0001cghxq2m4v8rz/open-loan",
"field": "txHash",
"body": { "clientReference": "CL-000417", "txHash": null }
},
"payeeAddress": null,
"payee": null,
"offering": {
"approximateDueAt": "1797004800",
"termGracePeriodSeconds": "259200",
"termPenaltyBps": 200,
"stampedAtSigning": ["dueAt", "termGracePeriod", "termPenalty"],
"termStamping": "PER_LOAN",
"appliesToOpenLoans": []
},
"transactionFee": {
"kind": "OPEN",
"fee": "2.0",
"feeRaw": "2000000",
"borrowed": "50000.0",
"netDisbursed": "49998.0",
"netDisbursedRaw": "49998000000",
"message": "This market charges a loan opening fee of 2.0 USDC on this draw, withheld from the amount paid out: the debt grows by 50000.0 USDC and the borrower receives 49998.0 USDC."
},
"warnings": [
"This market charges a loan opening fee of 2.0 USDC on this draw, withheld from the amount paid out: the debt grows by 50000.0 USDC and the borrower receives 49998.0 USDC."
],
"clientReference": "CL-000417",
"customerId": "cmt0k3b9x0004cghxw7r2d5ya",
"confirmWithClientReference": true
}
}
{
"success": true,
"data": {
"action": "SIGN_TRANSACTIONS",
"steps": [
{
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "openLoan"
}
],
"mode": "FREEZE",
"confirmStepIndex": 0,
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/positions/cmssh7p1k0001cghxq2m4v8rz/open-loan",
"field": "txHash"
},
"payeeAddress": null,
"payee": null
}
}
{
"success": true,
"data": {
"action": "SIGN_TRANSACTIONS",
"steps": [
{
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "openLoan",
"description": "Open the loan and pay the proceeds to 0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9"
}
],
"mode": "FREEZE",
"confirmStepIndex": 0,
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/positions/cmssh7p1k0001cghxq2m4v8rz/open-loan",
"field": "txHash",
"body": { "clientReference": "CL-000417", "txHash": null }
},
"payeeAddress": "0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9",
"payee": {
"address": "0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9",
"borrowerOfRecord": "0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3",
"receives": "borrowAmount minus the open fee",
"netDisbursed": "50000.0",
"fixedForLife": true,
"receivesBorrowMore": true
},
"warnings": [
"The proceeds are paid to 0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9, not to the signing wallet: it receives the full 50000.0 USDC. 0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3 signs, provides the collateral and stays the borrower of record: every collateral return, any liquidation surplus and every surplus or escrow claim go back to it. The payee is fixed for the life of the loan and also receives every later borrowMore draw."
],
"clientReference": "CL-000417",
"customerId": "cmt0k3b9x0004cghxw7r2d5ya",
"confirmWithClientReference": true
}
}
{
"success": false,
"error": {
"code": "CLIENT_NOT_ELIGIBLE",
"message": "The client CL-000417 is recorded as rejected on this instance, so no loan is booked against it.",
"details": { "reason": "REJECTED", "clientReference": "CL-000417" }
}
}
{
"success": false,
"error": {
"code": "PAYEE_NOT_VERIFIED",
"message": "The payee 0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9 is not admitted to the Professional eligibility register (0x4f2a9c81d06e3b75a1c8e24d9b07f56e3a1d8c90), which this market applies to the payee as well as the borrower, so the market would refuse the payout (PayeeNotVerified). Onboard the payee wallet under the Professional profile and admit it, or omit payToAddress to pay the borrower.",
"details": {
"payee": "0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9",
"register": "0x4f2a9c81d06e3b75a1c8e24d9b07f56e3a1d8c90",
"requiredProfile": {
"key": "PROFESSIONAL",
"name": "Professional",
"entityType": "INDIVIDUAL",
"professionalOnly": true,
"requirementsHash": "0x8919ea37bac1f44ea0e8a37529be970cb49e9779ebd4c4e98490cfd754e9d390"
},
"accessList": null
}
}
}
{
"success": false,
"error": {
"code": "COLLATERAL_ADAPTER_NOT_AUTHORIZED",
"message": "The custody adapter is not allow-listed on the collateral token yet. Complete market configuration (authorizeAdapterOnToken) before opening loans."
}
}
{
"success": false,
"error": {
"code": "EXCEEDS_MAX_BORROW",
"message": "That collateral supports at most 48250.0 USDC of borrowing."
}
}
{
"success": false,
"error": {
"code": "MARKET_PENDING_CURATOR",
"message": "This market has no lender of record yet, so opening a loan is not possible. A nominated candidate must take the role before the market can hold liquidity or collateral."
}
}
{
"success": false,
"error": {
"code": "PROFESSIONAL_BORROWER_GATE_REQUIRED",
"message": "This market prices its collateral from a live market price, which carries the free-hand realization path under § 1259 BGB and is only available against professional borrowers. The borrower register 0x9e3c8a15f4d70b2681c5a3f9027d4be8115c7d21 admits wallets for a verification profile of another instance, which is not limited to professional clients. Choose the register of a profile limited to professional clients as the borrower gate. Borrowing stays closed until the market admin sets that borrower gate under Gates on the operator desk.",
"details": { "field": "borrowerRegistry" }
}
}
Error Codes
The price, supply-cap, collateral-value and liquidity checks run in the order the contract reverts in. A refusal therefore names the condition the borrower would otherwise have hit on-chain.| Code | HTTP | Cause |
|---|---|---|
BORROWER_ADDRESS_REQUIRED | 400 | borrowerAddress was omitted while building calldata |
NO_MARKET_ADDRESS | 400 | The market has no on-chain address recorded |
MISSING_MARKET_ID | 400 | marketId is longer than 100 characters |
VALIDATION_ERROR | 400 | collateralAmount or borrowAmount is missing on the build call, not a positive decimal string, or carries more decimal places than its token supports. Also a malformed address, txHash or clientReference |
CLIENT_REFERENCE_FIELD | 400 | The body carries customerId, clientId, clientReferenceKey or externalId. Pass the value as clientReference |
INVALID_CLIENT_REFERENCE | 400 | clientReference is blank |
INVALID_PAY_TO_ADDRESS | 400 | payToAddress is not 0x followed by 40 hex characters, is the zero address, or fails its EIP-55 checksum |
PAY_TO_NOT_ALLOWED | 400 | payToAddress is the market or a contract it is wired to. details carries reason (MARKET, ROUTER, ADAPTER, AUCTION_MODULE, INSURANCE_FUND, SETTLEMENT_TOKEN, COLLATERAL_TOKEN, ORACLE, INTEREST_MODEL or VAULT) and payToAddress. Name the wallet that should receive the proceeds |
PAYEE_NOT_VERIFIED | 400 | The payee is not admitted to the market’s borrower register. details carries payee, register, requiredProfile and accessList. Admit the payee, or omit payToAddress |
COLLATERAL_ADAPTER_NOT_AUTHORIZED | 400 | The adapter lacks the agent role (FREEZE) or allow-listed or verified holder status (CUSTODY) on the collateral token |
FREEZE_MODE_UNSUPPORTED_TOKEN | 400 | The token does not implement the ERC-3643 freeze interface. Use a CUSTODY market |
MARKET_PAUSED | 400 | The market is paused. Its admin must unpause it |
IDENTITY_NOT_VERIFIED | 400 | borrowerAddress is not verified on the market’s borrower register. details.register names the register, details.requiredProfile the verification profile whose eligibility register it is, and details.accessList the whitelist it is, each null when it does not apply. With a profile, the message says to onboard and admit the wallet under it. With a whitelist, it says to have the list’s owner or a manager list the wallet |
IDENTITY_GATE_RETIRED | 409 | Returned instead of IDENTITY_NOT_VERIFIED, or instead of PAYEE_NOT_VERIFIED for the payee (details.payee, details.gate borrower), when the borrower register that refuses the wallet is the previous Trusset ID Register. It takes no new identities, so the wallet cannot be added to it. details carries register, gate (identity), retired: true and currentRegistry. The market admin moves the gate to the current register with Set Identity Gates, then retry |
BELOW_MIN_BORROW | 400 | borrowAmount is under the market’s minBorrowAmount |
PRICE_STALE | 400 | The price is older than the oracle’s staleness window. On an attested market, push a price with Sync Oracle Price or supply a signedPrice. On an external-feed market, wait for the feed to publish |
SUPPLY_CAP_EXCEEDED | 400 | The pledge would push total collateral past maxTotalCollateral |
USER_SUPPLY_CAP_EXCEEDED | 400 | The pledge would push this borrower past maxUserCollateral |
EXCEEDS_MAX_BORROW | 400 | borrowAmount exceeds what the collateral supports, at the signed price when a signedPrice is supplied |
INVALID_SIGNED_PRICE | 400 | The signedPrice is malformed, expired, dated in the future or older than the oracle’s price age window, or it was signed by a wallet that is not an authorized signer on the market’s oracle, which details.signer then names. Sign a fresh quote with Sign Price |
PRICE_DEVIATION_TOO_LARGE | 400 | The signedPrice moves further from the oracle’s current price than its maxDeviationBps allows |
PRICE_WINDOW_DEVIATION_TOO_LARGE | 400 | The signedPrice falls outside the band the oracle accepts within its current one-hour window |
INSUFFICIENT_LIQUIDITY | 400 | The pool, after its reserve earmark and any vault draw it could make, holds less than the loan needs |
FIRST_DRAW_BELOW_FLOOR | 400 | The pool is empty and the vault draw that would fund this borrow is below the first-deposit floor this market’s implementation applies to draws |
INSUFFICIENT_FREE_COLLATERAL | 400 | On a FREEZE market, borrowerAddress holds fewer unfrozen tokens than collateralAmount. details carries holder, balance, frozen and free |
COLLATERAL_TRANSFER_RESTRICTED | 400 | The collateral token’s compliance rules block moving this collateral into the custody adapter |
COLLATERAL_GRANULARITY_MISMATCH | 400 | collateralAmount is not a whole multiple of the collateral token’s ERC-1400 granularity, so the token would refuse the transfer. The message names the nearest amounts that are |
CLIENT_REFERENCE_NOT_FOUND | 404 | clientReference matches no customer of your instance |
MARKET_NOT_FOUND | 404 | No market with this ID on your instance |
MARKET_PENDING_CURATOR | 409 | The market has no lender of record yet. See Lender of record |
PROFESSIONAL_BORROWER_GATE_REQUIRED | 409 | The market is MARKET-priced and its borrower register does not qualify as a professional gate. details.field is borrowerRegistry. See Professional borrowers |
MARKET_TERM_EXPIRED | 409 | The market’s terminal due date has passed, so the contract refuses every new loan. Repayment and withdrawals stay open |
COLLATERAL_MATURED | 409 | The collateral instrument passed its recorded maturity, and the record was asserted by a party to this market. error.details carries maturityDate |
EXTERNAL_FEED_ACTIVE | 409 | A signedPrice was sent to a market that prices from an external feed. Retry without it |
FIXED_PRICE_ACTIVE | 409 | A signedPrice was sent to a market with a fixed price, whose oracle refuses every signed price. Retry without it: the loan prices against the fixed price |
SIGNED_PRICE_OUTDATED | 409 | The signedPrice is not dated after the price the oracle already holds, so the oracle would refuse it. Sign a fresh quote |
PAY_TO_UNSUPPORTED | 409 | payToAddress was sent to a market whose implementation predates pay-to. Upgrade the market, or omit payToAddress |
ELIGIBILITY_REMOVAL_PENDING | 409 | The borrower’s or the payee’s admission to the borrower register is waiting for removal, so no new loan is prepared for it. details carries register and wallet, plus reason and profileKey when the admission is your instance’s |
CLIENT_NOT_ELIGIBLE | 409 | The client named by clientReference may not carry this loan. details.reason is REJECTED, REVOKED, COUNTRY_BLOCKED, NOT_PROFESSIONAL or NOT_VERIFIED. See Client of record |
CLIENT_REFERENCE_CONFLICT | 409 | On confirm, the loan is already attributed to another client and reattribute was not true |
MARKET_STATE_UNAVAILABLE | 503 | The market could not be read to run a check, its price source, its borrower register, whether it can pay a third party, the contracts it is wired to and the collateral token’s granularity included, so no transaction was prepared. Retry shortly |
STATE_UNAVAILABLE | 503 | The verification profiles behind a MARKET market’s borrower register could not be read, or the admissions to the borrower register could not be checked for a pending removal. Retry shortly |
PRICE_STATE_UNAVAILABLE | 503 | The oracle could not be read to check the signedPrice, so nothing was built. Retry shortly |
txHash can also return any transaction verification error.⌘I
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/positions/{marketId}/open-loan" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"collateralAmount": "1000",
"borrowAmount": "50000",
"borrowerAddress": "0xabc7f1093d5e26b804a1c3f78de025916b47c0d3",
"clientReference": "CL-000417"
}'
const url = `https://api.trusset.org/lending-external-securities-v2/api/positions/${marketId}/open-loan`;
const headers = {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
};
const res = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({
collateralAmount: '1000',
borrowAmount: '50000',
borrowerAddress: await wallet.getAddress(),
clientReference: 'CL-000417',
signedPrice: {
price: signed.price,
timestamp: signed.timestamp,
validUntil: signed.validUntil,
signature: signed.signature
}
})
});
const { data } = await res.json();
const hashes: string[] = [];
for (const step of data.steps) {
const tx = await wallet.sendTransaction(step.transaction);
await tx.wait();
hashes.push(tx.hash);
}
const confirmed = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ txHash: hashes[data.confirmStepIndex], clientReference: 'CL-000417' })
});
const { data: loan } = await confirmed.json();
{
"success": true,
"data": {
"txHash": "0x9f2c41d8b7e05a3164c2870fbd935e1a4c7802db6135ea9048f7c21b5d3ea41b",
"loanId": "5",
"positionId": "cmswb4n1e0021cghx9k2t6pdm",
"collateralAmount": "1000.0",
"borrowAmount": "50000.0",
"payeeAddress": null,
"paidTo": "0xabc7f1093d5e26b804a1c3f78de025916b47c0d3",
"customerId": "cmt0k3b9x0004cghxw7r2d5ya",
"clientReference": "CL-000417",
"attribution": {
"customerId": "cmt0k3b9x0004cghxw7r2d5ya",
"attributedAt": "2026-09-26T10:14:07.000Z",
"changed": true,
"previousCustomerId": null
},
"vaultDraws": [
{
"vault": "0x4d0cfe39a3431b145d1a1393d901a36d459a1b13",
"amount": "12500.0"
}
]
}
}
{
"success": true,
"data": {
"action": "SIGN_TRANSACTIONS",
"steps": [
{
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x9f8c1d4b2e7a3056c1b8f4d29e0a7c3518b6d24f",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "approve",
"description": "Approve the custody adapter to pull collateral"
},
{
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "openLoan"
}
],
"mode": "CUSTODY",
"confirmStepIndex": 1,
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/positions/cmssh7p1k0001cghxq2m4v8rz/open-loan",
"field": "txHash",
"body": { "clientReference": "CL-000417", "txHash": null }
},
"payeeAddress": null,
"payee": null,
"offering": {
"approximateDueAt": "1797004800",
"termGracePeriodSeconds": "259200",
"termPenaltyBps": 200,
"stampedAtSigning": ["dueAt", "termGracePeriod", "termPenalty"],
"termStamping": "PER_LOAN",
"appliesToOpenLoans": []
},
"transactionFee": {
"kind": "OPEN",
"fee": "2.0",
"feeRaw": "2000000",
"borrowed": "50000.0",
"netDisbursed": "49998.0",
"netDisbursedRaw": "49998000000",
"message": "This market charges a loan opening fee of 2.0 USDC on this draw, withheld from the amount paid out: the debt grows by 50000.0 USDC and the borrower receives 49998.0 USDC."
},
"warnings": [
"This market charges a loan opening fee of 2.0 USDC on this draw, withheld from the amount paid out: the debt grows by 50000.0 USDC and the borrower receives 49998.0 USDC."
],
"clientReference": "CL-000417",
"customerId": "cmt0k3b9x0004cghxw7r2d5ya",
"confirmWithClientReference": true
}
}
{
"success": true,
"data": {
"action": "SIGN_TRANSACTIONS",
"steps": [
{
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "openLoan"
}
],
"mode": "FREEZE",
"confirmStepIndex": 0,
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/positions/cmssh7p1k0001cghxq2m4v8rz/open-loan",
"field": "txHash"
},
"payeeAddress": null,
"payee": null
}
}
{
"success": true,
"data": {
"action": "SIGN_TRANSACTIONS",
"steps": [
{
"action": "SIGN_TRANSACTION",
"transaction": {
"to": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"data": "0x...",
"value": "0",
"chainId": 11155111
},
"functionName": "openLoan",
"description": "Open the loan and pay the proceeds to 0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9"
}
],
"mode": "FREEZE",
"confirmStepIndex": 0,
"confirmWith": {
"endpoint": "POST /lending-external-securities-v2/api/positions/cmssh7p1k0001cghxq2m4v8rz/open-loan",
"field": "txHash",
"body": { "clientReference": "CL-000417", "txHash": null }
},
"payeeAddress": "0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9",
"payee": {
"address": "0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9",
"borrowerOfRecord": "0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3",
"receives": "borrowAmount minus the open fee",
"netDisbursed": "50000.0",
"fixedForLife": true,
"receivesBorrowMore": true
},
"warnings": [
"The proceeds are paid to 0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9, not to the signing wallet: it receives the full 50000.0 USDC. 0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3 signs, provides the collateral and stays the borrower of record: every collateral return, any liquidation surplus and every surplus or escrow claim go back to it. The payee is fixed for the life of the loan and also receives every later borrowMore draw."
],
"clientReference": "CL-000417",
"customerId": "cmt0k3b9x0004cghxw7r2d5ya",
"confirmWithClientReference": true
}
}
{
"success": false,
"error": {
"code": "CLIENT_NOT_ELIGIBLE",
"message": "The client CL-000417 is recorded as rejected on this instance, so no loan is booked against it.",
"details": { "reason": "REJECTED", "clientReference": "CL-000417" }
}
}
{
"success": false,
"error": {
"code": "PAYEE_NOT_VERIFIED",
"message": "The payee 0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9 is not admitted to the Professional eligibility register (0x4f2a9c81d06e3b75a1c8e24d9b07f56e3a1d8c90), which this market applies to the payee as well as the borrower, so the market would refuse the payout (PayeeNotVerified). Onboard the payee wallet under the Professional profile and admit it, or omit payToAddress to pay the borrower.",
"details": {
"payee": "0x2B6eD29A95753C3Ad948348e3e7b1A251080Ffb9",
"register": "0x4f2a9c81d06e3b75a1c8e24d9b07f56e3a1d8c90",
"requiredProfile": {
"key": "PROFESSIONAL",
"name": "Professional",
"entityType": "INDIVIDUAL",
"professionalOnly": true,
"requirementsHash": "0x8919ea37bac1f44ea0e8a37529be970cb49e9779ebd4c4e98490cfd754e9d390"
},
"accessList": null
}
}
}
{
"success": false,
"error": {
"code": "COLLATERAL_ADAPTER_NOT_AUTHORIZED",
"message": "The custody adapter is not allow-listed on the collateral token yet. Complete market configuration (authorizeAdapterOnToken) before opening loans."
}
}
{
"success": false,
"error": {
"code": "EXCEEDS_MAX_BORROW",
"message": "That collateral supports at most 48250.0 USDC of borrowing."
}
}
{
"success": false,
"error": {
"code": "MARKET_PENDING_CURATOR",
"message": "This market has no lender of record yet, so opening a loan is not possible. A nominated candidate must take the role before the market can hold liquidity or collateral."
}
}
{
"success": false,
"error": {
"code": "PROFESSIONAL_BORROWER_GATE_REQUIRED",
"message": "This market prices its collateral from a live market price, which carries the free-hand realization path under § 1259 BGB and is only available against professional borrowers. The borrower register 0x9e3c8a15f4d70b2681c5a3f9027d4be8115c7d21 admits wallets for a verification profile of another instance, which is not limited to professional clients. Choose the register of a profile limited to professional clients as the borrower gate. Borrowing stays closed until the market admin sets that borrower gate under Gates on the operator desk.",
"details": { "field": "borrowerRegistry" }
}
}
