For developers

API & MCP reference

Every Teller Pro feature — balances, the Teller Score, credit pre-qualification, swaps, sends, collateralized and score-backed borrowing, repayment, rollovers, yield vaults and staking rewards — available to your application. 69 capabilities, served over a REST API and over MCP from one definition, so the two can't drift apart.

You need a Teller API key. Every capability on both transports is authenticated — there is no anonymous mode. The only public endpoints are the capability index GET /api/v1 and GET /api/v1/openapi.json, and neither returns customer data. MCP is the same key: send it as a header, or connect over OAuth and paste it into the consent screen. Keys are issued by Teller — ask in Discord if you don't have one.
Teller never holds a private key and never broadcasts. For anything on-chain you pass the end user's wallet address and get back ordered unsigned transactions. Their wallet signs each one, you post the hash back, and we hand you the next. No custody is involved at any point.

Getting started

Base URL is https://pro.teller.org/api/v1. Authenticate with your partner API key on every request:

curl 'https://pro.teller.org/api/v1/wallet/overview?walletAddress=0xabc…' \
  -H 'x-teller-api-key: sk_live_…'

Authorization: Bearer sk_live_… works identically. POST bodies are JSON; query parameters are also accepted on POST routes, whichever is more convenient. Two endpoints are public and need no key:

  • GET /api/v1 — the live capability index (name, method, path, scope).
  • GET /api/v1/openapi.json — the OpenAPI 3.1 document, for client generators and Postman.

Amounts are always integer strings in the token's smallest unit — 1 USDC is "1000000". Use lookup_token for decimals.

Authentication & scopes

A key carries a scope list. A call outside your scopes is a 403 forbidden naming the scope you'd need.

ScopeGrants
readWallet, score, market and offer reads.
txBuild transactions for a wallet to sign, and advance them as each is signed.
prequalRun the pre-qualification funnel and read back what lenders said.
score:writeCredit score points, record activity, verify income.

Keys are issued by Teller and carry all four scopes unless you ask for something narrower. Ask for narrower if it fits your product — read alone covers a dashboard, and only credit-bearing integrations need tx. To request a key, or to sort out one that isn't working, find us in Teller's Discord.

whoami reports what your key actually carries. It reads nothing and needs no arguments, so it is also the quickest way to tell an auth problem apart from a bad request:

curl 'https://pro.teller.org/api/v1/whoami' -H 'x-teller-api-key: sk_live_…'
# {"id":"acme","name":"Acme Wallet","kind":"partner","scopes":["*"]}

"scopes": ["*"] means every scope. 401 means the key wasn't recognised at all — check you copied it whole, then ask in Discord. 403 on any call names the scope you'd need alongside the ones you have.

Your partner code

Every capability accepts a partnerCode — a value you choose, for whatever you need to reconcile against: a campaign, a placement, a sub-affiliate, an internal user or session reference.

# per call
-d '{ "walletAddress": "0xabc…", "partnerCode": "spring-campaign:web", … }'

# or once per request
-H 'x-teller-partner-code: spring-campaign:web'

The explicit argument wins over the header. Format is 1–64 characters of letters, digits, dot, dash, underscore or colon, starting with a letter or digit; anything else is a 400 rather than being silently dropped.

Where it landsStored as
Any create_*_intentA column on the intent, echoed on every read
create_prequal_submissionpartnerCode in the submission's stored answers
record_score_event / record_swap / record_borrowpartnerCode in the score event's meta
Intent completionsTaken from the intent, so credit follows whoever built it — not whoever reported the last hash

Your partner id is recorded alongside it automatically. list_tx_intents takes partnerCode as a filter, which turns “what did this campaign do” into one call.

The transaction flow

Every on-chain action follows the same four beats.

  1. 1Build. Call a create_*_intent capability with the end user's wallet address. Nothing is broadcast — you get an intent listing every transaction, with nextStep ready to sign.
  2. 2Sign. Hand nextStep to the wallet verbatim. { chainId, to, data, value } is a complete eth_sendTransaction payload; value is decimal wei.
  3. 3Continue. POST the resulting hash to /tx/intents/{id}/continue. We check the receipt, stamp the step, and return the intent again with the next transaction.
  4. 4Repeat. Until nextStep is null. The intent is then completed and the bookkeeping has run — activity recorded, score points credited, caches invalidated. The outcome lands in result.
create_swap_intent
POST /api/v1/swap/intents
{
  "walletAddress": "0xabc…",
  "fromChain": 8453,
  "toChain": 8453,
  "fromToken": "0x8335…2913",
  "toToken": "0x4200…0006",
  "fromAmount": "25000000",
  "slippage": 0.005,
  "partnerCode": "spring-campaign:web"
}
{
  "intentId": "txi_9f2c8a41…",
  "kind": "swap",
  "status": "awaiting_signature",
  "chainId": 8453,
  "walletAddress": "0xabc…",
  "partnerCode": "spring-campaign:web",
  "stepsTotal": 2,
  "stepsRemaining": 2,
  "steps": [
    {
      "id": "step_1", "index": 0, "kind": "approve", "action": "swap", "chainId": 8453,
      "to": "0x8335…2913", "data": "0x095ea7b3…", "value": "0",
      "description": "Approve the lifi router to move your tokens",
      "status": "pending", "txHash": null
    },
    {
      "id": "step_2", "index": 1, "kind": "execute", "action": "swap", "chainId": 8453,
      "to": "0x1231…4eae", "data": "0x4630a0d8…", "value": "0",
      "gasLimit": "0x7a120",
      "description": "Swap USDC → WETH",
      "status": "pending", "txHash": null
    }
  ],
  "preview": {
    "provider": "lifi",
    "fromAmount": "25000000",
    "toAmount": "8420000000000000",
    "toAmountMin": "8377900000000000",
    "fromAmountUsd": "25.00",
    "estimatedDurationSeconds": 30,
    "display": {
      "fromAmount": "25",
      "toAmount": "0.00842",
      "toAmountMin": "0.0083779"
    },
    // Each side resolved — render these directly, no lookup needed.
    "fromToken": {
      "address": "0x8335…2913", "symbol": "USDC", "name": "USD Coin",
      "decimals": 6, "logoUrl": "https://…/usdc.png",
      "amount": "25000000", "amountFormatted": "25", "amountUsd": "25.00"
    },
    "toToken": {
      "address": "0x4200…0006", "symbol": "WETH", "name": "Wrapped Ether",
      "decimals": 18, "logoUrl": "https://…/weth.png",
      "amount": "8420000000000000", "amountFormatted": "0.00842", "amountUsd": "24.90"
    }
  },
  "nextStep": { /* === steps[0] */ },
  "instructions": "Send transaction 1 of 2 (\"Approve the lifi router…\") from 0xabc… on chain 8453. …",
  "expiresAt": "2026-08-11T09:12:03.776Z"
}
continue_tx_intent
POST /api/v1/tx/intents/txi_9f2c8a41…/continue
{
  "stepId": "step_1",
  "txHash": "0x5c1f…"
}
{
  "intentId": "txi_9f2c8a41…",
  "status": "awaiting_signature",
  "stepsRemaining": 1,
  "completedStep": {
    "id": "step_1", "status": "confirmed", "txHash": "0x5c1f…",
    "confirmedAt": "2026-08-11T08:44:10.001Z"
  },
  "nextStep": {
    "id": "step_2", "kind": "execute", "chainId": 8453,
    "to": "0x1231…4eae", "data": "0x4630a0d8…", "value": "0",
    "description": "Swap USDC → WETH"
  },
  "instructions": "Send transaction 2 of 2 …"
}

// …and after the final step:
{
  "status": "completed",
  "stepsRemaining": 0,
  "nextStep": null,
  "instructions": "All steps are signed. Nothing further is required.",
  "result": { "ok": true, "points": 25, "feeUsd": 250, "score": { "total": 345 } }
}

Things worth knowing

  • Approvals can be capped. By default an approve step asks for an unlimited allowance, so a repeat swap needs no further approval. Pass approvalMode: "exact" and it asks for exactly what the action moves — a compromised spender can then take that and nothing more. The trade is transactions: an exact allowance is spent to zero by the action it was granted for, so every repeat swap needs a fresh approval. Capping is the safer grant and worth turning on.
  • Approvals are computed, not guessed. We read the live allowance and include an approval step only when one is genuinely needed — including the zero-first reset that USDT-style tokens require. The check is per spender, so a quote routed through a different router than last time legitimately needs its own approval.
  • Amounts ship with their decimals. Every figure in preview is a raw integer in the token's smallest unit, and preview.fromToken / preview.toToken carry the symbol and decimals they are denominated in. preview.display has the same amounts pre-formatted. Render one of those, not the raw integer — "98215" is 0.098 USDC, not 98,215 of them.
  • Pass stepId. It makes /continue reject a hash meant for a different step instead of silently advancing.
  • A reverted receipt fails the intent rather than advancing it. If your chain isn't reachable from our RPCs, pass skipReceiptCheck: true.
  • Intents expire after 30 minutes. Quotes go stale — build a new one.
  • Bookkeeping never fails the action. The user's transaction has landed; if crediting hiccups the intent still completes and result.completionError says what went wrong.
  • A partner key only ever sees its own intents.

Proving wallet control

Your API key authenticates your app. It says nothing about whether the person in front of you controls the wallet address in the request. For anything that extends credit we want that second fact too.

  1. 1Mint a challenge. GET /api/v1/wallet/challenge?walletAddress=0xabc…
  2. 2Sign it. Have the wallet sign the returned message with personal_sign (EIP-191).
  3. 3Send it back. Pass walletProof: { message, signature } on the capability call.

The message is HMAC-stamped and expires in 10 minutes, so it can't be forged or edited, and it is bound to one address — a signature obtained for wallet A is rejected against wallet B. Smart-contract wallets (Safe, Privy smart accounts, Coinbase Smart Wallet) are verified via ERC-1271 when EOA recovery doesn't match.

create_unsecured_borrow_intent always requires a proof — a partner key alone must never be able to draw down someone else's borrowing power. Every other intent accepts one optionally, and Teller can require one everywhere for your account on request.
create_wallet_challenge
GET /api/v1/wallet/challenge?walletAddress=0xabc…&purpose=borrow
{
  "message": "Teller Pro wants to verify you control this wallet.\n\nWallet: 0xabc…\nOrigin: https://pro.teller.org\nPurpose: borrow\nNonce: n7UxKxvsJDZ-Nd0AEVwi8A\nIssued At: 2026-08-11T06:42:24.560Z\nExpires At: 2026-08-11T06:52:24.560Z\nStamp: mfxHKto828B2_O1Hstt5GleR6amIrzz3",
  "walletAddress": "0xabc…",
  "expiresAt": "2026-08-11T06:52:24.560Z",
  "instructions": "Have the wallet sign `message` verbatim with personal_sign (EIP-191)…"
}

Running a lending pool

The borrow capabilities treat Teller's lending pools as something to borrow from. This is the other side: launching one, supplying to it, and operating it. A pool is a self-contained market — one collateral token, one principal token, an interest curve, an LTV and a market that fixes the loan term. Lenders supply the principal and receive shares; borrowers post collateral and draw the principal; the interest accrues to the shares.

A pool cannot be reconfigured after launch. Not the rate curve, not the LTV, not the market, and — the one that matters most — not the AMM route it prices collateral through. There is no setter and no upgrade path; the only remedy for a badly-configured pool is to abandon it and launch another. That is why preview_pool_launch exists as a separate read-only call.
  1. 1Pick a market. list_pool_markets — the marketId also fixes the loan duration, so there is no separate term parameter. It also reports which pool generations each chain can launch.
  2. 2Dry-run it. preview_pool_launch returns the factory, the basis-point config that would be written on-chain, and the oracle route it found — with the TVL of its thinnest hop.
  3. 3Launch it. create_pool_launch_intent builds the approval and the factory call. The wallet that signs becomes the pool's owner, and continue_tx_intent returns the new pool's address when the deploy confirms.

Rates are whole percents, and interestRateUpperBoundPct means the rate at your liquidity threshold — not at 100% utilization. The pool interpolates across the full range, so 20% quoted at an 80% threshold is stored as 23.75%. Teller does that projection for you, which is why get_pool reports both numbers, and it matches the Teller app's own deploy flow exactly.

The oracle route is the part to read. A pool prices collateral by reading a TWAP through concrete AMM pools, fixed at deploy time. A thin hop is a permanently manipulable oracle, so it is reported in warnings and thinnestHopUsd rather than blocked — some pairs only have thin markets, and that call is yours. deployable: false means no route was found at all; pass oracleRoutes yourself if you know which AMM pool you want.

preview_pool_launch
POST /api/v1/pools/launch/preview
{
  "chainId": 8453,
  "principalToken": "0x8335…2913",
  "collateralToken": "0x4200…0006",
  "marketId": 2,
  "interestRateLowerBoundPct": 5,
  "interestRateUpperBoundPct": 20,
  "liquidityThresholdPct": 80,
  "loanToValuePct": 75
}
{
  "chainId": 8453,
  "version": "v1",
  "factoryAddress": "0x7FBC…41BC",
  "market": { "marketId": 2, "durationSeconds": 2592000, "durationLabel": "30 days" },
  "terms": { "interestRateLowerBoundPct": 5, "interestRateUpperBoundPct": 20,
             "liquidityThresholdPct": 80, "loanToValuePct": 75 },
  // Basis points, as stored on-chain. The upper bound is 2375, not
  // 2000: 20% is the rate you want at 80% utilization, and the pool
  // interpolates across the full range, so it is projected out to 100%.
  "config": { "marketId": "2", "maxLoanDuration": 2592000,
              "interestRateLowerBound": 500, "interestRateUpperBound": 2375,
              "liquidityThresholdPercent": 8000, "collateralRatio": 7500 },
  "oracleRoutes": [
    { "pool": "0xd0b5…F224", "zeroForOne": false, "twapInterval": 5,
      "token0Decimals": 6, "token1Decimals": 18, "totalValueLockedUsd": 5182000 }
  ],
  "oracleRouteSource": "discovered",
  "thinnestHopUsd": 5182000,
  "warnings": [],
  "deployable": true
}
create_pool_launch_intent
POST /api/v1/pools/launch-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "principalToken": "0x8335…2913",
  "collateralToken": "0x4200…0006",
  "marketId": 2,
  "interestRateLowerBoundPct": 5,
  "interestRateUpperBoundPct": 20,
  "liquidityThresholdPct": 80,
  "loanToValuePct": 75,
  "initialPrincipalRaw": "1000000000"
}
{
  "intentId": "txi_9c42…",
  "kind": "pool_launch",
  "stepsTotal": 2,
  "steps": [
    // The factory pulls the seed deposit and forwards it into the pool
    // it creates, so the allowance is the factory's — the pool has no
    // address yet.
    { "id": "step_1", "kind": "approve", "to": "0x8335…2913", "data": "0x095ea7b3…",
      "description": "Approve the Teller pool factory to move your tokens" },
    { "id": "step_2", "kind": "execute", "to": "0x7FBC…41BC", "data": "0x1f69eda9…",
      "description": "Launch a v1 lending pool for 0x4200…0006 collateral against 0x8335…2913" }
  ],
  "preview": { /* the full preview_pool_launch plan */ }
}

// …and once step_2 confirms, continue_tx_intent hands back the address:
{
  "status": "completed",
  "completion": { "poolAddress": "0x1a2b…", "chainId": 8453 }
}

Supplying and withdrawing are create_pool_supply_intent and create_pool_withdraw_intent. Three generations of pool are live and their supply and withdraw calls are genuinely different functions — v1 keeps shares in a separate token and needs a prepare-then-burn sequence, v2 and v3 are ERC-4626. Teller probes the pool and emits the right steps, so you pass the same arguments either way.

  • Withdraw in shares, not assets. Omitting sharesRaw redeems the whole position. There is no asset amount, because that rounds against a moving share price and leaves dust.
  • Shares are locked when they arrive. The delay stops anyone depositing, borrowing against their own liquidity and withdrawing in one block. A withdraw inside the window is refused with precondition_failed naming the unlock time, rather than handed to you as a transaction that would revert.
  • Withdrawals come from idle principal. A fully-lent pool has none until a loan repays. The preview reports poolPrincipalAvailableRaw so you can see it coming.
create_pool_supply_intent
POST /api/v1/pools/supply-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "poolAddress": "0x1a2b…",
  "amountRaw": "1000000000"
}
{
  "intentId": "txi_b7f0…",
  "kind": "pool_supply",
  "stepsTotal": 2,
  "steps": [
    { "id": "step_1", "kind": "approve", "to": "0x8335…2913", "data": "0x095ea7b3…",
      "description": "Approve the WETH/USDC pool to move your tokens" },
    { "id": "step_2", "kind": "execute", "to": "0x1a2b…", "data": "0x6e553f65…",
      "description": "Supply USDC to the WETH/USDC pool" }
  ],
  "preview": {
    "poolAddress": "0x1a2b…", "poolVersion": "v2",
    "amountRaw": "1000000000", "amount": "1000.0",
    "currentApyPct": 7, "utilizationPct": 10,
    // v1 pools take a minimum-shares floor here; v2 and v3 take none.
    "minSharesOutRaw": null,
    // Shares are locked this long after they arrive, so a withdraw
    // attempted sooner is refused rather than reverted.
    "withdrawDelaySeconds": 300
  }
}
create_pool_withdraw_intent
POST /api/v1/pools/withdraw-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "poolAddress": "0x1a2b…"
}
// Omitting sharesRaw redeems the whole position — shares are the
// only unit that leaves no dust behind.
{
  "intentId": "txi_c318…",
  "kind": "pool_withdraw",
  "stepsTotal": 1,
  "steps": [
    { "id": "step_1", "kind": "execute", "to": "0x1a2b…", "data": "0xba087652…",
      "description": "Redeem your WETH/USDC pool shares" }
  ],
  "preview": {
    "poolAddress": "0x1a2b…", "poolVersion": "v2",
    "sharesRaw": "996000000", "sharesHeldRaw": "996000000",
    "redeemsEverything": true,
    "estimatedAssetsRaw": "1000103000", "estimatedAssets": "1000.103",
    // Withdrawals are paid from idle principal. A fully-lent pool has
    // none until a loan repays, and the redeem reverts.
    "poolPrincipalAvailableRaw": "5000000000"
  }
}

create_pool_config_intent covers the changes that alter a pool rather than move value through it — and they do not all belong to the same party.

ActionWho may call it
set_max_principal_per_collateralthe pool's owner (v1/v2 only)
transfer_ownershipthe pool's owner
renounce_ownershipthe pool's owner
pause_pool / unpause_poolthe Teller protocol pausing manager
pause_borrowing / unpause_borrowingthe protocol pausing manager (v2/v3)
pause_liquidations / unpause_liquidationsthe protocol pausing manager
set_withdraw_delaythe TellerV2 owner
sweep_escrow_vaultanyone
The first three are the complete set of owner powers — not a subset we picked. Once a pool is deployed, its market, rate curve, LTV, loan duration and oracle route are immutable; what is left is the manual price cap and who holds the keys.

