Skip to main content

Overview

This guide walks through integrating Grantex agent passports with the Machine Payments Protocol (MPP). You’ll learn how to:
  1. Agent side — Issue passports and attach them to outgoing MPP requests
  2. Merchant side — Verify passports on incoming requests with one line of code
  3. Trust registry — Look up organization trust levels before fulfilling requests

Prerequisites

  • A Grantex account with an API key (sign up free)
  • A registered agent with an active grant that includes payments:mpp:* scopes
  • Node.js 18+ for the TypeScript SDK

Installation

npm install @grantex/sdk @grantex/mpp

Agent Side: Issue and Attach Passports

Step 1: Issue a Passport

import { Grantex } from '@grantex/sdk';

const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY });

// Issue a passport for your agent
const passport = await grantex.passports.issue({
  agentId: 'ag_01HXYZ...',
  grantId: 'grnt_01HXYZ...',
  allowedMPPCategories: ['inference', 'compute'],
  maxTransactionAmount: { amount: 50, currency: 'USDC' },
  paymentRails: ['tempo'],
  expiresIn: '24h',  // max: 720h (30 days)
});

console.log(passport.passportId);
// "urn:grantex:passport:01HXYZ..."
The grant must include the corresponding payments:mpp:* scopes for each category. For example, requesting ['inference', 'compute'] requires the grant to have payments:mpp:inference and payments:mpp:compute scopes.

Step 2: Attach to MPP Requests

Use the middleware to automatically inject the X-Grantex-Passport header:
import { createMppPassportMiddleware } from '@grantex/mpp';

const middleware = createMppPassportMiddleware({
  passport,
  autoRefreshThreshold: 300, // warn 5 min before expiry
});

// Wrap every outgoing fetch
const request = new Request('https://merchant.example.com/api/inference', {
  method: 'GET',
  headers: { 'Authorization': 'Payment <mpp-token>' },
});

const enrichedRequest = await middleware(request);
const response = await fetch(enrichedRequest);
// enrichedRequest has X-Grantex-Passport header attached

Merchant Side: Verify Passports

One line to protect any route:
import express from 'express';
import { requireAgentPassport } from '@grantex/mpp';

const app = express();

// Protect inference endpoints — require inference category
app.use('/api/inference', requireAgentPassport({
  requiredCategories: ['inference'],
  maxAmount: 10,
}));

app.get('/api/inference', (req, res) => {
  // req.agentPassport is populated
  console.log(req.agentPassport.humanDID);        // "did:grantex:user_alice"
  console.log(req.agentPassport.organizationDID);  // "did:web:acme.com"
  console.log(req.agentPassport.allowedCategories); // ["inference", "compute"]

  res.json({ result: 'inference complete' });
});
Invalid passports return 403 with a typed error code:
{
  "error": "CATEGORY_MISMATCH",
  "message": "Passport does not cover required categories: storage"
}

Option B: Standalone Verification

import { verifyPassport } from '@grantex/mpp';

const encodedPassport = req.headers['x-grantex-passport'];

try {
  const verified = await verifyPassport(encodedPassport, {
    trustedIssuers: ['did:web:grantex.dev'],
    requiredCategories: ['inference'],
    maxAmount: 10,
    checkRevocation: false,  // true for online revocation check
  });

  console.log(verified.valid);          // true
  console.log(verified.humanDID);       // "did:grantex:user_alice"
  console.log(verified.agentDID);       // "did:grantex:ag_01HXYZ..."
  console.log(verified.grantId);        // "grnt_01HXYZ..."
  console.log(verified.delegationDepth); // 0
} catch (err) {
  // err.code is a typed PassportErrorCode
  console.error(err.code, err.message);
}

Trust Registry Lookup

Before fulfilling a high-value request, check the organization’s trust level:
import { lookupOrgTrust } from '@grantex/mpp';

const record = await lookupOrgTrust(verified.organizationDID);

if (!record || record.trustLevel === 'basic') {
  // Require additional verification for unverified orgs
  return res.status(403).json({ error: 'ORG_NOT_VERIFIED' });
}

// record.trustLevel: "basic" | "verified" | "soc2"
// record.verificationMethod: "dns-txt" | "manual" | "soc2"
Results are cached in-memory for 1 hour by default.

Revoking Passports

Instantly revoke a passport — flips the StatusList2021 bit:
const result = await grantex.passports.revoke('urn:grantex:passport:01HXYZ...');
// { revoked: true, revokedAt: "2026-03-20T..." }
After revocation, any verifyPassport() call with checkRevocation: true will throw PASSPORT_REVOKED.

Verification Options Reference

OptionTypeDefaultDescription
jwksUristringhttps://api.grantex.dev/.well-known/jwks.jsonJWKS endpoint for signature verification
trustedIssuersstring[]['did:web:grantex.dev']Allowed issuer DIDs
requiredCategoriesMPPCategory[]undefinedRequired MPP categories
maxAmountnumberundefinedMinimum passport amount ceiling
checkRevocationbooleanfalseOnline revocation check via StatusList2021
revocationEndpointstringRequired when checkRevocation: true

Next Steps