MetaLend Pools API

Protocol and pool information

OpenAPI Specification

metalend-pools-api-openapi.yml Raw ↑
openapi: 3.0.3
info:
  title: MetaLend Rebalancing AI Agent Guide Pools API
  version: 0.1.0
  description: "API for managing cross-protocol cross-chain DeFi rebalancing operations exposing: <ul>\n  <li><b>Auth</b> – challenge/verify flow for JWT issuance.</li>\n  <li><b>Project settings</b> – fetch current project settings.</li>\n  <li><b>User configuration</b> – register or update token-level user-rebalancer settings and fetch current wallet configs.</li>\n  <li><b>Deposits</b> – create deposit intents, receive tracking IDs, and poll status through PROCESSING -> BRIDGING (cross-chain only) -> SUCCESS/FAILED/EMERGENCY_WITHDRAW. New deposits may return <b>503</b> with <b>DEPOSITS_PAUSED</b> during maintenance.</li>\n  <li><b>Withdrawals</b> – submit signed withdrawal requests, receive tracking IDs, and monitor status PROCESSING -> SUCCESS/FAILED.</li>\n  <li><b>Balances</b> – query wallet-level balances and withdraw details, as well as balances in bridge progress.</li>\n  <li><b>Rewards</b> – aggregated rewards and claim details per wallet.</li>\n  <li><b>Pools</b> – list supported protocols (Aave, Morpho, Euler, etc.), their pools, APY breakdowns, and TVL snapshots.</li>\n  <li><b>Transaction Costs</b> – current deposit/withdraw on-behalf transaction fees and minimum amounts by chain.</li>\n  <li><b>AI Agent Guide</b> – Markdown flow guide at <code>/SKILL.md</code> with signing formats, wizard requirements, and known integration gotchas.</li>\n</ul> All endpoints require an <b>X-API-Key</b>. <b>JWT authentication</b> is required on <b>deposit, withdrawal, and config update</b> operations.\n<b>Obtain API key by <a href=\"https://developer.metalend.tech/\" target=\"_blank\" rel=\"noopener noreferrer\">creating a new project</a>.</b>\nAll requests must include the Origin header. Use Origin: https://app.metalend.tech unless you have been assigned a different allowed origin."
  contact:
    name: MetaLend API Support
    email: support@metalend.tech
  termsOfService: https://metalend-inc.gitbook.io/litepaper/terms-of-service
  x-guidance: AI agents MUST read https://api.metalend.tech/SKILL.md before implementing any MetaLend operation. It contains critical signing formats, required headers, wizard flows for config setup, and known failure modes.
  x-default-api-key: a7f2b8c4d9e1f3a6b5c8d2e7f1a4b9c3d6e8f2a5b7c1d4e9f3a6b8c2d5e7f1a4
  x-api-key-guidance: Use x-default-api-key as X-API-Key for agent testing/integration. If you are the project owner, create or use your own project API key from https://developer.metalend.tech instead.
servers:
- url: https://api.metalend.tech
  description: Production server
security:
- ApiKeyAuth: []
  OriginHeader: []
tags:
- name: Pools
  description: Protocol and pool information
paths:
  /v1/pools:
    get:
      summary: Get pools information
      description: "Retrieves comprehensive information about supported DeFi protocols, pools, Total Value Locked (TVL), and Annual Percentage Yield (APY) data on supported networks.<br><br>\n\nThe response is a flat list of pools across supported protocols (Aave, Morpho, Euler) and tokens (USDC, MUSD, USDT, RLUSD). Each pool includes protocol metadata, pool address/name, underlying token details, TVL, liquidity, collateral exposure, APY breakdowns, signing metadata, and blacklist status.<br><br>\n\n**APY Information**: The APY data includes native lending rates, reward provider APYs (e.g., MERKLE, BREVIS), and total combined APY. All APY values are formatted to 2 decimal places and represent 7-day average market rates adjusted by fees.<br><br>\n\n**Aave pool address collision example**: On Ethereum, the following pools \nall share `poolAddress: \"0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2\"`:\n- Aave V3 USDC (underlyingToken.symbol: \"USDC\")\n- Aave V3 USDT (underlyingToken.symbol: \"USDT\")  \n- Aave V3 RLUSD (underlyingToken.symbol: \"RLUSD\")\n- Aave V3 MUSD (underlyingToken.symbol: \"MUSD\")\n\nWhen rendering a token-specific pool list, filter by `underlyingToken.symbol`, \nnot by `poolAddress`.\n\nThis endpoint requires an API key and can be used to discover available investment opportunities before configuring a rebalancer or making deposits.\n\n**Rate limiting**: 1 request per 3 seconds per client IP. Excess requests receive HTTP `429` with `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers (see `RateLimitExceeded` response).\n"
      operationId: getPools
      tags:
      - Pools
      responses:
        '200':
          description: Pools information retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetPoolsResponse'
        '401':
          $ref: '#/components/responses/UnauthorizedNoHeader'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
      x-agentcash-auth:
        mode: free