set_max_principal_per_collateral is the only one that changes lending behaviour, and it moves in one direction: the pool applies min(oraclePrice, cap), so an owner can make their pool more conservative than its oracle but never looser. The value is principal per whole collateral token scaled by 1e18; pass 0 to clear it. renounce_ownership is permanent and needs confirm: true. sweep_escrow_vault is the odd one out — repayments can land in TellerV2's escrow vault instead of the pool, and anyone may move them back, because they belong to the pool's lenders either way.

The owner actions are checked against the pool's on-chain owner() and refused for anyone else. The protocol-level ones can't be checked from the pool — that authority lives on a separate manager contract — so they are built for whoever asks, with preview.requiredAuthority saying who will actually be allowed to send them.

create_pool_config_intent
POST /api/v1/pools/config-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "poolAddress": "0x1a2b…",
  "action": "pause_borrowing"
}
{
  "intentId": "txi_d901…",
  "kind": "pool_config",
  "stepsTotal": 1,
  "steps": [
    { "id": "step_1", "kind": "execute", "to": "0x1a2b…", "data": "0x1a3ee7b4…",
      "description": "Stop new borrows while leaving deposits, withdrawals and liquidations alone (WETH/USDC)" }
  ],
  "preview": {
    "action": "pause_borrowing",
    // Pausing belongs to the Teller protocol, not to the pool's owner,
    // and lives on a manager contract we can't check from the pool — so
    // the transaction is built and the authority is named instead.
    "requiredAuthority": "protocol_pauser",
    "poolOwner": "0xabc…",
    "callerIsPoolOwner": true,
    "authorityNote": "pausing is held by the Teller protocol pausing manager, not by the pool's owner — this will revert with 'OP' for anyone else",
    "controlsBefore": { "paused": false, "borrowingPaused": false,
                        "liquidationsPaused": false, "withdrawDelaySeconds": 300 }
  }
}

Reading pools: get_pool for one, list_pools for a chain (filterable by owner, i.e. pools a wallet launched, or lender, i.e. pools it has supplied to), and list_pool_positions for the latter directly. Both filters read every indexed pool on-chain, so the scan is capped and the response says truncated: true when it didn't reach the end — an empty result under truncation means “not in the part we checked”, not “none”.

get_pool
GET /api/v1/pools/detail?chainId=8453&poolAddress=0x1a2b…&walletAddress=0xabc…
{
  "pool": {
    "chainId": 8453,
    "poolAddress": "0x1a2b…",
    "version": "v2",
    "owner": "0xabc…",
    "principalToken": { "address": "0x8335…2913", "symbol": "USDC", "decimals": 6 },
    "collateralToken": { "address": "0x4200…0006", "symbol": "WETH", "decimals": 18 },
    "marketId": 2,
    "maxLoanDurationSeconds": 2592000,
    // From v2 on the pool is its own share token; a v1 pool points at a
    // separate ERC-20 here.
    "sharesTokenAddress": "0x1a2b…",
    "terms": {
      "interestRateLowerBoundPct": 5,
      "interestRateUpperBoundPct": 20,
      "interestRateAtFullUtilizationPct": 23.75,
      "liquidityThresholdPct": 80,
      "loanToValuePct": 75,
      "maxPrincipalPerCollateralAmountRaw": "0"
    },
    "rates": { "currentMinInterestRatePct": 7, "utilizationPct": 10 },
    "liquidity": {
      "principalAvailableToBorrowRaw": "5000000000",
      "principalAvailableToBorrow": "5000.0",
      "totalPrincipalCommittedRaw": "10000000000",
      "totalInterestCollectedRaw": "41230000",
      "sharesExchangeRateRaw": "1004120000000000000000000000000000000"
    },
    "controls": { "paused": false, "borrowingPaused": false,
                  "liquidationsPaused": false, "withdrawDelaySeconds": 300 },
    "priceAdapter": null
  },
  "position": {
    "sharesRaw": "996000000",
    "assetsRaw": "1000103000",
    "assets": "1000.103",
    "withdrawUnlocksAt": 1755691200,
    "withdrawable": true,
    "sharesPreparedRaw": null
  }
}

Making a lending offer

A pool is a shared vault whose terms are frozen at deploy time. A lending offer is the other primitive, and the one a single wallet can run: “I will lend up to 10,000 USDC against WETH, at 8% or better, for up to 30 days, until the end of the month.” It lives on Teller's commitment forwarder, and the lender can re-price it, resize it or withdraw it whenever they like.

Nothing is escrowed. The principal stays in the lender's wallet and TellerV2 pulls it at the moment a borrower accepts, so an offer is only as good as the ERC-20 allowance standing behind it. That is why create_lending_offer_intent opens with that approval, and why every read reports the lender's live balance, allowance and fundablePrincipalRaw — an offer whose lender has since spent the money is live on-chain and undrawable in practice.

The price is two numbers, not one. The commitment stores a fixed ceiling — principal per unit of collateral — and a Uniswap V3 route with an LTV. On every acceptance the contract takes the lower of the stored ceiling and the route's live TWAP scaled by that LTV, and pricing.boundBy says which one is binding right now.

  1. 1Pick a market. list_pool_markets — the same catalogue pools launch into. The market fixes the payment cycle, fees and default window the loans inherit, and supplies the default maxLoanDurationSeconds.
  2. 2Dry-run it. preview_lending_offer returns the exact commitment struct, the route it found, that route's live TWAP read through the forwarder itself, and the collateral a borrower would post to draw the whole allocation.
  3. 3Publish it. create_lending_offer_intent builds the TellerV2 approval, a one-time market-forwarder approval, and the commitment. continue_tx_intent returns the new commitmentId when it confirms.

loanToValuePct is the normal way to price an offer: the LTV is applied to the discovered route's TWAP and written twice — once as the stored ceiling, once as the oracle's own LTV. The offer therefore starts out agreeing with the market, then re-prices itself downward as the collateral falls, while the ceiling stops a rally quietly making it more generous. The alternative is a fixed price via principalPerCollateral (or the raw maxPrincipalPerCollateralAmountRaw), which is a different and riskier product — and the preview says so in warnings.

preview_lending_offer
POST /api/v1/lending-offers/preview
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "principalToken": "0x8335…2913",
  "collateralToken": "0x4200…0006",
  "marketId": 2,
  "maxPrincipalRaw": "10000000000",
  "minApyPct": 8,
  "loanToValuePct": 75
}
{
  "chainId": 8453,
  "forwarderAddress": "0xfA87…c2b5",
  "market": { "marketId": 2, "durationSeconds": 2592000, "durationLabel": "30 days" },
  "principalToken": { "address": "0x8335…2913", "symbol": "USDC", "decimals": 6 },
  "collateralToken": { "address": "0x4200…0006", "symbol": "WETH", "decimals": 18 },
  "collateralTokenType": "ERC20",
  "commitment": {
    "maxPrincipal": "10000000000",
    "expiration": 1735689600,
    "maxDuration": 2592000,
    "minInterestRate": 800,
    "maxPrincipalPerCollateralAmount": "1500000000",
    "collateralTokenType": 1,
    "marketId": "2"
  },
  "pricing": {
    // The TWAP the forwarder itself reads: 2,000 USDC per WETH.
    "oraclePriceRatioRaw": "2000000000",
    "poolOracleLtvBps": 7500,
    // 75% of it, stored as the fixed ceiling. The oracle leg re-prices
    // downward from here; the ceiling stops a rally loosening the offer.
    "maxPrincipalPerCollateralAmountRaw": "1500000000",
    "principalPerCollateral": "1500",
    "ratioSource": "oracle",
    "collateralForMaxPrincipalRaw": "6666666666666666667",
    "collateralForMaxPrincipal": "6.666666666666666667"
  },
  "oracleRoutes": [
    { "pool": "0xd0b5…3a1e", "zeroForOne": false, "twapInterval": 5,
      "token0Decimals": 6, "token1Decimals": 18 }
  ],
  "oracleRouteSource": "discovered",
  // Which AMM those pools belong to. On BNB Chain (56) this reads
  // { "id": "pancakeswap-v3", "label": "PancakeSwap V3", "pinned": true }
  // — pinned means the route was resolved through that venue's own
  // factory rather than an index, so the pools are PancakeSwap's by
  // construction.
  "oracleVenue": { "id": "uniswap-v3", "label": "Uniswap V3", "pinned": false },
  "thinnestHopUsd": 41800000,
  "warnings": [],
  "creatable": true
}
create_lending_offer_intent
POST /api/v1/lending-offers/intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "principalToken": "0x8335…2913",
  "collateralToken": "0x4200…0006",
  "marketId": 2,
  "maxPrincipalRaw": "10000000000",
  "minApyPct": 8,
  "loanToValuePct": 75,
  "expiresInSeconds": 2592000
}
{
  "intentId": "txi_4d81…",
  "kind": "lending_offer_create",
  "stepsTotal": 3,
  "steps": [
    // The principal never moves until someone borrows. This allowance
    // is what makes the offer fundable; without it the commitment is
    // live on-chain and cannot be drawn.
    { "id": "step_1", "kind": "approve", "to": "0x8335…2913", "data": "0x095ea7b3…",
      "description": "Approve TellerV2, which moves your principal when a borrower accepts" },
    // One-time, per market, per wallet.
    { "id": "step_2", "kind": "execute", "to": "0x5daE…2cB0", "data": "0x4f4ff0d4…",
      "description": "Allow the Teller commitment forwarder to act for you in market 2" },
    { "id": "step_3", "kind": "execute", "to": "0xfA87…c2b5", "data": "0x9b5c9b0f…",
      "description": "Offer USDC against WETH at 8% APR" }
  ],
  "preview": { /* the full preview_lending_offer plan */ }
}

// …and once the last step confirms, continue_tx_intent hands back the id
// every later read, update and delete is keyed on:
{
  "status": "completed",
  "completion": { "commitmentId": "412", "chainId": 8453 }
}

Changing an offer is update_lending_offer_intent: everything you don't name keeps its current value, so extending an expiry is one argument.

Four things cannot be changed at all — the lender, the principal token, the market, and the Uniswap route with its LTV. updateCommitment rewrites the commitment struct and the routes live outside it. Passing oracleRoutes or loanToValuePct to an update is refused rather than ignored: an update that appeared to accept one would hand you a successful transaction that changed nothing about your pricing.

Re-pricing within the route an offer already has is principalPerCollateral (or the raw maxPrincipalPerCollateralAmountRaw). Changing the route itself — or the market, or the token being lent — is replace_lending_offer_intent: the approvals, deleteCommitment, then createCommitmentWithUniswap with the new terms, in one intent. Everything you don't name carries over from the old offer, borrower allowlist included, and preview.carriedOver lists what was inherited. An LTV-priced offer is re-priced at today's TWAP through a freshly discovered route at the same LTV; a fixed-price one keeps its price and its lack of an oracle.

The delete is sequenced before the create. The other order leaves both offers live between two signatures, and both draw on the same wallet — a borrower could take the whole allocation twice. A gap with no offer costs nothing but the gap. The replacement is a new commitment: new commitmentId, and its principal allocation starts from zero.
replace_lending_offer_intent
POST /api/v1/lending-offers/replace-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "commitmentId": "412",
  "loanToValuePct": 60
}
{
  "intentId": "txi_b30f…",
  "kind": "lending_offer_replace",
  "stepsTotal": 2,
  "steps": [
    // Delete first, on purpose. The other order leaves both offers live
    // between two signatures, drawable twice against the same wallet.
    { "id": "step_1", "kind": "execute", "to": "0xfA87…c2b5", "data": "0x0eb4b2c9…",
      "description": "Withdraw lending offer 412, to replace it" },
    { "id": "step_2", "kind": "execute", "to": "0xfA87…c2b5", "data": "0x9b5c9b0f…",
      "description": "Re-publish it: USDC against WETH at 8% APR" }
  ],
  "preview": {
    "replaces": { "commitmentId": "412", "offer": { /* the offer being withdrawn */ } },
    // Re-priced at today's TWAP through a freshly discovered route, at
    // the new 60% LTV.
    "pricing": { "oraclePriceRatioRaw": "2000000000", "poolOracleLtvBps": 6000,
                 "maxPrincipalPerCollateralAmountRaw": "1200000000",
                 "principalPerCollateral": "1200", "ratioSource": "oracle" },
    // Everything not restated in the request, inherited from the old
    // offer — the allowlist included, since losing it would silently
    // reopen a restricted offer to everyone.
    "carriedOver": ["principalToken", "collateralToken", "marketId", "maxPrincipalRaw",
                    "minApyPct", "maxLoanDurationSeconds", "borrowerAllowlist", "expiration"],
    "sequencing": "the old offer is withdrawn before the replacement is published, …"
  }
}

// The replacement is a new commitment, so it gets a new id:
{
  "status": "completed",
  "completion": { "commitmentId": "419", "chainId": 8453 }
}

set_lending_offer_borrowers_intent manages the allowlist — and an empty allowlist means unrestricted, not blocked, so removing the last address reopens the offer to everyone. cancel_lending_offer_intent deletes the commitment: no further loans can be drawn, but loans already written against it run to their own terms and the TellerV2 allowance stays exactly as it was. All four writes are refused for anyone but the offer's lender, because the contract refuses them too.

get_lending_offer
GET /api/v1/lending-offers/detail?chainId=8453&commitmentId=412&principalAmountRaw=1500000000
{
  "commitmentId": "412",
  "lender": "0xabc…",
  "marketId": 2,
  "principalToken": { "address": "0x8335…2913", "symbol": "USDC", "decimals": 6 },
  "collateralToken": { "address": "0x4200…0006", "symbol": "WETH", "decimals": 18 },
  "collateralTokenType": "ERC20",
  "terms": {
    "maxPrincipalRaw": "10000000000", "maxPrincipal": "10000",
    "minApyPct": 8, "maxLoanDurationSeconds": 2592000,
    "expirationTimestamp": 1735689600, "expiresInSeconds": 1904312, "expired": false
  },
  "pricing": {
    "maxPrincipalPerCollateralAmountRaw": "1500000000",
    // WETH has fallen since the offer was made, so the oracle leg is
    // now the binding one and the offer lends less against it.
    "oraclePriceRatioRaw": "1600000000",
    "poolOracleLtvBps": 7500,
    "effectiveMaxPrincipalPerCollateralAmountRaw": "1200000000",
    "principalPerCollateral": "1200",
    "boundBy": "oracle"
  },
  "availability": {
    "acceptedPrincipalRaw": "2000000000",
    "remainingPrincipalRaw": "8000000000", "remainingPrincipal": "8000",
    "lenderBalanceRaw": "9400000000", "lenderAllowanceRaw": "10000000000",
    "fundablePrincipalRaw": "8000000000", "fundable": true
  },
  "borrowerAllowlist": [],
  "warnings": [],
  "quote": {
    "principalRaw": "1500000000",
    "requiredCollateralRaw": "1250000000000000000",
    "requiredCollateral": "1.25",
    "exceedsRemaining": false
  }
}

Borrowing against one

Offers are the second thing Teller lends from, and nothing in list_borrow_pools knows about them — that read covers lender-group pools only. So “what can this wallet borrow” is two questions. For the offer half, call list_lending_offers with a borrower: every entry comes back with a borrowing block saying how much that wallet could draw, which ceiling binds, and what would stop it. Add borrowableOnly to keep only the ones it can draw from today.

get_lending_offer_borrow_terms is the wallet-scaled quote for one offer — the borrower's mirror of get_borrow_terms. It reports the largest possible draw and which of three ceilings produced it (the offer's remaining allocation, what the lender can still fund, or the wallet's own collateral), the collateral the draw would post, the rate and term it would default to, and blockers — every condition that would make the acceptance revert, named. An empty blockers means the borrow will go through.

get_lending_offer_borrow_terms
GET /api/v1/lending-offers/borrow-terms
  ?chainId=8453&commitmentId=412&walletAddress=0xdef…
{
  "commitmentId": "412",
  "walletAddress": "0xdef…",
  "market": {
    "marketId": 2, "marketOpen": true, "borrowerVerified": true,
    "paymentCycleSeconds": 2592000, "paymentCycleType": "MONTHLY", "paymentType": "EMI"
  },
  "collateralBalanceRaw": "4000000000000000000",
  "collateralBalance": "4",
  "terms": {
    "borrowable": true,
    "blockers": [],
    // Three ceilings; the lowest wins, and `limitedBy` names it. Here
    // the lender has 6,400 USDC left approved to TellerV2 and the
    // offer's own allocation still has 9,000 — so the lender binds.
    "maxPrincipalRaw": "6400000000", "maxPrincipal": "6400",
    "limitedBy": "lender-funding",
    "principalRaw": "6400000000",
    "requiredCollateralRaw": "3200000000000000000",
    "requiredCollateral": "3.2",
    "interestRateBps": 800, "aprPct": 8, "loanDurationSeconds": 2592000
  },
  "nextStep": { "capability": "create_lending_offer_borrow_intent", "note": "…" }
}
Two of the blockers are invisible on the offer. An acceptance ends in TellerV2._submitBid, which refuses a closed market and refuses a borrower the market has not attested — neither fact lives on the commitment, and neither shows up in get_lending_offer. A third belongs to the market too: on an EMI market NumbersLib.pmt requires the loan to run at least one payment cycle, so a short term reverts however healthy the offer is.

create_lending_offer_borrow_intent draws it: acceptCommitmentWithRecipient, behind a collateral approval and the one-time market-forwarder approval. Omit collateralAmountRaw and exactly the required amount is posted, computed from whichever price is currently binding. A principal larger than the offer's remaining allocation, or than your collateral supports, is clamped down — preview.limitedBy names which ceiling bit, and preview.adjusted says what was asked for. Asking for more than the lender can currently fund is the exception: a 412 carrying fundablePrincipalRaw, because that ceiling is invisible from outside the lender's wallet and the gap can be a thousandfold. Everything in blockers is refused up front in the same way. The collateral approval goes to Teller's collateral manager, not to the forwarder — that mismatch is the classic way this reverts on the final step. What comes out is an ordinary TellerV2 loan, so list_loans and create_repay_intent take it from there.

create_lending_offer_borrow_intent
POST /api/v1/lending-offers/borrow-intents
{
  "walletAddress": "0xdef…",
  "chainId": 8453,
  "commitmentId": "412",
  "principalAmountRaw": "1500000000"
}
{
  "intentId": "txi_7a19…",
  "kind": "borrow",
  "stepsTotal": 3,
  "steps": [
    // The collateral manager escrows, not the forwarder — approving the
    // forwarder is the classic way this reverts on the last step.
    { "id": "step_1", "kind": "approve", "to": "0x4200…0006", "data": "0x095ea7b3…",
      "description": "Approve the Teller collateral manager to move your tokens" },
    { "id": "step_2", "kind": "execute", "to": "0x5daE…2cB0", "data": "0x4f4ff0d4…",
      "description": "Allow the Teller commitment forwarder to act for you in market 2" },
    { "id": "step_3", "kind": "execute", "to": "0xfA87…c2b5", "data": "0x8a1b2c3d…",
      "description": "Borrow USDC against lending offer 412" }
  ],
  "preview": {
    "principalRaw": "1500000000", "principal": "1500",
    "collateralAmountRaw": "1250000000000000000",
    "requiredCollateralRaw": "1250000000000000000",
    // What the draw could have been, and which ceiling stopped it going
    // higher: "offer-remaining", "lender-funding" or "your-collateral".
    "maxPrincipalRaw": "6400000000", "limitedBy": "lender-funding",
    "interestRateBps": 800, "aprPct": 8, "loanDurationSeconds": 2592000
  }
}

