Create portfolio
Creates a new yield portfolio (jar) for the specified end user on the given chain and pool. The portfolio starts with zero deposited funds — use POST .../deposit to fund it. Returns a snapshot of the newly created portfolio. Supports both Entra token + user token.
Obtain the token_address and market_address from GET /v1/partner/pools — do not invent or hard-code these values.
Path parameters
Your stable identifier for the end user.
Headers
Entra M2M access token. Format: Bearer <token>. See Entra authentication.
Partner-minted JWT for the end user. The sub claim must equal external_id. See User authentication.
UUID v4. Generate fresh per logical create-portfolio attempt; reuse the same key when retrying a dropped request. See Idempotency-Key.
Must be application/json.
Body
Display name for the portfolio. 1–50 characters.
Numeric chain ID as a string. Example: "84532" for Base Sepolia.
Ethereum address of the underlying ERC-20 token (e.g. USDC). Obtain from GET /v1/partner/pools
→ token_address.
Pool or vault contract address. Must come from GET /v1/partner/pools → market_address —
this is not the same as token_address. For Morpho, this is the MetaMorpho vault address.
Lending protocol identifier. One of "aave" or "morpho". Default: "aave". Must match the
protocol associated with market_address.
Optional description for the portfolio. Maximum 500 characters.
Example request
const baseUrl = process.env.YIELDFORCE_API_BASE_URL ?? 'https://yieldforce.io/api';
const res = await fetch(`${baseUrl}/v1/partner/end_users/alice-bunq-id/portfolios`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${entraToken}`,
'X-User-Token': partnerJwt,
},
body: JSON.stringify({
name: 'USDC vault on Base',
chain_id: '84532',
token_address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
market_address: '0x6A0935DEF442D92c3456FBb38B888375F022C646',
protocol: 'morpho',
}),
});
const data = await res.json();Response
201 Portfolio created — post-creation snapshot
{
"portfolio_id": "jar_01HZ4KXQM5E8WRTYN3P7VBJD6F",
"name": "USDC vault on Base",
"description": null,
"protocol": "morpho",
"chain_id": "84532",
"token_address": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"market_address": "0x6A0935DEF442D92c3456FBb38B888375F022C646",
"token_symbol": "USDC",
"token_decimals": 6,
"deposited": "0",
"yield_earned": "0",
"yield_earned_realized": "0",
"total_balance": "0",
"compounding_enabled": true,
"created_at": "2026-05-20T10:00:00.000Z",
"updated_at": "2026-05-20T10:00:00.000Z"
}Response shape differs from list-portfolios
The create response uses flat token_address, token_symbol, and token_decimals fields (not nested under token). The list-portfolios endpoint uses token: { symbol, address, decimals }. Both shapes represent the same data — the difference is intentional to allow independent evolution of read vs write responses.
Errors
400 validation_error — One or more body fields failed validation. Check name length, that chain_id is a numeric string, and that token_address / market_address are valid Ethereum addresses.
401 token_missing — No Authorization: Bearer header on the request.
401 invalid_entra_token — Token is expired, malformed, or has an unexpected audience.
401 invalid_user_token — The X-User-Token failed signature verification or is expired.
403 sub_url_mismatch — The sub claim in X-User-Token does not match external_id in the URL.
404 end_user_not_found — No user with this external_id exists in your tenant.
422 pool_not_found — The (chain_id, token_address, market_address, protocol) combination does not match any pool registered for your tenant. Verify values from GET /v1/partner/pools.