Stage 2 of the two-stage withdrawal flow — submits the user-signed EIP-712 envelope. Accepted operations dispatch async.
message object returned by
stage 1 verbatim, plus the user’s signature over the typed data. The
server reconstructs the EIP-712 hash, verifies the signer is the portfolio
owner, re-checks live onchain balances, and dispatches the transfer
onchain.
x-api-key header (required)portfolios:withdrawrecipientAccountId 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.(tenantId, apiKeyId, "POST /v2/portfolios/:id/withdraw", nonce).
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 reserves and proceeds, the other sees
409 IDEMPOTENCY_IN_PROGRESS — retry the identical body after 2–3 seconds.
If stage 2 fails after reserving the idempotency row (e.g., transient
RPC failure during signature verification, or balance drops below the
requested amount between stages), the row is released so the integrator
can re-submit the same message + signature once the transient issue
resolves. This is different from enrollment semantics where failed rows
stay in-flight until TTL.
operationId. Poll
GET /v2/portfolios/{portfolioId}/operations/{operationId}
until the operation reaches a terminal (completed / failed / cancelled)
state.
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 vault
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, vault self-transfer, or
non-EVM namespace.404 API_200 PORTFOLIO_NOT_FOUND — portfolioId doesn’t exist or
belongs to a different tenant (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 — the RPC backing ERC-1271
verification is temporarily unavailable. Safe to retry; the idempotency
row has been released.