The collateral requirement moves with the oracle leg between the quote and the signature, and the contract re-reads it at acceptance — so a borrower posting the bare minimum can be refused by a tick of price drift. Post a margin over requiredCollateralRaw, or size the draw below the maximum.

Lending offers need the commitment forwarder, which is not on every chain TellerV2 is. list_chains reports it as supports.lendingOffers, and a call against a chain without it is a 404 naming the chain, not a 502. Listing offers scans the forwarder's creation logs rather than an index, so truncated: true means the node would not serve the full history and older offers are missing rather than absent.

MCP: add Teller to an AI

The same capabilities are an MCP server at https://pro.teller.org/api/mcp/v1 over Streamable HTTP. Tool names match capability names exactly and arguments match the REST parameters, so everything on this page applies unchanged — including the API key. The server authenticates before it will answer initialize, so a client with no key can't even list the tools.

The server's initialize instructions explain the sign-and-continue flow, which means an agent that connects cold can drive a swap or a borrow without being told how first.

Authenticating

Two ways, depending on what your client supports. OAuth — paste the URL and nothing else; the client discovers the auth server, registers itself, and Teller shows a consent screen where you paste your partner key. The token that comes back carries exactly that key's scopes and never more; revoking the key revokes the connector. Header — send x-teller-api-key and skip the consent flow entirely.

What any client needs

Every host below is the same three facts. If yours isn't listed, or its connector UI has moved, these are all you need:

SettingValue
Server URLhttps://pro.teller.org/api/mcp/v1
TransportStreamable HTTP (remote MCP)
AuthOAuth 2.1 — paste the URL and nothing else — or the header x-teller-api-key: sk_live_…
Optionalx-teller-partner-code to tag everything the client does

Assistants

Claude — web, desktop, mobile

Settings → Connectors → Add custom connector. Paste the URL; Claude runs the OAuth flow and opens a Teller consent screen where you paste your partner key.

https://pro.teller.org/api/mcp/v1
ChatGPT / OpenAI

Add it as a connector in ChatGPT settings, or pass it as an MCP tool from the Responses / Agents API with your key as a header.

{
  "type": "mcp",
  "server_label": "teller",
  "server_url": "https://pro.teller.org/api/mcp/v1",
  "headers": { "x-teller-api-key": "sk_live_…" },
  "require_approval": "always"
}
Microsoft Copilot

Copilot Studio → your agent → Tools → Add a tool → New tool → Model Context Protocol. Give it the server URL and choose API key or OAuth 2.0 for authentication; the agent (and any Microsoft 365 Copilot surface it is published to) then sees Teller's tools. In VS Code, GitHub Copilot reads .vscode/mcp.json — see the developer tools below.

Server URL   https://pro.teller.org/api/mcp/v1
Transport    Streamable HTTP
Auth         API key header  x-teller-api-key: sk_live_…
             or OAuth 2.0 (discovery is automatic)
Google — Gemini & Vertex AI

The Gemini and Vertex AI SDKs accept an MCP client session as a tool, so the model calls Teller directly. Agent Builder / Agent Engine can register the same URL as a remote MCP tool. For the Gemini CLI, see the developer tools below.

# python-genai: hand the model an MCP session
from google import genai
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async with streamablehttp_client(
    "https://pro.teller.org/api/mcp/v1",
    headers={"x-teller-api-key": "sk_live_…"},
) as (read, write, _):
    async with ClientSession(read, write) as session:
        await session.initialize()
        client = genai.Client()
        response = await client.aio.models.generate_content(
            model="gemini-2.5-pro",
            contents="What can this wallet borrow?",
            config=genai.types.GenerateContentConfig(tools=[session]),
        )
Perplexity

Custom MCP connectors are available on the plans that expose Settings → Connectors → Add connector; paste the URL and authenticate with your partner key. If your plan doesn't offer custom connectors, use the REST API directly — it is the same capability set.

https://pro.teller.org/api/mcp/v1

Editors, CLIs and agent frameworks

Claude Code

Add it from the CLI. The first call opens the consent screen in your browser.

claude mcp add --transport http teller https://pro.teller.org/api/mcp/v1

# or skip the browser entirely with a header:
claude mcp add --transport http teller https://pro.teller.org/api/mcp/v1 \
  --header "x-teller-api-key: sk_live_…"
Cursor

Settings → MCP → Add new server, or commit .cursor/mcp.json to the repo.

{
  "mcpServers": {
    "teller": {
      "url": "https://pro.teller.org/api/mcp/v1",
      "headers": { "x-teller-api-key": "sk_live_…" }
    }
  }
}
VS Code — GitHub Copilot

Add to .vscode/mcp.json. Use an input so the key is prompted for and stored, not committed.

{
  "inputs": [
    { "id": "teller-key", "type": "promptString",
      "description": "Teller partner API key", "password": true }
  ],
  "servers": {
    "teller": {
      "type": "http",
      "url": "https://pro.teller.org/api/mcp/v1",
      "headers": { "x-teller-api-key": "${input:teller-key}" }
    }
  }
}
Windsurf

Cascade → MCP servers → Add.

{
  "mcpServers": {
    "teller": {
      "serverUrl": "https://pro.teller.org/api/mcp/v1",
      "headers": { "x-teller-api-key": "sk_live_…" }
    }
  }
}
Zed

settings.json → context_servers.

{
  "context_servers": {
    "teller": {
      "source": "custom",
      "url": "https://pro.teller.org/api/mcp/v1",
      "headers": { "x-teller-api-key": "sk_live_…" }
    }
  }
}
Gemini CLI

~/.gemini/settings.json.

{
  "mcpServers": {
    "teller": {
      "httpUrl": "https://pro.teller.org/api/mcp/v1",
      "headers": { "x-teller-api-key": "sk_live_…" }
    }
  }
}
Anything that only speaks stdio

Run the bridge; it forwards to the same endpoint, so the tool list is identical.

TELLER_API_BASE=https://pro.teller.org \
TELLER_API_KEY=sk_live_… \
npx teller-integration-mcp

Connector support differs by product and plan and moves quickly. Where a host hasn't shipped custom MCP connectors yet, the REST API covers the identical capability set — nothing is MCP-only.

Pre-qualification

The credit funnel behind the form on this site, available to your own UI. It runs the same server pipeline, so a submission from your app is treated identically downstream — there is no separate partner path.

StepWhat it does
get_prequal_form_schemaEvery answer key, its legal values, and which lender each unlocks. Render whatever subset you want.
preview_prequal_matchesDry-run a set of answers. Nothing stored, no lead posted. Safe on every keystroke.
create_prequal_linkHave Teller ask the rest. Returns a link that opens this form already filled in with what you collected — and for US users opens past the pages your link settled, so nobody answers the loan-type question twice.
create_prequal_submissionThe real thing. Computes matches, persists, and routes the lead to live lenders.
get_prequal_lead_statusPoll for the outcome. Once a lender takes the lead you get a redirect URL for your user.

Preview and submission both return a matches array holding everything a lender card needs: brand, logoUrl (with brandColor and initial as the fallback chip), amount and the separate amountLabel that says whether it means UP TO or a RANGE, body copy, a click url, and a disclosure. On a submission the response's own matches is the one to render — prequal.matches beside it is the trimmed set we persisted for our reporting, with no logo, URL or disclosure on it.

Two of those fields are not styling. disclosure.text is verbatim compliance copy that must be shown with the offer — render it as given, and use inline to tell whether it belongs under that card or can be pooled at the foot of the list. And url points at Teller rather than the lender on purpose: that hop re-checks the offer's eligibility at click time and diverts a blocked click to a compliant explanation page, records the click, awards the score points, and carries the affiliate attribution. Linking straight to a lender skips all four. On a submission the URL also carries a signed 24-hour token identifying the borrower — without it your user reaches us with no Teller cookie, looks like a first-time visitor, and a prequal-gated lender turns them away.

Pass a uuid clientSubmissionId to make retries idempotent. Three optional sibling blocks — usShortTermDetail, caLenderDetail and usHelocDetail — carry the extra detail those products require (bank account, SSN, property status). They are forwarded to the lenders that need them and never persisted on the submission row.

Two things are yours as the integrator: collecting the borrower's consent to lender contact before you submit, and only submitting real people. create_prequal_submission routes to lenders who pay for leads — use preview_prequal_matches for anything test-shaped.
preview_prequal_matches
POST /api/v1/prequal/preview
{
  "answers": {
    "loanTypes": ["personal"],
    "amountUsd": 15000,
    "country": "United States",
    "isUS": true,
    "state": "CA",
    "credit": "good",
    "annualIncomeUsd": 90000,
    "employment": "w2"
  }
}
{
  "count": 3,
  "matches": [
    {
      "id": "upstart",
      "brand": "Upstart",
      "product": "Personal loan",
      "badge": "PERSONAL LOAN",
      "bestMatch": true,

      "logoUrl": "https://…/upstart.png",
      "brandColor": "#ffffff",
      "initial": "U",
      "logoContain": true,

      "amount": "$1k–50k",
      "amountLabel": "RANGE",

      "headline": "Fixed rates, no prepayment fee",
      "detail": "Funds as soon as next day",
      "about": "…",

      // Send the click here, as given — this hop re-checks eligibility,
      // records the click and carries the affiliate attribution.
      "url": "https://pro.teller.org/api/ref/upstart",
      "scorePoints": 10,

      // Verbatim compliance copy. inline=true means it belongs under
      // THIS card; inline=false may be pooled at the bottom of the list.
      "disclosure": {
        "key": "upstart-personal-loans",
        "text": "Important Disclosures: …",
        "inline": true
      }
    }
  ]
}

// Preview URLs carry no borrower identity — nothing has been submitted
// yet. Submit to get links that do.
create_prequal_submission
POST /api/v1/prequal/submissions
{
  "walletAddress": "0xabc…",
  "clientSubmissionId": "0f1e2d3c-…",
  "partnerCode": "spring-campaign:web",
  "answers": {
    "loanTypes": ["personal"], "amountUsd": 15000,
    "country": "United States", "isUS": true, "state": "CA",
    "credit": "good", "annualIncomeUsd": 90000, "employment": "w2",
    "firstName": "Ada", "lastName": "Lovelace",
    "email": "[email protected]", "phone": "+15555550100",
    "birthYear": 1990, "consent": true
  }
}
{
  "created": true,
  "anonId": "6b1e…",
  "partnerId": "acme",
  "partnerCode": "spring-campaign:web",

  // THIS is the array to render — same card shape preview returns, but
  // the urls now carry a signed token identifying the borrower, so a
  // prequal-gated lender doesn't turn your user away on arrival.
  "matches": [
    {
      "id": "upstart", "brand": "Upstart", "product": "Personal loan",
      "logoUrl": "https://…/upstart.png", "brandColor": "#ffffff", "initial": "U",
      "amount": "$1k–50k", "amountLabel": "RANGE",
      "headline": "Fixed rates, no prepayment fee",
      "url": "https://pro.teller.org/api/ref/upstart?t=eyJwcmVx…",
      "disclosure": { "key": "upstart-personal-loans", "text": "…", "inline": true }
      /* …and the rest — see preview_prequal_matches */
    }
  ],

  "prequal": {
    "id": "9d2f…", "status": "matched",
    "loanTypes": ["personal"], "amountUsd": 15000, "creditBand": "good",
    // What we persisted, for our own reporting. Trimmed: no logo, no
    // url, no disclosure. Don't render off this one.
    "matches": [ { "id": "upstart", "brand": "Upstart", "amount": "$1k–50k", "…": "…" } ]
  },
  "routing": {
    "coverage": "partial",
    "originRegistered": false,
    "action": {
      "code": "register_origin",
      "message": "Some lenders require the origin that collected the lead to be registered before they will accept it. Contact Teller with the domain your users see to widen your coverage."
    }
  },
  "leadStatusHint": "poll get_prequal_lead_status with prequalId=9d2f… until state leaves \"searching\""
}
get_prequal_lead_status
GET /api/v1/prequal/submissions/9d2f…/lead-status
// A lender took the lead — send the borrower here.
{
  "prequalId": "9d2f…",
  "state": "matched",
  "redirectUrl": "https://lender.example/apply?lead=…",
  "pollForMs": 0
}

// Still working. Keep polling for up to pollForMs.
{ "prequalId": "9d2f…", "state": "searching", "redirectUrl": null, "pollForMs": 128000 }

// Nobody took it.
{ "prequalId": "9d2f…", "state": "no_match", "redirectUrl": null, "pollForMs": 0 }

Lender coverage & your origin

Teller routes a submission across a network of lenders. How much of that network a submission can reach depends on where it was collected.

Part of the network accepts leads from anywhere, because the borrower completes on the lender's own page under the lender's own disclosures. The rest are told which site the borrower was on when they consented, and match it against an origin registered against the disclosure they hold. We will not tell a lender a lead came from pro.teller.org when it came from your app — so reaching that part of the network takes a one-time step: tell us the domain your users see, we register it, and from then on your leads go out naming your site.

Until then your submissions still work and still match; they just reach a narrower set of lenders. Every submission tells you where you stand, so you never have to guess:

"routing": {
  "coverage": "partial",
  "originRegistered": false,
  "action": {
    "code": "register_origin",
    "message": "Some lenders require the origin that collected the lead to be registered before they will accept it. Contact Teller with the domain your users see to widen your coverage."
  }
}

// once your origin is registered:
"routing": { "coverage": "full", "originRegistered": true, "action": null }
Nothing about which lenders exist, how many there are, or which ones gate on origin is exposed through the API — coverage and action are the whole contract. Lead status likewise reports the outcome (searching, matched, no_match) and the redirect to send your borrower to, not who bought the lead.

Errors

Every failure has the same shape, with a stable machine-readable code.

{ "error": { "code": "wallet_proof_required", "message": "…", "details": {} } }
CodeHTTPMeaning
invalid_request400Bad or missing arguments — including a token address or chain the routing provider rejects
unauthorized401Missing or unrecognised key
wallet_proof_invalid401Proof doesn't verify against the address
forbidden403Your key isn't scoped for this capability
not_found404Unknown capability, intent or record — or a token that doesn't exist on that chain
conflict409Intent already advanced, cancelled, or expired
precondition_failed412A gate on the wallet failed (KYC, score, active loan)
wallet_proof_required428This action needs a signed challenge
upstream_error502A provider (LI.FI, the Teller API, an RPC) failed — retry
unavailable503A Teller dependency is down — retry

What to retry

Only upstream_error, unavailable and conflict. Those mean a dependency was briefly unwell and the same request may succeed shortly — retry twice with jittered backoff, then surface the failure. Quotes are time-sensitive, so a long retry chain returns a stale price rather than a useful one.

Never retry a 4xx. invalid_request and not_found describe something wrong with the request itself, and it will be just as wrong the second time.

Token addresses

fromToken, toToken and lookup_token's address must be the real contract address on the chain you named. Resolve them with list_tokens or lookup_token rather than from memory — an address that is nearly right is not nearly enough.

EIP-55 checksummed, all-lowercase and all-uppercase are all accepted. A mixed-case address whose checksum doesn't match is refused as invalid_request before we call the provider: mixed casing is a checksum claim, and a failing one means the value is corrupt. Solana mints are base58 and pass through untouched.

Reference

What you can do

Generated from the same definition the API serves, so this list is never out of date. Each entry gives the REST route, the MCP tool name, the required scope and every parameter.

Wallet & balances

Read an address: holdings, identity, verification state.

GET/api/v1/wallet/overviewread
get_wallet_overviewMCP tool

Balances, Teller Score, unsecured credit headroom, KYC state and live loans for a wallet. Each leg degrades independently, so a slow upstream never blanks the whole response. Start here.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
chainIdintegerChain to read loans on. Defaults to Base (8453).
solanaAddressstringOptional Solana address to fold into the balance total.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/wallet/overview?walletAddress=0xabc…&chainId=8453
Response
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "portfolio": {
    "totalUsd": 4820.15,
    "tokens": [
      { "chain": "base-mainnet", "symbol": "USDC", "balance": "4200.00",
        "priceUsd": 1, "valueUsd": 4200 },
      { "chain": "base-mainnet", "symbol": "WETH", "balance": "0.21",
        "priceUsd": 2953.1, "valueUsd": 620.15 }
    ]
  },
  "score": {
    "total": 320,
    "monthlyDelta": 45,
    "unlockedUsdc": 320,
    "categories": [
      { "key": "swap",   "label": "Swap",   "points": 120, "max": 200 },
      { "key": "borrow", "label": "Borrow", "points": 80,  "max": 200 },
      { "key": "apply",  "label": "Apply",  "points": 100, "max": 200 },
      { "key": "refer",  "label": "Refer",  "points": 0,   "max": 200 },
      { "key": "hold",   "label": "Hold",   "points": 20,  "max": 100 },
      { "key": "income", "label": "Income", "points": 0,   "max": 100 }
    ],
    "recent": [ /* last 10 score events */ ]
  },
  "credit": { "unlockedUsdc": 320, "feesAccruedUsd": 12.4, "kycVerified": true },
  "loans": [ /* live Teller loans on this chain */ ],
  "loanCount": 1
}
GET/api/v1/wallet/portfolioread
get_portfolioMCP tool

Live balances across every supported EVM chain (and Solana when a solanaAddress is given), priced in USD and sorted by value.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
solanaAddressstringOptional Solana address to include.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/wallet/identityread
get_identityMCP tool

Reverse-resolves an address to its ENS name and Farcaster username, when either exists.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/wallet/kycread
get_kyc_statusMCP tool

Reports the wallet's most recent verified KYC record (Self Protocol zk-passport or another configured provider). Unsecured borrowing requires this to be true.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/wallet/referralsread
get_referral_infoMCP tool

Mints the wallet's referral code if it doesn't have one yet, and reports pending vs funded referrals.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/wallet/challengeread
create_wallet_challengeMCP tool

Returns a short-lived message for the end user's wallet to sign. Replay the signature as `walletProof` to authorize credit-bearing actions. Required for create_unsecured_borrow_intent, and for every intent when the operator has enabled TELLER_PARTNER_REQUIRE_WALLET_PROOF.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
purposestringFree-text label bound into the message, shown to the user in their wallet.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/wallet/challenge?walletAddress=0xabc…&purpose=borrow
Response
{
  "message": "Teller Pro wants to verify you control this wallet.\n\nWallet: 0xabc…\nOrigin: https://pro.teller.org\nPurpose: borrow\nNonce: n7UxKxvsJDZ-Nd0AEVwi8A\nIssued At: 2026-08-11T06:42:24.560Z\nExpires At: 2026-08-11T06:52:24.560Z\nStamp: mfxHKto828B2_O1Hstt5GleR6amIrzz3",
  "walletAddress": "0xabc…",
  "expiresAt": "2026-08-11T06:52:24.560Z",
  "instructions": "Have the wallet sign `message` verbatim with personal_sign (EIP-191)…"
}

Swap

Cross-chain and same-chain token swaps.

POST/api/v1/swap/intentstx
create_swap_intentMCP tool

