Documentation Index
Fetch the complete documentation index at: https://docs.grantex.dev/llms.txt
Use this file to discover all available pages before exploring further.
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