> ## Documentation Index
> Fetch the complete documentation index at: https://docs.turtle.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Opportunities

> Discover and query the vaults, money markets, and earning opportunities available on Turtle.

<Note>
  All requests require an API key via the `X-API-Key` header.
  See [API Keys](/sdk/authentication/api-keys) for details.
</Note>

## Overview

The Opportunities API is how you discover what a user can deposit into. Each opportunity carries its accepted tokens, chain, estimated APR, current TVL, incentives, and which deposit modes it supports. This page is the canonical reference for the Opportunity object; every other endpoint that returns one links here.

There are three read endpoints:

* `GET /v2/opportunities/` lists the full catalog, with optional filters.
* `GET /v2/opportunities/{id}` returns one opportunity by ID.
* `GET /v2/opportunities/distributors/{distributorId}` returns the set configured for a distributor.

For the product context (what configuration is and why you'd scope a set), see [Turtle Earn](/partner-products/turtle-earn).

## Get All Opportunities

Retrieve all available opportunities with simplified token information.

<CodeGroup>
  ```bash curl theme={null}
  curl -X GET "https://earn.turtle.xyz/v2/opportunities/" \
    -H "X-API-Key: pk_live_xxxxx"
  ```

  ```typescript TypeScript theme={null}
  interface OpportunityResponse {
    data: Opportunity[];
    pagination: {
      page: number;
      limit: number;
      total: number;
      totalPages: number;
    };
  }

  const response = await fetch('https://earn.turtle.xyz/v2/opportunities/', {
    headers: { 'X-API-Key': 'pk_live_xxxxx' },
  });
  const data: OpportunityResponse = await response.json();
  ```
</CodeGroup>

**Query Parameters**

<ParamField query="chainIds" type="string">
  Comma-separated list of chain IDs to filter by. Example: `1,8453,42161` for Ethereum, Base, and Arbitrum.
</ParamField>

<ParamField query="depositToken" type="string">
  Filter by deposit token in the format `address-chainId`. Example: `0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7-43114`.
</ParamField>

<ParamField query="tvlMin" type="number">
  Return only opportunities with TVL at or above this USD value. Example: `1000000` returns opportunities above \$1M.
</ParamField>

<ParamField query="tvlMax" type="number">
  Return only opportunities with TVL at or below this USD value.
</ParamField>

**Response**

```json theme={null}
{
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "USDC Vault",
      "slug": "usdc-vault",
      "description": "Stable yield on USDC deposits",
      "type": "vault",
      "tvl": 5000000.50,
      "estimatedApr": 8.5,
      "featured": true,
      "minDepositAmountUsd": 0.01,
      "meta": {
        "directInteractionEnabled": true,
        "routedSwapEnabled": true,
        "depositEnabled": true,
        "withdrawEnabled": true,
        "asyncDeposit": false,
        "asyncWithdraw": false,
        "isSecondaryMarket": false,
        "depositDisabledReason": "",
        "withdrawalDisabledReason": ""
      },
      "depositTokens": [
        {
          "symbol": "USDC",
          "address": "0xA0b86991...",
          "chainId": 1,
          "decimals": 6,
          "logoUrl": "https://..."
        }
      ],
      "baseToken": {
        "symbol": "USDC",
        "address": "0xA0b86991...",
        "chainId": 1,
        "decimals": 6,
        "logoUrl": "https://..."
      },
      "receiptToken": {
        "symbol": "tUSDC",
        "address": "0x...",
        "chainId": 1,
        "decimals": 6,
        "logoUrl": "https://..."
      },
      "curator": {
        "id": "curator-uuid",
        "name": "Curator Name",
        "description": "Curator description",
        "iconUrl": "https://...",
        "landingUrl": "https://..."
      },
      "incentives": [
        {
          "id": "incentive-uuid",
          "name": "Incentive Name",
          "description": "Incentive description",
          "iconUrl": "https://...",
          "rewardType": "tokens",
          "rewardTypeName": "Tokens",
          "fdvEstimate": null,
          "tokenSupplyAllocation": null,
          "apr": 0.001,
          "minApr": null,
          "maxApr": null,
          "estPriceUsd": null,
          "indexed": false
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 12,
    "totalPages": 1
  }
}
```

## Get Opportunity by ID

Retrieve a single opportunity by its unique identifier.

<CodeGroup>
  ```bash curl theme={null}
  curl -X GET "https://earn.turtle.xyz/v2/opportunities/b91fab34-3998-468b-adcf-645d9b68bc9c" \
    -H "X-API-Key: pk_live_xxxxx"
  ```

  ```typescript TypeScript theme={null}
  const opportunityId = 'b91fab34-3998-468b-adcf-645d9b68bc9c';
  const response = await fetch(`https://earn.turtle.xyz/v2/opportunities/${opportunityId}`, {
    headers: { 'X-API-Key': 'pk_live_xxxxx' },
  });
  const opportunity = await response.json();
  ```
</CodeGroup>

**Path Parameters**

<ParamField path="id" type="uuid" required>
  Opportunity unique identifier.
</ParamField>

**Response**

Returns a single Opportunity object directly, using the structure documented under [Response Fields](#response-fields).

## Get Distributor Opportunities

Every distributor has a set of opportunities configured in the [Client Portal](https://dashboard.turtle.xyz). This endpoint returns only that set: the opportunities your users should see. Use it instead of the full catalog when you want to serve exactly what you have selected for your integration.

<CodeGroup>
  ```bash curl theme={null}
  curl -X GET "https://earn.turtle.xyz/v2/opportunities/distributors/MpOgVDnc" \
    -H "X-API-Key: pk_live_xxxxx"
  ```

  ```typescript TypeScript theme={null}
  const distributorId = 'MpOgVDnc';
  const response = await fetch(
    `https://earn.turtle.xyz/v2/opportunities/distributors/${distributorId}`,
    { headers: { 'X-API-Key': 'pk_live_xxxxx' } }
  );
  const { data, pagination } = await response.json();
  ```
</CodeGroup>

**Path Parameters**

<ParamField path="distributorId" type="string" required>
  Your distributor ID. Find it under Distribution in the [Client Portal](https://dashboard.turtle.xyz).
</ParamField>

**Response**

Same shape as Get All Opportunities (a `data` array plus a `pagination` object), filtered to the configured set. An empty `data` array means no opportunities have been configured yet; that is a `200`, not an error.

**Which endpoint to use**

| Scenario                                                | Endpoint                                        |
| ------------------------------------------------------- | ----------------------------------------------- |
| Power your app with your configured opportunities       | Get Distributor Opportunities                   |
| Browse the full Turtle catalog to decide what to enable | Get All Opportunities                           |
| Load a single opportunity for a detail page             | [Get Opportunity by ID](#get-opportunity-by-id) |

How configuration works: select opportunities in the Client Portal, which stores them in the distributor's earn-details configuration, and this endpoint returns that selection. For the concept and how attribution ties to it, see [Distributor Model](/sdk/concepts/distributor-model).

## Response Fields

The same Opportunity object is returned by all three endpoints above.

### Opportunity Object

<ResponseField name="id" type="uuid" required>
  Opportunity unique identifier.
</ResponseField>

<ResponseField name="name" type="string" required>
  Opportunity display name.
</ResponseField>

<ResponseField name="slug" type="string">
  URL-friendly identifier for the opportunity.
</ResponseField>

<ResponseField name="description" type="string">
  Opportunity detailed description.
</ResponseField>

<ResponseField name="type" type="string">
  Opportunity type, for example `vault` or `lending`.
</ResponseField>

<ResponseField name="tvl" type="number">
  Total Value Locked in USD.
</ResponseField>

<ResponseField name="estimatedApr" type="number">
  Estimated annual percentage rate.
</ResponseField>

<ResponseField name="featured" type="boolean">
  Whether the opportunity is featured.
</ResponseField>

<ResponseField name="minDepositAmountUsd" type="number">
  Minimum deposit amount in USD. Deposits below this value are rejected.
</ResponseField>

<ResponseField name="meta" type="OpportunityMeta">
  Interaction and availability flags for the opportunity. See [Meta Object](#meta-object).
</ResponseField>

<ResponseField name="depositTokens" type="Token[]">
  Tokens accepted for deposit.
</ResponseField>

<ResponseField name="baseToken" type="Token">
  Base token for the opportunity.
</ResponseField>

<ResponseField name="receiptToken" type="Token">
  Token received as a receipt for deposits.
</ResponseField>

<ResponseField name="curator" type="Curator">
  Curator organization for the opportunity.
</ResponseField>

<ResponseField name="incentives" type="Incentive[]">
  Incentives available on this opportunity.
</ResponseField>

### Token Object

<ResponseField name="symbol" type="string">
  Token symbol, for example `USDC` or `ETH`.
</ResponseField>

<ResponseField name="address" type="string">
  Token contract address.
</ResponseField>

<ResponseField name="chainId" type="integer">
  Numeric chain ID the token belongs to.
</ResponseField>

<ResponseField name="decimals" type="integer">
  ERC20 decimals.
</ResponseField>

<ResponseField name="logoUrl" type="string">
  Token logo image URL.
</ResponseField>

### Meta Object

<ResponseField name="directInteractionEnabled" type="boolean">
  Whether direct interaction with the opportunity is available. When true, the user can deposit the vault's native token directly with `mode=direct`.
</ResponseField>

<ResponseField name="routedSwapEnabled" type="boolean">
  Whether entering via a routed swap is supported. When true, the user can deposit a different input token with `mode=swap` and the API routes through a DEX. See [Deposit Modes](/sdk/concepts/deposit-modes).
</ResponseField>

<ResponseField name="depositEnabled" type="boolean">
  Whether deposits are currently enabled.
</ResponseField>

<ResponseField name="withdrawEnabled" type="boolean">
  Whether withdrawals are currently enabled.
</ResponseField>

<ResponseField name="asyncDeposit" type="boolean">
  Whether deposits settle asynchronously and require a follow-up claim.
</ResponseField>

<ResponseField name="asyncWithdraw" type="boolean">
  Whether withdrawals settle asynchronously.
</ResponseField>

<ResponseField name="isSecondaryMarket" type="boolean">
  Whether the opportunity can only be entered via a secondary market.
</ResponseField>

<ResponseField name="depositDisabledReason" type="string">
  Reason deposits are disabled, if any.
</ResponseField>

<ResponseField name="withdrawalDisabledReason" type="string">
  Reason withdrawals are disabled, if any.
</ResponseField>

### Curator Object

<ResponseField name="id" type="uuid">
  Curator organization ID.
</ResponseField>

<ResponseField name="name" type="string">
  Curator name.
</ResponseField>

<ResponseField name="description" type="string">
  Curator description.
</ResponseField>

<ResponseField name="iconUrl" type="string">
  Curator icon image URL.
</ResponseField>

<ResponseField name="landingUrl" type="string">
  Curator website URL.
</ResponseField>

### Incentive Object

<ResponseField name="id" type="uuid">
  Incentive unique identifier.
</ResponseField>

<ResponseField name="name" type="string">
  Incentive name.
</ResponseField>

<ResponseField name="description" type="string">
  Incentive description.
</ResponseField>

<ResponseField name="iconUrl" type="string">
  Incentive icon URL.
</ResponseField>

<ResponseField name="rewardType" type="string">
  Type of reward: `points`, `tokens`, `yield`, or `vesting`.
</ResponseField>

<ResponseField name="rewardTypeName" type="string">
  Human-readable reward type name.
</ResponseField>

<ResponseField name="apr" type="number">
  Annual percentage rate. May be null.
</ResponseField>

<ResponseField name="minApr" type="number">
  Minimum annual percentage rate. May be null.
</ResponseField>

<ResponseField name="maxApr" type="number">
  Maximum annual percentage rate. May be null.
</ResponseField>

<ResponseField name="fdvEstimate" type="number">
  Fully diluted valuation estimate. May be null.
</ResponseField>

<ResponseField name="tokenSupplyAllocation" type="number">
  Token supply allocation percentage. May be null.
</ResponseField>

<ResponseField name="estPriceUsd" type="number">
  Estimated price in USD. May be null.
</ResponseField>

<ResponseField name="indexed" type="boolean">
  Whether the incentive is indexed.
</ResponseField>

## Operational Notes

<AccordionGroup>
  <Accordion title="Determining the deposit flow (instant vs async)">
    Some vaults settle deposits instantly; others (such as Mellow and Lagoon) are asynchronous and require a follow-up claim. In v2, the `meta.asyncDeposit` flag signals this: when `true`, the deposit settles asynchronously and the user must submit a follow-up claim. The `meta.asyncWithdraw` flag signals the same for withdrawals.
  </Accordion>

  <Accordion title="Empty distributor set">
    `GET /v2/opportunities/distributors/{distributorId}` returns `{ "data": [], "pagination": { "page": 1, "limit": 20, "total": 0, "totalPages": 0 } }` when nothing is configured. Treat this as a prompt to configure the set in the Client Portal, not as a failure.
  </Accordion>
</AccordionGroup>

## Error Handling

<AccordionGroup>
  <Accordion title="Missing or invalid API key">
    **Status Code:** 401 Unauthorized

    **Solution:** Pass a valid `X-API-Key` header. See [API Keys](/sdk/authentication/api-keys).
  </Accordion>

  <Accordion title="Opportunity or distributor not found">
    **Status Code:** 404 Not Found

    ```json theme={null}
    {
      "code": 404,
      "status": "NOT_FOUND",
      "error": "distributor not found"
    }
    ```

    **Solution:** Verify the opportunity ID or distributor ID is correct and active.
  </Accordion>

  <Accordion title="Unexpected internal error">
    **Status Code:** 500 Internal Server Error

    **Solution:** Retry with exponential backoff and contact [support](https://discord.turtle.xyz) if it persists.
  </Accordion>
</AccordionGroup>