Quotes the swap (racing LI.FI against ShapeShift where ShapeShift is stronger) and returns the ordered unsigned transactions — token approval first when needed, then the swap. Sign each with the user's wallet and call continue_tx_intent with the hash. On completion the swap is recorded and Swap-category score points are credited automatically.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
fromChainintegerSource chain id.
toChainintegerDestination chain id.
fromTokenstringToken address to sell (0x0…0 for the native coin).
toTokenstringToken address to buy.
fromAmountstringAmount to sell, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
Optional
toAddressstringWhere to deliver the bought token. Defaults to walletAddress.
slippagenumberSlippage tolerance as a decimal (0.005 = 0.5%). Defaults to 0.005.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
approvalModestringexactunlimiteddefault unlimitedHow much to approve when this action needs an ERC-20 allowance. `unlimited` (the current default) approves once and never again, so repeat actions need no further approval. `exact` approves only the amount this action moves, so a compromised spender can take that and nothing more — at the cost of a fresh approval on every repeat, because an exact allowance is spent to zero by the action it was granted for. Pass `exact` if you would rather your users granted a capped allowance. Either way, no approval step is included at all when the spender's existing allowance already covers the amount.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/swap/intents
{
  "walletAddress": "0xabc…",
  "fromChain": 8453,
  "toChain": 8453,
  "fromToken": "0x8335…2913",
  "toToken": "0x4200…0006",
  "fromAmount": "25000000",
  "slippage": 0.005,
  "partnerCode": "spring-campaign:web"
}
Response
{
  "intentId": "txi_9f2c8a41…",
  "kind": "swap",
  "status": "awaiting_signature",
  "chainId": 8453,
  "walletAddress": "0xabc…",
  "partnerCode": "spring-campaign:web",
  "stepsTotal": 2,
  "stepsRemaining": 2,
  "steps": [
    {
      "id": "step_1", "index": 0, "kind": "approve", "action": "swap", "chainId": 8453,
      "to": "0x8335…2913", "data": "0x095ea7b3…", "value": "0",
      "description": "Approve the lifi router to move your tokens",
      "status": "pending", "txHash": null
    },
    {
      "id": "step_2", "index": 1, "kind": "execute", "action": "swap", "chainId": 8453,
      "to": "0x1231…4eae", "data": "0x4630a0d8…", "value": "0",
      "gasLimit": "0x7a120",
      "description": "Swap USDC → WETH",
      "status": "pending", "txHash": null
    }
  ],
  "preview": {
    "provider": "lifi",
    "fromAmount": "25000000",
    "toAmount": "8420000000000000",
    "toAmountMin": "8377900000000000",
    "fromAmountUsd": "25.00",
    "estimatedDurationSeconds": 30,
    "display": {
      "fromAmount": "25",
      "toAmount": "0.00842",
      "toAmountMin": "0.0083779"
    },
    // Each side resolved — render these directly, no lookup needed.
    "fromToken": {
      "address": "0x8335…2913", "symbol": "USDC", "name": "USD Coin",
      "decimals": 6, "logoUrl": "https://…/usdc.png",
      "amount": "25000000", "amountFormatted": "25", "amountUsd": "25.00"
    },
    "toToken": {
      "address": "0x4200…0006", "symbol": "WETH", "name": "Wrapped Ether",
      "decimals": 18, "logoUrl": "https://…/weth.png",
      "amount": "8420000000000000", "amountFormatted": "0.00842", "amountUsd": "24.90"
    }
  },
  "nextStep": { /* === steps[0] */ },
  "instructions": "Send transaction 1 of 2 (\"Approve the lifi router…\") from 0xabc… on chain 8453. …",
  "expiresAt": "2026-08-11T09:12:03.776Z"
}
GET/api/v1/swap/statusread
get_swap_statusMCP tool

LI.FI execution status for a broadcast swap, including the destination-chain fill.

Required
txHashstringSource-chain transaction hash.
fromChainstringSource chain id.
toChainstringDestination chain id.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.

Send

Transfer tokens to an address, ENS name, Basename or Farcaster handle.

POST/api/v1/send/intentstx
create_send_intentMCP tool

Resolves the recipient (raw address, ENS, Basename, or Farcaster handle) and returns one unsigned transfer transaction.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
recipientstringAddress, ENS name, Basename, or Farcaster handle to send to.
amountRawstringAmount to send, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
Optional
tokenAddressstringToken to send. Omit or pass the zero address for the chain's native coin.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/send/intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "recipient": "vitalik.eth",
  "tokenAddress": "0x8335…2913",
  "amountRaw": "2500000"
}
Response
{
  "intentId": "txi_c0de…",
  "kind": "send",
  "stepsTotal": 1,
  "nextStep": {
    "id": "step_1", "kind": "execute", "chainId": 8453,
    "to": "0x8335…2913",
    "data": "0xa9059cbb000000000000000000000000d8da…00000000000000000000000000000000002625a0",
    "value": "0",
    "description": "Send tokens to vitalik.eth"
  },
  "preview": {
    "recipient": "0xd8dA…6045",
    "recipientDisplay": "vitalik.eth",
    "recipientSource": "ens",
    "amountRaw": "2500000"
  }
}

Borrow

Price a loan, borrow against collateral, or borrow against the Teller Score.

GET/api/v1/borrow/poolsread
list_borrow_poolsMCP tool

The matrix of Teller lender-group pools: which collateral borrows which asset on which chain, with APR, collateral ratio and available liquidity. Pools are only half of what Teller lends: the other half is lending offers, one lender's standing commitment on the Alpha forwarder, which are listed by list_lending_offers (pass `borrower` for the ones a wallet can actually draw from) and drawn on with create_lending_offer_borrow_intent. The response carries that pointer as `lendingOffers` so a borrow search does not stop at pools.

Optional
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
collateralTokenstringFilter by collateral token address.
borrowTokenstringFilter by borrowed token address.
poolAddressstringFilter to one pool.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/borrow/pools?chainId=8453&collateralToken=0x4200…0006
Response
{
  "updated_at": 1754899200000,
  "count": 2,
  "results": [
    {
      "chainId": 8453,
      "pool_address": "0xcc…",
      "collateral_token_address": "0x4200…0006",
      "collateral_token_symbol": "WETH",
      "borrow_token_address": "0x8335…2913",
      "borrow_token_symbol": "USDC",
      "enrichment": {
        "collateralRatioPct": 150,
        "minInterestRatePct": 8.5,
        "paymentCycleDuration": 2592000,
        "principalAvailableUsd": 184000
      }
    }
  ]
}
GET/api/v1/borrow/termsread
get_borrow_termsMCP tool

Wallet-scaled terms for one pool: APR, collateral ratio, max borrow, plus the two on-chain fields (`requiredCollateralPerPrincipal`, `poolPrincipalAvailableRaw`) that the contract actually enforces. Prefer those over the USD-derived fields when sizing a borrow. This is the pool side; get_lending_offer_borrow_terms is the same question asked of a lending offer, and create_borrow_intent and create_lending_offer_borrow_intent are the two ways a wallet ends up with a TellerV2 loan.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
collateralTokenstringCollateral token address.
poolAddressstringLender-group pool address.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/borrow/terms?walletAddress=0xabc…&chainId=8453
  &collateralToken=0x4200…0006&poolAddress=0xcc…
Response
{
  "poolAddress": "0xcc…",
  "collateralTokenSymbol": "WETH",
  "borrowTokenSymbol": "USDC",
  "aprPct": 9.25,
  "collateralRatioPct": 150,
  "paymentCycleDuration": 2592000,
  "principalTokenDecimals": 6,

  // Prefer these two — they are read from the pool contract and are
  // what it actually enforces. The USD-derived fields above overestimate
  // capacity on pools whose oracle prices collateral below market.
  "requiredCollateralPerPrincipal": "512000000000000",
  "poolPrincipalAvailableRaw": "184000000000"
}
POST/api/v1/borrow/intentstx
create_borrow_intentMCP tool

Returns the approval + borrow transactions for a Teller lender-group pool. The requested principal is clamped down to what the collateral actually supports on-chain (the pool reverts otherwise) — check `preview.adjusted` to see whether that happened. Completion records the borrow and credits Borrow-category points. This borrows from a pool; borrowing against one lender's standing offer is create_lending_offer_borrow_intent instead, and the two produce the same kind of TellerV2 loan.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
poolAddressstringLender-group pool to borrow from.
collateralTokenAddressstringCollateral token address.
collateralAmountstringCollateral to lock, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
principalAmountstringPrincipal to borrow, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
Optional
loanDurationintegerLoan duration in seconds. Defaults to the pool's own term.
amountUsdnumberUSD value of the principal, used for score crediting on completion.
aprnumberAPR as a percentage, for the audit row.
borrowSymbolstringBorrowed token symbol, for activity display.
collateralSymbolstringCollateral token symbol, for activity display.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/borrow/intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "poolAddress": "0xcc…",
  "collateralTokenAddress": "0x4200…0006",
  "collateralAmount": "200000000000000000",
  "principalAmount": "300000000",
  "amountUsd": 300,
  "borrowSymbol": "USDC",
  "collateralSymbol": "WETH"
}
Response
{
  "intentId": "txi_71bd…",
  "kind": "borrow",
  "stepsTotal": 2,
  "steps": [
    { "id": "step_1", "kind": "approve", "to": "0x4200…0006", "data": "0x095ea7b3…",
      "description": "Approve collateral" },
    { "id": "step_2", "kind": "execute", "to": "0xcc…", "data": "0x…",
      "description": "Borrow" }
  ],
  "preview": {
    "poolAddress": "0xcc…",
    "principalAmount": "287400000",
    // Set when the request overshot what the collateral supports on-chain.
    // The pool would have reverted; we clamped instead.
    "adjusted": {
      "requestedPrincipal": "300000000",
      "requiredCollateral": "208800000000000000"
    },
    "summary": { "totalTransactions": 2, "needsApproval": true }
  },
  "nextStep": { /* steps[0] */ }
}
POST/api/v1/borrow/unsecured-intentstx
create_unsecured_borrow_intentMCP tool

Requests an unsecured USDC loan on Base against the wallet's Teller Score. Gated on verified KYC, the amount fitting inside `unlockedUsdc`, and no other unsecured loan outstanding. A signed walletProof is ALWAYS required here — a partner API key alone must never be able to draw down someone else's borrowing power.

Always requires a signed walletProof.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
amountUsdcnumberAmount to borrow, in whole USDC. Must not exceed the wallet's unlockedUsdc.
Optional
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
# 1. mint a challenge
GET /api/v1/wallet/challenge?walletAddress=0xabc…&purpose=borrow

# 2. have the wallet personal_sign the returned message, then:
POST /api/v1/borrow/unsecured-intents
{
  "walletAddress": "0xabc…",
  "amountUsdc": 250,
  "walletProof": {
    "message": "Teller Pro wants to verify you control this wallet.\n\nWallet: 0xabc…\n…",
    "signature": "0x9f21…"
  }
}
Response
{
  "intentId": "txi_3ac9…",
  "kind": "unsecured_borrow",
  "chainId": 8453,
  "walletProofVerified": true,
  "stepsTotal": 1,
  "nextStep": {
    "id": "step_1", "kind": "execute", "chainId": 8453,
    "to": "0xTellerV2…", "data": "0x…", "value": "0",
    "description": "Submit unsecured loan request for 250 USDC on Base"
  },
  "preview": {
    "amountUsdc": 250, "durationDays": 30, "aprPct": 0,
    "unlockedUsdc": 320
  }
}

// Blocked before any of that if a gate fails — 412:
{ "error": { "code": "precondition_failed",
             "message": "Identity verification required for unsecured loans.",
             "details": { "code": "kyc_required" } } }
POST/api/v1/borrow/loop/intentstx
create_loop_intentMCP tool

Borrows against a LenderGroup pool and swaps the principal into the final token inside one transaction, locking the result as collateral. Loop and short are the same on-chain call: borrowing a stable and swapping into your volatile collateral levers you long, while posting stable collateral and borrowing the volatile leaves you owing it, which is a short. A short is borrowing something volatile and auto-selling it into a stable in the same transaction, so you end up holding dollars and owing the asset. Which one you are opening is derived from the principal and final tokens and reported as `preview.positionKind` and the intent's own `kind` — you do not pass it. Step count varies by wallet, because the collateral approval, the market approval and the BorrowSwap extension are each one-time.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
poolAddressstringLenderGroup pool to borrow from. From list_borrow_pools.
principalTokenAddressstringToken borrowed from the pool. It is also the swap's input — it gets sold in the same transaction. To short a token, name it here.
principalAmountRawstringPrincipal to borrow, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
finalTokenAddressstringToken the borrowed principal is sold into — what you end up holding. Normally the same as collateralTokenAddress, which is what the Teller app always sends. Naming a stablecoin here while borrowing something volatile is what makes the position a short.
collateralAmountRawstringCollateral you are seeding from your own balance, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
collateralTokenAddressstringToken locked as collateral, seeded from your own balance.
Optional
loanDurationSecondsintegerLoan duration. Defaults to the pool's payment cycle (30 days).
amountUsdnumberUSD value of the borrow, recorded against the Teller Score on completion.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
approvalModestringexactunlimiteddefault unlimitedHow much to approve when this action needs an ERC-20 allowance. `unlimited` (the current default) approves once and never again, so repeat actions need no further approval. `exact` approves only the amount this action moves, so a compromised spender can take that and nothing more — at the cost of a fresh approval on every repeat, because an exact allowance is spent to zero by the action it was granted for. Pass `exact` if you would rather your users granted a capped allowance. Either way, no approval step is included at all when the spender's existing allowance already covers the amount.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
# Short WETH: post USDC, borrow WETH, auto-sell it back to USDC.
# For a loop, swap the two token roles.
POST /api/v1/borrow/loop/intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "poolAddress": "0xdd…",
  "principalTokenAddress": "0x4200…0006",
  "principalAmountRaw": "100000000000000000",
  "finalTokenAddress": "0x8335…2913",
  "collateralTokenAddress": "0x8335…2913",
  "collateralAmountRaw": "250000000"
}
Response
{
  "intentId": "txi_1d0c…",
  "kind": "short",
  "stepsTotal": 2,
  "steps": [
    { "id": "step_1", "kind": "approve", "to": "0x8335…2913", "data": "0x095ea7b3…",
      "description": "Approve the pool to move your collateral" },
    { "id": "step_2", "kind": "execute", "to": "0xBorrowSwap…", "data": "0x…",
      "description": "Open the position" }
  ],
  "preview": {
    "positionKind": "short",
    "poolAddress": "0xdd…",
    "collateralToken": { "address": "0x8335…2913", "symbol": "USDC" },
    "principalToken": { "address": "0x4200…0006", "symbol": "WETH" },
    "finalTokenAddress": "0x8335…2913",
    "principalAmountRaw": "100000000000000000",
    // The floor the swap leg accepts, slippage already applied. Size the
    // collateral against this, not the mid-price.
    "amountOutMinimum": "412300000",
    "loanDurationSeconds": 2592000,
    // Which one-time steps this wallet still needed — why two users
    // opening the same position can sign a different number of times.
    "setup": { "collateralApproval": true, "forwarderApproval": false, "extension": false }
  }
}
GET/api/v1/loansread
list_loansMCP tool

Every Teller Protocol loan the wallet holds on a chain, straight from the subgraph — principal, APR, collateral, next due date, status.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/loans/unsecuredread
list_unsecured_loansMCP tool

The wallet's unsecured USDC loans on Base, including bids still pending a lender.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.

Repay & roll over

Close a loan out, or roll it into a fresh term.

POST/api/v1/borrow/repay-intentstx
create_repay_intentMCP tool

Returns approval + repayment transactions for an open Teller loan. Omit `amount` to repay in full.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
bidIdstringThe loan's TellerV2 bid id (from list_loans).
Optional
amountstringRaw amount to repay. Omit for a full repayment.
amountUsdnumberUSD value repaid, for the audit row.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
POST/api/v1/borrow/rollover-intentstx
create_rollover_intentMCP tool

Flash-swap rollover of an active loan into a fresh term. The preview breaks the make-up amount into principal owed, interest owed, origination fee, flash fee and any principal reduction, so the borrower can see exactly what they are paying before signing.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
loanIdstringThe active loan's bid id.
poolAddressstringPool the rollover targets.
lendingTokenAddressstringCurrently-borrowed token.
collateralTokenAddressstringCollateral token.
collateralAmountRawstringCollateral to lock against the new loan, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
principalRawstringCurrent loan principal, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
Optional
currentCollateralRawstringWhat the old loan locked. Omit for an unadjusted rollover.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.

Earn

Deposit into and withdraw from yield vaults, then stake the shares for rewards.

GET/api/v1/earn/opportunitiesread
list_earn_opportunitiesMCP tool

ERC-4626 vaults (Yearn V3 and Teller supply pools) with their base APY, vault address and underlying asset. Feed the `symbol` straight into create_earn_deposit_intent.

Optional
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
POST/api/v1/earn/deposit-intentstx
create_earn_deposit_intentMCP tool

Approval (when needed) plus an ERC-4626 `deposit` into the vault behind the given symbol.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
symbolstringAsset symbol from list_earn_opportunities.
amountRawstringAmount to deposit, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
Optional
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
approvalModestringexactunlimiteddefault unlimitedHow much to approve when this action needs an ERC-20 allowance. `unlimited` (the current default) approves once and never again, so repeat actions need no further approval. `exact` approves only the amount this action moves, so a compromised spender can take that and nothing more — at the cost of a fresh approval on every repeat, because an exact allowance is spent to zero by the action it was granted for. Pass `exact` if you would rather your users granted a capped allowance. Either way, no approval step is included at all when the spender's existing allowance already covers the amount.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/earn/deposit-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "symbol": "USDC",
  "amountRaw": "1000000000"
}
Response
{
  "intentId": "txi_5ee1…",
  "kind": "earn_deposit",
  "stepsTotal": 2,
  "steps": [
    { "id": "step_1", "kind": "approve", "to": "0x8335…2913", "data": "0x095ea7b3…",
      "description": "Approve the USDC vault to move your tokens" },
    { "id": "step_2", "kind": "execute", "to": "0xVault…", "data": "0x6e553f65…",
      "description": "Deposit USDC into the Yearn vault" }
  ],
  "preview": {
    "symbol": "USDC", "apyPct": 7.5, "vaultKind": "yearn",
    "vaultAddress": "0xVault…", "underlyingAddress": "0x8335…2913",
    "amountRaw": "1000000000"
  }
}
POST/api/v1/earn/withdraw-intentstx
create_earn_withdraw_intentMCP tool

Pass `amountRaw` to withdraw a specific amount of the underlying asset, or `sharesRaw` to redeem shares — use shares for "withdraw everything" so no dust is left behind.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
symbolstringAsset symbol from list_earn_opportunities.
Optional
amountRawstringUnderlying asset amount to withdraw, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
sharesRawstringVault shares to redeem, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/earn/stakesread
list_stake_positionsMCP tool

