All Posts

Building Agent-Ready Financial Services with UCP

A step-by-step guide for fintech companies implementing UCP: authentication patterns, rate limiting, agent trust signals, and practical code for payment token exchange.

August 10, 2026UCPList Team
UCP implementation guidefintech UCP integrationagent-ready financial servicesUCP authenticationpayment token exchange implementationUCP rate limiting

What Agent-Ready Means

An agent-ready financial service is one that an AI agent can discover, authenticate with, and use to complete a transaction without human intervention at each step. That means a machine-readable manifest, a token-based auth flow, and documented capability endpoints.

This guide walks through the implementation steps for a fintech platform adding UCP support.

Step 1: The Manifest

Every UCP-compatible service starts with a manifest at /.well-known/ucp. This is a JSON document that describes your service's capabilities, endpoints, and authentication requirements.

{
  "ucpVersion": "1.0",
  "name": "Acme Financial Platform",
  "description": "Embedded banking and payment APIs for software platforms",
  "capabilities": ["checkout", "payment-token-exchange"],
  "services": {
    "dev.ucp.payment": [
      {
        "transport": "https",
        "endpoint": "https://api.acmefinancial.com/ucp/payment",
        "authScheme": "bearer"
      }
    ]
  },
  "authentication": {
    "type": "ucp-identity-token",
    "tokenEndpoint": "https://api.acmefinancial.com/ucp/auth/token"
  },
  "contact": "developer@acmefinancial.com"
}

Keep the manifest accurate. Advertise only capabilities you have implemented. Agents will call the advertised endpoints and treat a 404 or 500 as a broken integration.

Serve the manifest with Content-Type: application/json and set a reasonable Cache-Control header. Agents may cache it between sessions.

// Next.js App Router: app/.well-known/ucp/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json({
    ucpVersion: '1.0',
    name: 'Acme Financial Platform',
    capabilities: ['checkout', 'payment-token-exchange'],
    // ... rest of manifest
  }, {
    headers: {
      'Cache-Control': 'public, max-age=3600',
    },
  });
}

Step 2: Authentication

UCP uses identity tokens, not API keys. An agent presents a UCP identity token that was issued to the consumer by their UCP identity provider. Your service validates that token before processing any request.

The validation flow:

  1. Agent sends a request with the UCP identity token in the Authorization: Bearer header
  2. Your service fetches the token's public key from the issuer's JWKS endpoint
  3. You verify the token signature, expiry, and audience claim
  4. You extract the consumer identifier and any embedded claims (spend limits, allowed merchants, etc.)
import { jwtVerify, createRemoteJWKSet } from 'jose';

const UCP_ISSUER = 'https://identity.ucp.dev';
const JWKS = createRemoteJWKSet(new URL(`${UCP_ISSUER}/.well-known/jwks.json`));

async function verifyUcpToken(token: string): Promise<UcpClaims> {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: UCP_ISSUER,
    audience: 'https://api.acmefinancial.com',
  });

  return {
    consumerId: payload.sub as string,
    spendLimit: payload['ucp:spend_limit'] as number | undefined,
    allowedMerchants: payload['ucp:merchants'] as string[] | undefined,
    expiresAt: new Date((payload.exp as number) * 1000),
  };
}

Cache the JWKS response. Fetching it on every request adds latency and creates a dependency on the identity provider's availability. Most JOSE libraries handle this automatically with a remote JWKS set.

Step 3: Payment Token Exchange

Payment token exchange is the core capability. The agent presents a payment token; your service exchanges it with the payment network to get authorization.

The payment token is embedded in the UCP identity token or passed as a separate claim depending on the transaction flow. Extract it and pass it to your payment processor.

import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

async function exchangeUcpPaymentToken(
  ucpPaymentToken: string,
  amount: number,
  currency: string
): Promise<{ success: boolean; chargeId?: string; error?: string }> {
  try {
    // Exchange the UCP payment token for a Stripe payment method
    // The exact exchange mechanism depends on your payment processor
    // and their UCP integration layer
    const paymentIntent = await stripe.paymentIntents.create({
      amount,
      currency,
      payment_method_data: {
        type: 'card',
        // UCP token exchange produces a processor-specific token
        ucp_token: ucpPaymentToken,
      } as any,
      confirm: true,
      automatic_payment_methods: { enabled: false },
    });

    return { success: true, chargeId: paymentIntent.id };
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Payment failed',
    };
  }
}

Always validate the payment token's constraints before attempting the exchange. If the token has a spend limit and the requested amount exceeds it, return a 422 with a clear error message. Agents need machine-readable errors to handle constraints gracefully.

