INTEGRATION GUIDE

Make the payment gateway consume authority exactly once.

Strict verification reserves approved authority. A separate, audience-bound processor credential must consume it before provider submission, then commit or release the authenticated outcome.

ZERO-DEPENDENCY JAVASCRIPT

Vendor one ESM file.

The client uses the runtime's built-in fetch and AbortController. It has no install script, transitive packages or telemetry. Commit the downloaded file so upgrades are explicit and reviewable.

mkdir -p vendor/mandateshield
curl --proto '=https' --tlsv1.2 -fsSLo \
  vendor/mandateshield/mandateshield.mjs \
  https://mandateshield.com/sdk/mandateshield.mjs

RUNTIME

Node.js 18+ or a current browser.

Production keys belong only in trusted server code. A browser may use the anonymous sandbox for analysis, but a sandbox result can never pass requireAllowed.

Download JavaScript client →

EXECUTION CONTRACT

A reserved ALLOW is not yet a provider call.

isPolicyAllow is useful for sandbox evaluation. requireAllowed accepts only a live, persisted, account-pinned ALLOW with a consumable RESERVED authorization. The trusted gateway must then use a separate PROCESSOR key, bound to the exact relying-party audience, to CONSUME once before provider submission and COMMIT or RELEASE afterwards.

PURCHASE ENVELOPE

Send final, normalized facts—not payment credentials.

Production loads limits and consent from the immutable mandate registry. The execution envelope carries final transaction facts and a new account-wide idempotency key that remains stable for one attempt. Bind the same final envelope to every transition. MandateShield never submits or deduplicates the provider charge; the payment gateway must refuse to call the provider unless CONSUME succeeds.

{
  "protocol": "CUSTOM",
  "mandate_id": "mnd_2048",
  "agent_id": "agent_travel_07",
  "merchant_id": "merchant_rail",
  "amount": { "value": 124.90, "currency": "USD" },
  "created_at": "<current ISO-8601 timestamp>",
  "idempotency_key": "checkout_9f22a1",
  "checkout_hash": "sha256:final-checkout-digest",
  "intent_hash": "sha256:..."
}

JAVASCRIPT SDK

Separate authority verification from processor enforcement.

import {
  createClient,
  requireAllowed
} from "./vendor/mandateshield/mandateshield.mjs"

const verifier = createClient({
  apiKey: process.env.MANDATESHIELD_VERIFY_KEY,
  timeoutMs: 10_000
})
const processorGate = createClient({
  apiKey: process.env.MANDATESHIELD_PROCESSOR_KEY,
  timeoutMs: 10_000
})

const challenge = await verifier.createChallenge({
  mandate_id: purchase.mandate_id,
  protocol: purchase.protocol,
  key_thumbprint: process.env.MANDATESHIELD_KEY_THUMBPRINT
})

// Your trusted issuer signs the canonical purchase digest, challenge nonce,
// and the exact issuer/audience returned for the selected account pin.
const evidence = await trustedIssuer.sign(purchase, {
  nonce: challenge.nonce,
  issuer: challenge.issuer,
  audience: challenge.audience,
  issuedAt: new Date().toISOString(),
  expiresAt: challenge.expires_at_iso
})
const reservation = requireAllowed(await verifier.verifyAuthority({
  envelope: purchase,
  evidence
}))
const transitionBase = {
  compact: reservation.signed_receipt.compact,
  expected_envelope: purchase,
  expected_audience: challenge.audience
}

const consumed = await processorGate.transitionExecutionAuthorization({
  ...transitionBase,
  action: "CONSUME",
  idempotency_key: `${purchase.idempotency_key}:consume`
})
if (consumed.transition_state !== "CONSUMED") stopPayment()

const outcome = await submitOnceToPaymentProvider(purchase)
if (outcome.status === "UNKNOWN") {
  await queueProviderReconciliation(outcome.reference)
  throw new Error("Provider outcome requires reconciliation")
}

await processorGate.transitionExecutionAuthorization({
  ...transitionBase,
  action: outcome.status === "COMMITTED" ? "COMMIT" : "RELEASE",
  idempotency_key:
    `${purchase.idempotency_key}:${outcome.status.toLowerCase()}`,
  processor_result: outcome.authenticatedAssertion
})

RAW HTTP

Use the same transition contract in any language.

# VERIFY key: strict ALLOW creates a RESERVED authorization.
curl --fail-with-body \
  https://mandateshield.com/api/v2/verify \
  -X POST \
  -H "content-type: application/json" \
  -H "authorization: Bearer $MANDATESHIELD_VERIFY_KEY" \
  --data @strict-verification.json