What a wallet has staked in Teller stake pools and what it has waiting to be claimed. An ERC-4626 deposit earns the vault's base APY; staking the shares that deposit returns is a separate step that earns the reward token on top, and nothing in the deposit endpoints reports it. Pools the wallet has nothing in are omitted, and a pool that can't be read is skipped rather than failing the call.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
chainIdintegerLimit to one chain. Omit for every chain.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/earn/stakes?walletAddress=0xabc…&chainId=8453
Response
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "count": 1,
  "positions": [
    {
      "symbol": "USDC",
      "chainId": 8453,
      "stakeContract": "0xStakePool…",
      "stakedBalanceRaw": "998412337",
      "pendingRewardsRaw": "4182000000000000000",
      "rewardTokenSymbol": "TLR",
      "stakingApyPct": 4.25
    }
  ]
}

// Pools this wallet has nothing in are omitted, so an empty list means
// nothing staked — not that nothing is stakeable.
POST/api/v1/earn/stake-intentstx
create_stake_intentMCP tool

Stakes the ERC-4626 share tokens a deposit returned, which is what earns the pool's reward token. Amounts are in SHARE units, not the underlying — deposit first with create_earn_deposit_intent, then stake what it gave back. Includes the share-token approval when the stake pool's allowance is short.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
symbolstringEarn symbol, from list_earn_opportunities (e.g. "USDC").
amountRawstringShare tokens to stake, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
Optional
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
approvalModestringexactunlimiteddefault unlimitedHow much to approve when this action needs an ERC-20 allowance. `unlimited` (the current default) approves once and never again, so repeat actions need no further approval. `exact` approves only the amount this action moves, so a compromised spender can take that and nothing more — at the cost of a fresh approval on every repeat, because an exact allowance is spent to zero by the action it was granted for. Pass `exact` if you would rather your users granted a capped allowance. Either way, no approval step is included at all when the spender's existing allowance already covers the amount.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/earn/stake-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "symbol": "USDC",
  "amountRaw": "998412337"
}
Response
{
  "intentId": "txi_7a01…",
  "kind": "stake",
  "stepsTotal": 2,
  "steps": [
    { "id": "step_1", "kind": "approve", "to": "0xShare…", "data": "0x095ea7b3…",
      "description": "Approve the USDC stake pool to move your tokens" },
    { "id": "step_2", "kind": "execute", "to": "0xStakePool…", "data": "0xa694fc3a…",
      "description": "Stake your USDC vault shares to earn TLR" }
  ],
  "preview": {
    "symbol": "USDC",
    "stakeContract": "0xStakePool…",
    "shareToken": "0xShare…",
    "amountRaw": "998412337",
    "rewardTokenSymbol": "TLR",
    "stakingApyPct": 4.25
  }
}
POST/api/v1/earn/unstake-intentstx
create_unstake_intentMCP tool

Withdraws staked share tokens from the stake pool. This returns SHARES, not the underlying — redeeming those for the underlying is a separate create_earn_withdraw_intent. Does not claim rewards; use create_claim_rewards_intent for that.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
symbolstringEarn symbol, from list_earn_opportunities.
amountRawstringShare tokens to unstake, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
Optional
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/earn/unstake-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "symbol": "USDC",
  "amountRaw": "998412337"
}
Response
{
  "intentId": "txi_44a9…",
  "kind": "unstake",
  "stepsTotal": 1,
  "nextStep": {
    "id": "step_1", "kind": "execute", "chainId": 8453,
    "to": "0xStakePool…", "data": "0x2e1a7d4d…", "value": "0",
    "description": "Unstake your USDC vault shares"
  },
  "preview": {
    "symbol": "USDC",
    "stakeContract": "0xStakePool…",
    "shareToken": "0xShare…",
    "amountRaw": "998412337",
    // You get shares back, not USDC. Redeeming them for the underlying
    // is a further create_earn_withdraw_intent.
    "returns": "vault shares"
  }
}
POST/api/v1/earn/claim-intentstx
create_claim_rewards_intentMCP tool

Claims whatever the stake pool has accrued for this wallet. Takes no amount — the pool pays out its full pending balance. Rejected as a 400 when there is nothing pending, so a caller never spends gas on an empty claim.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
symbolstringEarn symbol, from list_earn_opportunities.
Optional
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/earn/claim-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "symbol": "USDC"
}
Response
{
  "intentId": "txi_b33f…",
  "kind": "claim_rewards",
  "stepsTotal": 1,
  "nextStep": {
    "id": "step_1", "kind": "execute", "chainId": 8453,
    "to": "0xStakePool…", "data": "0x372500ab", "value": "0",
    "description": "Claim your TLR rewards"
  },
  "preview": {
    "symbol": "USDC",
    "stakeContract": "0xStakePool…",
    "rewardTokenSymbol": "TLR",
    "pendingRewardsRaw": "4182000000000000000"
  }
}

// With nothing accrued you get a 400 instead of a transaction:
// { "error": { "code": "invalid_request",
//   "message": "nothing to claim on the USDC stake pool — signing this would only cost gas" } }

Lending pools

Run the lending side: launch a pool, supply to one, withdraw, and operate it. A pool's terms are fixed at deploy time, so preview before you launch.

GET/api/v1/pools/marketsread
list_pool_marketsMCP tool

The `marketId` values a pool can join on each chain, with the loan duration each one writes — they are the same choice, because a pool's `maxLoanDuration` has to match its market. Also reports, per chain, which pool generations (`v1`/`v2`/`v3`) have a factory deployed and which one a launch gets by default. Call this before preview_pool_launch: passing a market id that isn't on the chain is the most common way a launch fails.

Optional
chainIdintegerLimit to one chain. Omit for every chain.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/pools/markets?chainId=8453
Response
{
  "count": 9,
  "markets": [
    { "chainId": 8453, "marketId": 5, "durationSeconds": 43200,
      "durationLabel": "12 hours", "chainName": "Base" },
    { "chainId": 8453, "marketId": 2, "durationSeconds": 2592000,
      "durationLabel": "30 days", "chainName": "Base" }
  ],
  "launchSupport": [
    { "chainId": 8453, "chainName": "Base", "versions": ["v1", "v2", "v3"],
      "defaultVersion": "v1", "marketCount": 9, "launchable": true }
  ]
}
POST/api/v1/pools/launch/previewread
preview_pool_launchMCP tool

Everything create_pool_launch_intent would do, without building a transaction: which factory and generation the pool would use, the basis-point config that would be written on-chain, and — the part worth reading twice — the AMM oracle route it would price collateral through, with the TVL of its thinnest hop. That route is fixed forever at deploy time, so a thin one is a permanently manipulable oracle and is flagged in `warnings`. `deployable: false` means no route could be found and the launch cannot proceed without explicit `oracleRoutes`. Read-only and safe to call repeatedly; it is a POST only because `oracleRoutes` is a nested array that a query string cannot carry, and a preview that can't take the same arguments as the launch it precedes is not a preview.

Required
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
principalTokenstringThe token the pool lends. Lenders supply this and borrowers receive it.
collateralTokenstringThe token borrowers post as collateral.
marketIdintegerMarket to launch into, from list_pool_markets. It also fixes the pool's loan duration.
interestRateLowerBoundPctnumberAPR the pool charges when nothing is borrowed, as a percent (5 = 5%).
interestRateUpperBoundPctnumberAPR the pool charges once utilization reaches liquidityThresholdPct, as a percent. This is the top of your intended range, not the rate at 100% utilization — the pool stores a steeper bound so that your number is what it actually charges at the threshold.
loanToValuePctnumberHow much the pool will lend against collateral, as a percent of its value. Must be under 100.
Optional
liquidityThresholdPctnumberdefault 80Utilization the upper bound is quoted at, as a percent. 80 means the top rate is reached when 80% of the pool is lent out.
initialPrincipalRawstringdefault 0Principal to seed the pool with, deposited by the launching wallet in the same transaction, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
versionstringv1v2v3Pool generation to deploy. Defaults to the one list_pool_markets reports for the chain.
priceAdapterstringOverride the v3 price adapter. Ignored for v1/v2, which price through the factory's pricing helper.
twapIntervalSecondsintegerdefault 5TWAP window each oracle hop reads over. Defaults to 5, matching the Teller app.
oracleRoutesarrayPrice the collateral through these AMM pools instead of searching for a route. Each hop is { pool, zeroForOne, twapInterval, token0Decimals, token1Decimals }, ordered collateral → principal. Pools must be from the chain's own venue — PancakeSwap V3 on BNB Chain (56), Uniswap V3 elsewhere. Nothing validates a supplied route, and it is permanent once deployed.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/pools/launch/preview
{
  "chainId": 8453,
  "principalToken": "0x8335…2913",
  "collateralToken": "0x4200…0006",
  "marketId": 2,
  "interestRateLowerBoundPct": 5,
  "interestRateUpperBoundPct": 20,
  "liquidityThresholdPct": 80,
  "loanToValuePct": 75
}
Response
{
  "chainId": 8453,
  "version": "v1",
  "factoryAddress": "0x7FBC…41BC",
  "market": { "marketId": 2, "durationSeconds": 2592000, "durationLabel": "30 days" },
  "terms": { "interestRateLowerBoundPct": 5, "interestRateUpperBoundPct": 20,
             "liquidityThresholdPct": 80, "loanToValuePct": 75 },
  // Basis points, as stored on-chain. The upper bound is 2375, not
  // 2000: 20% is the rate you want at 80% utilization, and the pool
  // interpolates across the full range, so it is projected out to 100%.
  "config": { "marketId": "2", "maxLoanDuration": 2592000,
              "interestRateLowerBound": 500, "interestRateUpperBound": 2375,
              "liquidityThresholdPercent": 8000, "collateralRatio": 7500 },
  "oracleRoutes": [
    { "pool": "0xd0b5…F224", "zeroForOne": false, "twapInterval": 5,
      "token0Decimals": 6, "token1Decimals": 18, "totalValueLockedUsd": 5182000 }
  ],
  "oracleRouteSource": "discovered",
  "thinnestHopUsd": 5182000,
  "warnings": [],
  "deployable": true
}
POST/api/v1/pools/launch-intentstx
create_pool_launch_intentMCP tool

Deploys a new lender-group pool: the principal-token approval when an initial deposit is included, then the factory's `deployLenderCommitmentGroupPool`. The signing wallet ends up the pool's owner. Run preview_pool_launch first — a pool cannot be reconfigured after launch, and the oracle route in particular is permanent. When the final step confirms, the intent's `completion` carries the new pool's address, read out of the deploy event.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
principalTokenstringThe token the pool lends. Lenders supply this and borrowers receive it.
collateralTokenstringThe token borrowers post as collateral.
marketIdintegerMarket to launch into, from list_pool_markets. It also fixes the pool's loan duration.
interestRateLowerBoundPctnumberAPR the pool charges when nothing is borrowed, as a percent (5 = 5%).
interestRateUpperBoundPctnumberAPR the pool charges once utilization reaches liquidityThresholdPct, as a percent. This is the top of your intended range, not the rate at 100% utilization — the pool stores a steeper bound so that your number is what it actually charges at the threshold.
loanToValuePctnumberHow much the pool will lend against collateral, as a percent of its value. Must be under 100.
Optional
liquidityThresholdPctnumberdefault 80Utilization the upper bound is quoted at, as a percent. 80 means the top rate is reached when 80% of the pool is lent out.
initialPrincipalRawstringdefault 0Principal to seed the pool with, deposited by the launching wallet in the same transaction, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
versionstringv1v2v3Pool generation to deploy. Defaults to the one list_pool_markets reports for the chain.
priceAdapterstringOverride the v3 price adapter. Ignored for v1/v2, which price through the factory's pricing helper.
twapIntervalSecondsintegerdefault 5TWAP window each oracle hop reads over. Defaults to 5, matching the Teller app.
oracleRoutesarrayPrice the collateral through these AMM pools instead of searching for a route. Each hop is { pool, zeroForOne, twapInterval, token0Decimals, token1Decimals }, ordered collateral → principal. Pools must be from the chain's own venue — PancakeSwap V3 on BNB Chain (56), Uniswap V3 elsewhere. Nothing validates a supplied route, and it is permanent once deployed.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
approvalModestringexactunlimiteddefault unlimitedHow much to approve when this action needs an ERC-20 allowance. `unlimited` (the current default) approves once and never again, so repeat actions need no further approval. `exact` approves only the amount this action moves, so a compromised spender can take that and nothing more — at the cost of a fresh approval on every repeat, because an exact allowance is spent to zero by the action it was granted for. Pass `exact` if you would rather your users granted a capped allowance. Either way, no approval step is included at all when the spender's existing allowance already covers the amount.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/pools/launch-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "principalToken": "0x8335…2913",
  "collateralToken": "0x4200…0006",
  "marketId": 2,
  "interestRateLowerBoundPct": 5,
  "interestRateUpperBoundPct": 20,
  "liquidityThresholdPct": 80,
  "loanToValuePct": 75,
  "initialPrincipalRaw": "1000000000"
}
Response
{
  "intentId": "txi_9c42…",
  "kind": "pool_launch",
  "stepsTotal": 2,
  "steps": [
    // The factory pulls the seed deposit and forwards it into the pool
    // it creates, so the allowance is the factory's — the pool has no
    // address yet.
    { "id": "step_1", "kind": "approve", "to": "0x8335…2913", "data": "0x095ea7b3…",
      "description": "Approve the Teller pool factory to move your tokens" },
    { "id": "step_2", "kind": "execute", "to": "0x7FBC…41BC", "data": "0x1f69eda9…",
      "description": "Launch a v1 lending pool for 0x4200…0006 collateral against 0x8335…2913" }
  ],
  "preview": { /* the full preview_pool_launch plan */ }
}

// …and once step_2 confirms, continue_tx_intent hands back the address:
{
  "status": "completed",
  "completion": { "poolAddress": "0x1a2b…", "chainId": 8453 }
}
GET/api/v1/poolsread
list_poolsMCP tool

Pools on one chain, optionally narrowed to the ones a wallet launched (`owner`) or has supplied to (`lender`). Either filter costs an on-chain read per indexed pool, so the scan is capped at `scanLimit` and the response says `truncated: true` when it didn't reach the end — an empty result under truncation means "not in the part we checked", not "none".

Required
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
Optional
principalTokenstringFilter to pools lending this token.
collateralTokenstringFilter to pools taking this collateral.
poolAddressstringFilter to one pool.
ownerstringOnly pools whose on-chain `owner()` is this address — i.e. the pools this wallet launched.
lenderstringOnly pools this wallet holds shares in, each with the wallet's position attached.
limitintegerdefault 25Maximum rows to return.
scanLimitintegerdefault 250How many indexed pools to check on-chain when owner/lender is set.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/pools/detailread
get_poolMCP tool

Full state for a pool address: its generation, owner, principal and collateral tokens, market, the terms it was launched with (back in the whole-percent units they were set in), live utilization and the rate it is charging right now, committed/lent/repaid totals, the share token, the withdraw delay, and its paused flags. Pass `walletAddress` to get that wallet's position in the same call.

Required
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
poolAddressstringAddress of the lender-group pool. From list_pools, list_borrow_pools, or a completed launch intent.
Optional
walletAddressstringOptional — include this wallet's position in the pool.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/pools/detail?chainId=8453&poolAddress=0x1a2b…&walletAddress=0xabc…
Response
{
  "pool": {
    "chainId": 8453,
    "poolAddress": "0x1a2b…",
    "version": "v2",
    "owner": "0xabc…",
    "principalToken": { "address": "0x8335…2913", "symbol": "USDC", "decimals": 6 },
    "collateralToken": { "address": "0x4200…0006", "symbol": "WETH", "decimals": 18 },
    "marketId": 2,
    "maxLoanDurationSeconds": 2592000,
    // From v2 on the pool is its own share token; a v1 pool points at a
    // separate ERC-20 here.
    "sharesTokenAddress": "0x1a2b…",
    "terms": {
      "interestRateLowerBoundPct": 5,
      "interestRateUpperBoundPct": 20,
      "interestRateAtFullUtilizationPct": 23.75,
      "liquidityThresholdPct": 80,
      "loanToValuePct": 75,
      "maxPrincipalPerCollateralAmountRaw": "0"
    },
    "rates": { "currentMinInterestRatePct": 7, "utilizationPct": 10 },
    "liquidity": {
      "principalAvailableToBorrowRaw": "5000000000",
      "principalAvailableToBorrow": "5000.0",
      "totalPrincipalCommittedRaw": "10000000000",
      "totalInterestCollectedRaw": "41230000",
      "sharesExchangeRateRaw": "1004120000000000000000000000000000000"
    },
    "controls": { "paused": false, "borrowingPaused": false,
                  "liquidationsPaused": false, "withdrawDelaySeconds": 300 },
    "priceAdapter": null
  },
  "position": {
    "sharesRaw": "996000000",
    "assetsRaw": "1000103000",
    "assets": "1000.103",
    "withdrawUnlocksAt": 1755691200,
    "withdrawable": true,
    "sharesPreparedRaw": null
  }
}
GET/api/v1/pools/positionsread
list_pool_positionsMCP tool

Every pool on a chain the wallet holds shares in, with shares, what they are currently worth in the principal token, and when the pool's withdraw delay lets them out. Pools it has nothing in are omitted.

Required
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
limitintegerdefault 25Maximum rows to return.
scanLimitintegerdefault 250How many indexed pools to check on-chain.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
POST/api/v1/pools/supply-intentstx
create_pool_supply_intentMCP tool

Approval (when needed) plus the pool's supply call, which is `deposit` on v2/v3 pools and `addPrincipalToCommitmentGroup` on v1 — resolved from the pool, so the caller doesn't have to know which it is. Supplying earns the pool's interest, and the shares it returns are locked for the pool's withdraw delay (reported in the preview) before they can be redeemed.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
poolAddressstringAddress of the lender-group pool. From list_pools, list_borrow_pools, or a completed launch intent.
amountRawstringPrincipal to supply, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
Optional
slippageBpsintegerdefault 500Tolerance on the minimum-shares floor v1 pools take, in basis points. Ignored by v2/v3, which have no floor.
minSharesOutRawstringExplicit minimum shares floor (v1 only), as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
approvalModestringexactunlimiteddefault unlimitedHow much to approve when this action needs an ERC-20 allowance. `unlimited` (the current default) approves once and never again, so repeat actions need no further approval. `exact` approves only the amount this action moves, so a compromised spender can take that and nothing more — at the cost of a fresh approval on every repeat, because an exact allowance is spent to zero by the action it was granted for. Pass `exact` if you would rather your users granted a capped allowance. Either way, no approval step is included at all when the spender's existing allowance already covers the amount.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/pools/supply-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "poolAddress": "0x1a2b…",
  "amountRaw": "1000000000"
}
Response
{
  "intentId": "txi_b7f0…",
  "kind": "pool_supply",
  "stepsTotal": 2,
  "steps": [
    { "id": "step_1", "kind": "approve", "to": "0x8335…2913", "data": "0x095ea7b3…",
      "description": "Approve the WETH/USDC pool to move your tokens" },
    { "id": "step_2", "kind": "execute", "to": "0x1a2b…", "data": "0x6e553f65…",
      "description": "Supply USDC to the WETH/USDC pool" }
  ],
  "preview": {
    "poolAddress": "0x1a2b…", "poolVersion": "v2",
    "amountRaw": "1000000000", "amount": "1000.0",
    "currentApyPct": 7, "utilizationPct": 10,
    // v1 pools take a minimum-shares floor here; v2 and v3 take none.
    "minSharesOutRaw": null,
    // Shares are locked this long after they arrive, so a withdraw
    // attempted sooner is refused rather than reverted.
    "withdrawDelaySeconds": 300
  }
}
POST/api/v1/pools/withdraw-intentstx
create_pool_withdraw_intentMCP tool

