> ## 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.

# Deposit

> Build a ready-to-sign deposit into an opportunity, including the swap-mode variant, and broadcast it

<Note>
  Requires an API key via the `X-API-Key` header. See [API Keys](/sdk/authentication/api-keys). {/* VERIFY: canonical auth header. KB earn-api.md records both `X-API-Key: pk_live_...` and `Authorization: Bearer pk_live_...` as accepted; the public spec defines no security scheme on action endpoints. Confirm which is canonical for docs. */}
</Note>

<Info>
  The user must be a registered [Turtle member](/sdk/authentication/register-wallet) before you can deposit on their behalf. A non-member request fails (see Error Handling).
</Info>

Depositing is three moves: build the deposit, have the user broadcast the returned transactions in order, then attribution lands on its own. Direct and swap deposits use the same endpoint and differ only by the `mode` field. For the concept behind direct vs swap and instant vs async, see [Deposit modes](/sdk/concepts/deposit-modes).

## Overview

`POST /v2/actions/deposit/{opportunityId}`

The endpoint returns an `actionId` and an ordered `transactions` array. The user signs and submits each transaction in sequence (for an ERC-20 deposit, typically an `approve` followed by a `deposit`).

## Build the deposit

Pass the user's address, the input token, the amount in the token's smallest unit, and your `distributorId`. Use `mode: "direct"` to deposit the vault's native token.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST "https://earn.turtle.xyz/v2/actions/deposit/{opportunityId}" \
    -H "X-API-Key: pk_live_xxxxx" \
    -H "Content-Type: application/json" \
    -d '{
      "userAddress": "0x1234...",
      "tokenIn": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "amount": "1000000",
      "distributorId": "your-distributor-id",
      "mode": "direct",
      "slippageBps": 50
    }'
  ```

  ```typescript TypeScript theme={null}
  const opportunityId = '550e8400-e29b-41d4-a716-446655440000';

  const response = await fetch(
    `https://earn.turtle.xyz/v2/actions/deposit/${opportunityId}`,
    {
      method: 'POST',
      headers: { 'X-API-Key': 'pk_live_xxxxx', 'Content-Type': 'application/json' },
      body: JSON.stringify({
        userAddress: '0x1234...',
        tokenIn: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
        amount: '1000000', // 1 USDC (6 decimals)
        distributorId: 'your-distributor-id',
        mode: 'direct',
        slippageBps: 50,
      }),
    }
  );
  const data = await response.json();
  ```
</CodeGroup>

**Path Parameters**

<ParamField path="opportunityId" type="uuid" required>
  The opportunity to deposit into. Get it from [Get Opportunities](/sdk/opportunities/get-opportunities). IDs are UUIDs.
</ParamField>

**Body Parameters**

<ParamField body="userAddress" type="string" required>
  The user's EVM wallet address. Must belong to a registered Turtle member.
</ParamField>

<ParamField body="tokenIn" type="string" required>
  Address of the token being deposited. Must be supported on the opportunity's chain. Cannot be the vault's receipt token.
</ParamField>

<ParamField body="amount" type="string" required>
  Deposit amount in the token's smallest unit (wei). Must be greater than 0.
</ParamField>

<ParamField body="distributorId" type="string" required>
  Your distributor ID. Embedded into the deposit calldata for [automatic attribution](/sdk/concepts/distributor-model).
</ParamField>

<ParamField body="mode" type="string" default="direct">
  `direct` deposits the vault's native token. `swap` routes a different input token through a DEX first. See the swap-mode section below.
</ParamField>

<ParamField body="slippageBps" type="integer" default="50">
  Maximum acceptable slippage in basis points, applied in `swap` mode. `50` = 0.5%.
</ParamField>

<ParamField body="referralCode" type="string">
  Optional referral code for deposit attribution.
</ParamField>

**Response**

```json theme={null}
{
  "actionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "transactions": [
    {
      "type": "approve",
      "transaction": {
        "to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "data": "0x095ea7b3000000000000000000000000...",
        "value": "0",
        "gasLimit": "60000",
        "chainId": 1
      },
      "description": "Approve USDC spending"
    },
    {
      "type": "deposit",
      "transaction": {
        "to": "0x...",
        "data": "0x...",
        "value": "0",
        "gasLimit": "250000",
        "chainId": 1
      },
      "description": "Deposit USDC into vault"
    }
  ]
}
```

### Transaction Object

Each transaction in the `transactions` array contains:

<ResponseField name="type" type="string" required>
  Transaction type, e.g. `approve`, `deposit`, `withdraw`, `claimDeposit`, `cancelDeposit`.
</ResponseField>

<ResponseField name="transaction" type="object" required>
  The raw transaction data to sign and submit.
</ResponseField>

<ResponseField name="transaction.to" type="string" required>
  Target contract address.
</ResponseField>

<ResponseField name="transaction.data" type="string" required>
  Encoded calldata (hex string with `0x` prefix).
</ResponseField>

<ResponseField name="transaction.value" type="string" required>
  Value in wei. Usually `"0"` for token interactions; non-zero for native token deposits.
</ResponseField>

<ResponseField name="transaction.gasLimit" type="string" required>
  Estimated gas limit.
</ResponseField>

<ResponseField name="transaction.chainId" type="integer" required>
  Chain ID for the transaction.
</ResponseField>

<ResponseField name="description" type="string" required>
  Human-readable description of what this transaction does.
</ResponseField>

<ResponseField name="metadata" type="object">
  Optional metadata for swap transactions, including provider info, amount out, gas estimate, and route details.
</ResponseField>

## Swap mode

When the user wants to deposit a token that is not the vault's native deposit token, set `mode` to `swap` and pass that token as `tokenIn`. The API routes the swap through a DEX before depositing. Swap mode is available when `swapRouteEnabled` is `true` on the opportunity. See [Deposit modes](/sdk/concepts/deposit-modes) for the full availability matrix.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST "https://earn.turtle.xyz/v2/actions/deposit/{opportunityId}" \
    -H "X-API-Key: pk_live_xxxxx" \
    -H "Content-Type: application/json" \
    -d '{
      "userAddress": "0x1234...",
      "tokenIn": "0xInputTokenAddress",
      "amount": "1000000",
      "distributorId": "your-distributor-id",
      "mode": "swap",
      "slippageBps": 100
    }'
  ```

  ```typescript TypeScript theme={null}
  const opportunityId = '550e8400-e29b-41d4-a716-446655440000';

  const response = await fetch(
    `https://earn.turtle.xyz/v2/actions/deposit/${opportunityId}`,
    {
      method: 'POST',
      headers: { 'X-API-Key': 'pk_live_xxxxx', 'Content-Type': 'application/json' },
      body: JSON.stringify({
        userAddress: '0x1234...',
        tokenIn: '0xInputTokenAddress',
        amount: '1000000',
        distributorId: 'your-distributor-id',
        mode: 'swap',
        slippageBps: 100,
      }),
    }
  );
  const action = await response.json();
  ```
</CodeGroup>

The swap-mode response carries swap details in each transaction's `metadata` (provider, amount out, route). The rest of the broadcast flow is identical.

## Broadcast

Sign and submit each transaction in the returned order. Wait for each to confirm before sending the next, since a later transaction often depends on an earlier one (a `deposit` cannot land before its `approve`).

```typescript theme={null}
// 1. Build the deposit action
const depositResponse = await fetch(
  `https://earn.turtle.xyz/v2/actions/deposit/${opportunityId}`,
  {
    method: 'POST',
    headers: { 'X-API-Key': 'pk_live_xxxxx', 'Content-Type': 'application/json' },
    body: JSON.stringify({
      userAddress: walletAddress,
      tokenIn: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
      amount: '1000000000', // 1000 USDC
      distributorId: 'your-distributor-id',
      mode: 'direct',
    }),
  }
);
const { actionId, transactions } = await depositResponse.json();