# Validate the full ALLOW invariant and state == RESERVED, then:
# PROCESSOR key: consume exact receipt/envelope/audience before submission.
curl --fail-with-body \
  https://mandateshield.com/api/v2/execution-authorizations \
  -X POST \
  -H "content-type: application/json" \
  -H "authorization: Bearer $MANDATESHIELD_PROCESSOR_KEY" \
  --data @consume.json

# Only transition_state == CONSUMED unlocks one provider submission.
# Afterwards POST COMMIT or RELEASE with the bound processor_result.

STRICT CRYPTOGRAPHIC PROFILE

Verify signed authority before evaluating policy.

Caller-supplied public keys prove mathematical consistency, not a trusted identity. A live reservation requires an account-pinned key, followed by a separate processor-side CONSUME transition.

const decision = await client.verifyAuthority({
  envelope: purchase,
  evidence: {
    format: "jws",
    compact: signedAuthority,
    public_key: issuerPublicJwk,
    expected_audience: challenge.audience,
    expected_nonce: challenge.nonce
  }
})

// Confirms a live RESERVED authorization. It does not submit payment.
const reservation = requireAllowed(decision)
await handReservationToTrustedGateway({
  compact: reservation.signed_receipt.compact,
  envelope: purchase,
  audience: challenge.audience
})

STANDALONE CLI

Use the same fail-closed gate in scripts and CI.

The CLI is one executable JavaScript file with no package install. It reads JSON from a file or standard input and never prints the API key.

curl --proto '=https' --tlsv1.2 -fsSLo ./mandateshield \
  https://mandateshield.com/sdk/mandateshield-cli.mjs
chmod 0755 ./mandateshield

export MANDATESHIELD_API_KEY="$MANDATESHIELD_VERIFY_KEY"
./mandateshield challenge challenge.json
./mandateshield verify strict-verification.json

export MANDATESHIELD_API_KEY="$MANDATESHIELD_PROCESSOR_KEY"
./mandateshield transition consume.json
# submit once, then:
./mandateshield transition commit-or-release.json

SAFE AUTOMATION

Branch on the exit status.

VERIFY exit 0 means a consumable reservation exists; it is not a payment submission. The PROCESSOR-only transition command must return CONSUMED before the provider is called. Exit 2 includes sandbox/test ALLOW, REVIEW, BLOCK, caller-supplied trust and partially authorized batches.

Download CLI client →

REMOTE MCP

Call the discovered tool through the JavaScript client.

The server is stateless Streamable HTTP at https://mandateshield.com/api/mcp. It exposes check_ai_payment_authority and verify_cryptographic_payment_authority.

import {
  createClient,
  decisionFromMcp,
  requireAllowed
} from "./vendor/mandateshield/mandateshield.mjs"

const client = createClient({
  apiKey: process.env.MANDATESHIELD_VERIFY_KEY
})

const result = await client.callMcpTool(
  "verify_cryptographic_payment_authority",
  { envelope: purchase, evidence }
)

const decision = decisionFromMcp(result)
const reservation = requireAllowed(decision)
await trustedGateway.consumeBeforeProviderSubmission(reservation)

IMPORTANT BOUNDARY

Tool discovery is not enforcement.

Giving a model the MCP tool helps it plan safely, but prompts cannot authorize money movement. MCP can produce the RESERVED decision, but the model must never receive the PROCESSOR key. A trusted payment tool or gateway must enforce CONSUME before touching the provider and report the terminal outcome.

Inspect MCP server metadata →

OPENAI AGENTS SDK

Attach the remote Streamable HTTP server.

This uses the SDK's published MCPServerStreamableHttp lifecycle. It is framework connectivity, not a MandateShield-specific package.

npm install @openai/agents zod

import {
  Agent,
  MCPServerStreamableHttp,
  run
} from "@openai/agents"

const server = new MCPServerStreamableHttp({
  name: "MandateShield",
  url: "https://mandateshield.com/api/mcp",
  cacheToolsList: true,
  errorFunction: null,
  requestInit: {
    headers: {
      authorization:
        `Bearer ${process.env.MANDATESHIELD_VERIFY_KEY}`
    }
  }
})

await server.connect()
try {
  const agent = new Agent({
    name: "Checkout planner",
    instructions:
      "Use MandateShield to inspect the final purchase. Never claim that a payment executed.",
    mcpServers: [server]
  })
  const result = await run(agent, checkoutRequest)
  console.log(result.finalOutput)
} finally {
  await server.close()
}