Redeems pool shares back into the principal token. Omit `sharesRaw` to redeem the wallet's entire position. On v1 pools this includes the `prepareSharesForBurn` step the contract requires first, and only when the shares aren't already flagged. Refused with `precondition_failed` while the shares are still inside the pool's withdraw delay, and note that a withdrawal is paid out of idle principal — a fully-lent pool has none until a loan repays, which the preview reports as `poolPrincipalAvailableRaw`.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
poolAddressstringAddress of the lender-group pool. From list_pools, list_borrow_pools, or a completed launch intent.
Optional
sharesRawstringShares to redeem, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
slippageBpsintegerdefault 500Tolerance on the minimum-out floor v1 pools take, in basis points. Ignored by v2/v3.
minAmountOutRawstringExplicit minimum principal floor (v1 only), as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/pools/withdraw-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "poolAddress": "0x1a2b…"
}
Response
// Omitting sharesRaw redeems the whole position — shares are the
// only unit that leaves no dust behind.
{
  "intentId": "txi_c318…",
  "kind": "pool_withdraw",
  "stepsTotal": 1,
  "steps": [
    { "id": "step_1", "kind": "execute", "to": "0x1a2b…", "data": "0xba087652…",
      "description": "Redeem your WETH/USDC pool shares" }
  ],
  "preview": {
    "poolAddress": "0x1a2b…", "poolVersion": "v2",
    "sharesRaw": "996000000", "sharesHeldRaw": "996000000",
    "redeemsEverything": true,
    "estimatedAssetsRaw": "1000103000", "estimatedAssets": "1000.103",
    // Withdrawals are paid from idle principal. A fully-lent pool has
    // none until a loan repays, and the redeem reverts.
    "poolPrincipalAvailableRaw": "5000000000"
  }
}
POST/api/v1/pools/config-intentstx
create_pool_config_intentMCP tool

The operations that alter a pool rather than move value through it, and they belong to three different parties. **The pool owner's** — and this is the complete set the contracts give an owner, there is nothing else to change: `set_max_principal_per_collateral` (cap what the pool lends per unit of collateral; the contract takes min(oracle, cap), so this can only ever make a pool more conservative, never looser — v1/v2 only, since v3 prices through its adapter), `transfer_ownership`, and `renounce_ownership` (permanent, and requires `confirm: true`). **The Teller protocol's**: `pause_pool` / `unpause_pool`, `pause_borrowing` / `unpause_borrowing` and `pause_liquidations` / `unpause_liquidations` sit with the protocol pausing manager, and `set_withdraw_delay` with the TellerV2 owner. **Anyone's**: `sweep_escrow_vault` moves repayments stranded in the TellerV2 escrow vault back into the pool. An owner-only action attempted by a non-owner is refused here; a protocol-level one is built and flagged with `requiredAuthority`, because only the chain can settle it.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
poolAddressstringAddress of the lender-group pool. From list_pools, list_borrow_pools, or a completed launch intent.
actionstringset_max_principal_per_collateraltransfer_ownershiprenounce_ownershipsweep_escrow_vaultpause_poolunpause_poolpause_borrowingunpause_borrowingpause_liquidationsunpause_liquidationsset_withdraw_delayWhich change to make.
Optional
newOwnerstringRequired for transfer_ownership.
maxPrincipalPerCollateralAmountRawstringRequired for set_max_principal_per_collateral: principal per whole collateral token, scaled by 1e18. 0 clears the cap and prices from the oracle alone, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
amountRawstringRequired for sweep_escrow_vault: how much principal to pull back into the pool, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
confirmbooleanRequired for renounce_ownership. Leaving a pool with no owner is permanent and cannot be undone, so it has to be asked for twice.
withdrawDelaySecondsintegerRequired for set_withdraw_delay.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/pools/config-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "poolAddress": "0x1a2b…",
  "action": "pause_borrowing"
}
Response
{
  "intentId": "txi_d901…",
  "kind": "pool_config",
  "stepsTotal": 1,
  "steps": [
    { "id": "step_1", "kind": "execute", "to": "0x1a2b…", "data": "0x1a3ee7b4…",
      "description": "Stop new borrows while leaving deposits, withdrawals and liquidations alone (WETH/USDC)" }
  ],
  "preview": {
    "action": "pause_borrowing",
    // Pausing belongs to the Teller protocol, not to the pool's owner,
    // and lives on a manager contract we can't check from the pool — so
    // the transaction is built and the authority is named instead.
    "requiredAuthority": "protocol_pauser",
    "poolOwner": "0xabc…",
    "callerIsPoolOwner": true,
    "authorityNote": "pausing is held by the Teller protocol pausing manager, not by the pool's owner — this will revert with 'OP' for anyone else",
    "controlsBefore": { "paused": false, "borrowingPaused": false,
                        "liquidationsPaused": false, "withdrawDelaySeconds": 300 }
  }
}
GET/api/v1/pools/liquidations/quoteread
get_pool_liquidation_quoteMCP tool

For one bid against one pool: the amount owed right now, and the pool's `minimumAmountDifference` — the auction premium on top of the debt. It is signed and it decays: positive just after default, falling to zero and then negative over the following 24 hours, at which point the pool pays a liquidator to take the collateral. Re-quote immediately before signing, because it moves every block.

Required
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
poolAddressstringAddress of the lender-group pool. From list_pools, list_borrow_pools, or a completed launch intent.
bidIdstringTellerV2 bid id of the defaulted loan.
Optional
defaultedAtSecondsintegerUnix second the loan defaulted (nextDueDate + paymentDefaultDuration). Defaults to now, which quotes the premium at the top of the auction.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
POST/api/v1/pools/liquidate-intentstx
create_pool_liquidate_intentMCP tool

Approval plus `liquidateDefaultedLoanWithIncentive`: the caller repays the pool what the defaulted borrower owes, plus the decaying auction premium, and takes the collateral. The premium is quoted fresh and padded by `bufferBps` because it moves every block — an unpadded floor quoted a few seconds ago reverts. The preview reports `maxSpendRaw`, which is what the approval covers.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
poolAddressstringAddress of the lender-group pool. From list_pools, list_borrow_pools, or a completed launch intent.
bidIdstringTellerV2 bid id of the defaulted loan.
Optional
defaultedAtSecondsintegerUnix second the loan defaulted. Defaults to now.
bufferBpsintegerdefault 500Headroom added to the quoted auction premium so the call survives a few blocks of delay.
tokenAmountDifferenceRawstringOverride the premium entirely, as a signed integer string. Advanced — the quote is normally right.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
approvalModestringexactunlimiteddefault unlimitedHow much to approve when this action needs an ERC-20 allowance. `unlimited` (the current default) approves once and never again, so repeat actions need no further approval. `exact` approves only the amount this action moves, so a compromised spender can take that and nothing more — at the cost of a fresh approval on every repeat, because an exact allowance is spent to zero by the action it was granted for. Pass `exact` if you would rather your users granted a capped allowance. Either way, no approval step is included at all when the spender's existing allowance already covers the amount.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.

Lending offers

Lend as one wallet rather than through a pool: publish a standing offer, re-price it, withdraw it — and borrow against someone else's. The principal never leaves the lender's wallet until a borrower draws on it.

POST/api/v1/lending-offers/previewread
preview_lending_offerMCP tool

Everything create_lending_offer_intent would write, without building a transaction: the exact commitment struct, the AMM route the offer would price its collateral through, that route's live TWAP read from the forwarder itself, the collateral ratio the two produce, and how much collateral a borrower would have to post to draw the whole allocation. `oracleVenue` names the AMM those pools belong to — Uniswap V3 on most chains, PancakeSwap V3 on BNB Chain, where routes are resolved through the PancakeSwap factory itself rather than an index, so `oracleVenue.pinned` is true and the pools are PancakeSwap's by construction. Read `warnings` before creating: an offer with no route keeps lending at the price it was created at, however far the collateral falls, and an offer whose route runs through a thin AMM pool is priced off something cheap to manipulate. A POST only because `oracleRoutes` is a nested array a query string cannot carry.

Required
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
principalTokenstringThe token being lent. The lender holds it until a borrower draws on the offer.
marketIdintegerTeller market the offer lends into, from list_pool_markets. It sets the payment cycle, fees and default window the loans inherit, and the lender must be admitted to it.
maxPrincipalRawstringMost the offer will lend in total, across every borrower, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
minApyPctnumberThe lowest APR the offer will lend at, as a percent (5 = 5%). A borrower may bid higher; anything lower reverts.
Optional
walletAddressstringThe lender's wallet — the commitment's `lender`, and the wallet the principal is pulled from at acceptance. Required to create an offer; optional on a preview, where it only fills that one field.
collateralTokenstringThe token borrowers must post. Omit only for an uncollateralized offer (collateralTokenType NONE).
maxLoanDurationSecondsintegerLongest loan the offer will write, in seconds. Defaults to the duration of the market named in `marketId` — the two are the same choice for a catalogued market.
loanToValuePctnumberHow much the offer lends against collateral, as a percent of its live TWAP value. Written as both the stored ceiling and the on-chain oracle's LTV. Omit only when setting a price by hand.
maxPrincipalPerCollateralAmountRawstringCollateral price, set by hand: principal per whole collateral token, scaled by 1e18 for ERC20 collateral (and unscaled for NFTs). Setting it skips the LTV maths entirely, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
principalPerCollateralstringThe same fixed price in human units — "1800" means one whole collateral token draws 1800 whole principal tokens. Converted using both tokens' decimals; an alternative to maxPrincipalPerCollateralAmountRaw, not an addition to it.
expirationTimestampintegerUnix second the offer stops being acceptable. Mutually exclusive with expiresInSeconds.
expiresInSecondsintegerHow long the offer stands, in seconds from now. Defaults to 30 days.
collateralTokenTypestringNONEERC20ERC721ERC1155ERC721_ANY_IDERC1155_ANY_IDERC721_MERKLE_PROOFERC1155_MERKLE_PROOFdefault ERC20What kind of collateral the offer takes. ERC20 is the default and the only type that can carry a Uniswap oracle route. The *_ANY_ID types accept any token in a collection; the *_MERKLE_PROOF types accept whatever a merkle root admits, in which case collateralTokenId carries the root. NONE lends unsecured, and the contract enforces nothing about repayment.
collateralTokenIdstringToken id for ERC721/ERC1155 collateral, or the merkle root for the *_MERKLE_PROOF types. Must be 0 for ERC20, which the contract enforces.
borrowerAllowlistarrayRestrict the offer to these borrowers. An empty list — the default — means anyone may borrow against it.
oracleRoutesarrayPrice the collateral through these AMM pools instead of searching for a route. At most two hops, ordered collateral → principal, each { pool, zeroForOne, twapInterval, token0Decimals, token1Decimals }. Pools must be from the chain's own venue: Uniswap V3 on most chains, but PancakeSwap V3 on BNB Chain (56), which is where that chain's liquidity is and what Teller's BSC contracts are wired to — a Uniswap pool address on BNB is accepted by the contract and prices loans off the shallow side of the market. Nothing validates that a supplied route prices the right pair, and the route is permanent once the offer is created.
disableOraclebooleandefault falseStore no price route at all, making this a fixed-price offer that never re-prices. Requires a hand-set ratio, and means the offer keeps lending at that price however far the collateral falls.
twapIntervalSecondsintegerdefault 5TWAP window each discovered hop reads over. Defaults to 5, matching the Teller app.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/lending-offers/preview
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "principalToken": "0x8335…2913",
  "collateralToken": "0x4200…0006",
  "marketId": 2,
  "maxPrincipalRaw": "10000000000",
  "minApyPct": 8,
  "loanToValuePct": 75
}
Response
{
  "chainId": 8453,
  "forwarderAddress": "0xfA87…c2b5",
  "market": { "marketId": 2, "durationSeconds": 2592000, "durationLabel": "30 days" },
  "principalToken": { "address": "0x8335…2913", "symbol": "USDC", "decimals": 6 },
  "collateralToken": { "address": "0x4200…0006", "symbol": "WETH", "decimals": 18 },
  "collateralTokenType": "ERC20",
  "commitment": {
    "maxPrincipal": "10000000000",
    "expiration": 1735689600,
    "maxDuration": 2592000,
    "minInterestRate": 800,
    "maxPrincipalPerCollateralAmount": "1500000000",
    "collateralTokenType": 1,
    "marketId": "2"
  },
  "pricing": {
    // The TWAP the forwarder itself reads: 2,000 USDC per WETH.
    "oraclePriceRatioRaw": "2000000000",
    "poolOracleLtvBps": 7500,
    // 75% of it, stored as the fixed ceiling. The oracle leg re-prices
    // downward from here; the ceiling stops a rally loosening the offer.
    "maxPrincipalPerCollateralAmountRaw": "1500000000",
    "principalPerCollateral": "1500",
    "ratioSource": "oracle",
    "collateralForMaxPrincipalRaw": "6666666666666666667",
    "collateralForMaxPrincipal": "6.666666666666666667"
  },
  "oracleRoutes": [
    { "pool": "0xd0b5…3a1e", "zeroForOne": false, "twapInterval": 5,
      "token0Decimals": 6, "token1Decimals": 18 }
  ],
  "oracleRouteSource": "discovered",
  // Which AMM those pools belong to. On BNB Chain (56) this reads
  // { "id": "pancakeswap-v3", "label": "PancakeSwap V3", "pinned": true }
  // — pinned means the route was resolved through that venue's own
  // factory rather than an index, so the pools are PancakeSwap's by
  // construction.
  "oracleVenue": { "id": "uniswap-v3", "label": "Uniswap V3", "pinned": false },
  "thinnestHopUsd": 41800000,
  "warnings": [],
  "creatable": true
}
POST/api/v1/lending-offers/intentstx
create_lending_offer_intentMCP tool

Publishes a standing offer to lend: the ERC-20 approval to TellerV2 (which is what makes the offer fundable — the principal stays in the lender's wallet until a borrower draws on it), the one-time market-forwarder approval when the wallet hasn't granted it in this market before, then `createCommitmentWithUniswap`. On BNB Chain the discovered route is a PancakeSwap V3 pool, resolved through the PancakeSwap V3 factory — that is the venue Teller's BSC contracts are wired to, and a supplied route whose pools come from anywhere else is flagged in `warnings`. Price it by passing `loanToValuePct`, which is applied to the live TWAP of the discovered route and stored as both the fixed ceiling and the oracle's LTV — or set `maxPrincipalPerCollateralAmountRaw` / `principalPerCollateral` yourself and the offer becomes a fixed-price one. Run preview_lending_offer first: an offer with no oracle route will keep lending at its creation-day price after the collateral has fallen. When the final step confirms, the intent's `completion` carries the new offer's `commitmentId`.

Required
walletAddressstringThe lender's wallet. It signs every step, ends up as the commitment's `lender`, and is the wallet the principal is pulled from at acceptance.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
principalTokenstringThe token being lent. The lender holds it until a borrower draws on the offer.
marketIdintegerTeller market the offer lends into, from list_pool_markets. It sets the payment cycle, fees and default window the loans inherit, and the lender must be admitted to it.
maxPrincipalRawstringMost the offer will lend in total, across every borrower, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
minApyPctnumberThe lowest APR the offer will lend at, as a percent (5 = 5%). A borrower may bid higher; anything lower reverts.
Optional
collateralTokenstringThe token borrowers must post. Omit only for an uncollateralized offer (collateralTokenType NONE).
maxLoanDurationSecondsintegerLongest loan the offer will write, in seconds. Defaults to the duration of the market named in `marketId` — the two are the same choice for a catalogued market.
loanToValuePctnumberHow much the offer lends against collateral, as a percent of its live TWAP value. Written as both the stored ceiling and the on-chain oracle's LTV. Omit only when setting a price by hand.
maxPrincipalPerCollateralAmountRawstringCollateral price, set by hand: principal per whole collateral token, scaled by 1e18 for ERC20 collateral (and unscaled for NFTs). Setting it skips the LTV maths entirely, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
principalPerCollateralstringThe same fixed price in human units — "1800" means one whole collateral token draws 1800 whole principal tokens. Converted using both tokens' decimals; an alternative to maxPrincipalPerCollateralAmountRaw, not an addition to it.
expirationTimestampintegerUnix second the offer stops being acceptable. Mutually exclusive with expiresInSeconds.
expiresInSecondsintegerHow long the offer stands, in seconds from now. Defaults to 30 days.
collateralTokenTypestringNONEERC20ERC721ERC1155ERC721_ANY_IDERC1155_ANY_IDERC721_MERKLE_PROOFERC1155_MERKLE_PROOFdefault ERC20What kind of collateral the offer takes. ERC20 is the default and the only type that can carry a Uniswap oracle route. The *_ANY_ID types accept any token in a collection; the *_MERKLE_PROOF types accept whatever a merkle root admits, in which case collateralTokenId carries the root. NONE lends unsecured, and the contract enforces nothing about repayment.
collateralTokenIdstringToken id for ERC721/ERC1155 collateral, or the merkle root for the *_MERKLE_PROOF types. Must be 0 for ERC20, which the contract enforces.
borrowerAllowlistarrayRestrict the offer to these borrowers. An empty list — the default — means anyone may borrow against it.
oracleRoutesarrayPrice the collateral through these AMM pools instead of searching for a route. At most two hops, ordered collateral → principal, each { pool, zeroForOne, twapInterval, token0Decimals, token1Decimals }. Pools must be from the chain's own venue: Uniswap V3 on most chains, but PancakeSwap V3 on BNB Chain (56), which is where that chain's liquidity is and what Teller's BSC contracts are wired to — a Uniswap pool address on BNB is accepted by the contract and prices loans off the shallow side of the market. Nothing validates that a supplied route prices the right pair, and the route is permanent once the offer is created.
disableOraclebooleandefault falseStore no price route at all, making this a fixed-price offer that never re-prices. Requires a hand-set ratio, and means the offer keeps lending at that price however far the collateral falls.
twapIntervalSecondsintegerdefault 5TWAP window each discovered hop reads over. Defaults to 5, matching the Teller app.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
approvalModestringexactunlimiteddefault unlimitedHow much to approve when this action needs an ERC-20 allowance. `unlimited` (the current default) approves once and never again, so repeat actions need no further approval. `exact` approves only the amount this action moves, so a compromised spender can take that and nothing more — at the cost of a fresh approval on every repeat, because an exact allowance is spent to zero by the action it was granted for. Pass `exact` if you would rather your users granted a capped allowance. Either way, no approval step is included at all when the spender's existing allowance already covers the amount.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/lending-offers/intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "principalToken": "0x8335…2913",
  "collateralToken": "0x4200…0006",
  "marketId": 2,
  "maxPrincipalRaw": "10000000000",
  "minApyPct": 8,
  "loanToValuePct": 75,
  "expiresInSeconds": 2592000
}
Response
{
  "intentId": "txi_4d81…",
  "kind": "lending_offer_create",
  "stepsTotal": 3,
  "steps": [
    // The principal never moves until someone borrows. This allowance
    // is what makes the offer fundable; without it the commitment is
    // live on-chain and cannot be drawn.
    { "id": "step_1", "kind": "approve", "to": "0x8335…2913", "data": "0x095ea7b3…",
      "description": "Approve TellerV2, which moves your principal when a borrower accepts" },
    // One-time, per market, per wallet.
    { "id": "step_2", "kind": "execute", "to": "0x5daE…2cB0", "data": "0x4f4ff0d4…",
      "description": "Allow the Teller commitment forwarder to act for you in market 2" },
    { "id": "step_3", "kind": "execute", "to": "0xfA87…c2b5", "data": "0x9b5c9b0f…",
      "description": "Offer USDC against WETH at 8% APR" }
  ],
  "preview": { /* the full preview_lending_offer plan */ }
}

