X Enterprises
fastify-xplaid

link.exchangePublicToken

Exchange a Plaid public token for a permanent access token after the user completes Plaid Link.

link.exchangePublicToken

Exchange the temporary one-time public_token received from Plaid Link's onSuccess callback for a permanent accessToken and itemId. Always call this server-side — never expose the access token to the browser.

Signature

fastify.xplaid.link.exchangePublicToken(
  publicToken: string
): Promise<{ accessToken: string; itemId: string; requestId: string }>

Params

NameTypeRequiredDescription
publicTokenstringYesThe public_token from Plaid Link's onSuccess callback. Starts with public-.

Returns

FieldTypeDescription
accessTokenstringPermanent access token for this Item. Store encrypted in your database.
itemIdstringPlaid Item ID — stable identifier for the linked bank connection.
requestIdstringPlaid request ID for support and logging.

Throws

  • [xPlaid] publicToken is required and must be a non-empty string — when publicToken is missing or empty.
  • Plaid API errors wrapped via wrapPlaidError — shape: { message, statusCode, plaidError }.

Examples

Basic — exchange and store

fastify.post("/plaid/exchange-token", async (request, reply) => {
  const { publicToken, institutionId } = request.body;

  const { accessToken, itemId } = await fastify.xplaid.link.exchangePublicToken(publicToken);

  // Never return the accessToken to the client
  await db.plaidItems.create({
    data: {
      userId: request.user.id,
      accessToken: encrypt(accessToken),
      itemId,
      institutionId,
    },
  });

  return reply.code(201).send({ itemId });
});

Realistic — exchange, fetch accounts, and persist

fastify.post("/plaid/connect", async (request, reply) => {
  const { publicToken, metadata } = request.body;

  let accessToken, itemId;
  try {
    ({ accessToken, itemId } = await fastify.xplaid.link.exchangePublicToken(publicToken));
  } catch (error) {
    if (error.plaidError) {
      request.log.error({ code: error.plaidError.error_code }, "Token exchange failed");
      return reply.status(error.statusCode ?? 500).send({ error: error.message });
    }
    throw error;
  }

  const { accounts } = await fastify.xplaid.accounts.get(accessToken);

  await db.plaidItems.create({
    data: {
      userId: request.user.id,
      itemId,
      accessToken: encrypt(accessToken),
      institutionName: metadata.institution.name,
      accounts: accounts.map((a) => ({
        id: a.account_id,
        name: a.name,
        type: a.type,
        subtype: a.subtype,
        mask: a.mask,
      })),
    },
  });

  return reply.code(201).send({
    itemId,
    institutionName: metadata.institution.name,
    accountCount: accounts.length,
  });
});

See also

AI Context

package: "@xenterprises/fastify-xplaid"
method: fastify.xplaid.link.exchangePublicToken(publicToken)
use-when: Exchange the temporary one-time public_token from Plaid Link's onSuccess callback for a permanent accessToken — call server-side only, never expose the accessToken to the browser
params: publicToken (string, starts with "public-")
returns: { accessToken, itemId, requestId }
Copyright © 2026