Model Context Protocol endpoint
Pay By AI exposes an MCP server at POST https://www.paybymyai.com/api/mcp. Any AI agent that speaks the Model Context Protocol can connect with a budget code and spend against it within the consent rules the user set.
Transport
- Protocol:
JSON-RPC 2.0over HTTPS. - Mode: stateless (one request, one response; no persistent session ID).
- MCP protocol version:
2025-03-26. - Content-Type:
application/json.
Authentication
Send a budget code (internally: agent session bearer) in the Authorization header. The user minted it from the dashboard at /user/budgets and pasted it into your MCP client's config.
Authorization: Bearer pbai_sess_abc123...Browse + checkout tools (get_merchant, search_merchants, search_products, get_product, checkout, get_payment_request) work without a bearer — agents can window-shop and even mint a payment token before a budget is attached. Tools that touch money or user data (pay_payment_request, list_payment_methods, get_current_budget, list_payments) require a valid, non-revoked, non-expired bearer.
Consent enforcement
Every budget carries three limits the server enforces before creating any Stripe PaymentIntent:
Cumulative cents this budget can spend. spending_cap_exceeded if a single payment or running total would exceed it.
Optional list of allowed merchant IDs. merchant_not_allowed if the target merchant isn't in it. Empty list = any merchant on Pay By AI.
expiresAt is an absolute timestamp. Once past, every call returns 401 expired.
POST /api/payment_requests/{token}/pay endpoint exists for the user to pay directly and does not enforce these limits — because the REST caller is the user, not an agent acting for them.Handshake
MCP clients start with initialize to negotiate protocol version and capabilities.
POST /api/mcp
Authorization: Bearer pbai_sess_...
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": { "name": "my-client", "version": "0.1.0" }
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": { "tools": {} },
"serverInfo": { "name": "paybyai", "version": "0.0.0" }
}
}After initialize, the client may send a notifications/initialized notification. We respond 204 No Content and get to work.
List tools
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{ "name": "get_merchant", ... },
{ "name": "search_merchants", ... },
{ "name": "search_products", ... },
{ "name": "get_product", ... },
{ "name": "checkout", ... },
{ "name": "get_payment_request", ... },
{ "name": "pay_payment_request", ... },
{ "name": "list_payment_methods", ... },
{ "name": "get_current_budget", ... },
{ "name": "list_payments", ... }
]
}
}Tool reference
get_merchantPublicLook up a merchant by ID.
{
"id": "string"
}{
"id": "string",
"name": "string",
"stripeAccountId": "string",
"mode": "\"test\" | \"live\""
}search_merchantsPublicSearch merchants by name (case-insensitive substring match).
{
"q": "string (optional)",
"limit": "number (optional, max 100, default 20)"
}Merchant[] — ordered newest first.search_productsPublicBrowse/keyword-search a merchant's catalog. Pay By AI proxies to the merchant's own commerce MCP (their WooCommerce/Shopify plugin) — agents only need to connect to Pay By AI.
{
"merchantId": "string (from search_merchants)",
"q": "string (optional keyword)",
"limit": "number (optional, 1-50, default 20)",
"page": "number (optional, 1-based, default 1)"
}{
products: [{ id, title, price, currency, availableForSale, imageUrl, ... }],
page, limit,
merchantId, merchantName
}get_productPublicFetch a single product's full detail (variants, stock) at a merchant. Proxied to the merchant's commerce MCP.
{
"merchantId": "string",
"productId": "string | number"
}{
id, title, description, type, price, currency,
stockQuantity, availableForSale,
variants: [{ id, attributes, price, availableForSale, stockQuantity }],
imageUrl,
merchantId, merchantName
}checkoutPublicCreate an order at a merchant and mint a Pay By AI payment token — proxied to the merchant's commerce MCP. Agent collects shipping + email from the user and passes them here. Then call pay_payment_request with the returned token.
{
"merchantId": "string",
"items": "Array<{ productId, variantId?, quantity }>",
"email": "string",
"shippingAddress": "{ firstName, lastName, address1, address2?, city, state, country, postcode, phone? }",
"note": "string (optional)"
}{
token: "pr_... — pass to pay_payment_request",
amountCents, currency, description,
merchantId, merchantName
}The merchant creates the order in a pending state — it's not fulfilled until Pay By AI webhooks the merchant payment_request.paid after the token is paid.
get_payment_requestPublicPreview a payment request before paying. The token IS the credential for this tool.
{
"token": "string"
}{
id, merchantId, merchant: { id, name, stripeAccountId },
amountCents, currency, description, status,
merchantMetadata, expiresAt, createdAt
}pay_payment_requestAuth requiredPay a payment request with one of the authenticated user's stored payment methods. Consent-bounded.
{
"token": "string",
"paymentMethodId": "string (one of the user's stored PMs)"
}{
paymentRequestId: "pr_...",
paymentIntentId: "pi_...",
status: Stripe PaymentIntent status (usually "succeeded" or "processing")
}Before dispatching the PaymentIntent we check spending_cap_exceeded and merchant_not_allowed. After success we increment the budget's spent counter. Stripe webhooks handle the eventual settle + merchant notification separately.
list_payment_methodsAuth requiredList the authenticated user's stored payment methods. Default card is first.
{} — no arguments.
PaymentMethod[] — each with { id, brand, last4, expMonth, expYear, isDefault }get_current_budgetAuth requiredReturn this session's own budget — limit, spent, remaining, allowed stores, expiry. Does not reveal the user's other budgets. Use it to answer 'how much can I still spend?' and similar.
{} — no arguments.
{
limitCents: number | null, // null = unlimited
spentCents: number,
remainingCents: number | null, // null when limitCents is null
allowedMerchantIds: string[], // empty = any merchant
expiresAt: string | null, // ISO timestamp or null = no expiry
createdAt: string // ISO timestamp
}spentCents reflects the session state loaded at the start of this MCP request. If a payment happened during the same request, the new spend is recorded asynchronously and may not appear in the response. For nearly all user queries this is fine; if you need exact post-pay accuracy, call get_current_budget again in a subsequent turn.
Calling a tool — full example
curl -X POST https://www.paybymyai.com/api/mcp \
-H "Authorization: Bearer pbai_sess_YOUR_BUDGET_CODE" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "pay_payment_request",
"arguments": {
"token": "pr_kXf7z8...",
"paymentMethodId": "cmpm..."
}
}
}'{
"jsonrpc": "2.0",
"id": 42,
"result": {
"content": [
{
"type": "text",
"text": "{\"paymentRequestId\":\"pr_kXf7z8...\",\"paymentIntentId\":\"pi_...\",\"status\":\"succeeded\"}"
}
]
}
}{
"jsonrpc": "2.0",
"id": 42,
"result": {
"isError": true,
"content": [
{ "type": "text", "text": "spending_cap_exceeded: payment of 2000 cents exceeds remaining cap of 500 cents" }
]
}
}isError: true rather than JSON-RPC protocol errors, so agents can read the message and adapt. Authentication failures (bad / revoked / expired bearer) are returned as HTTP 401.Quick client configs
{
"mcpServers": {
"paybyai": {
"url": "https://www.paybymyai.com/api/mcp",
"headers": { "Authorization": "Bearer pbai_sess_YOUR_CODE" }
}
}
}claude mcp add paybyai \
--transport http \
--url https://www.paybymyai.com/api/mcp \
--header "Authorization=Bearer pbai_sess_YOUR_CODE"import httpx
resp = httpx.post(
"https://www.paybymyai.com/api/mcp",
headers={"Authorization": "Bearer pbai_sess_YOUR_CODE"},
json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
timeout=10,
)
print(resp.json())