components:
  responses:
    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          examples:
            Internal Error:
              value:
                code: INTERNAL_ERROR
                internalCode: INTERNAL_ERROR
                message: An unexpected error occurred while processing your request
                requestId: 2c2f3dc1-d19a-49cb-b695-37d025e551b3
                retryAfterSeconds: null
    UnauthorizedNoHeader:
      description: No authorization header provided
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          examples:
            unauthorizedNoHeader:
              $ref: '#/components/examples/UnauthorizedNoHeader'
    RateLimitExceeded:
      description: 'Too many requests for this endpoint. Limits are enforced per client IP for the public API routes backed by `RebalancerResource` (and related resources using the same filter).


        Response headers (when throttled):

        - `Retry-After`: seconds to wait before retrying (matches the rate-limit window duration for that endpoint).

        - `X-RateLimit-Limit`: maximum requests allowed in the window (e.g. `1`).

        - `X-RateLimit-Remaining`: remaining requests in the window (`0` when throttled).

        - `X-RateLimit-Reset`: Unix timestamp (seconds) when the limit window resets.


        `internalCode` in the JSON body is `THROTTLE_PER_IP` for IP-scoped limits (other values may apply for different scopes in the backend).

        '
      headers:
        Retry-After:
          description: Seconds to wait before retrying this endpoint.
          schema:
            type: integer
            example: 3
        X-RateLimit-Limit:
          description: Maximum number of requests allowed in the current window for this endpoint.
          schema:
            type: integer
            example: 1
        X-RateLimit-Remaining:
          description: Remaining requests in the current window.
          schema:
            type: integer
            example: 0
        X-RateLimit-Reset:
          description: Unix timestamp (seconds) when the current limit window resets.
          schema:
            type: integer
            format: int64
            example: 1713264123
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          examples:
            rateLimitExceededPerIp:
              summary: Rate limit exceeded (per client IP)
              value:
                code: RATE_LIMIT_EXCEEDED
                internalCode: THROTTLE_PER_IP
                message: Rate limit exceeded. Maximum 1 requests per 3 seconds allowed. Please try again later.
                requestId: 2c2f3dc1-d19a-49cb-b695-37d025e551b3
                retryAfterSeconds: 3
  schemas:
    GetPoolsResponse:
      type: object
      required:
      - pools
      properties:
        pools:
          type: array
          items:
            $ref: '#/components/schemas/Pool'
    Pool:
      description: "**Important: `poolAddress` is NOT globally unique across tokens.** For Aave V3, the same `poolAddress` (the Aave lending pool contract) is shared  across all tokens on a given chain. For example, Aave V3 USDC, USDT, and RLUSD  on Ethereum all have the same `poolAddress`.\nA pool is uniquely identified by the combination of THREE fields together:\n  - `poolAddress`\n  - `chain`\n  - `underlyingToken.symbol`\n\nWhen filtering pools by token (e.g. \"show only USDC pools\"), ALWAYS filter on  `underlyingToken.symbol`. Never filter on `poolAddress` alone for Aave pools.\n"
      type: object
      required:
      - protocolName
      - protocolVersion
      - protocolDescription
      - protocolUrl
      - poolAddress
      - poolName
      - chain
      - chainId
      - underlyingToken
      - poolTvl
      - poolLiquidity
      - poolApy
      - signData
      - blacklisted
      properties:
        protocolName:
          type: string
          description: Protocol name
          example: Aave
        protocolVersion:
          type: string
          description: Protocol version
          example: V3
        protocolDescription:
          type: string
          description: Protocol description
        protocolUrl:
          type: string
          format: uri
          description: Protocol website URL
        poolAddress:
          type: string
          description: Pool contract address. The pool contract address. For Aave V3 pools, this is the Aave lending pool contract address, which is the SAME for all tokens (USDC, USDT, RLUSD, MUSD) on a given chain. Do NOT use this field alone as a unique pool identifier — it must always be combined with `chain` and `underlyingToken.symbol`. For Morpho and Euler, this address IS unique per pool
          example: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'
        poolName:
          type: string
          description: Pool name
        chain:
          type: string
          description: Chain name
          example: linea
        chainId:
          type: integer
          description: Chain ID
          example: 59144
        underlyingToken:
          $ref: '#/components/schemas/Token'
        poolTvl:
          type: string
          description: Total Value Locked for the pool
        poolLiquidity:
          type: string
          description: Available liquidity for the pool
        collateralExposure:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: 'Collateral exposure for the pool. Keys are Morpho market identifiers, values are the collateral token symbols for each market. Null for non-Morpho pools (Aave, Euler).

            '
          example:
            '0x3b3769cfb1649e84374e7f0e0e46cbf9a1a39a62fa6a8b82e9e9edd1b3a3e3f7': WETH
            '0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2': cbBTC
        poolApy:
          $ref: '#/components/schemas/PoolApyInfo'
        signData:
          $ref: '#/components/schemas/PoolSignData'
        blacklisted:
          type: boolean
          description: Indicates whether the pool has been blacklisted (banned) due to a third party critical security issue. Blacklisted pools are deprecated and retained solely for backwards compatibility. New deposits are never routed to blacklisted pools, and all existing balances are automatically rebalanced out of such pools when possible (balance is large enough to pay on-behalf fee and there is enough liquidity to withdraw).
    PoolApyInfo:
      type: object
      required:
      - native
      - rewards
      - total
      - totalNet
      - performanceFee
      properties:
        native:
          type: string
          description: Native APY percentage (formatted to 2 decimals)
          example: '3.45'
        rewards:
          type: object
          additionalProperties:
            type: string
          description: Map of reward tokens to their APYs (formatted to 2 decimals)
          example:
            Merkle: '1.23'
            Brevis: '0.87'
        total:
          type: string
          description: Total APY percentage (formatted to 2 decimals)
          example: '5.55'
        totalNet:
          type: string
          description: Total net APY percentage (formatted to 2 decimals)
          example: '4.55'
        performanceFee:
          type: string
          description: Performance fee percentage (formatted to 2 decimals)
          example: '1.00'
    Token:
      type: object
      required:
      - symbol
      - name
      - address
      - decimals
      properties:
        symbol:
          type: string
          description: Token symbol
          enum:
          - USDC
          - MUSD
          - USDT
          - RLUSD
          example: USDC
        name:
          type: string
          description: Token full name
          example: USD Coin
        address:
          type: string
          pattern: ^0x[a-fA-F0-9]{40}$
          example: '0x1234567890123456789012345678901234567890'
          description: Token contract address
        decimals:
          type: integer
          description: Token decimals
          example: 6
        version:
          type: string
          nullable: true
          description: Token contract version
    PoolSignData:
      type: object
      required:
      - protocolId
      - poolAddress
      - domainId
      properties:
        protocolId:
          type: integer
          description: Protocol ID (0=Aave, 1=Morpho, 2=Euler)
        poolAddress:
          type: string
          description: Pool contract address
        domainId:
          type: integer
          description: Domain ID for the chain
    ApiError:
      type: object
      required:
      - code
      - internalCode
      - message
      properties:
        code:
          type: string
          description: Error category identifier
        internalCode:
          type: string
          description: More specific error identifier
        message:
          type: string
          description: Human-readable error message
        requestId:
          type: string
          format: uuid
          description: Unique request identifier for tracking
        retryAfterSeconds:
          type: integer
          description: Seconds to wait before retrying (for rate limiting)
  examples:
    UnauthorizedNoHeader:
      summary: No authorization header
      value:
        code: AUTHENTICATION_FAILED
        internalCode: NO_AUTH_INFO
        message: No authentication information provided
        requestId: 2c2f3dc1-d19a-49cb-b695-37d025e551b3
        retryAfterSeconds: null
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key required for all endpoints.
    JwtAuth:
      type: apiKey
      in: header
      name: Authorization
      description: 'JWT issued by POST /v1/auth/verify. Pass as Authorization: Bearer <token>.


        x-agent-hint: agentcash and other agentic wallets strip the Authorization header for their own SIWX flow. Use X-MetaLend-JWT: Bearer <token> instead — the backend accepts both headers. Affected endpoints: POST /v1/deposits, POST /v1/withdrawals, PUT /v1/config.

        '
    OriginHeader:
      type: apiKey
      in: header
      name: Origin
      description: 'Required for CORS requests from browsers. Must match the allowed origins configured for the API key. Example: Origin: https://app.metalend.tech. Localhost is always allowed but must be specified.

        '
externalDocs:
  description: Litepaper
  url: https://metalend-inc.gitbook.io/litepaper