// …and once the last step confirms, continue_tx_intent hands back the id
// every later read, update and delete is keyed on:
{
  "status": "completed",
  "completion": { "commitmentId": "412", "chainId": 8453 }
}
GET/api/v1/lending-offersread
list_lending_offersMCP tool

Offers created on a chain, newest first, each read fresh from the contract — remaining principal, the effective collateral ratio right now, the borrower allowlist, and whether the lender can still actually fund it. Narrow with `lender` to get one wallet's own offers, or with `borrower` to answer the borrower's question instead: every entry then carries a `borrowing` block saying how much that wallet could draw, which of the three ceilings binds (the offer's remaining principal, what the lender can fund, or the wallet's own collateral), and the named conditions that would make an acceptance revert — add `borrowableOnly` to keep only the ones it can actually draw from today. This is the list to reach for when someone asks what a wallet can borrow, alongside list_borrow_pools for the pool side. There is no index to page: the forwarder's `CreatedCommitment` logs are scanned, so `truncated: true` means the node would not serve the full history and older offers are missing rather than absent. Expired and fully-drawn offers are omitted unless `includeInactive` is set.

Required
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
Optional
lenderstringOnly offers made by this wallet.
principalTokenstringOnly offers lending this token.
collateralTokenstringOnly offers taking this collateral.
borrowerstringAnswer from this wallet's point of view: adds a `borrowing` block to every offer with what it could draw and what would stop it.
borrowableOnlybooleandefault falseKeep only the offers `borrower` can draw from right now. Needs `borrower`.
includeInactivebooleandefault falseInclude expired and fully-drawn offers, which are hidden by default.
limitintegerdefault 25Maximum rows to return.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/lending-offers/detailread
get_lending_offerMCP tool

Full state for one commitment id: its lender, market, tokens, rate floor, duration ceiling and expiry; the stored collateral ratio alongside the live oracle ratio and which of the two is currently binding; principal drawn and left; the borrower allowlist (empty means anyone); and the lender's balance and TellerV2 allowance, because an offer whose lender has spent the money is live on-chain and unfundable in practice. Pass `principalAmountRaw` to get the collateral a borrower would have to post for that draw. This read knows nothing about any particular wallet — for that, get_lending_offer_borrow_terms answers the same question scaled to one borrower, including the market conditions this read does not touch.

Required
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
commitmentIdstringThe offer's commitment id, as a decimal string. From list_lending_offers, or from a completed create intent's `completion.commitmentId`.
Optional
principalAmountRawstringQuote the collateral needed to draw this much principal, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/lending-offers/detail?chainId=8453&commitmentId=412&principalAmountRaw=1500000000
Response
{
  "commitmentId": "412",
  "lender": "0xabc…",
  "marketId": 2,
  "principalToken": { "address": "0x8335…2913", "symbol": "USDC", "decimals": 6 },
  "collateralToken": { "address": "0x4200…0006", "symbol": "WETH", "decimals": 18 },
  "collateralTokenType": "ERC20",
  "terms": {
    "maxPrincipalRaw": "10000000000", "maxPrincipal": "10000",
    "minApyPct": 8, "maxLoanDurationSeconds": 2592000,
    "expirationTimestamp": 1735689600, "expiresInSeconds": 1904312, "expired": false
  },
  "pricing": {
    "maxPrincipalPerCollateralAmountRaw": "1500000000",
    // WETH has fallen since the offer was made, so the oracle leg is
    // now the binding one and the offer lends less against it.
    "oraclePriceRatioRaw": "1600000000",
    "poolOracleLtvBps": 7500,
    "effectiveMaxPrincipalPerCollateralAmountRaw": "1200000000",
    "principalPerCollateral": "1200",
    "boundBy": "oracle"
  },
  "availability": {
    "acceptedPrincipalRaw": "2000000000",
    "remainingPrincipalRaw": "8000000000", "remainingPrincipal": "8000",
    "lenderBalanceRaw": "9400000000", "lenderAllowanceRaw": "10000000000",
    "fundablePrincipalRaw": "8000000000", "fundable": true
  },
  "borrowerAllowlist": [],
  "warnings": [],
  "quote": {
    "principalRaw": "1500000000",
    "requiredCollateralRaw": "1250000000000000000",
    "requiredCollateral": "1.25",
    "exceedsRemaining": false
  }
}
POST/api/v1/lending-offers/update-intentstx
update_lending_offer_intentMCP tool

Changes an offer in place with `updateCommitment`, plus a top-up approval when the new size outruns the standing allowance. Every field you don't name keeps its current value, so raising an expiry is one argument. Three fields cannot be changed at all — the lender, the principal token and the market — and are taken from the offer rather than the request, so asking for them is not an error, it is simply not possible. Nor is the Uniswap route or its LTV: `updateCommitment` rewrites the commitment struct and the routes live outside it, so an offer whose oracle needs re-pointing has to be replaced rather than edited. Re-pricing within the existing route is what `maxPrincipalPerCollateralAmountRaw` is for. Refused when the caller isn't the offer's lender, since the contract gates it the same way. Note that lowering `maxPrincipalRaw` does not recall loans already written against the offer.

Required
walletAddressstringMust be the offer's lender.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
commitmentIdstringThe offer's commitment id, as a decimal string. From list_lending_offers, or from a completed create intent's `completion.commitmentId`.
Optional
maxPrincipalRawstringNew maximum principal, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
minApyPctnumberThe lowest APR the offer will lend at, as a percent (5 = 5%). A borrower may bid higher; anything lower reverts.
maxLoanDurationSecondsintegerLongest loan the offer will write, in seconds. Defaults to the duration of the market named in `marketId` — the two are the same choice for a catalogued market.
expirationTimestampintegerUnix second the offer stops being acceptable. Mutually exclusive with expiresInSeconds.
expiresInSecondsintegerHow long the offer stands, in seconds from now. Defaults to 30 days.
maxPrincipalPerCollateralAmountRawstringCollateral price, set by hand: principal per whole collateral token, scaled by 1e18 for ERC20 collateral (and unscaled for NFTs). Setting it skips the LTV maths entirely, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
principalPerCollateralstringRe-price in human units — "1800" means one whole collateral token draws 1800 whole principal tokens. Converted with the offer's own token decimals; an alternative to maxPrincipalPerCollateralAmountRaw, not an addition to it.
oracleRoutesarrayNot changeable here — an offer's price route has no setter. Passing it is refused, and names replace_lending_offer_intent, which withdraws the offer and re-publishes it with the new route in one sequence.
loanToValuePctnumberNot changeable here — the oracle's LTV is stored outside the commitment struct. Passing it is refused, and names replace_lending_offer_intent. To re-price within the offer's existing route, use principalPerCollateral.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
approvalModestringexactunlimiteddefault unlimitedHow much to approve when this action needs an ERC-20 allowance. `unlimited` (the current default) approves once and never again, so repeat actions need no further approval. `exact` approves only the amount this action moves, so a compromised spender can take that and nothing more — at the cost of a fresh approval on every repeat, because an exact allowance is spent to zero by the action it was granted for. Pass `exact` if you would rather your users granted a capped allowance. Either way, no approval step is included at all when the spender's existing allowance already covers the amount.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
POST/api/v1/lending-offers/replace-intentstx
replace_lending_offer_intentMCP tool

The one change `update_lending_offer_intent` cannot make. An offer's Uniswap route and its LTV are stored outside the commitment struct with no setter, and its market and principal token are fixed — so re-pointing an oracle, moving markets or switching the lent token means replacing the offer, not editing it. This does that as one intent: the approvals, `deleteCommitment`, then `createCommitmentWithUniswap` with the new terms. Everything you don't name carries over from the old offer, borrower allowlist included, and `preview.carriedOver` lists exactly what was inherited. Pricing carries over as intent rather than as a number: an LTV-priced offer is re-priced at today's TWAP through a freshly discovered route at the same LTV, while a fixed-price one keeps its price and its lack of an oracle. The delete is sequenced before the create on purpose — the other order leaves both offers live between two signatures, drawable twice against the same wallet. The replacement is a new commitment, so it gets a new id (returned on the completion) and its principal allocation starts from zero.

Required
walletAddressstringMust be the offer's lender.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
commitmentIdstringThe offer's commitment id, as a decimal string. From list_lending_offers, or from a completed create intent's `completion.commitmentId`.
Optional
principalTokenstringThe token being lent. The lender holds it until a borrower draws on the offer.
collateralTokenstringThe token borrowers must post. Omit only for an uncollateralized offer (collateralTokenType NONE).
marketIdintegerTeller market the offer lends into, from list_pool_markets. It sets the payment cycle, fees and default window the loans inherit, and the lender must be admitted to it.
maxPrincipalRawstringMost the offer will lend in total, across every borrower, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
minApyPctnumberThe lowest APR the offer will lend at, as a percent (5 = 5%). A borrower may bid higher; anything lower reverts.
maxLoanDurationSecondsintegerLongest loan the offer will write, in seconds. Defaults to the duration of the market named in `marketId` — the two are the same choice for a catalogued market.
loanToValuePctnumberHow much the offer lends against collateral, as a percent of its live TWAP value. Written as both the stored ceiling and the on-chain oracle's LTV. Omit only when setting a price by hand.
maxPrincipalPerCollateralAmountRawstringCollateral price, set by hand: principal per whole collateral token, scaled by 1e18 for ERC20 collateral (and unscaled for NFTs). Setting it skips the LTV maths entirely, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
principalPerCollateralstringThe same fixed price in human units — "1800" means one whole collateral token draws 1800 whole principal tokens. Converted using both tokens' decimals; an alternative to maxPrincipalPerCollateralAmountRaw, not an addition to it.
expirationTimestampintegerUnix second the offer stops being acceptable. Mutually exclusive with expiresInSeconds.
expiresInSecondsintegerHow long the offer stands, in seconds from now. Defaults to 30 days.
collateralTokenTypestringNONEERC20ERC721ERC1155ERC721_ANY_IDERC1155_ANY_IDERC721_MERKLE_PROOFERC1155_MERKLE_PROOFdefault ERC20What kind of collateral the offer takes. ERC20 is the default and the only type that can carry a Uniswap oracle route. The *_ANY_ID types accept any token in a collection; the *_MERKLE_PROOF types accept whatever a merkle root admits, in which case collateralTokenId carries the root. NONE lends unsecured, and the contract enforces nothing about repayment.
collateralTokenIdstringToken id for ERC721/ERC1155 collateral, or the merkle root for the *_MERKLE_PROOF types. Must be 0 for ERC20, which the contract enforces.
borrowerAllowlistarrayRestrict the offer to these borrowers. An empty list — the default — means anyone may borrow against it.
oracleRoutesarrayPrice the collateral through these AMM pools instead of searching for a route. At most two hops, ordered collateral → principal, each { pool, zeroForOne, twapInterval, token0Decimals, token1Decimals }. Pools must be from the chain's own venue: Uniswap V3 on most chains, but PancakeSwap V3 on BNB Chain (56), which is where that chain's liquidity is and what Teller's BSC contracts are wired to — a Uniswap pool address on BNB is accepted by the contract and prices loans off the shallow side of the market. Nothing validates that a supplied route prices the right pair, and the route is permanent once the offer is created.
disableOraclebooleandefault falseStore no price route at all, making this a fixed-price offer that never re-prices. Requires a hand-set ratio, and means the offer keeps lending at that price however far the collateral falls.
twapIntervalSecondsintegerdefault 5TWAP window each discovered hop reads over. Defaults to 5, matching the Teller app.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
approvalModestringexactunlimiteddefault unlimitedHow much to approve when this action needs an ERC-20 allowance. `unlimited` (the current default) approves once and never again, so repeat actions need no further approval. `exact` approves only the amount this action moves, so a compromised spender can take that and nothing more — at the cost of a fresh approval on every repeat, because an exact allowance is spent to zero by the action it was granted for. Pass `exact` if you would rather your users granted a capped allowance. Either way, no approval step is included at all when the spender's existing allowance already covers the amount.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/lending-offers/replace-intents
{
  "walletAddress": "0xabc…",
  "chainId": 8453,
  "commitmentId": "412",
  "loanToValuePct": 60
}
Response
{
  "intentId": "txi_b30f…",
  "kind": "lending_offer_replace",
  "stepsTotal": 2,
  "steps": [
    // Delete first, on purpose. The other order leaves both offers live
    // between two signatures, drawable twice against the same wallet.
    { "id": "step_1", "kind": "execute", "to": "0xfA87…c2b5", "data": "0x0eb4b2c9…",
      "description": "Withdraw lending offer 412, to replace it" },
    { "id": "step_2", "kind": "execute", "to": "0xfA87…c2b5", "data": "0x9b5c9b0f…",
      "description": "Re-publish it: USDC against WETH at 8% APR" }
  ],
  "preview": {
    "replaces": { "commitmentId": "412", "offer": { /* the offer being withdrawn */ } },
    // Re-priced at today's TWAP through a freshly discovered route, at
    // the new 60% LTV.
    "pricing": { "oraclePriceRatioRaw": "2000000000", "poolOracleLtvBps": 6000,
                 "maxPrincipalPerCollateralAmountRaw": "1200000000",
                 "principalPerCollateral": "1200", "ratioSource": "oracle" },
    // Everything not restated in the request, inherited from the old
    // offer — the allowlist included, since losing it would silently
    // reopen a restricted offer to everyone.
    "carriedOver": ["principalToken", "collateralToken", "marketId", "maxPrincipalRaw",
                    "minApyPct", "maxLoanDurationSeconds", "borrowerAllowlist", "expiration"],
    "sequencing": "the old offer is withdrawn before the replacement is published, …"
  }
}

// The replacement is a new commitment, so it gets a new id:
{
  "status": "completed",
  "completion": { "commitmentId": "419", "chainId": 8453 }
}
POST/api/v1/lending-offers/borrower-intentstx
set_lending_offer_borrowers_intentMCP tool

Adds to or removes from an offer's borrower allowlist. The empty list is not "nobody": the contract reads it as unrestricted, so removing the last entry reopens the offer to everyone, and the preview says which of the two the change lands on. Refused when the caller isn't the offer's lender.

Required
walletAddressstringMust be the offer's lender.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
commitmentIdstringThe offer's commitment id, as a decimal string. From list_lending_offers, or from a completed create intent's `completion.commitmentId`.
borrowersarrayBorrower addresses to add or remove.
Optional
actionstringaddremovedefault addWhether to add these addresses to the allowlist or remove them from it.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
POST/api/v1/lending-offers/cancel-intentstx
cancel_lending_offer_intentMCP tool

Deletes the commitment, so no further loans can be drawn from it. Two things it deliberately does not do: loans already written against the offer are untouched and run to their own terms, and the ERC-20 allowance the lender granted TellerV2 stays exactly as it was — revoke that separately if that is what was meant. Refused when the caller isn't the offer's lender.

Required
walletAddressstringMust be the offer's lender.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
commitmentIdstringThe offer's commitment id, as a decimal string. From list_lending_offers, or from a completed create intent's `completion.commitmentId`.
Optional
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/lending-offers/borrow-termsread
get_lending_offer_borrow_termsMCP tool

The borrower's mirror of get_borrow_terms, and the call to make before create_lending_offer_borrow_intent. For one wallet and one offer it reports the most that wallet could draw right now and which of three ceilings produced it — the offer's remaining principal, what the lender can still fund, or the wallet's own collateral balance — plus the collateral the draw would post, the rate and term it would default to, and the market's payment cycle. `blockers` is the part worth reading: every condition that would make `acceptCommitmentWithRecipient` revert, named, including the two that belong to TellerV2 rather than the commitment and are therefore invisible on the offer itself — a closed market, and a market whose borrower attestation this wallet does not hold. An empty `blockers` with a non-zero `terms.maxPrincipalRaw` means the borrow will go through.

Required
walletAddressstringThe prospective borrower's wallet.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
commitmentIdstringThe offer's commitment id, as a decimal string. From list_lending_offers, or from a completed create intent's `completion.commitmentId`.
Optional
principalAmountRawstringQuote this draw. Omit to quote the largest one possible, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
collateralAmountRawstringQuote against this much collateral instead of the minimum, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
interestRateBpsintegerQuote at this APR rather than the offer's floor.
loanDurationSecondsintegerQuote this term rather than the offer's maximum.
collateralTokenIdstringThe NFT the wallet would post, for an offer taking ERC-721 or ERC-1155 collateral with any token id.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/lending-offers/borrow-terms
  ?chainId=8453&commitmentId=412&walletAddress=0xdef…
Response
{
  "commitmentId": "412",
  "walletAddress": "0xdef…",
  "market": {
    "marketId": 2, "marketOpen": true, "borrowerVerified": true,
    "paymentCycleSeconds": 2592000, "paymentCycleType": "MONTHLY", "paymentType": "EMI"
  },
  "collateralBalanceRaw": "4000000000000000000",
  "collateralBalance": "4",
  "terms": {
    "borrowable": true,
    "blockers": [],
    // Three ceilings; the lowest wins, and `limitedBy` names it. Here
    // the lender has 6,400 USDC left approved to TellerV2 and the
    // offer's own allocation still has 9,000 — so the lender binds.
    "maxPrincipalRaw": "6400000000", "maxPrincipal": "6400",
    "limitedBy": "lender-funding",
    "principalRaw": "6400000000",
    "requiredCollateralRaw": "3200000000000000000",
    "requiredCollateral": "3.2",
    "interestRateBps": 800, "aprPct": 8, "loanDurationSeconds": 2592000
  },
  "nextStep": { "capability": "create_lending_offer_borrow_intent", "note": "…" }
}
POST/api/v1/lending-offers/borrow-intentstx
create_lending_offer_borrow_intentMCP tool

The borrower side of an offer, and the call that turns a standing offer into a loan: the collateral approval to the Teller collateral manager (not to the forwarder — that is the classic way this reverts halfway through), the one-time market-forwarder approval, then `acceptCommitmentWithRecipient`. Omit `collateralAmountRaw` and exactly the required amount is posted, computed from whichever of the offer's two prices is currently binding. A principal larger than the offer's remaining allocation, or than your collateral supports, is clamped down rather than refused, and `preview.limitedBy` names which ceiling bit. Asking for more than the lender can currently fund is the exception: that comes back as a 412 carrying `fundablePrincipalRaw`, because nothing is escrowed and the gap between what an offer advertises and what its lender actually holds can be a thousandfold — a silently smaller debt is not what was asked for. Everything that would instead revert is checked first and refused with the reason: an expired offer, an allowlist you are not on, a lender who has spent the principal, a closed market, a market whose borrower attestation you do not hold, and a term shorter than the market's payment cycle. get_lending_offer_borrow_terms reports all of that without building anything. What comes out the other side is an ordinary TellerV2 loan, so list_loans and create_repay_intent take it from there.