OpenAI Agents MCP documentation ↗

VERCEL AI SDK

Convert remote MCP tools into AI SDK tools.

The official HTTP transport discovers both schemas. Close short-lived clients after the model run.

npm install ai @ai-sdk/mcp

import { createMCPClient } from "@ai-sdk/mcp"
import { generateText } from "ai"

const mcp = await createMCPClient({
  transport: {
    type: "http",
    url: "https://mandateshield.com/api/mcp",
    headers: {
      authorization:
        `Bearer ${process.env.MANDATESHIELD_VERIFY_KEY}`
    }
  }
})

try {
  const tools = await mcp.tools()
  const result = await generateText({
    model, // your configured AI SDK model
    tools,
    prompt: checkoutRequest
  })
  console.log(result.text)
} finally {
  await mcp.close()
}

AI SDK MCP documentation ↗

LANGCHAIN JAVASCRIPT

Let the model reserve; keep execution in the gateway.

There is no claimed first-party LangChain adapter here. This is a standard LangChain tool around the public JavaScript client, with the verification gate in the callback. The PROCESSOR key and provider call remain outside the model tool.

npm install langchain zod

import { createAgent, tool } from "langchain"
import * as z from "zod"
import {
  createClient,
  requireAllowed
} from "./vendor/mandateshield/mandateshield.mjs"

const client = createClient({
  apiKey: process.env.MANDATESHIELD_VERIFY_KEY
})

const reservePaymentAuthority = tool(async ({
  purchase,
  evidence,
  processorAudience
}) => {
  const decision = await client.verifyAuthority({
    envelope: purchase,
    evidence: {
      ...evidence,
      expected_audience: processorAudience
    }
  })
  const reservation = requireAllowed(decision)
  // The model receives no PROCESSOR key. A trusted gateway owns
  // CONSUME -> provider submission -> COMMIT/RELEASE.
  await trustedGateway.enqueueReservation({
    compact: reservation.signed_receipt.compact,
    envelope: purchase,
    audience: processorAudience
  })
  return {
    state: "RESERVED",
    expires_at: reservation.execution_authorization.expires_at
  }
}, {
  name: "reserve_payment_authority",
  description:
    "Reserve final purchase authority for a trusted payment gateway.",
  schema: z.object({
    purchase: z.record(z.string(), z.unknown()),
    evidence: z.record(z.string(), z.unknown()),
    processorAudience: z.string().url()
  })
})

const agent = createAgent({
  model, // your configured LangChain model
  tools: [reservePaymentAuthority]
})

LangChain tool documentation ↗

A2A CLIENT

Delegate one structured authority task.

Discover the public agent card, then send the purchase envelope as a structured parts.data value. The helper returns the A2A task; extract its decision artifact before enforcement.

import {
  createClient,
  decisionFromA2aTask,
  requireAllowed
} from "./vendor/mandateshield/mandateshield.mjs"

const client = createClient({
  apiKey: process.env.MANDATESHIELD_VERIFY_KEY
})

const task = await client.sendA2a({ envelope: purchase, evidence })
const decision = decisionFromA2aTask(task)
const reservation = requireAllowed(decision)
await trustedGateway.consumeBeforeProviderSubmission(reservation)

WIRE CONTRACT

JSON-RPC, A2A version 1.0.

This endpoint supports the exact SendMessage method. It does not advertise streaming, legacy method aliases, or push notifications.

Open A2A agent card →

A2A WIRE EXAMPLE

Send the exact final envelope as message data.

POST https://mandateshield.com/a2a
content-type: application/json
a2a-version: 1.0
authorization: Bearer ms_live_...

{
  "jsonrpc": "2.0",
  "id": "checkout_9f22a1",
  "method": "SendMessage",
  "params": {
    "message": {
      "role": "ROLE_USER",
      "messageId": "msg_9f22a1",
      "parts": [{ "data": { "...": "purchase envelope" } }]
    }
  }
}

OPERATING RULES

Four transitions, one enforceable handoff.

VERIFY reserves cumulative headroom. CONSUME succeeds once and is the gateway's only unlock for provider submission. COMMIT records a confirmed success; RELEASE returns the reservation only for confirmed non-submission or failure. processor_result is an assertion authenticated by the PROCESSOR key and exact audience binding, not independent proof signed by the provider. Unknown provider outcomes must be reconciled, never automatically released or retried.

START IN SANDBOX, ENFORCE IN LIVE

Test the policy, then create a live server-side key.

Open dashboard →