fastify-xplaid
investments.getHoldings(accessToken, options?)
Get current investment holdings and security metadata for a Plaid Item.
investments.getHoldings(accessToken, options?)
Returns the current investment portfolio — positions held across brokerage, retirement, and other investment accounts — along with security metadata for each holding. Requires the investments product to have been enabled in the Link token.
Signature
fastify.xplaid.investments.getHoldings(
accessToken: string,
options?: {
accountIds?: string[]
}
): Promise<{
accounts: PlaidAccount[]
holdings: Holding[]
securities: Security[]
item: PlaidItem
requestId: string
}>
Params
| Name | Type | Required | Description |
|---|---|---|---|
accessToken | string | Yes | The Item's permanent access token. |
options.accountIds | string[] | No | Restrict results to specific account IDs. |
Returns
Promise<{ accounts, holdings, securities, item, requestId }>
| Field | Description |
|---|---|
accounts | Investment accounts for the Item. |
holdings | Current positions. Each holding links to a security_id and includes quantity, institution_value, and cost_basis. |
securities | Security metadata keyed by security_id — includes name, ticker_symbol, type, close_price. |
Throws
[xPlaid] accessToken is required and must be a non-empty string- Plaid API errors — shape:
{ message, statusCode, plaidError }. PRODUCTS_NOT_SUPPORTEDPlaid error ifinvestmentswas not enabled for this Item.ITEM_LOGIN_REQUIREDPlaid error if the Item needs re-authentication.
Examples
Basic — portfolio value
const { holdings, securities } = await fastify.xplaid.investments.getHoldings(accessToken);
const securityMap = Object.fromEntries(securities.map((s) => [s.security_id, s]));
const portfolio = holdings.map((h) => ({
name: securityMap[h.security_id]?.name,
ticker: securityMap[h.security_id]?.ticker_symbol,
quantity: h.quantity,
value: h.institution_value,
costBasis: h.cost_basis,
}));
Realistic — portfolio route with gain/loss calculation
fastify.get("/investments/portfolio", async (request, reply) => {
const item = await db.plaidItems.findFirst({ where: { userId: request.user.id } });
if (!item) return reply.status(404).send({ error: "No linked account" });
try {
const { holdings, securities } = await fastify.xplaid.investments.getHoldings(
decrypt(item.accessToken)
);
const securityMap = Object.fromEntries(securities.map((s) => [s.security_id, s]));
const totalValue = holdings.reduce((sum, h) => sum + (h.institution_value ?? 0), 0);
return {
totalValue,
positions: holdings.map((h) => ({
name: securityMap[h.security_id]?.name,
ticker: securityMap[h.security_id]?.ticker_symbol,
type: securityMap[h.security_id]?.type,
quantity: h.quantity,
value: h.institution_value,
costBasis: h.cost_basis,
gainLoss:
h.institution_value != null && h.cost_basis != null
? h.institution_value - h.cost_basis
: null,
})),
};
} catch (error) {
if (error.plaidError) {
request.log.error({ code: error.plaidError.error_code }, "Holdings fetch failed");
return reply.status(error.statusCode ?? 500).send({ error: error.message });
}
throw error;
}
});
See also
- investments.getTransactions(accessToken, startDate, endDate, options?) — Investment transaction history.
- accounts.get(accessToken, options?) — All accounts including investment accounts.
- link.createToken(options) — Must include
"investments"inproducts. - fastify-xplaid overview — Plugin setup and options.
AI Context
package: "@xenterprises/fastify-xplaid"
method: fastify.xplaid.investments.getHoldings(accessToken, options?)
use-when: Get current investment portfolio positions and security metadata — requires "investments" product
returns: { holdings, securities, accounts, item, requestId }
