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

# Submit Signed Withdrawal (Stage 2)

> Stage 2 of the two-stage withdrawal flow — submits the user-signed EIP-712 envelope. Accepted operations dispatch async.

Stage 2 of the two-stage withdrawal flow. Accepts the `message` object
returned by stage 1 verbatim, plus the user's `signature` over the typed
data. Verifies the signature, re-checks live onchain balances, and
dispatches the transfer onchain.

* Auth: `x-api-key` header (required)
* Scope: `portfolios:withdraw`

<Note>
  **Temporary constraint:** `recipientAccountId` must resolve to the
  portfolio owner's address. Third-party recipients are not yet supported;
  any other recipient returns `400 API_211 INVALID_RECIPIENT`. The
  examples below use a placeholder address — substitute your portfolio's
  owner address when testing.
</Note>

### Idempotency

Keyed on `nonce` (scoped to your API key). Retries with the **same**
`message` + `signature` replay the cached 202 response. Retries with the
same `nonce` but a **different** body return `409 IDEMPOTENCY_KEY_CONFLICT`
— fix the client, don't retry. Two parallel requests with the same body:
one proceeds, the other sees `409 IDEMPOTENCY_IN_PROGRESS` — retry the
identical body after 2–3 seconds.

If stage 2 fails after accepting the request (e.g., a transient signature
verification error, or balance drops below the requested amount between
stages), the idempotency lock is released so the integrator can re-submit
the same message + signature once the transient issue resolves.

### Polling for onchain status

The response returns `operationId`. Poll
[`GET /v2/portfolios/{portfolioId}/operations/{operationId}`](./v2-get-operation)
until the operation reaches a terminal (`completed` / `failed` / `cancelled`)
state.

### Common error responses

* `400 API_216 WITHDRAWAL_AUTHORIZATION_EXPIRED` — signed authorization's
  `expiresAt` is in the past. Restart the flow via stage 1.
* `400 API_217 INVALID_WITHDRAWAL_SIGNATURE` — signature does not recover
  to the portfolio owner. Almost always a client bug.
* `400 API_210 INSUFFICIENT_BALANCE` — balance dropped below `amountRaw`
  between stage 1 and stage 2. Restart stage 1 to issue a fresh
  authorization.
* `400 API_214 WITHDRAWAL_PORTFOLIO_MISMATCH` — `message.portfolioId`
  doesn't match the URL path.
* `400 API_213 WITHDRAWAL_CHAIN_MISMATCH` — at least one asset is on a
  different chain than `recipientAccountId`.
* `400 API_215 PORTFOLIO_HAS_NO_VAULT_ON_CHAIN` — portfolio has no smart
  account deployed on the recipient's chain.
* `400 API_212 DUPLICATE_WITHDRAW_ASSET` — two or more assets in `assets`
  share the same `assetId`.
* `400 API_211 INVALID_RECIPIENT` — zero address, smart account self-transfer,
  or non-EVM namespace.
* `404 API_200 PORTFOLIO_NOT_FOUND` — `portfolioId` doesn't exist or
  your API key cannot access it (can surface at stage 2 if the portfolio
  was archived/reassigned between stages).
* `409 API_007 IDEMPOTENCY_IN_PROGRESS` — another request with the same
  nonce is still running. Retry the identical body.
* `409 API_008 IDEMPOTENCY_KEY_CONFLICT` — nonce reused with a different
  body. Don't retry; restart stage 1.
* `503 API_506 SIGNATURE_VERIFIER_UNAVAILABLE` — signature verification is
  temporarily unavailable. Safe to retry; the idempotency lock has been
  released.

<RequestExample>
  ```bash cURL theme={null}
  curl --request POST \
    --url 'https://api.glider.fi/v2/portfolios/01JWZEE2MF30KVRMRX53N88VA4/withdraw' \
    --header 'x-api-key: gldr_sk_your_api_key' \
    --header 'Content-Type: application/json' \
    --data '{
      "message": {
        "portfolioId": "01JWZEE2MF30KVRMRX53N88VA4",
        "recipientAccountId": "eip155:1:0x4444444444444444444444444444444444444444",
        "assets": [
          {
            "assetId": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
            "amountRaw": "1000500000"
          }
        ],
        "nonce": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
        "expiresAt": 1744898400
      },
      "signature": "0xabc..."
    }'
  ```

  ```javascript JavaScript theme={null}
  // Typical flow: stage 1 → user signs with wagmi/viem → stage 2.
  import { useSignTypedData } from "wagmi";

  const stage1 = await fetch(`${API}/v2/portfolios/${portfolioId}/withdraw/signature`, {
    method: "POST",
    headers: { "x-api-key": KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      recipientAccountId: `eip155:1:${recipient}`,
      assets: [{ assetId, amountRaw }],
    }),
  }).then((r) => r.json());

  const signature = await signTypedDataAsync(stage1.data.typedData);

  // Stage 2 echoes `typedData.message` back as `body.message`.
  const stage2 = await fetch(`${API}/v2/portfolios/${portfolioId}/withdraw`, {
    method: "POST",
    headers: { "x-api-key": KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ message: stage1.data.typedData.message, signature }),
  }).then((r) => r.json());

  // Poll execution status via GET /v2/portfolios/:id/operations/:operationId
  // until a terminal state is reached.
  const { operationId } = stage2.data;
  ```
</RequestExample>

<ResponseExample>
  ```json 202 theme={null}
  {
    "success": true,
    "data": {
      "operationId": "op_01JWZEE2MF30KVRMRX53N88VA4",
      "submittedAt": "2026-04-17T12:05:00.000Z"
    }
  }
  ```

  ```json 400 — expired theme={null}
  {
    "success": false,
    "error": {
      "code": "API_216",
      "message": "Withdrawal authorization expired at 2026-04-17T11:50:00.000Z"
    }
  }
  ```

  ```json 400 — bad signature theme={null}
  {
    "success": false,
    "error": {
      "code": "API_217",
      "message": "Withdrawal signature does not match the portfolio owner"
    }
  }
  ```

  ```json 409 — in flight theme={null}
  {
    "success": false,
    "error": {
      "code": "API_007",
      "message": "Another withdrawal with the same nonce is still in progress"
    }
  }
  ```

  ```json 503 — verifier down theme={null}
  {
    "success": false,
    "error": {
      "code": "API_506",
      "message": "Signature verifier is temporarily unavailable"
    }
  }
  ```
</ResponseExample>
