Overview
This guide walks through integrating Grantex agent passports with the Machine Payments Protocol (MPP). You’ll learn how to:
- Agent side — Issue passports and attach them to outgoing MPP requests
- Merchant side — Verify passports on incoming requests with one line of code
- 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
Option A: Express Middleware (Recommended)
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
| Option | Type | Default | Description |
|---|
jwksUri | string | https://api.grantex.dev/.well-known/jwks.json | JWKS endpoint for signature verification |
trustedIssuers | string[] | ['did:web:grantex.dev'] | Allowed issuer DIDs |
requiredCategories | MPPCategory[] | undefined | Required MPP categories |
maxAmount | number | undefined | Minimum passport amount ceiling |
checkRevocation | boolean | false | Online revocation check via StatusList2021 |
revocationEndpoint | string | — | Required when checkRevocation: true |
Next Steps