// 2. Sign and submit each transaction in order
for (const tx of transactions) {
  const txResponse = await wallet.sendTransaction(tx.transaction);
  await txResponse.wait(); // wait for on-chain confirmation before the next
}

// 3. Attribution is automatic. Turtle detects the tracking signature in
//    the deposit calldata and attributes it to your distributor.
```

## Verify the deposit

Attribution is automatic, so there is nothing to call to record it. If you want to confirm a specific deposit carried your tracking data, pass its transaction hash to the verify endpoint. That step has its own page: [Verify Attribution](/sdk/earn/verify-attribution).

If the opportunity is async (`complex` settlement), the deposit will sit pending until you claim it. Handle that on [Async Deposits](/sdk/earn/async-deposits).

## Operational Notes

<AccordionGroup>
  <Accordion title="Amount is in the token's smallest unit">
    `amount` is in wei for the input token's decimals. 1 USDC (6 decimals) is `"1000000"`; 1 DAI (18 decimals) is `"1000000000000000000"`. Pass the raw integer string. Never send a human-readable decimal.
  </Accordion>

  <Accordion title="Submit transactions in order">
    The `transactions` array is ordered. An ERC-20 deposit returns an `approve` then a `deposit`. Confirm each before broadcasting the next.
  </Accordion>

  <Accordion title="Swap mode needs swapRouteEnabled">
    `mode: "swap"` only works when the opportunity has `swapRouteEnabled: true`. Read availability off the opportunity object. See [Deposit modes](/sdk/concepts/deposit-modes).
  </Accordion>

  <Accordion title="Async vaults need a claim step">
    For opportunities with `complex` settlement (for example Mellow, Lagoon), the deposit queues and is not active until claimed. See [Async Deposits](/sdk/earn/async-deposits).
  </Accordion>

  <Accordion title="Membership is required">
    The user must be a registered Turtle member. Complete the [membership flow](/sdk/authentication/register-wallet) first.
  </Accordion>
</AccordionGroup>

## Error Handling

<AccordionGroup>
  <Accordion title="User is not a Turtle member">
    **Status:** 403 Forbidden

    ```json theme={null}
    {
      "error": "not_a_member",
      "message": "User is not a Turtle member. Please complete the membership flow first.",
      "docsUrl": "https://docs.turtle.xyz/sdk/authentication/register-wallet"
    }
    ```

    **Solution:** Complete the [membership flow](/sdk/authentication/register-wallet) before depositing. {/* VERIFY: 403 vs 404 for a non-member wallet. quickstart says 403; authentication/overview says 404. Confirm the actual status against the live API. */}
  </Accordion>

  <Accordion title="Distributor not found">
    **Status:** 404 Not Found

    ```json theme={null}
    {
      "error": "distributor_not_found",
      "message": "Distributor not found with the provided ID"
    }
    ```

    **Solution:** Verify your distributor ID is correct and active.
  </Accordion>

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

    ```json theme={null}
    {
      "error": "opportunity_not_found",
      "message": "Opportunity not found"
    }
    ```

    **Solution:** Check the opportunity ID with [Get Opportunities](/sdk/opportunities/get-opportunities).
  </Accordion>

  <Accordion title="Deposits disabled">
    **Status:** 400 Bad Request

    ```json theme={null}
    {
      "error": "deposits_disabled",
      "message": "Deposits are disabled for this opportunity"
    }
    ```

    **Solution:** Deposits are temporarily disabled for this opportunity. Try a different one.
  </Accordion>

  <Accordion title="Unsupported token">
    **Status:** 400 Bad Request

    ```json theme={null}
    {
      "error": "invalid_token",
      "message": "Token 0x... not supported for chain 1"
    }
    ```

    **Solution:** Use a supported deposit token. Check the opportunity's `depositTokens` array.
  </Accordion>
</AccordionGroup>