Required
walletAddressstringThe borrower's wallet.
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
commitmentIdstringThe offer's commitment id, as a decimal string. From list_lending_offers, or from a completed create intent's `completion.commitmentId`.
principalAmountRawstringPrincipal to borrow, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
Optional
collateralAmountRawstringCollateral to post. Omit to post exactly what the offer requires, as an integer string in the token's smallest unit (no decimal point). 1 USDC = "1000000".
interestRateBpsintegerAPR to bid, in basis points. Defaults to the offer's floor. A higher number is accepted and only costs the borrower more; a lower one reverts.
loanDurationSecondsintegerLoan term. Defaults to the offer's maximum, and cannot exceed it.
collateralTokenIdstringWhich NFT to post, for an offer taking ERC-721 or ERC-1155 collateral with any token id. Required there, refused where the offer pins its own id.
recipientstringWhere the principal is paid. Defaults to the borrower.
walletProofobjectOptional proof that the caller controls `walletAddress`: the challenge returned by create_wallet_challenge plus the user's EIP-191 signature over it. Required for unsecured (credit-based) borrowing, and for every transaction intent when TELLER_PARTNER_REQUIRE_WALLET_PROOF is enabled.
approvalModestringexactunlimiteddefault unlimitedHow much to approve when this action needs an ERC-20 allowance. `unlimited` (the current default) approves once and never again, so repeat actions need no further approval. `exact` approves only the amount this action moves, so a compromised spender can take that and nothing more — at the cost of a fresh approval on every repeat, because an exact allowance is spent to zero by the action it was granted for. Pass `exact` if you would rather your users granted a capped allowance. Either way, no approval step is included at all when the spender's existing allowance already covers the amount.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/lending-offers/borrow-intents
{
  "walletAddress": "0xdef…",
  "chainId": 8453,
  "commitmentId": "412",
  "principalAmountRaw": "1500000000"
}
Response
{
  "intentId": "txi_7a19…",
  "kind": "borrow",
  "stepsTotal": 3,
  "steps": [
    // The collateral manager escrows, not the forwarder — approving the
    // forwarder is the classic way this reverts on the last step.
    { "id": "step_1", "kind": "approve", "to": "0x4200…0006", "data": "0x095ea7b3…",
      "description": "Approve the Teller collateral manager to move your tokens" },
    { "id": "step_2", "kind": "execute", "to": "0x5daE…2cB0", "data": "0x4f4ff0d4…",
      "description": "Allow the Teller commitment forwarder to act for you in market 2" },
    { "id": "step_3", "kind": "execute", "to": "0xfA87…c2b5", "data": "0x8a1b2c3d…",
      "description": "Borrow USDC against lending offer 412" }
  ],
  "preview": {
    "principalRaw": "1500000000", "principal": "1500",
    "collateralAmountRaw": "1250000000000000000",
    "requiredCollateralRaw": "1250000000000000000",
    // What the draw could have been, and which ceiling stopped it going
    // higher: "offer-remaining", "lender-funding" or "your-collateral".
    "maxPrincipalRaw": "6400000000", "limitedBy": "lender-funding",
    "interestRateBps": 800, "aprPct": 8, "loanDurationSeconds": 2592000
  }
}

Signing & intent status

Advance a transaction as your user signs each step, and read where one got to.

POST/api/v1/tx/intents/:intentId/continuetx
continue_tx_intentMCP tool

The heart of the flow. After the user's wallet broadcasts the current step, post its hash here. We check the receipt, stamp the step, and return the next transaction to sign — or, on the last step, run the bookkeeping (score credit, loan record) and close the intent out.

Required
intentIdstringThe intent to advance.
txHashstringHash the wallet returned for the current step.
Optional
stepIdstringThe step this hash is for. Passing it guards against advancing the wrong step.
skipReceiptCheckbooleandefault falseSkip the on-chain receipt lookup. Use only on chains our RPCs can't reach.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/tx/intents/txi_9f2c8a41…/continue
{
  "stepId": "step_1",
  "txHash": "0x5c1f…"
}
Response
{
  "intentId": "txi_9f2c8a41…",
  "status": "awaiting_signature",
  "stepsRemaining": 1,
  "completedStep": {
    "id": "step_1", "status": "confirmed", "txHash": "0x5c1f…",
    "confirmedAt": "2026-08-11T08:44:10.001Z"
  },
  "nextStep": {
    "id": "step_2", "kind": "execute", "chainId": 8453,
    "to": "0x1231…4eae", "data": "0x4630a0d8…", "value": "0",
    "description": "Swap USDC → WETH"
  },
  "instructions": "Send transaction 2 of 2 …"
}

// …and after the final step:
{
  "status": "completed",
  "stepsRemaining": 0,
  "nextStep": null,
  "instructions": "All steps are signed. Nothing further is required.",
  "result": { "ok": true, "points": 25, "feeUsd": 250, "score": { "total": 345 } }
}
GET/api/v1/tx/intents/:intentIdtx
get_tx_intentMCP tool

Current state of an intent: every step, which are signed, and what to sign next.

Required
intentIdstringIntent id.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/tx/intentstx
list_tx_intentsMCP tool

Your recent intents, optionally filtered by wallet or status.

Optional
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
statusstringawaiting_signaturecompletedcancelledexpiredfailedFilter by lifecycle state.
limitintegerdefault 25Maximum rows to return.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/tx/intents?partnerCode=spring-campaign:web&status=completed&limit=25
Response
{
  "count": 2,
  "intents": [
    { "intentId": "txi_9f2c…", "kind": "swap", "status": "completed",
      "partnerCode": "spring-campaign:web", "stepsTotal": 2, "stepsRemaining": 0,
      "result": { "ok": true, "points": 25 } }
  ]
}
POST/api/v1/tx/intents/:intentId/canceltx
cancel_tx_intentMCP tool

Marks an unfinished intent cancelled. Nothing on-chain is affected.

Required
intentIdstringIntent id.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.

Teller Score

Read the score, and credit the activity your app drove.

GET/api/v1/wallet/scoreread
get_scoreMCP tool

The canonical 0–1000 score with its six category components (swap, borrow, apply, refer, hold, income), the month-to-date delta, recent activity, and `unlockedUsdc` — the amount this wallet can borrow unsecured.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/wallet/score?walletAddress=0xabc…
Response
{
  "total": 320,
  "monthlyDelta": 45,
  "unlockedUsdc": 320,
  "categories": [
    { "key": "swap", "label": "Swap", "points": 120, "max": 200,
      "progressLabel": "$120k of $200k/wk" }
  ],
  "recent": [
    { "id": "…", "category": "swap", "points": 25,
      "title": "Swapped ~$25000.00 USDC → WETH", "when": "Today" }
  ]
}
GET/api/v1/wallet/score/eventsread
list_score_eventsMCP tool

Individual point awards for a wallet, newest first.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
limitintegerdefault 50Maximum rows to return.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
POST/api/v1/score/eventsscore:write
record_score_eventMCP tool

Append a score event in any category and recompute the wallet's score. This is how partner programs award Apply- and Refer-category points.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
categorystringswapborrowapplyreferholdincomeScore category to credit.
pointsnumberPoints to award.
descriptionstringHuman-readable reason, shown in activity.
Optional
metaobjectArbitrary metadata stored on the event.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/score/events
{
  "walletAddress": "0xabc…",
  "category": "apply",
  "points": 50,
  "description": "Completed the Acme card application",
  "partnerCode": "spring-campaign:web"
}
Response
{
  "event": {
    "id": "…", "category": "apply", "points": "50.0000",
    "description": "Completed the Acme card application",
    "meta": { "partnerId": "acme", "partnerCode": "spring-campaign:web" }
  },
  "score": { "total": 370, "applyPoints": 150, "walletAddress": "0xabc…" }
}
POST/api/v1/score/swapsscore:write
record_swapMCP tool

Books a completed swap and credits Swap-category points. Idempotent on txHash globally, so one on-chain swap can only ever credit one wallet. Swaps done through create_swap_intent are recorded automatically — this is for swaps your app executed itself.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
fromChainstringSource chain id.
toChainstringDestination chain id.
fromTokenstringToken sold.
toTokenstringToken bought.
fromAmountstringRaw amount sold.
amountUsdnumberUSD value of the swap.
Optional
txHashstringOn-chain hash. Required for idempotency.
toAmountEstimatestringRaw amount received.
fromSymbolstringSold token symbol.
toSymbolstringBought token symbol.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
POST/api/v1/score/borrowsscore:write
record_borrowMCP tool

Books a borrow origination (credits Borrow-category points and the 1% protocol fee) or a repayment.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
chainstringChain identifier.
assetstringBorrowed asset symbol.
amountUsdnumberUSD value.
Optional
kindstringoriginationrepaymentdefault originationWhether this is a new loan or a repayment.
productSlugstringProduct identifier, for attribution.
aprnumberAPR as a percentage.
txHashstringOn-chain hash.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
POST/api/v1/score/recomputescore:write
recompute_wallet_scoreMCP tool

Rebuilds the wallet's persisted score from its underlying events. Normally unnecessary — every write recomputes.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
POST/api/v1/income/verifyscore:write
verify_incomeMCP tool

Runs the free on-chain recurring-inflow detector against the wallet, or records a self-attested figure (weighted down to 0.25 confidence). Raises the Income component of the Teller Score, which raises unsecured borrowing headroom.

Required
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
Optional
methodstringonchain_recurringself_attesteddefault onchain_recurringVerification method.
annualIncomeUsdnumberRequired when method is self_attested.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.

Offers & pre-qualification

Match a borrower to lenders and run the credit funnel.

POST/api/v1/offersread
list_offersMCP tool

The same eligibility-filtered, geo-aware, routed offer set the Teller app shows, grouped by category. Pass prequal answers for a sharper match, or a walletAddress to reuse that wallet's latest stored prequal.

Optional
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
prequalAnswersobjectPrequal answers to match against. See get_prequal_form_schema.
scorenumberCredit score to filter on.
incomenumberAnnual income in USD to filter on.
acceptLanguagestringLanguage preference (e.g. "fr-CA", "es-US") — some lenders are language-gated.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/prequal/schemaprequal
get_prequal_form_schemaMCP tool

Every answer key create_prequal_submission accepts, with legal values and which lender each unlocks. Use this to render the prequal form in your own UI.

Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
POST/api/v1/prequal/previewprequal
preview_prequal_matchesMCP tool

Computes the lender matches a set of answers would produce without persisting anything and without posting the lead to any buyer. Safe to call on every keystroke. Each match carries everything a lender card needs — brand, logo, amount and its label, body copy, the click URL and any required disclosure. Click URLs from a preview carry no borrower identity, since nothing has been submitted yet; submit to get links that do.

Required
answersobjectPrequal answers. See get_prequal_form_schema.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/prequal/preview
{
  "answers": {
    "loanTypes": ["personal"],
    "amountUsd": 15000,
    "country": "United States",
    "isUS": true,
    "state": "CA",
    "credit": "good",
    "annualIncomeUsd": 90000,
    "employment": "w2"
  }
}
Response
{
  "count": 3,
  "matches": [
    {
      "id": "upstart",
      "brand": "Upstart",
      "product": "Personal loan",
      "badge": "PERSONAL LOAN",
      "bestMatch": true,

      "logoUrl": "https://…/upstart.png",
      "brandColor": "#ffffff",
      "initial": "U",
      "logoContain": true,

      "amount": "$1k–50k",
      "amountLabel": "RANGE",

      "headline": "Fixed rates, no prepayment fee",
      "detail": "Funds as soon as next day",
      "about": "…",

      // Send the click here, as given — this hop re-checks eligibility,
      // records the click and carries the affiliate attribution.
      "url": "https://pro.teller.org/api/ref/upstart",
      "scorePoints": 10,

      // Verbatim compliance copy. inline=true means it belongs under
      // THIS card; inline=false may be pooled at the bottom of the list.
      "disclosure": {
        "key": "upstart-personal-loans",
        "text": "Important Disclosures: …",
        "inline": true
      }
    }
  ]
}

// Preview URLs carry no borrower identity — nothing has been submitted
// yet. Submit to get links that do.
POST/api/v1/prequal/submissionsprequal
create_prequal_submissionMCP tool

Runs the full Teller prequal pipeline: server-side match computation, persistence, the live lender-buyer cascade, and CRM enrollment. This posts a REAL lead to real buyers — use preview_prequal_matches for testing. Poll get_prequal_lead_status afterwards to pick up a lender redirect for your user.

Required
answersobjectPrequal answers. See get_prequal_form_schema.
Optional
walletAddressstringOptional wallet to attach the submission to, so it joins the rest of that wallet's Teller history.
anonIdstringYour own stable uuid for this end user. One is minted and returned if you omit it.
clientSubmissionIdstringIdempotency key (uuid). Retrying with the same value never double-writes.
usShortTermDetailobjectExtra detail required by US short-term / small-dollar lenders (bank account, SSN, pay schedule). Forwarded to whichever lenders need it and never stored on the submission.
caLenderDetailobjectExtra detail required by Canadian lenders (address, province, pay schedule, employment). Forwarded and never stored on the submission.
usHelocDetailobjectExtra detail required for US HELOC applications (property type and use, mortgage status, bankruptcy, military). Forwarded and never stored on the submission.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
POST /api/v1/prequal/submissions
{
  "walletAddress": "0xabc…",
  "clientSubmissionId": "0f1e2d3c-…",
  "partnerCode": "spring-campaign:web",
  "answers": {
    "loanTypes": ["personal"], "amountUsd": 15000,
    "country": "United States", "isUS": true, "state": "CA",
    "credit": "good", "annualIncomeUsd": 90000, "employment": "w2",
    "firstName": "Ada", "lastName": "Lovelace",
    "email": "[email protected]", "phone": "+15555550100",
    "birthYear": 1990, "consent": true
  }
}
Response
{
  "created": true,
  "anonId": "6b1e…",
  "partnerId": "acme",
  "partnerCode": "spring-campaign:web",

  // THIS is the array to render — same card shape preview returns, but
  // the urls now carry a signed token identifying the borrower, so a
  // prequal-gated lender doesn't turn your user away on arrival.
  "matches": [
    {
      "id": "upstart", "brand": "Upstart", "product": "Personal loan",
      "logoUrl": "https://…/upstart.png", "brandColor": "#ffffff", "initial": "U",
      "amount": "$1k–50k", "amountLabel": "RANGE",
      "headline": "Fixed rates, no prepayment fee",
      "url": "https://pro.teller.org/api/ref/upstart?t=eyJwcmVx…",
      "disclosure": { "key": "upstart-personal-loans", "text": "…", "inline": true }
      /* …and the rest — see preview_prequal_matches */
    }
  ],

  "prequal": {
    "id": "9d2f…", "status": "matched",
    "loanTypes": ["personal"], "amountUsd": 15000, "creditBand": "good",
    // What we persisted, for our own reporting. Trimmed: no logo, no
    // url, no disclosure. Don't render off this one.
    "matches": [ { "id": "upstart", "brand": "Upstart", "amount": "$1k–50k", "…": "…" } ]
  },
  "routing": {
    "coverage": "partial",
    "originRegistered": false,
    "action": {
      "code": "register_origin",
      "message": "Some lenders require the origin that collected the lead to be registered before they will accept it. Contact Teller with the domain your users see to widen your coverage."
    }
  },
  "leadStatusHint": "poll get_prequal_lead_status with prequalId=9d2f… until state leaves \"searching\""
}
GET/api/v1/prequal/submissions/:prequalIdprequal
get_prequal_submissionMCP tool

The stored submission, including its computed matches.

Required
prequalIdstringSubmission id.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/prequal/submissionsprequal
list_prequal_submissionsMCP tool

Scoped by wallet address or by the anonId you supplied at submission time.

Optional
walletAddressstringThe end user's EVM wallet address. Every on-chain capability is keyed on this — the integrating app passes the address of the wallet that will sign, and never a Teller session.
anonIdstringThe anonId you submitted under.
limitintegerdefault 25Maximum rows to return.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/prequal/submissions/:prequalId/lead-statusprequal
get_prequal_lead_statusMCP tool

Per-buyer outcome for a submission and, when one bought the lead, the redirect URL to send the borrower to. The cascade is asynchronous — poll this for up to ~150s after submitting.

Required
prequalIdstringSubmission id.
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/prequal/submissions/9d2f…/lead-status
Response
// A lender took the lead — send the borrower here.
{
  "prequalId": "9d2f…",
  "state": "matched",
  "redirectUrl": "https://lender.example/apply?lead=…",
  "pollForMs": 0
}

// Still working. Keep polling for up to pollForMs.
{ "prequalId": "9d2f…", "state": "searching", "redirectUrl": null, "pollForMs": 128000 }

// Nobody took it.
{ "prequalId": "9d2f…", "state": "no_match", "redirectUrl": null, "pollForMs": 0 }

Your key, chains & tokens

What your key can do, and the networks and assets everything above is denominated in.

GET/api/v1/whoamiread
whoamiMCP tool

Echoes back the id, display name and granted scopes of the credential making the call. The fastest way to confirm a newly-issued key reached the server and carries the scopes you expect. A 401 means the key is not configured; a 403 means it is configured but lacks the read scope, and names what it does grant.

Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
Request
GET /api/v1/whoami
Response
{
  "id": "acme",
  "name": "Acme Wallet",
  "kind": "partner",
  "scopes": ["read", "tx", "prequal"]
}
GET/api/v1/chainsread
list_chainsMCP tool

Every EVM (and Solana) network Teller can quote, swap, borrow or earn on. Each entry carries a `supports` block — `{swap, borrow, loop, rollover}` — read from the deployment manifest, because being listed here has never meant every action works there: swaps route anywhere LI.FI does, while borrowing needs Teller's contracts deployed. Check it before building a borrow on a chain you haven't used.

Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/tokensread
list_tokensMCP tool

The token list Teller quotes against, with logos and USD prices.

Required
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
Optional
searchstringCase-insensitive filter over symbol, name, or address.
limitintegerdefault 200Maximum tokens to return. Defaults to 200, which is well below the number of tokens on a busy chain — check `hasMore` and page with `offset` rather than assuming `count` describes the array.
offsetintegerdefault 0Index to start from, for paging through `count` tokens. Use `nextOffset` from the previous response.
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.
GET/api/v1/tokens/lookupread
lookup_tokenMCP tool

Symbol, name, decimals, logo and price for a single token. Falls through to LI.FI when we haven't cached it.

Required
chainIdintegerEVM chain id the action executes on (1 = Ethereum, 8453 = Base, …).
addressstringToken contract address (or SPL mint).
Optional
partnerCodestringYour own attribution code for this call — a campaign, placement, sub-affiliate or internal user reference. Freeform, 1–64 chars of letters, digits, dot, dash, underscore or colon. It is stored on whatever the call creates (transaction intent, prequal submission, score event) and echoed back, so you can reconcile Teller activity against your own records. Can also be sent once per request as the `x-teller-partner-code` header.