function validateSpendConstraints(
  claims: UcpClaims,
  amount: number,
  merchantDomain: string
): { valid: boolean; reason?: string } {
  if (claims.spendLimit !== undefined && amount > claims.spendLimit) {
    return {
      valid: false,
      reason: `Transaction amount ${amount} exceeds token spend limit ${claims.spendLimit}`,
    };
  }

  if (claims.allowedMerchants && !claims.allowedMerchants.includes(merchantDomain)) {
    return {
      valid: false,
      reason: `Merchant ${merchantDomain} is not in the token's allowed merchant list`,
    };
  }

  return { valid: true };
}

Step 4: Rate Limiting

Agents can operate at high frequency. A shopping agent comparing prices across ten merchants might hit your endpoint dozens of times per minute during a single user session. Standard IP-based rate limiting breaks this.

Rate limit by agent token, not by IP. Extract the agent identifier from the UCP identity token and apply limits per consumer, not per network address.

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(60, '1 m'), // 60 requests per minute per consumer
  prefix: 'ucp:ratelimit',
});

async function checkRateLimit(consumerId: string): Promise<boolean> {
  const { success } = await ratelimit.limit(consumerId);
  return success;
}

// In your UCP endpoint handler:
export async function POST(request: Request) {
  const token = request.headers.get('Authorization')?.replace('Bearer ', '');
  if (!token) return Response.json({ error: 'Unauthorized' }, { status: 401 });

  const claims = await verifyUcpToken(token);

  const allowed = await checkRateLimit(claims.consumerId);
  if (!allowed) {
    return Response.json(
      { error: 'Rate limit exceeded' },
      {
        status: 429,
        headers: { 'Retry-After': '60' },
      }
    );
  }

  // ... handle the request
}

Set separate limits for read operations (manifest fetches, product queries) and write operations (checkout, payment). Reads can be more permissive. Writes should be stricter and logged individually.

Step 5: Agent Trust Signals

Not all agents should have the same level of access. A well-known commerce agent with a verified identity deserves more trust than an anonymous agent making its first request.

UCP identity tokens can carry trust claims that your platform can use to tier access:

interface UcpClaims {
  consumerId: string;
  spendLimit?: number;
  allowedMerchants?: string[];
  expiresAt: Date;
  // Trust signals from the identity provider
  verificationLevel?: 'none' | 'email' | 'phone' | 'kyc';
  agentId?: string;         // Identifier of the specific agent
  agentTrust?: 'unknown' | 'community' | 'verified';
}

function determineTransactionLimit(claims: UcpClaims): number {
  // Tiered limits based on identity verification level
  if (claims.verificationLevel === 'kyc') return 10000;
  if (claims.verificationLevel === 'phone') return 1000;
  if (claims.verificationLevel === 'email') return 100;
  return 10; // Unverified gets minimal access
}

Log the trust signal tier with every transaction. This data becomes useful for fraud analysis and for adjusting your trust tiers over time as you learn which agent patterns correlate with risk.

Step 6: Error Responses

Agents need structured errors, not HTML error pages. Every error response from a UCP endpoint should be JSON with a machine-readable error code.

export const UCP_ERRORS = {
  INVALID_TOKEN: { code: 'invalid_token', status: 401 },
  SPEND_LIMIT_EXCEEDED: { code: 'spend_limit_exceeded', status: 422 },
  MERCHANT_NOT_ALLOWED: { code: 'merchant_not_allowed', status: 403 },
  PAYMENT_DECLINED: { code: 'payment_declined', status: 402 },
  RATE_LIMITED: { code: 'rate_limited', status: 429 },
  CAPABILITY_NOT_SUPPORTED: { code: 'capability_not_supported', status: 501 },
} as const;

function ucpError(
  type: keyof typeof UCP_ERRORS,
  detail?: string
): Response {
  const { code, status } = UCP_ERRORS[type];
  return Response.json({ error: code, detail }, { status });
}

Agents use error codes to decide whether to retry, fall back to an alternative, or surface the error to the user. A generic 500 with no body leaves the agent with no options. A structured 422 with spend_limit_exceeded lets the agent tell the user exactly what happened.

Putting It Together

The minimum viable UCP implementation for a fintech platform is:

  1. A /.well-known/ucp manifest with accurate capability declarations
  2. A token validation function using the JWKS endpoint
  3. A payment-token-exchange endpoint that validates constraints before processing
  4. Rate limiting keyed to consumer identity
  5. Structured JSON error responses

Everything else, trust tiers, KYC integration, spend analytics, comes after you have the basics working and agents are hitting your endpoint. Build the foundation first. Get something live. Then instrument and iterate based on actual agent traffic.

The fintech platforms that ship a working UCP endpoint this year will have a head start on agent commerce that compounds over time. Agents default to what works. Get in front of them early.

Read next