# PhotoPrintAgent MCP: complete a print and ship checkout

> An agent can complete a print without a browser if it can: connect to `/mcp`, authenticate (guest x402 or agent key), upload a print-ready image, quote, create order, settle payment, and poll status — all via MCP tools documented below.

**Start with guest + Base USDC. No account or pre-minted API key is required.** MCP coordinates checkout; image bytes travel in a separate authenticated HTTP upload. A signing wallet with approved spending authority is required for x402. Card checkout returns a private URL for the human buyer and requires their browser. Do not collect card numbers, wallet keys, passwords, or guest/account tokens in chat.

This document and the public JSON schemas are generated from the same definitions used to register and validate live MCP tools. `tools/list` is available without authentication and includes full input and output schemas. [JSON contract and upload response schema](/docs/mcp.json). [Discovery](/.well-known/mcp.json). [Markdown](/docs/mcp.md). The older `/agent-guide.md` is a generated copy. Read current availability from `list_products` and `list_payment_assets`; examples are illustrative, not live prices or payment authorizations.

## Image formats and conversion

Upload JPEG (`.jpg`, `.jpeg`, `.jpe`, `.jfif`), PNG/APNG, WebP, GIF, HEIC/HEIF (`.heic`, `.heif`, `.hif`), TIFF (`.tif`, `.tiff`), AVIF, BMP, or self-contained SVG/SVGZ. `create_upload` and `list_products.product.specs` return the authoritative MIME types, extensions, byte/pixel limits, and `imageHandling` policies. Use the matching image Content-Type or `application/octet-stream` if unknown. Upload raw file bytes; do not rename unsupported files to a supported extension.

**One uploaded file produces one still print image.** GIF and animated WebP use the first frame; APNG uses its default still image. TIFF uses the first page. HEIC/HEIF uses the primary image, falling back to the first. AVIF uses the first image. To choose another page/frame, extract and upload it separately. Live Photo video and audio are not printed. Transparency is composited on white; orientation is normalized; metadata is stripped; print output is an 8-bit sRGB JPEG. HDR/wide-gamut appearance can change during conversion. Obtain buyer approval of the resulting still image and crop.

Limits remain **15 MB (15,728,640 bytes)** and **24,000,000 pixels** per image. Every image must decode successfully within processing limits. SVG must be self-contained; scripts, stylesheets, embedded images, and external resources are rejected. Unsupported compression variants, PDF, camera RAW, PSD, JPEG XL and JPEG 2000 need export to JPEG/PNG or another supported format before upload. A corrupt file can still fail despite a recognized extension. Upload responses optionally include `sourceFormat` and `sourceImages`; these are informational, not an instruction to create more orders. See [Photo & ordering help](https://photoprintagent.com/help).

The storefront uses `/api/photo-preview` to show converted stills without saving them. Agents should continue to use the authenticated `/api/photos` endpoint returned by `create_upload`; a preview does not return a printable photo ID.

## Product selection

Call `list_products` before quoting. Its `products` array contains active, configured products; pass the chosen `id` as `productId` to `quote_prints`. Omitting `productId` selects the original `photo-4x6` product for compatibility. Each order contains one product and may contain multiple photos and quantities. The legacy `product.specs` describes the currently supported 4×6 image pipeline. Other product sizes and unimplemented fulfillment connectors remain drafts and cannot accept orders. Prices always come from a live supplier quote; product IDs are catalog IDs, not supplier IDs. Re-quote if the store reports that a product or its routing changed.

## Connection: copy and paste

Canonical endpoint: **https://photoprintagent.com/mcp**. **https://photoprintagent.com/api/mcp** is an equivalent alias, with the same authentication and content negotiation. Transport: stateless **Streamable HTTP**, JSON-RPC 2.0 over POST. GET returns 405 with `Allow: POST`; this server does not maintain an SSE session or require `Mcp-Session-Id`.

Every MCP POST needs:

```http
Content-Type: application/json
Accept: application/json, text/event-stream
```

Both Accept media types are required, even though this server responds with JSON. Missing either returns **HTTP 406**. For protected tools, send `Authorization: Bearer <credential>` or the `guestToken` tool argument. Public discovery and session creation need no Authorization header; omit it entirely rather than sending an empty or fake key. Invalid Bearer credentials return HTTP 401 even for public tools. Never send both Bearer and `guestToken`.

Cursor-style remote HTTP configuration, also usable by clients accepting this `mcpServers` format:

```json
{"mcpServers":{"photoprintagent":{"url":"https://photoprintagent.com/mcp","headers":{"Content-Type":"application/json","Accept":"application/json, text/event-stream"}}}}
```

Claude Code remote HTTP configuration:

```json
{"mcpServers":{"photoprintagent":{"type":"http","url":"https://photoprintagent.com/mcp","headers":{"Content-Type":"application/json","Accept":"application/json, text/event-stream"}}}}
```

These configurations support guest checkout by passing the returned `guestToken` to protected tools. For an existing credential, configure the additional Authorization header using your client's secret/environment facility. Claude Code uses `"Authorization":"Bearer ${PHOTOPRINTAGENT_API_KEY}"`; Cursor uses `"Authorization":"Bearer ${env:PHOTOPRINTAGENT_API_KEY}"`. Verify expansion resolves locally and is never sent literally. Do not commit a real key to a config file or paste one in chat. Some Claude products require a remote-connector UI rather than this JSON; the endpoint and headers are unchanged.

Initialize using the supported protocol revision shown below, then send `notifications/initialized` (HTTP 202, empty response). SDK clients do this automatically:

```json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"print-agent","version":"1.0.0"}}}
```

The response includes `result.protocolVersion`, `result.capabilities.tools`, and `result.serverInfo`. Send the negotiated version in `MCP-Protocol-Version` on later requests. Stateless tools/call examples below are individually executable; no session ID is needed.

```json
{"jsonrpc":"2.0","method":"notifications/initialized"}
```

```json
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
```

## Authentication agents can use

### Primary: guest + x402 (or card handoff)

Call `start_guest_checkout` without Authorization. Its success response returns `token` and the identical `guestApiKey` alias, prefixed `ppg_`. The secret is disclosed only when the session is minted, **before upload**; it is not minted again by create_print_order or get_order. Save it immediately in your secret store, alongside order references. It authorizes uploads, quotes, payment, and get_order for that guest only. No Firebase account or login is created. Use the same credential throughout the order.

New guest checkouts may be created for 24 hours (`checkoutUntil`); order reads remain available for 30 days (`expiresAt`). Both are Unix milliseconds. Separate guests cannot read one another's photos/orders. Guest tokens cannot access admin settings or account-key management. Guest checkout budgets currently allow 20 uploads, 10 quotes, and 3 new orders per session per day, plus shared service limits. HTTP 429 means wait; do not evade limits with new sessions.

The equivalent public mint API is `POST /api/guest-sessions`, JSON body `{}`, without Authorization. A lost token cannot be recovered through public APIs; contact support with the order reference after payment. Do not start a new guest session to retry an uncertain payment.

### Account: machine-readable mint after Firebase login

At [For agents](https://photoprintagent.com/agents), account holders can use **Your agent keys**. Programmatically, obtain an authorized Firebase ID token using Google OAuth/Firebase sign-in or verified email/password login, then call `create_agent_key` with that ID token in the Bearer header, or:

```http
POST /api/agent-keys
Authorization: Bearer <FIREBASE_ID_TOKEN>
Content-Type: application/json

{}
```

Response (201): `{"key":"ppa_<64 hex characters>","id":"<key record id>","expiresAt":<Unix milliseconds>}`. The key is disclosed once, expires after 90 days, and grants the owner's photo and order operations (including quoting and payment). Scopes are fixed, not configurable. Keys cannot mint/revoke other keys or access admin settings. At most 10 active account keys; mint budget 5/day. List metadata with `GET /api/agent-keys`; revoke with a Firebase ID token using `DELETE /api/agent-keys/<id>`, returning `{"ok":true}`. Revocation is immediate for subsequent requests. Store credentials in an environment/secret store, never chat.

For an existing verified email/password account, fetch the **public** web `apiKey` from `/firebase-config.json`, then call Firebase directly using credentials supplied through a secure login flow:

```http
POST https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=<PUBLIC_FIREBASE_WEB_API_KEY>
Content-Type: application/json

{"email":"<ACCOUNT_EMAIL>","password":"<ACCOUNT_PASSWORD>","returnSecureToken":true}
```

Use the returned `idToken` in the mint request. Passwords go only to Firebase, never MCP; unverified email accounts receive 403 from this service. Google accounts use Google/Firebase OAuth, not a Google password. There is no PhotoPrintAgent device-code grant or MCP OAuth authorization-server implementation; guest checkout is the browser-free path when an account ID token is unavailable. `get_checkout_options` and `/api/auth-options` expose these paths as JSON.

## Canonical happy path

The reference below includes complete JSON-RPC requests, success responses, and common tool errors for every tool. Replace all example IDs with actual returned values. Examples assume the guest token is held in the Authorization header; for clients with fixed headers, add `guestToken` to each protected tool's arguments. The binary upload always needs Bearer authentication.

1. **Discover product and create guest access.** Call `list_products`, then `start_guest_checkout` and save the credential. Require `orderingAvailable: true` and `x402Available: true` before planning a crypto checkout. Read `product.specs`; price is `quote_required`, because shipping, quantity, and tax affect the total. There is no advertised fixed per-print price.
2. **Prepare and upload.** For a landscape 4×6 print, prepare 1800×1200 pixels (portrait 1200×1800), ideally sRGB, corresponding to 300 DPI. Call `create_upload` with the desired crop. POST raw image bytes to the returned `uploadUrl` using the returned `X-Photo-Options` and a matching image Content-Type. Save the response `id` as `photoId`. This endpoint is an authenticated upload, not a presigned PUT; no storage is allocated by the helper. There is no base64-in-MCP or arbitrary remote-image-URL input.
3. **Quote the approved photos/address.** Call `quote_prints` with uploaded photo IDs, quantities, shipping, and `consent: true` after permission to share images/address with the print partner. For an office address described as `name, line1, line2?, city, state, postal, country`, map the recipient's confirmed first/last names to `firstName`/`lastName`, and `postal` to `zip`. Include `email` and `phone` in `shipping`. The API does not accept literal `name`/`postal` aliases; do not guess a person's name split or missing contact details. The returned `price` contains subtotal, tax, shipping and total in **integer USD cents**; `lineItems` identifies photos/product/quantities. The provider supplies aggregate pricing, so per-line prices are not fabricated. `expiresAt` and `validUntil` are identical, 15 minutes from quote creation.
4. **Obtain human approval.** Show the photos/crops, quantities, delivery address and complete USD total, including shipping and tax. Obtain explicit approval before creating and paying for every order; `buyerApproved: true` records that requirement. Also obtain approval for the exact crypto amount if choosing a non-default token. A demo price in these docs is never approval.
5. **Create once.** Call `create_print_order` with the quote ID and a newly generated stable UUID as `idempotencyKey`. Keep both for retries of the same intent. Buyer contact and shipping are inherited from the quote. Default method is `x402`; no money moves at creation. It returns an unpaid order and `paymentUrl`; it does **not** return HTTP 402 itself. One quote can create only one order. Do not create another order to recover an ambiguous payment.
6. **Pay the existing order.** Call `pay_print_order` with just `orderId` for default Base USDC. The MCP response is HTTP 200 with `structuredContent.status: "payment_required"`, `httpStatus: 402`, `payment_required`, and `paymentRequiredHeader`; it also includes the PAYMENT-REQUIRED HTTP header. Sign those exact v2 terms with the approved wallet, then retry the same tool/order with `paymentSignature` (base64 JSON). Alternatively POST to `paymentUrl`: this REST path returns actual **HTTP 402**, then accepts `PAYMENT-SIGNATURE`. Both paths execute the same verification, settlement, and fulfillment code. See the signing recipe below.
7. **Poll the same order.** Call `get_order` after payment, on timeouts, and for subsequent status. Poll every 15–30 seconds initially and back off to minutes after lab acceptance. `paymentStatus: paid` confirms captured payment; `fulfillmentStatus: accepted` / `status: processing` means the lab accepted it. Tracking may be empty. This server does not currently emit `printing`, `shipped`, or `delivered`, and does not continuously synchronize carrier events. Do not wait forever for a nonexistent shipped enum or tell the buyer it shipped without tracking evidence.

Binary upload example, using the token already saved securely in the environment (do not run shell tracing):

```sh
curl --fail-with-body 'https://photoprintagent.com/api/photos' \
  -H "Authorization: Bearer $PHOTOPRINTAGENT_API_KEY" \
  -H 'Content-Type: image/jpeg' \
  -H 'X-Photo-Options: {"fit":"cover","orientation":"landscape","x":50,"y":50}' \
  --data-binary @print-ready.jpg
```

Success: HTTP 201, `{"id":"22222222-2222-4222-8222-222222222222","width":1800,"height":1200,"quality":"high","expiresAt":1896048000000}`. These IDs/timestamps are examples. JPEG, PNG/APNG, WebP, GIF, HEIC/HEIF, TIFF, AVIF, BMP and self-contained SVG accepted (see image handling rules above); maximum **15 MiB (15,728,640 bytes)** and 24 million input pixels. Output is sRGB JPEG, metadata stripped after EXIF orientation, resized to the print dimensions. `cover` crops around x/y percentages (default 50/50); `contain` adds white borders. Non-3:2 inputs are processed according to that choice, not automatically rejected. Below 100 DPI after the effective crop is rejected; quality is `high` at 250+ DPI and `reduced` otherwise. For landscape, the minimum usable crop is 600×400 pixels. Files expire after 30 days; active paid fulfillment reserves image access. An expired/unavailable photo blocks payment.

Upload errors are ordinary HTTP JSON: 401 missing/expired Bearer; 400 `{"error":"This photo is too small for a 4×6 print. Choose a larger original."}`; 400 invalid format/crop or >24MP; 413 body over 15 MiB; 429 upload budget. Never put the bytes into tools/call (MCP JSON limit 96 KiB).

## x402 payment: exact signing and retry

The server uses **x402 v2**, `exact`. Do not send v1 `X-PAYMENT`, `maxAmountRequired`, or network `base`; v2 uses `PAYMENT-SIGNATURE`, `amount`, and CAIP-2 network identifiers. An unsigned REST payment POST returns the same challenge JSON in its body and the `PAYMENT-REQUIRED` header encoded as `base64(UTF8(JSON))`. The MCP equivalent exposes the same decoded object as `payment_required`. For signatures, `paymentSignature` is that same encoded header **string**, not the decoded object. For a given call, use the tool argument or the HTTP header, never both.

Default terms are Base mainnet **`eip155:8453`**, Circle USDC contract **`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`**, six decimals. A $5.50 approved total means `amount: "5500000"`. Read `payTo` from the live challenge and verify it against `list_products.x402PayTo` (or the selected token quote); never send funds to documentation example addresses. Default timeout is 300 seconds, additionally bounded by the order's 15-minute payment deadline. USDC uses EIP-3009 with EIP-712 domain **name `USD Coin`, version `2`, chainId `8453`, verifyingContract the USDC contract**. Let the x402 SDK construct/sign the typed authorization; do not manually transfer USDC and expect an order to become paid.

The decoded signed v2 envelope has this shape (all example placeholders must be replaced by SDK output):

```json
{"x402Version":2,"resource":{"url":"https://photoprintagent.com/api/orders/<ORDER_ID>/pay","description":"4×6 photo print order <ORDER_ID>","mimeType":"application/json"},"accepted":{"scheme":"exact","network":"eip155:8453","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","amount":"5500000","payTo":"<LIVE_MERCHANT_ADDRESS>","maxTimeoutSeconds":300,"extra":{"name":"USD Coin","version":"2"}},"payload":{"signature":"<EIP712_SIGNATURE>","authorization":{"from":"<BUYER_WALLET>","to":"<LIVE_MERCHANT_ADDRESS>","value":"5500000","validAfter":"<UNIX_SECONDS>","validBefore":"<UNIX_SECONDS>","nonce":"<RANDOM_32_BYTE_HEX>"}}}
```

Use the exact returned `resource` and `accepted` terms. Monetary API prices are cents, token amounts are atomic-unit strings, API expiry times are milliseconds, and EIP-3009 validity times are **seconds**. The server verifies authorization and settles through its facilitator; the agent does not need merchant facilitator credentials or call /settle itself. One claimed authorization cannot fund multiple orders. Successful settlement returns `PAYMENT-RESPONSE` (base64 JSON with `success`, `transaction`, `network`, possibly `payer`), and MCP also returns `paymentResponse` and `paymentResponseHeader`. Read the order even after success: payment may succeed while print submission needs review. A repeat payment call after processing returns the order, without a fresh settlement header and without another charge.

### JavaScript signing recipe (MCP)

The installed server/client integration uses x402 2.25.x. Use `@x402/core/client` and `@x402/evm/exact/client` from a compatible x402 v2 release. The following runs **only after buyer approval**, with an authorized signer implementing `address` and `signTypedData` (for example viem or a managed wallet signer). Obtain the signer through your wallet/secret provider; never ask for its private key in chat. `callTool` is your authenticated MCP client's tool-call function, `order` is create_print_order's returned order, and `products` is list_products's response.

```js
import { x402Client } from '@x402/core/client';
import { ExactEvmScheme } from '@x402/evm/exact/client';

const challengeResult = await callTool('pay_print_order', { orderId: order.id });
if (challengeResult.isError) throw new Error('Read get_order before retrying');
const challenge = challengeResult.structuredContent;
if (challenge.status !== 'payment_required') throw new Error('Read returned order status');
const required = challenge.payment_required;
const client = new x402Client()
  .register('eip155:8453', new ExactEvmScheme(authorizedSigner))
  .setSpendControls({ maxAmountPerPayment: '$' + (order.price.total / 100).toFixed(2) });
// The SDK default spend ceiling can be below a print order; set an exact approved cap.
client.onBeforePaymentCreation(async ({ selectedRequirements: r }) => {
  if (r.scheme !== 'exact' || r.network !== 'eip155:8453' ||
      r.asset.toLowerCase() !== '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' ||
      r.payTo.toLowerCase() !== products.x402PayTo.toLowerCase() ||
      r.amount !== (BigInt(order.price.total) * 10000n).toString() ||
      r.extra?.name !== 'USD Coin' || r.extra?.version !== '2') {
    throw new Error('Challenge does not match approved checkout');
  }
});
const payload = await client.createPaymentPayload(required);
const paymentSignature = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64');
// Do not log the authorization. Send once; on timeout read get_order, not a new payment.
const settled = await callTool('pay_print_order', { orderId: order.id, paymentSignature });
const latest = await callTool('get_order', { orderId: order.id });
```

### REST equivalent and other tokens/networks

Unsigned: `POST /api/orders/<id>/pay`, Bearer and JSON `{}` → HTTP 402, PAYMENT-REQUIRED plus challenge body. Signed retry: same URL/Bearer/body plus `PAYMENT-SIGNATURE: <base64 payload>` → HTTP 200 and PAYMENT-RESPONSE on new settlement. MCP pay_print_order always keeps the JSON-RPC response at HTTP 200 for tool results, including the structured challenge. Transport/auth failures still use their HTTP error status.

Call `list_payment_assets` to choose another asset, then `quote_crypto_payment` with `orderId` and a returned `tokenId`. It locks the server-calculated atomic amount for **60 seconds**, never past the order's payment deadline. Include `cryptoQuoteId` in both unsigned and signed pay_print_order arguments (or both REST JSON bodies). Validate asset, amount, network and recipient against this quote before signing. Never reprice while signing. Refresh an expired token quote only when the same order is confirmed unpaid; obtain fresh approval of the new amount.

Solana challenges retain the original facilitator fee payer, blockhash, and payment terms for that token quote. Request the unsigned challenge before signing, then echo its complete `accepted` object unchanged. Do not reconstruct the terms or substitute a fee payer from another challenge. A new token quote needs a new challenge and signature. If payment is ambiguous, read the existing order before switching networks or attempting another payment.

Base ERC-20 tokens use EIP-3009 (USDC) or Permit2. There is **no top-100 / market-cap restriction**. The server catalog includes CoinGecko-listed Base tokens and Solana mints, with stable IDs for existing tokens. Newly discovered IDs use `base:<contract>` or `solana:<mint>`; always use the returned `id` as `tokenId`. Catalog entries are candidates: an authoritative crypto quote requires a verified on-chain contract/mint, matching decimals, a fresh positive token-address USD price, and a compatible receiving account. Catalog presence does not guarantee settlement; restricted tokens can be rejected by the facilitator. Native ETH, BTC and SOL require their supported wrapped token representation. No automatic wrapping, bridging or conversion to USDC occurs; the merchant keeps the selected token.

The catalog refreshes daily at 3:00 AM America/New_York. `catalogUpdatedAt`, `catalogSource` (`bundled` or `refreshed`) and `catalogStale` identify its freshness; a feed or storage failure retains the last good snapshot. New entries become discovery candidates, not automatic payment approvals. Each listed asset reports `priceChecked: false` and `availability: quote_required` for Base or `receiving_ready` for verified Solana receiving accounts. Both require `quote_crypto_payment` before payment.

`list_payment_assets` accepts optional `search` (name, symbol, or exact address), `network`, `offset` and `limit` (default 50, maximum 100). Continue with the returned `nextOffset` until null. REST uses the same fields as query parameters on `/api/payment-assets`. `priceStatus: available` means discovery is available; **fresh price availability is checked by quote_crypto_payment**, not by listing. A stale or missing price rejects the quote with 503. `solanaPayTo` advertises the configured merchant wallet; each Solana asset appears only after its receiving account is checked.

```json
{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"list_payment_assets","arguments":{"network":"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp","search":"USDC","limit":50}}}
```

For Base Permit2, a separate bounded token approval and Base ETH for gas may be required. Cap approval at the approved amount, never unlimited. Fresh USD pricing is rounded up to atomic units; the amount is locked for 60 seconds. Newly cataloged Base token settlement also checks the transaction's net token Transfer events into the merchant wallet. A transfer-tax shortfall or unverifiable receipt goes to needs_review, preserving the transaction ID and preventing another charge or automatic fulfillment.

Solana mainnet is **`solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`**. Both standard SPL Token and compatible Token-2022 mints are supported. Existing IDs include `solana-usdc` (mint `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`, six decimals) and `solana-wsol` (mint `So11111111111111111111111111111111111111112`, nine decimals). Other mints are discovered from the catalog. Tokens with transfer fees, transfer hooks, confidential transfers or other extensions that alter exact transfer behavior are unavailable. Metadata-only extensions are allowed.

Use only Solana assets returned by `list_payment_assets`; `solanaAvailable` alone does not prove their receiving accounts exist. Use the x402 v2 Solana exact client and transaction signer, preserve the returned feePayer/terms, and compare case-sensitive mint/recipient with the quote. Its payload is a signed SPL or Token-2022 transaction. WSOL must already be wrapped. The merchant initializes receiving accounts in **Admin → Payments → Solana receiving accounts**, selecting up to six tokens per wallet-approved setup. Existing compatible associated accounts are discovered automatically; setup never imports a wallet key or signs on the server. Base receipts use `https://basescan.org/tx/<transaction>`; Solana signatures use `https://solscan.io/tx/<transaction>`.

`x402Available: false` means merchant setup/live billing is incomplete or disabled. Expect 503 rather than a usable payment challenge. There is no separate x402 order minimum; the print quote must have a positive total and may not exceed **$1,000 USD**. Provider quantity/shipping pricing determines the actual minimum payable order (one print minimum).

### Card path exposed by MCP

Apple Pay and Google Pay use `method: "card"` and the same private buyer checkout URL. New card and wallet orders use Square hosted checkout. They are browser wallet options, not additional MCP payment methods or crypto tokens. Buttons appear only when supported by the buyer's device and the payment provider's domain/account configuration. A wallet still requires the human buyer's approval; do not collect wallet credentials or try to authorize it through MCP.

Call create_print_order with `method: "card"`; give the human the returned private **checkoutUrl**, retaining its `#access=ppc_…` fragment. This is the specific order's checkout, not a generic instruction to browse the site. The buyer can open it without an account, review the total/address, and continue to Square. The server verifies the stored Square order, location, exact USD total, completed payment, and lack of refunds before submitting fulfillment via Printicular external billing. Agents can subsequently call get_order; REST `POST /api/orders/<id>/confirm` with their credential also verifies that exact stored payment. Historical Stripe orders retain their original verification path. A client assertion that payment succeeded is never sufficient. Never collect card numbers through MCP. Card completion is a human browser handoff, not autonomous headless card charging.

## Address, fulfillment and recovery

US destinations only (`country: "US"`). Required: firstName, lastName, email, phone, line1, city, uppercase two-letter state, and zip (five digits or ZIP+4); line2 defaults to empty. Names/address lines/city max 150 characters, email max 254. Phone punctuation is removed, optional leading US 1 removed, then exactly 10 digits required. The schema checks syntax; provider quote validation checks delivery serviceability. Two letters alone are not a guarantee a state/destination is deliverable. PO boxes are not categorically rejected locally, but **delivery depends on the provider's selected service accepting that address**; do not promise PO box eligibility without a successful quote. There is no guaranteed production or transit SLA exposed by this API. No estimated arrival date is returned.

| status | paymentStatus / fulfillmentStatus | Meaning / action |
| --- | --- | --- |
| creating_checkout | unpaid / not_submitted | Card checkout is being prepared. Poll existing order. |
| awaiting_payment | unpaid / not_submitted | Unpaid; payment allowed only before its deadline. |
| verifying_payment | pending / not_submitted | Authorization validation in progress. Poll; do not sign again. |
| settling_payment | pending / not_submitted | Settlement in progress or unresolved. Poll; do not pay again. |
| paid | paid / not_submitted | Payment captured; print submission pending. |
| submitting | paid / submitting | Sending to the lab; never recreate the order. |
| processing | paid / accepted | Lab accepted. This is not shipment confirmation. |
| needs_review | paid or needs_review / needs_review | `paidAt` confirms known payment; absent paidAt can mean unknown outcome, not safe-to-repay. Support investigates payment/lab state. |
| expired | expired / not_submitted | Unpaid checkout deadline passed. Check the stored order before considering a new approved quote. |

`paidAt`, `transaction`, and `network` are nullable. `trackingUrls` is an array of HTTPS links from the print partner and may stay empty; no fabricated carrier, tracking number, ETA, shippedAt, or delivery state is returned. Current automatic reconciliation retries known paid fulfillment safely and flags ambiguous outcomes; it does not poll carrier tracking continuously. For unresolved paid orders contact **support@photoprintagent.com** with the order reference. Do not send credentials or signatures to support.

## Errors agents encounter

MCP tool execution failures return HTTP 200, `result.isError: true`, and `structuredContent.error: {httpStatus, message}` plus the same JSON as text. Schema validation failures are also isError but may have text only (SDK validation occurs before the handler); check isError before interpreting success content. A payment challenge is a normal tool result, **not** isError. Top-level transport/auth failures are HTTP errors; JSON-RPC protocol errors use the standard `error` envelope. Never assume HTTP 200 means a paid or successful order.

| Failure | Where / response | Safe action |
| --- | --- | --- |
| Missing/invalid Bearer | REST 401; invalid MCP header 401; protected MCP tool without credential has isError, error.httpStatus 401 | Start guest checkout without Authorization, then reuse its token. Refresh authorized account login for expired account credentials. |
| Incomplete Accept | MCP HTTP 406 | Send both `application/json, text/event-stream`, including on initialize/tools/list. |
| Wrong Content-Type | MCP HTTP 415, or JSON parsing 400 | Send `application/json`; upload uses a supported image MIME type or `application/octet-stream` instead. |
| SPA HTML on discovery/docs | Wrong deployment/proxy routing | `/.well-known/mcp.json` and `/docs/mcp.json` must parse as JSON; `/docs/mcp` and `.md` must be Markdown. Do not parse index.html or fabricate discovery. These paths are explicitly routed before SPA fallback. |
| Bad address, missing consent/contact, quantity | quote_prints isError (400 or SDK input error); REST 400 | Correct required fields; unique photo IDs, 1–99 each, 50 distinct photos, at most 500 prints. Never guess contact/address details. |
| Image too small / wrong aspect | Upload 400 for too small; aspect is cropped/padded according to chosen fit | Supply a larger original and explicitly choose crop or white borders. |
| Photo missing/foreign/expired | 404 or 410 | Upload under the same credential; never pay for unavailable photos. |
| Quote expired/settings or price changed | create_print_order 409; payment deadline 410; changed terms 409 | Read existing order first. Requote only an unpaid unambiguous purchase; request approval again. |
| Token quote expired | pay_print_order/REST 409 or 410 | If unpaid, refresh the token quote and approve the new amount; never reuse its old signed terms. |
| Insufficient USDC / rejected authorization | Wallet signing may reject before transmission; server verify rejection 402 | Check wallet balance on the quoted network/asset. Read get_order first; only if definitively awaiting_payment may a corrected fresh authorization be attempted. A claimed rejected nonce cannot be reused. |
| Duplicate authorization | 409 | Poll the original order; do not try the same authorization on a new order. |
| Settlement failed/timed out | 409 or generic 500; order may be needs_review | Treat outcome as ambiguous. Poll/get support; do not recharge, resettle blindly, or resubmit to the lab. |
| Service unavailable | 503 | Check availability and retry discovery later. No fake payment success. |
| Rate limit | 429 (REST Retry-After: 60; MCP uses structured error status) | Back off at least 60 seconds; longer daily budgets need a later window. |

## Protocol references

- [MCP Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports)
- [x402 v2 facilitator settlement](https://docs.cdp.coinbase.com/api-reference/v2/rest-api/x402-facilitator/settle-payment)
- [x402 troubleshooting](https://docs.cdp.coinbase.com/x402/support/troubleshooting)
- [Claude Code MCP configuration](https://code.claude.com/docs/en/mcp)
- [Cursor MCP configuration](https://prod.cursor.com/docs/mcp)
- [Firebase REST authentication](https://firebase.google.com/docs/reference/rest/auth)

The live service contract below takes precedence over general protocol examples. API prices, availability and delivery promises must come from actual responses.

## Coupons and complimentary orders

Pass optional `couponCode` to `quote_prints` with the ordinary photo/address/consent payload. Codes ignore case, spaces, and hyphens. The returned `price` includes the original print/shipping/tax components, `originalTotal`, `discount`, and `coupon` breakdown. All amounts are integer USD cents; `price.total` is the only buyer amount to approve and pay. Only one coupon applies per order. Taxes remain the provider quote; optional tax credit is a merchant subsidy, not a tax recalculation.

For example, add `"couponCode":"WELCOME10"` to the documented quote request (example code only; it is not preactivated). A 10% print discount on a 100-cent subtotal with 400-cent shipping and 50-cent tax returns `originalTotal:550`, `discount:10`, `total:540`. Approve 540 cents and call `create_print_order` with that quote ID. You may repeat `couponCode` in create_print_order as an assertion; a different code returns 409 and requires a fresh quote and approval. Neither order creation nor payment can silently change an approved discount.

Positive totals: all new card orders, including discounted orders, use Square and return a private `checkoutUrl` with the hosted checkout handoff. If Square credentials or Printicular external billing are unavailable, card creation returns 503 without falling back to Stripe. Poll `get_order` after the buyer pays. Base and Solana x402 quotes/challenges use the same discounted amount. Follow the normal signature retry recipe, preserving the order ID and crypto quote ID.

Zero totals: `create_print_order` still requires `buyerApproved:true`. It reserves the coupon, verifies photos and current provider cost, records paymentStatus `not_required`, and submits fulfillment once. Do not request a card payment, crypto quote, or payment signature for a zero-total order. Reuse the same idempotency key on timeout, then call get_order. `processing` means the lab accepted the print; it does not mean shipped.

Coupons may have start/end dates, minimum print subtotal, usage/per-email limits and promotion budgets. 400 means invalid, inactive, not started, expired or ineligible; 409 means capacity exhausted, changed coupon/quote, or coupon mismatch. Remove the code or get a fresh eligible quote and seek buyer approval of the new total. Quotes do not reserve usage; order creation atomically does. Ambiguous payments retain capacity until resolved. Admin coupon endpoints require a verified allowlisted admin and are not exposed as public MCP tools.

Optional `campaignId` on quote_prints attributes an order to an active campaign. It never changes the price by itself; include the campaign's couponCode explicitly.

## Complete tool reference (server 1.7.0)

Each success object appears in structuredContent and as JSON in content[0].text for older clients. The following requests/responses are complete illustrative wire messages. Credentials, IDs, timestamps, addresses, rankings and prices here are examples; never pay them. Common errors are examples of tool execution errors; SDK input validation may use text only.

### list_products

Public active product catalog, print specifications, and availability. Pass a returned products[].id as productId when quoting; omission selects the original photo-4x6 product. Price depends on the items and destination; call quote_prints for an authoritative total.

Input JSON Schema:

```json
{
  "type": "object",
  "properties": {},
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output JSON Schema (structuredContent):

```json
{
  "type": "object",
  "properties": {
    "product": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "const": "photo-4x6"
        },
        "title": {
          "type": "string"
        },
        "countries": {
          "type": "array",
          "items": {
            "type": "string",
            "const": "US"
          }
        },
        "maxPhotos": {
          "type": "integer"
        },
        "maxPrints": {
          "type": "integer"
        },
        "specs": {
          "type": "object",
          "properties": {
            "widthInches": {
              "type": "number"
            },
            "heightInches": {
              "type": "number"
            },
            "landscapePixels": {
              "type": "array",
              "items": {
                "type": "number"
              }
            },
            "portraitPixels": {
              "type": "array",
              "items": {
                "type": "number"
              }
            },
            "recommendedDpi": {
              "type": "number"
            },
            "minimumDpi": {
              "type": "number"
            },
            "colorSpace": {
              "type": "string",
              "const": "sRGB"
            },
            "outputFormat": {
              "type": "string",
              "const": "image/jpeg"
            },
            "maxFileBytes": {
              "type": "number"
            },
            "maxInputPixels": {
              "type": "number"
            },
            "acceptedMimeTypes": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "acceptedExtensions": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "imageHandling": {
              "type": "object",
              "properties": {
                "animationPolicy": {
                  "type": "string"
                },
                "multipagePolicy": {
                  "type": "string"
                },
                "transparencyPolicy": {
                  "type": "string"
                },
                "colorPolicy": {
                  "type": "string"
                },
                "svgPolicy": {
                  "type": "string"
                },
                "unsupportedPolicy": {
                  "type": "string"
                },
                "helpUrl": {
                  "type": "string"
                }
              },
              "required": [
                "animationPolicy",
                "multipagePolicy",
                "transparencyPolicy",
                "colorPolicy",
                "svgPolicy",
                "unsupportedPolicy",
                "helpUrl"
              ],
              "additionalProperties": false
            },
            "aspectRatio": {
              "type": "string"
            },
            "cropRules": {
              "type": "string"
            },
            "priceMode": {
              "type": "string",
              "const": "quote_required"
            }
          },
          "required": [
            "widthInches",
            "heightInches",
            "landscapePixels",
            "portraitPixels",
            "recommendedDpi",
            "minimumDpi",
            "colorSpace",
            "outputFormat",
            "maxFileBytes",
            "maxInputPixels",
            "acceptedMimeTypes",
            "acceptedExtensions",
            "imageHandling",
            "aspectRatio",
            "cropRules",
            "priceMode"
          ],
          "additionalProperties": false
        }
      },
      "required": [
        "id",
        "title",
        "countries",
        "maxPhotos",
        "maxPrints",
        "specs"
      ],
      "additionalProperties": false
    },
    "products": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "widthInches": {
            "type": "number"
          },
          "heightInches": {
            "type": "number"
          }
        },
        "required": [
          "id",
          "title",
          "description",
          "widthInches",
          "heightInches"
        ],
        "additionalProperties": false
      }
    },
    "orderingAvailable": {
      "type": "boolean"
    },
    "googleEnabled": {
      "type": "boolean"
    },
    "xEnabled": {
      "type": "boolean"
    },
    "x402Available": {
      "type": "boolean"
    },
    "paymentNetwork": {
      "type": "string"
    },
    "solanaAvailable": {
      "type": "boolean"
    },
    "x402PayTo": {
      "type": [
        "string",
        "null"
      ]
    },
    "support": {
      "type": "string"
    }
  },
  "required": [
    "product",
    "orderingAvailable",
    "googleEnabled",
    "xEnabled",
    "x402Available",
    "paymentNetwork",
    "solanaAvailable",
    "x402PayTo",
    "support"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "list_products",
    "arguments": {}
  }
}
```

Success response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"products\":[{\"id\":\"photo-4x6\",\"title\":\"Classic 4 × 6 photo prints\",\"description\":\"Your photos printed for home delivery.\",\"widthInches\":6,\"heightInches\":4}],\"product\":{\"id\":\"photo-4x6\",\"title\":\"4 × 6 inch photo prints\",\"countries\":[\"US\"],\"maxPhotos\":50,\"maxPrints\":500,\"specs\":{\"widthInches\":6,\"heightInches\":4,\"landscapePixels\":[1800,1200],\"portraitPixels\":[1200,1800],\"recommendedDpi\":300,\"minimumDpi\":100,\"colorSpace\":\"sRGB\",\"outputFormat\":\"image/jpeg\",\"maxFileBytes\":15728640,\"maxInputPixels\":24000000,\"acceptedMimeTypes\":[\"image/jpeg\",\"image/jpg\",\"image/pjpeg\",\"image/png\",\"image/apng\",\"image/webp\",\"image/gif\",\"image/heic\",\"image/heif\",\"image/heic-sequence\",\"image/heif-sequence\",\"image/tiff\",\"image/x-tiff\",\"image/avif\",\"image/avif-sequence\",\"image/bmp\",\"image/x-bmp\",\"image/x-ms-bmp\",\"image/svg+xml\"],\"acceptedExtensions\":[\".jpg\",\".jpeg\",\".jpe\",\".jfif\",\".png\",\".apng\",\".webp\",\".gif\",\".heic\",\".heif\",\".hif\",\".tif\",\".tiff\",\".avif\",\".bmp\",\".svg\",\".svgz\"],\"imageHandling\":{\"animationPolicy\":\"Print the first frame only; APNG uses its default still image.\",\"multipagePolicy\":\"Print the first TIFF page; HEIC/HEIF uses the primary image, falling back to the first.\",\"transparencyPolicy\":\"Composite transparency on white.\",\"colorPolicy\":\"Convert to an 8-bit sRGB JPEG and strip metadata. HDR and wide-gamut appearance may change.\",\"svgPolicy\":\"Self-contained SVG only; no scripts, external resources, embedded images, or stylesheets.\",\"unsupportedPolicy\":\"PDF, camera RAW, PSD, JPEG XL, JPEG 2000, and other unsupported encodings must be exported to a supported format first.\",\"helpUrl\":\"https://photoprintagent.com/help\"},\"aspectRatio\":\"3:2 landscape or 2:3 portrait\",\"cropRules\":\"Only a still image is printed. Transparency is composited on white. cover crops at x/y percent (default center); contain adds white borders. EXIF orientation is applied. Metadata is stripped.\",\"priceMode\":\"quote_required\"}},\"orderingAvailable\":true,\"googleEnabled\":true,\"xEnabled\":false,\"x402Available\":true,\"paymentNetwork\":\"Base and Solana\",\"solanaAvailable\":false,\"x402PayTo\":\"0x1111111111111111111111111111111111111111\",\"support\":\"support@photoprintagent.com\"}"
      }
    ],
    "structuredContent": {
      "products": [
        {
          "id": "photo-4x6",
          "title": "Classic 4 × 6 photo prints",
          "description": "Your photos printed for home delivery.",
          "widthInches": 6,
          "heightInches": 4
        }
      ],
      "product": {
        "id": "photo-4x6",
        "title": "4 × 6 inch photo prints",
        "countries": [
          "US"
        ],
        "maxPhotos": 50,
        "maxPrints": 500,
        "specs": {
          "widthInches": 6,
          "heightInches": 4,
          "landscapePixels": [
            1800,
            1200
          ],
          "portraitPixels": [
            1200,
            1800
          ],
          "recommendedDpi": 300,
          "minimumDpi": 100,
          "colorSpace": "sRGB",
          "outputFormat": "image/jpeg",
          "maxFileBytes": 15728640,
          "maxInputPixels": 24000000,
          "acceptedMimeTypes": [
            "image/jpeg",
            "image/jpg",
            "image/pjpeg",
            "image/png",
            "image/apng",
            "image/webp",
            "image/gif",
            "image/heic",
            "image/heif",
            "image/heic-sequence",
            "image/heif-sequence",
            "image/tiff",
            "image/x-tiff",
            "image/avif",
            "image/avif-sequence",
            "image/bmp",
            "image/x-bmp",
            "image/x-ms-bmp",
            "image/svg+xml"
          ],
          "acceptedExtensions": [
            ".jpg",
            ".jpeg",
            ".jpe",
            ".jfif",
            ".png",
            ".apng",
            ".webp",
            ".gif",
            ".heic",
            ".heif",
            ".hif",
            ".tif",
            ".tiff",
            ".avif",
            ".bmp",
            ".svg",
            ".svgz"
          ],
          "imageHandling": {
            "animationPolicy": "Print the first frame only; APNG uses its default still image.",
            "multipagePolicy": "Print the first TIFF page; HEIC/HEIF uses the primary image, falling back to the first.",
            "transparencyPolicy": "Composite transparency on white.",
            "colorPolicy": "Convert to an 8-bit sRGB JPEG and strip metadata. HDR and wide-gamut appearance may change.",
            "svgPolicy": "Self-contained SVG only; no scripts, external resources, embedded images, or stylesheets.",
            "unsupportedPolicy": "PDF, camera RAW, PSD, JPEG XL, JPEG 2000, and other unsupported encodings must be exported to a supported format first.",
            "helpUrl": "https://photoprintagent.com/help"
          },
          "aspectRatio": "3:2 landscape or 2:3 portrait",
          "cropRules": "Only a still image is printed. Transparency is composited on white. cover crops at x/y percent (default center); contain adds white borders. EXIF orientation is applied. Metadata is stripped.",
          "priceMode": "quote_required"
        }
      },
      "orderingAvailable": true,
      "googleEnabled": true,
      "xEnabled": false,
      "x402Available": true,
      "paymentNetwork": "Base and Solana",
      "solanaAvailable": false,
      "x402PayTo": "0x1111111111111111111111111111111111111111",
      "support": "support@photoprintagent.com"
    }
  }
}
```

Common error response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"error\":{\"httpStatus\":503,\"message\":\"Service temporarily unavailable.\"}}"
      }
    ],
    "structuredContent": {
      "error": {
        "httpStatus": 503,
        "message": "Service temporarily unavailable."
      }
    },
    "isError": true
  }
}
```

### start_guest_checkout

Public: mint a private guest credential once. No account, payment or print is created. Save it in a secret store.

Input JSON Schema:

```json
{
  "type": "object",
  "properties": {},
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output JSON Schema (structuredContent):

```json
{
  "type": "object",
  "properties": {
    "token": {
      "type": "string"
    },
    "guestApiKey": {
      "type": "string"
    },
    "checkoutUntil": {
      "type": "integer",
      "minimum": 0,
      "description": "Unix time in milliseconds"
    },
    "expiresAt": {
      "$ref": "#/properties/checkoutUntil"
    },
    "tokenType": {
      "type": "string",
      "const": "Bearer"
    },
    "instructions": {
      "type": "string"
    }
  },
  "required": [
    "token",
    "guestApiKey",
    "checkoutUntil",
    "expiresAt",
    "tokenType",
    "instructions"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "start_guest_checkout",
    "arguments": {}
  }
}
```

Success response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"token\":\"ppg_0000000000000000000000000000000000000000000000000000000000000000\",\"guestApiKey\":\"ppg_0000000000000000000000000000000000000000000000000000000000000000\",\"checkoutUntil\":1893542400000,\"expiresAt\":1896048000000,\"tokenType\":\"Bearer\",\"instructions\":\"Keep this private guest token. Use it as Authorization: Bearer on API/MCP requests, or as guestToken in MCP tools. Create checkouts within 24 hours; order access lasts 30 days. No account is created.\"}"
      }
    ],
    "structuredContent": {
      "token": "ppg_0000000000000000000000000000000000000000000000000000000000000000",
      "guestApiKey": "ppg_0000000000000000000000000000000000000000000000000000000000000000",
      "checkoutUntil": 1893542400000,
      "expiresAt": 1896048000000,
      "tokenType": "Bearer",
      "instructions": "Keep this private guest token. Use it as Authorization: Bearer on API/MCP requests, or as guestToken in MCP tools. Create checkouts within 24 hours; order access lasts 30 days. No account is created."
    }
  }
}
```

Common error response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"error\":{\"httpStatus\":400,\"message\":\"You already have checkout access. Use the existing credential.\"}}"
      }
    ],
    "structuredContent": {
      "error": {
        "httpStatus": 400,
        "message": "You already have checkout access. Use the existing credential."
      }
    },
    "isError": true
  }
}
```

### create_upload

Get the authenticated binary upload endpoint and crop headers. POST the image bytes separately; never put base64 or remote image URLs in MCP. No storage is allocated until upload. JPEG, PNG/APNG, WebP, GIF, HEIC/HEIF, TIFF, AVIF, BMP and self-contained SVG are supported. Read acceptedMimeTypes, acceptedExtensions and imageHandling for frame/page selection, transparency, and limits. Use application/octet-stream when MIME type is unavailable. Unsupported encodings must be converted before upload.

Input JSON Schema:

```json
{
  "type": "object",
  "properties": {
    "crop": {
      "type": "object",
      "properties": {
        "fit": {
          "type": "string",
          "enum": [
            "cover",
            "contain"
          ],
          "default": "cover"
        },
        "orientation": {
          "type": "string",
          "enum": [
            "landscape",
            "portrait"
          ],
          "default": "landscape"
        },
        "x": {
          "type": "number",
          "minimum": 0,
          "maximum": 100,
          "default": 50
        },
        "y": {
          "type": "number",
          "minimum": 0,
          "maximum": 100,
          "default": 50
        }
      },
      "additionalProperties": false
    },
    "guestToken": {
      "type": "string",
      "pattern": "^ppg_[a-f0-9]{64}$",
      "description": "Private guest credential; omit when Authorization Bearer is present."
    }
  },
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output JSON Schema (structuredContent):

```json
{
  "type": "object",
  "properties": {
    "uploadUrl": {
      "type": "string",
      "format": "uri"
    },
    "method": {
      "type": "string",
      "const": "POST"
    },
    "authorization": {
      "type": "string",
      "const": "Bearer: use your existing account or guest credential"
    },
    "headers": {
      "type": "object",
      "properties": {
        "X-Photo-Options": {
          "type": "string"
        }
      },
      "required": [
        "X-Photo-Options"
      ],
      "additionalProperties": false
    },
    "acceptedMimeTypes": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "acceptedExtensions": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "imageHandling": {
      "type": "object",
      "properties": {
        "animationPolicy": {
          "type": "string"
        },
        "multipagePolicy": {
          "type": "string"
        },
        "transparencyPolicy": {
          "type": "string"
        },
        "colorPolicy": {
          "type": "string"
        },
        "svgPolicy": {
          "type": "string"
        },
        "unsupportedPolicy": {
          "type": "string"
        },
        "helpUrl": {
          "type": "string"
        }
      },
      "required": [
        "animationPolicy",
        "multipagePolicy",
        "transparencyPolicy",
        "colorPolicy",
        "svgPolicy",
        "unsupportedPolicy",
        "helpUrl"
      ],
      "additionalProperties": false
    },
    "maxFileBytes": {
      "type": "integer"
    },
    "maxInputPixels": {
      "type": "integer"
    },
    "helpUrl": {
      "type": "string",
      "format": "uri"
    },
    "responseSchemaUrl": {
      "type": "string",
      "format": "uri"
    }
  },
  "required": [
    "uploadUrl",
    "method",
    "authorization",
    "headers",
    "acceptedMimeTypes",
    "acceptedExtensions",
    "imageHandling",
    "maxFileBytes",
    "maxInputPixels",
    "helpUrl",
    "responseSchemaUrl"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "create_upload",
    "arguments": {
      "crop": {
        "fit": "cover",
        "orientation": "landscape",
        "x": 50,
        "y": 50
      }
    }
  }
}
```

Success response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"uploadUrl\":\"https://photoprintagent.com/api/photos\",\"method\":\"POST\",\"authorization\":\"Bearer: use your existing account or guest credential\",\"headers\":{\"X-Photo-Options\":\"{\\\"fit\\\":\\\"cover\\\",\\\"orientation\\\":\\\"landscape\\\",\\\"x\\\":50,\\\"y\\\":50}\"},\"acceptedMimeTypes\":[\"image/jpeg\",\"image/jpg\",\"image/pjpeg\",\"image/png\",\"image/apng\",\"image/webp\",\"image/gif\",\"image/heic\",\"image/heif\",\"image/heic-sequence\",\"image/heif-sequence\",\"image/tiff\",\"image/x-tiff\",\"image/avif\",\"image/avif-sequence\",\"image/bmp\",\"image/x-bmp\",\"image/x-ms-bmp\",\"image/svg+xml\"],\"acceptedExtensions\":[\".jpg\",\".jpeg\",\".jpe\",\".jfif\",\".png\",\".apng\",\".webp\",\".gif\",\".heic\",\".heif\",\".hif\",\".tif\",\".tiff\",\".avif\",\".bmp\",\".svg\",\".svgz\"],\"imageHandling\":{\"animationPolicy\":\"Print the first frame only; APNG uses its default still image.\",\"multipagePolicy\":\"Print the first TIFF page; HEIC/HEIF uses the primary image, falling back to the first.\",\"transparencyPolicy\":\"Composite transparency on white.\",\"colorPolicy\":\"Convert to an 8-bit sRGB JPEG and strip metadata. HDR and wide-gamut appearance may change.\",\"svgPolicy\":\"Self-contained SVG only; no scripts, external resources, embedded images, or stylesheets.\",\"unsupportedPolicy\":\"PDF, camera RAW, PSD, JPEG XL, JPEG 2000, and other unsupported encodings must be exported to a supported format first.\",\"helpUrl\":\"https://photoprintagent.com/help\"},\"helpUrl\":\"https://photoprintagent.com/help\",\"maxFileBytes\":15728640,\"maxInputPixels\":24000000,\"responseSchemaUrl\":\"https://photoprintagent.com/docs/mcp.json#/uploadResponseSchema\"}"
      }
    ],
    "structuredContent": {
      "uploadUrl": "https://photoprintagent.com/api/photos",
      "method": "POST",
      "authorization": "Bearer: use your existing account or guest credential",
      "headers": {
        "X-Photo-Options": "{\"fit\":\"cover\",\"orientation\":\"landscape\",\"x\":50,\"y\":50}"
      },
      "acceptedMimeTypes": [
        "image/jpeg",
        "image/jpg",
        "image/pjpeg",
        "image/png",
        "image/apng",
        "image/webp",
        "image/gif",
        "image/heic",
        "image/heif",
        "image/heic-sequence",
        "image/heif-sequence",
        "image/tiff",
        "image/x-tiff",
        "image/avif",
        "image/avif-sequence",
        "image/bmp",
        "image/x-bmp",
        "image/x-ms-bmp",
        "image/svg+xml"
      ],
      "acceptedExtensions": [
        ".jpg",
        ".jpeg",
        ".jpe",
        ".jfif",
        ".png",
        ".apng",
        ".webp",
        ".gif",
        ".heic",
        ".heif",
        ".hif",
        ".tif",
        ".tiff",
        ".avif",
        ".bmp",
        ".svg",
        ".svgz"
      ],
      "imageHandling": {
        "animationPolicy": "Print the first frame only; APNG uses its default still image.",
        "multipagePolicy": "Print the first TIFF page; HEIC/HEIF uses the primary image, falling back to the first.",
        "transparencyPolicy": "Composite transparency on white.",
        "colorPolicy": "Convert to an 8-bit sRGB JPEG and strip metadata. HDR and wide-gamut appearance may change.",
        "svgPolicy": "Self-contained SVG only; no scripts, external resources, embedded images, or stylesheets.",
        "unsupportedPolicy": "PDF, camera RAW, PSD, JPEG XL, JPEG 2000, and other unsupported encodings must be exported to a supported format first.",
        "helpUrl": "https://photoprintagent.com/help"
      },
      "helpUrl": "https://photoprintagent.com/help",
      "maxFileBytes": 15728640,
      "maxInputPixels": 24000000,
      "responseSchemaUrl": "https://photoprintagent.com/docs/mcp.json#/uploadResponseSchema"
    }
  }
}
```

Common error response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"error\":{\"httpStatus\":401,\"message\":\"Call start_guest_checkout and provide guestToken, or use a Bearer credential. No account is required.\"}}"
      }
    ],
    "structuredContent": {
      "error": {
        "httpStatus": 401,
        "message": "Call start_guest_checkout and provide guestToken, or use a Bearer credential. No account is required."
      }
    },
    "isError": true
  }
}
```

### quote_prints

Quote private uploaded photo IDs and a US destination for an optional productId from list_products (defaults to photo-4x6). One product per order. Requires consent to share these photos and address with the lab. Unique photos, at most 500 total prints. Optional couponCode is validated here; price.total is the discounted buyer amount and price.discount is the savings in USD cents.

Input JSON Schema:

```json
{
  "type": "object",
  "properties": {
    "productId": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]{1,63}$"
    },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "photoId": {
            "type": "string",
            "format": "uuid"
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "maximum": 99
          }
        },
        "required": [
          "photoId",
          "quantity"
        ],
        "additionalProperties": false
      },
      "minItems": 1,
      "maxItems": 50
    },
    "shipping": {
      "type": "object",
      "properties": {
        "firstName": {
          "type": "string",
          "minLength": 1,
          "maxLength": 150
        },
        "lastName": {
          "$ref": "#/properties/shipping/properties/firstName"
        },
        "email": {
          "type": "string",
          "format": "email",
          "maxLength": 254
        },
        "phone": {
          "type": "string"
        },
        "line1": {
          "$ref": "#/properties/shipping/properties/firstName"
        },
        "line2": {
          "type": "string",
          "maxLength": 150,
          "default": ""
        },
        "city": {
          "$ref": "#/properties/shipping/properties/firstName"
        },
        "state": {
          "type": "string",
          "pattern": "^[A-Z]{2}$"
        },
        "zip": {
          "type": "string",
          "pattern": "^\\d{5}(-\\d{4})?$"
        },
        "country": {
          "type": "string",
          "const": "US"
        }
      },
      "required": [
        "firstName",
        "lastName",
        "email",
        "phone",
        "line1",
        "city",
        "state",
        "zip",
        "country"
      ],
      "additionalProperties": false
    },
    "consent": {
      "type": "boolean",
      "const": true
    },
    "couponCode": {
      "type": "string",
      "maxLength": 64
    },
    "campaignId": {
      "type": "string",
      "pattern": "^[a-f0-9]{16}$"
    },
    "guestToken": {
      "type": "string",
      "pattern": "^ppg_[a-f0-9]{64}$",
      "description": "Private guest credential; omit when Authorization Bearer is present."
    }
  },
  "required": [
    "items",
    "shipping",
    "consent"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output JSON Schema (structuredContent):

```json
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "price": {
      "type": "object",
      "properties": {
        "currency": {
          "type": "string",
          "const": "USD"
        },
        "subtotal": {
          "type": "integer",
          "minimum": 0,
          "description": "Integer USD cents"
        },
        "shipping": {
          "$ref": "#/properties/price/properties/subtotal"
        },
        "tax": {
          "$ref": "#/properties/price/properties/subtotal"
        },
        "total": {
          "$ref": "#/properties/price/properties/subtotal"
        },
        "originalTotal": {
          "$ref": "#/properties/price/properties/subtotal",
          "description": "Integer USD cents"
        },
        "discount": {
          "$ref": "#/properties/price/properties/subtotal",
          "description": "Integer USD cents"
        },
        "coupon": {
          "type": "object",
          "properties": {
            "code": {
              "type": "string"
            },
            "name": {
              "type": "string"
            },
            "printDiscount": {
              "$ref": "#/properties/price/properties/subtotal"
            },
            "shippingDiscount": {
              "$ref": "#/properties/price/properties/subtotal"
            },
            "taxCredit": {
              "$ref": "#/properties/price/properties/subtotal"
            }
          },
          "required": [
            "code",
            "name",
            "printDiscount",
            "shippingDiscount",
            "taxCredit"
          ],
          "additionalProperties": false
        }
      },
      "required": [
        "currency",
        "subtotal",
        "shipping",
        "tax",
        "total"
      ],
      "additionalProperties": false
    },
    "validUntil": {
      "type": "integer",
      "minimum": 0,
      "description": "Unix time in milliseconds"
    },
    "expiresAt": {
      "$ref": "#/properties/validUntil"
    },
    "lineItems": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "photoId": {
            "type": "string",
            "format": "uuid"
          },
          "productId": {
            "type": "string"
          },
          "quantity": {
            "type": "integer"
          }
        },
        "required": [
          "photoId",
          "productId",
          "quantity"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "id",
    "price",
    "validUntil",
    "expiresAt",
    "lineItems"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "quote_prints",
    "arguments": {
      "items": [
        {
          "photoId": "22222222-2222-4222-8222-222222222222",
          "quantity": 2
        }
      ],
      "shipping": {
        "firstName": "Jane",
        "lastName": "Buyer",
        "email": "jane@example.com",
        "phone": "2125550100",
        "line1": "123 Example Street",
        "line2": "Suite 2R",
        "city": "New York",
        "state": "NY",
        "zip": "10001",
        "country": "US"
      },
      "consent": true
    }
  }
}
```

Success response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"id\":\"33333333-3333-4333-8333-333333333333\",\"price\":{\"currency\":\"USD\",\"subtotal\":100,\"shipping\":400,\"tax\":50,\"total\":550},\"validUntil\":1893456900000,\"expiresAt\":1893456900000,\"lineItems\":[{\"photoId\":\"22222222-2222-4222-8222-222222222222\",\"quantity\":2,\"productId\":\"photo-4x6\"}]}"
      }
    ],
    "structuredContent": {
      "id": "33333333-3333-4333-8333-333333333333",
      "price": {
        "currency": "USD",
        "subtotal": 100,
        "shipping": 400,
        "tax": 50,
        "total": 550
      },
      "validUntil": 1893456900000,
      "expiresAt": 1893456900000,
      "lineItems": [
        {
          "photoId": "22222222-2222-4222-8222-222222222222",
          "quantity": 2,
          "productId": "photo-4x6"
        }
      ]
    }
  }
}
```

Common error response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"error\":{\"httpStatus\":400,\"message\":\"Check your photos, quantities, and delivery details.\"}}"
      }
    ],
    "structuredContent": {
      "error": {
        "httpStatus": 400,
        "message": "Check your photos, quantities, and delivery details."
      }
    },
    "isError": true
  }
}
```

### create_print_order

Create one order from the approved quote, which already contains buyer contact and address. Reuse the same idempotency key for retries. Returns a buyer card checkout URL or x402 payment URL; call pay_print_order for the challenge. A zero-total coupon order is submitted with buyerApproved:true and needs no card or wallet. Optional couponCode must match the approved quote.

Input JSON Schema:

```json
{
  "type": "object",
  "properties": {
    "quoteId": {
      "type": "string",
      "format": "uuid"
    },
    "couponCode": {
      "type": "string",
      "maxLength": 64
    },
    "idempotencyKey": {
      "type": "string",
      "pattern": "^[-\\w]{16,100}$"
    },
    "buyerApproved": {
      "type": "boolean",
      "const": true
    },
    "method": {
      "type": "string",
      "enum": [
        "card",
        "x402"
      ],
      "default": "x402"
    },
    "guestToken": {
      "type": "string",
      "pattern": "^ppg_[a-f0-9]{64}$",
      "description": "Private guest credential; omit when Authorization Bearer is present."
    }
  },
  "required": [
    "quoteId",
    "idempotencyKey",
    "buyerApproved"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output JSON Schema (structuredContent):

```json
{
  "type": "object",
  "properties": {
    "order": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-zA-Z0-9_-]{1,100}$"
        },
        "status": {
          "type": "string",
          "enum": [
            "creating_checkout",
            "awaiting_payment",
            "verifying_payment",
            "settling_payment",
            "paid",
            "submitting",
            "processing",
            "needs_review",
            "expired"
          ]
        },
        "method": {
          "type": "string",
          "enum": [
            "card",
            "x402"
          ]
        },
        "price": {
          "type": "object",
          "properties": {
            "currency": {
              "type": "string",
              "const": "USD"
            },
            "subtotal": {
              "type": "integer",
              "minimum": 0,
              "description": "Integer USD cents"
            },
            "shipping": {
              "$ref": "#/properties/order/properties/price/properties/subtotal"
            },
            "tax": {
              "$ref": "#/properties/order/properties/price/properties/subtotal"
            },
            "total": {
              "$ref": "#/properties/order/properties/price/properties/subtotal"
            },
            "originalTotal": {
              "$ref": "#/properties/order/properties/price/properties/subtotal",
              "description": "Integer USD cents"
            },
            "discount": {
              "$ref": "#/properties/order/properties/price/properties/subtotal",
              "description": "Integer USD cents"
            },
            "coupon": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "printDiscount": {
                  "$ref": "#/properties/order/properties/price/properties/subtotal"
                },
                "shippingDiscount": {
                  "$ref": "#/properties/order/properties/price/properties/subtotal"
                },
                "taxCredit": {
                  "$ref": "#/properties/order/properties/price/properties/subtotal"
                }
              },
              "required": [
                "code",
                "name",
                "printDiscount",
                "shippingDiscount",
                "taxCredit"
              ],
              "additionalProperties": false
            }
          },
          "required": [
            "currency",
            "subtotal",
            "shipping",
            "tax",
            "total"
          ],
          "additionalProperties": false
        },
        "cardPaymentUrl": {
          "anyOf": [
            {
              "type": "string",
              "format": "uri"
            },
            {
              "type": "null"
            }
          ]
        },
        "quantity": {
          "type": "integer"
        },
        "createdAt": {
          "type": "integer",
          "minimum": 0,
          "description": "Unix time in milliseconds"
        },
        "paidAt": {
          "anyOf": [
            {
              "$ref": "#/properties/order/properties/createdAt"
            },
            {
              "type": "null"
            }
          ],
          "description": "Unix time in milliseconds"
        },
        "paymentStatus": {
          "type": "string",
          "enum": [
            "unpaid",
            "pending",
            "paid",
            "not_required",
            "needs_review",
            "expired"
          ]
        },
        "fulfillmentStatus": {
          "type": "string",
          "enum": [
            "not_submitted",
            "submitting",
            "accepted",
            "needs_review"
          ]
        },
        "cryptoPayment": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "rankId": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "symbol": {
                  "type": "string"
                },
                "asset": {
                  "type": "string"
                },
                "decimals": {
                  "type": "integer"
                },
                "transferMethod": {
                  "type": "string",
                  "enum": [
                    "eip3009",
                    "permit2",
                    "spl"
                  ]
                },
                "network": {
                  "type": "string"
                },
                "tokenProgram": {
                  "type": "string"
                },
                "rank": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "wrapped": {
                  "type": "boolean"
                },
                "stable": {
                  "type": "boolean"
                },
                "availability": {
                  "type": "string",
                  "enum": [
                    "quote_required",
                    "receiving_ready"
                  ]
                },
                "priceChecked": {
                  "type": "boolean"
                },
                "tokenId": {
                  "type": "string"
                },
                "amount": {
                  "type": "string",
                  "pattern": "^\\d+$"
                },
                "displayAmount": {
                  "type": "string"
                },
                "usdTotal": {
                  "$ref": "#/properties/order/properties/price/properties/subtotal"
                },
                "usdPrice": {
                  "type": "string"
                },
                "priceAsOf": {
                  "$ref": "#/properties/order/properties/createdAt"
                },
                "source": {
                  "type": "string"
                },
                "payTo": {
                  "type": "string"
                },
                "quotedAt": {
                  "$ref": "#/properties/order/properties/createdAt"
                },
                "validUntil": {
                  "$ref": "#/properties/order/properties/createdAt"
                }
              },
              "required": [
                "id",
                "rankId",
                "name",
                "symbol",
                "asset",
                "decimals",
                "transferMethod",
                "network",
                "tokenId",
                "amount",
                "displayAmount",
                "usdTotal",
                "usdPrice",
                "priceAsOf",
                "source",
                "payTo",
                "quotedAt",
                "validUntil"
              ],
              "additionalProperties": false
            },
            {
              "type": "null"
            }
          ]
        },
        "transaction": {
          "type": [
            "string",
            "null"
          ]
        },
        "network": {
          "type": [
            "string",
            "null"
          ]
        },
        "trackingUrls": {
          "type": "array",
          "items": {
            "type": "string",
            "format": "uri"
          }
        },
        "message": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "id",
        "status",
        "method",
        "price",
        "quantity",
        "createdAt",
        "paidAt",
        "paymentStatus",
        "fulfillmentStatus",
        "cryptoPayment",
        "transaction",
        "network",
        "trackingUrls",
        "message"
      ],
      "additionalProperties": false
    },
    "paymentUrl": {
      "type": "string"
    },
    "checkoutUrl": {
      "type": "string",
      "format": "uri"
    }
  },
  "required": [
    "order",
    "checkoutUrl"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "create_print_order",
    "arguments": {
      "quoteId": "33333333-3333-4333-8333-333333333333",
      "idempotencyKey": "44444444-4444-4444-8444-444444444444",
      "buyerApproved": true,
      "method": "x402"
    }
  }
}
```

Success response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"order\":{\"id\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"status\":\"awaiting_payment\",\"method\":\"x402\",\"price\":{\"currency\":\"USD\",\"subtotal\":100,\"shipping\":400,\"tax\":50,\"total\":550},\"quantity\":2,\"createdAt\":1893456000000,\"paidAt\":null,\"paymentStatus\":\"unpaid\",\"fulfillmentStatus\":\"not_submitted\",\"cryptoPayment\":null,\"transaction\":null,\"network\":null,\"trackingUrls\":[],\"message\":null},\"paymentUrl\":\"/api/orders/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/pay\",\"checkoutUrl\":\"https://photoprintagent.com/checkout/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#access=ppc_0000000000000000000000000000000000000000000000000000000000000000\"}"
      }
    ],
    "structuredContent": {
      "order": {
        "id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "status": "awaiting_payment",
        "method": "x402",
        "price": {
          "currency": "USD",
          "subtotal": 100,
          "shipping": 400,
          "tax": 50,
          "total": 550
        },
        "quantity": 2,
        "createdAt": 1893456000000,
        "paidAt": null,
        "paymentStatus": "unpaid",
        "fulfillmentStatus": "not_submitted",
        "cryptoPayment": null,
        "transaction": null,
        "network": null,
        "trackingUrls": [],
        "message": null
      },
      "paymentUrl": "/api/orders/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/pay",
      "checkoutUrl": "https://photoprintagent.com/checkout/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#access=ppc_0000000000000000000000000000000000000000000000000000000000000000"
    }
  }
}
```

Common error response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"error\":{\"httpStatus\":409,\"message\":\"Refresh your quote before checking out.\"}}"
      }
    ],
    "structuredContent": {
      "error": {
        "httpStatus": 409,
        "message": "Refresh your quote before checking out."
      }
    },
    "isError": true
  }
}
```

### pay_print_order

Request an x402 v2 challenge without a signature, or settle an approved signature and submit the paid print. Uses the same guarded payment flow as REST. An unsigned call returns payment_required in structuredContent (MCP HTTP 200). Never repay an ambiguous order.

Input JSON Schema:

```json
{
  "type": "object",
  "properties": {
    "orderId": {
      "type": "string",
      "pattern": "^[a-zA-Z0-9_-]{1,100}$"
    },
    "cryptoQuoteId": {
      "type": "string",
      "format": "uuid"
    },
    "paymentSignature": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64000,
      "description": "Base64-encoded x402 v2 payload, same value as PAYMENT-SIGNATURE; never provide both."
    },
    "guestToken": {
      "type": "string",
      "pattern": "^ppg_[a-f0-9]{64}$",
      "description": "Private guest credential; omit when Authorization Bearer is present."
    }
  },
  "required": [
    "orderId"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output JSON Schema (structuredContent):

```json
{
  "type": "object",
  "properties": {
    "status": {
      "type": "string",
      "enum": [
        "payment_required",
        "order_status"
      ]
    },
    "httpStatus": {
      "type": "number",
      "enum": [
        200,
        402
      ]
    },
    "payment_required": {
      "type": "object",
      "properties": {
        "x402Version": {
          "type": "number",
          "const": 2
        },
        "resource": {
          "type": "object",
          "properties": {
            "url": {
              "type": "string",
              "format": "uri"
            },
            "description": {
              "type": "string"
            },
            "mimeType": {
              "type": "string"
            }
          },
          "required": [
            "url"
          ],
          "additionalProperties": false
        },
        "accepts": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "scheme": {
                "type": "string",
                "const": "exact"
              },
              "network": {
                "type": "string"
              },
              "asset": {
                "type": "string"
              },
              "amount": {
                "type": "string",
                "pattern": "^\\d+$"
              },
              "payTo": {
                "type": "string"
              },
              "maxTimeoutSeconds": {
                "type": "integer"
              },
              "extra": {
                "type": "object",
                "additionalProperties": {}
              }
            },
            "required": [
              "scheme",
              "network",
              "asset",
              "amount",
              "payTo",
              "maxTimeoutSeconds"
            ],
            "additionalProperties": false
          }
        },
        "error": {
          "type": "string"
        },
        "extensions": {
          "type": "object",
          "additionalProperties": {}
        }
      },
      "required": [
        "x402Version",
        "resource",
        "accepts"
      ],
      "additionalProperties": false
    },
    "paymentRequiredHeader": {
      "type": "string"
    },
    "order": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-zA-Z0-9_-]{1,100}$"
        },
        "status": {
          "type": "string",
          "enum": [
            "creating_checkout",
            "awaiting_payment",
            "verifying_payment",
            "settling_payment",
            "paid",
            "submitting",
            "processing",
            "needs_review",
            "expired"
          ]
        },
        "method": {
          "type": "string",
          "enum": [
            "card",
            "x402"
          ]
        },
        "price": {
          "type": "object",
          "properties": {
            "currency": {
              "type": "string",
              "const": "USD"
            },
            "subtotal": {
              "type": "integer",
              "minimum": 0,
              "description": "Integer USD cents"
            },
            "shipping": {
              "$ref": "#/properties/order/properties/price/properties/subtotal"
            },
            "tax": {
              "$ref": "#/properties/order/properties/price/properties/subtotal"
            },
            "total": {
              "$ref": "#/properties/order/properties/price/properties/subtotal"
            },
            "originalTotal": {
              "$ref": "#/properties/order/properties/price/properties/subtotal",
              "description": "Integer USD cents"
            },
            "discount": {
              "$ref": "#/properties/order/properties/price/properties/subtotal",
              "description": "Integer USD cents"
            },
            "coupon": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "printDiscount": {
                  "$ref": "#/properties/order/properties/price/properties/subtotal"
                },
                "shippingDiscount": {
                  "$ref": "#/properties/order/properties/price/properties/subtotal"
                },
                "taxCredit": {
                  "$ref": "#/properties/order/properties/price/properties/subtotal"
                }
              },
              "required": [
                "code",
                "name",
                "printDiscount",
                "shippingDiscount",
                "taxCredit"
              ],
              "additionalProperties": false
            }
          },
          "required": [
            "currency",
            "subtotal",
            "shipping",
            "tax",
            "total"
          ],
          "additionalProperties": false
        },
        "cardPaymentUrl": {
          "anyOf": [
            {
              "type": "string",
              "format": "uri"
            },
            {
              "type": "null"
            }
          ]
        },
        "quantity": {
          "type": "integer"
        },
        "createdAt": {
          "type": "integer",
          "minimum": 0,
          "description": "Unix time in milliseconds"
        },
        "paidAt": {
          "anyOf": [
            {
              "$ref": "#/properties/order/properties/createdAt"
            },
            {
              "type": "null"
            }
          ],
          "description": "Unix time in milliseconds"
        },
        "paymentStatus": {
          "type": "string",
          "enum": [
            "unpaid",
            "pending",
            "paid",
            "not_required",
            "needs_review",
            "expired"
          ]
        },
        "fulfillmentStatus": {
          "type": "string",
          "enum": [
            "not_submitted",
            "submitting",
            "accepted",
            "needs_review"
          ]
        },
        "cryptoPayment": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "rankId": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "symbol": {
                  "type": "string"
                },
                "asset": {
                  "type": "string"
                },
                "decimals": {
                  "type": "integer"
                },
                "transferMethod": {
                  "type": "string",
                  "enum": [
                    "eip3009",
                    "permit2",
                    "spl"
                  ]
                },
                "network": {
                  "type": "string"
                },
                "tokenProgram": {
                  "type": "string"
                },
                "rank": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "wrapped": {
                  "type": "boolean"
                },
                "stable": {
                  "type": "boolean"
                },
                "availability": {
                  "type": "string",
                  "enum": [
                    "quote_required",
                    "receiving_ready"
                  ]
                },
                "priceChecked": {
                  "type": "boolean"
                },
                "tokenId": {
                  "type": "string"
                },
                "amount": {
                  "type": "string",
                  "pattern": "^\\d+$"
                },
                "displayAmount": {
                  "type": "string"
                },
                "usdTotal": {
                  "$ref": "#/properties/order/properties/price/properties/subtotal"
                },
                "usdPrice": {
                  "type": "string"
                },
                "priceAsOf": {
                  "$ref": "#/properties/order/properties/createdAt"
                },
                "source": {
                  "type": "string"
                },
                "payTo": {
                  "type": "string"
                },
                "quotedAt": {
                  "$ref": "#/properties/order/properties/createdAt"
                },
                "validUntil": {
                  "$ref": "#/properties/order/properties/createdAt"
                }
              },
              "required": [
                "id",
                "rankId",
                "name",
                "symbol",
                "asset",
                "decimals",
                "transferMethod",
                "network",
                "tokenId",
                "amount",
                "displayAmount",
                "usdTotal",
                "usdPrice",
                "priceAsOf",
                "source",
                "payTo",
                "quotedAt",
                "validUntil"
              ],
              "additionalProperties": false
            },
            {
              "type": "null"
            }
          ]
        },
        "transaction": {
          "type": [
            "string",
            "null"
          ]
        },
        "network": {
          "type": [
            "string",
            "null"
          ]
        },
        "trackingUrls": {
          "type": "array",
          "items": {
            "type": "string",
            "format": "uri"
          }
        },
        "message": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "id",
        "status",
        "method",
        "price",
        "quantity",
        "createdAt",
        "paidAt",
        "paymentStatus",
        "fulfillmentStatus",
        "cryptoPayment",
        "transaction",
        "network",
        "trackingUrls",
        "message"
      ],
      "additionalProperties": false
    },
    "paymentResponse": {
      "type": "object",
      "properties": {
        "success": {
          "type": "boolean"
        },
        "transaction": {
          "type": "string"
        },
        "network": {
          "type": "string"
        },
        "payer": {
          "type": "string"
        },
        "errorReason": {
          "type": "string"
        }
      },
      "required": [
        "success",
        "transaction",
        "network"
      ],
      "additionalProperties": true
    },
    "paymentResponseHeader": {
      "type": "string"
    }
  },
  "required": [
    "status",
    "httpStatus"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "pay_print_order",
    "arguments": {
      "orderId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    }
  }
}
```

Success response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"status\":\"payment_required\",\"httpStatus\":402,\"payment_required\":{\"x402Version\":2,\"resource\":{\"url\":\"https://photoprintagent.com/api/orders/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/pay\",\"description\":\"4×6 photo print order aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"mimeType\":\"application/json\"},\"accepts\":[{\"scheme\":\"exact\",\"network\":\"eip155:8453\",\"asset\":\"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\"amount\":\"5500000\",\"payTo\":\"0x1111111111111111111111111111111111111111\",\"maxTimeoutSeconds\":300,\"extra\":{\"name\":\"USD Coin\",\"version\":\"2\"}}]},\"paymentRequiredHeader\":\"eyJ4NDAyVmVyc2lvbiI6MiwicmVzb3VyY2UiOnsidXJsIjoiaHR0cHM6Ly9waG90b3ByaW50YWdlbnQuY29tL2FwaS9vcmRlcnMvYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYS9wYXkiLCJkZXNjcmlwdGlvbiI6IjTDlzYgcGhvdG8gcHJpbnQgb3JkZXIgYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYSIsIm1pbWVUeXBlIjoiYXBwbGljYXRpb24vanNvbiJ9LCJhY2NlcHRzIjpbeyJzY2hlbWUiOiJleGFjdCIsIm5ldHdvcmsiOiJlaXAxNTU6ODQ1MyIsImFzc2V0IjoiMHg4MzM1ODlmQ0Q2ZURiNkUwOGY0YzdDMzJENGY3MWI1NGJkQTAyOTEzIiwiYW1vdW50IjoiNTUwMDAwMCIsInBheVRvIjoiMHgxMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExIiwibWF4VGltZW91dFNlY29uZHMiOjMwMCwiZXh0cmEiOnsibmFtZSI6IlVTRCBDb2luIiwidmVyc2lvbiI6IjIifX1dfQ==\"}"
      }
    ],
    "structuredContent": {
      "status": "payment_required",
      "httpStatus": 402,
      "payment_required": {
        "x402Version": 2,
        "resource": {
          "url": "https://photoprintagent.com/api/orders/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/pay",
          "description": "4×6 photo print order aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "mimeType": "application/json"
        },
        "accepts": [
          {
            "scheme": "exact",
            "network": "eip155:8453",
            "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
            "amount": "5500000",
            "payTo": "0x1111111111111111111111111111111111111111",
            "maxTimeoutSeconds": 300,
            "extra": {
              "name": "USD Coin",
              "version": "2"
            }
          }
        ]
      },
      "paymentRequiredHeader": "eyJ4NDAyVmVyc2lvbiI6MiwicmVzb3VyY2UiOnsidXJsIjoiaHR0cHM6Ly9waG90b3ByaW50YWdlbnQuY29tL2FwaS9vcmRlcnMvYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYS9wYXkiLCJkZXNjcmlwdGlvbiI6IjTDlzYgcGhvdG8gcHJpbnQgb3JkZXIgYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYSIsIm1pbWVUeXBlIjoiYXBwbGljYXRpb24vanNvbiJ9LCJhY2NlcHRzIjpbeyJzY2hlbWUiOiJleGFjdCIsIm5ldHdvcmsiOiJlaXAxNTU6ODQ1MyIsImFzc2V0IjoiMHg4MzM1ODlmQ0Q2ZURiNkUwOGY0YzdDMzJENGY3MWI1NGJkQTAyOTEzIiwiYW1vdW50IjoiNTUwMDAwMCIsInBheVRvIjoiMHgxMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExIiwibWF4VGltZW91dFNlY29uZHMiOjMwMCwiZXh0cmEiOnsibmFtZSI6IlVTRCBDb2luIiwidmVyc2lvbiI6IjIifX1dfQ=="
    }
  }
}
```

Common error response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"error\":{\"httpStatus\":402,\"message\":\"Payment authorization was rejected.\"}}"
      }
    ],
    "structuredContent": {
      "error": {
        "httpStatus": 402,
        "message": "Payment authorization was rejected."
      }
    },
    "isError": true
  }
}
```

Signed retry request (replace the placeholder with the base64 SDK output; preserve orderId):

```json
{
  "jsonrpc": "2.0",
  "id": 11,
  "method": "tools/call",
  "params": {
    "name": "pay_print_order",
    "arguments": {
      "orderId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "paymentSignature": "<BASE64_X402_V2_SIGNED_PAYLOAD>"
    }
  }
}
```

New settlement success response (also PAYMENT-RESPONSE header):

```json
{
  "jsonrpc": "2.0",
  "id": 11,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"status\":\"order_status\",\"httpStatus\":200,\"order\":{\"id\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"status\":\"processing\",\"method\":\"x402\",\"price\":{\"currency\":\"USD\",\"subtotal\":100,\"shipping\":400,\"tax\":50,\"total\":550},\"quantity\":2,\"createdAt\":1893456000000,\"paidAt\":1893456060000,\"paymentStatus\":\"paid\",\"fulfillmentStatus\":\"accepted\",\"cryptoPayment\":null,\"transaction\":\"0x5555555555555555555555555555555555555555555555555555555555555555\",\"network\":\"eip155:8453\",\"trackingUrls\":[],\"message\":\"Thank you! Your order is with our print partner.\"},\"paymentResponse\":{\"success\":true,\"transaction\":\"0x5555555555555555555555555555555555555555555555555555555555555555\",\"network\":\"eip155:8453\"},\"paymentResponseHeader\":\"eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbiI6IjB4NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NSIsIm5ldHdvcmsiOiJlaXAxNTU6ODQ1MyJ9\"}"
      }
    ],
    "structuredContent": {
      "status": "order_status",
      "httpStatus": 200,
      "order": {
        "id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "status": "processing",
        "method": "x402",
        "price": {
          "currency": "USD",
          "subtotal": 100,
          "shipping": 400,
          "tax": 50,
          "total": 550
        },
        "quantity": 2,
        "createdAt": 1893456000000,
        "paidAt": 1893456060000,
        "paymentStatus": "paid",
        "fulfillmentStatus": "accepted",
        "cryptoPayment": null,
        "transaction": "0x5555555555555555555555555555555555555555555555555555555555555555",
        "network": "eip155:8453",
        "trackingUrls": [],
        "message": "Thank you! Your order is with our print partner."
      },
      "paymentResponse": {
        "success": true,
        "transaction": "0x5555555555555555555555555555555555555555555555555555555555555555",
        "network": "eip155:8453"
      },
      "paymentResponseHeader": "eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbiI6IjB4NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NTU1NSIsIm5ldHdvcmsiOiJlaXAxNTU6ODQ1MyJ9"
    }
  }
}
```

### get_order

Read your private order. Payment and fulfillment states are separate; processing means lab acceptance, not shipped. Does not initiate charges. Polling also verifies a completed card checkout with the processor.

Input JSON Schema:

```json
{
  "type": "object",
  "properties": {
    "orderId": {
      "type": "string",
      "pattern": "^[a-zA-Z0-9_-]{1,100}$"
    },
    "guestToken": {
      "type": "string",
      "pattern": "^ppg_[a-f0-9]{64}$",
      "description": "Private guest credential; omit when Authorization Bearer is present."
    }
  },
  "required": [
    "orderId"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output JSON Schema (structuredContent):

```json
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[a-zA-Z0-9_-]{1,100}$"
    },
    "status": {
      "type": "string",
      "enum": [
        "creating_checkout",
        "awaiting_payment",
        "verifying_payment",
        "settling_payment",
        "paid",
        "submitting",
        "processing",
        "needs_review",
        "expired"
      ]
    },
    "method": {
      "type": "string",
      "enum": [
        "card",
        "x402"
      ]
    },
    "price": {
      "type": "object",
      "properties": {
        "currency": {
          "type": "string",
          "const": "USD"
        },
        "subtotal": {
          "type": "integer",
          "minimum": 0,
          "description": "Integer USD cents"
        },
        "shipping": {
          "$ref": "#/properties/price/properties/subtotal"
        },
        "tax": {
          "$ref": "#/properties/price/properties/subtotal"
        },
        "total": {
          "$ref": "#/properties/price/properties/subtotal"
        },
        "originalTotal": {
          "$ref": "#/properties/price/properties/subtotal",
          "description": "Integer USD cents"
        },
        "discount": {
          "$ref": "#/properties/price/properties/subtotal",
          "description": "Integer USD cents"
        },
        "coupon": {
          "type": "object",
          "properties": {
            "code": {
              "type": "string"
            },
            "name": {
              "type": "string"
            },
            "printDiscount": {
              "$ref": "#/properties/price/properties/subtotal"
            },
            "shippingDiscount": {
              "$ref": "#/properties/price/properties/subtotal"
            },
            "taxCredit": {
              "$ref": "#/properties/price/properties/subtotal"
            }
          },
          "required": [
            "code",
            "name",
            "printDiscount",
            "shippingDiscount",
            "taxCredit"
          ],
          "additionalProperties": false
        }
      },
      "required": [
        "currency",
        "subtotal",
        "shipping",
        "tax",
        "total"
      ],
      "additionalProperties": false
    },
    "cardPaymentUrl": {
      "anyOf": [
        {
          "type": "string",
          "format": "uri"
        },
        {
          "type": "null"
        }
      ]
    },
    "quantity": {
      "type": "integer"
    },
    "createdAt": {
      "type": "integer",
      "minimum": 0,
      "description": "Unix time in milliseconds"
    },
    "paidAt": {
      "anyOf": [
        {
          "$ref": "#/properties/createdAt"
        },
        {
          "type": "null"
        }
      ],
      "description": "Unix time in milliseconds"
    },
    "paymentStatus": {
      "type": "string",
      "enum": [
        "unpaid",
        "pending",
        "paid",
        "not_required",
        "needs_review",
        "expired"
      ]
    },
    "fulfillmentStatus": {
      "type": "string",
      "enum": [
        "not_submitted",
        "submitting",
        "accepted",
        "needs_review"
      ]
    },
    "cryptoPayment": {
      "anyOf": [
        {
          "type": "object",
          "properties": {
            "id": {
              "type": "string",
              "format": "uuid"
            },
            "rankId": {
              "type": "string"
            },
            "name": {
              "type": "string"
            },
            "symbol": {
              "type": "string"
            },
            "asset": {
              "type": "string"
            },
            "decimals": {
              "type": "integer"
            },
            "transferMethod": {
              "type": "string",
              "enum": [
                "eip3009",
                "permit2",
                "spl"
              ]
            },
            "network": {
              "type": "string"
            },
            "tokenProgram": {
              "type": "string"
            },
            "rank": {
              "type": [
                "number",
                "null"
              ]
            },
            "wrapped": {
              "type": "boolean"
            },
            "stable": {
              "type": "boolean"
            },
            "availability": {
              "type": "string",
              "enum": [
                "quote_required",
                "receiving_ready"
              ]
            },
            "priceChecked": {
              "type": "boolean"
            },
            "tokenId": {
              "type": "string"
            },
            "amount": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "displayAmount": {
              "type": "string"
            },
            "usdTotal": {
              "$ref": "#/properties/price/properties/subtotal"
            },
            "usdPrice": {
              "type": "string"
            },
            "priceAsOf": {
              "$ref": "#/properties/createdAt"
            },
            "source": {
              "type": "string"
            },
            "payTo": {
              "type": "string"
            },
            "quotedAt": {
              "$ref": "#/properties/createdAt"
            },
            "validUntil": {
              "$ref": "#/properties/createdAt"
            }
          },
          "required": [
            "id",
            "rankId",
            "name",
            "symbol",
            "asset",
            "decimals",
            "transferMethod",
            "network",
            "tokenId",
            "amount",
            "displayAmount",
            "usdTotal",
            "usdPrice",
            "priceAsOf",
            "source",
            "payTo",
            "quotedAt",
            "validUntil"
          ],
          "additionalProperties": false
        },
        {
          "type": "null"
        }
      ]
    },
    "transaction": {
      "type": [
        "string",
        "null"
      ]
    },
    "network": {
      "type": [
        "string",
        "null"
      ]
    },
    "trackingUrls": {
      "type": "array",
      "items": {
        "type": "string",
        "format": "uri"
      }
    },
    "message": {
      "type": [
        "string",
        "null"
      ]
    }
  },
  "required": [
    "id",
    "status",
    "method",
    "price",
    "quantity",
    "createdAt",
    "paidAt",
    "paymentStatus",
    "fulfillmentStatus",
    "cryptoPayment",
    "transaction",
    "network",
    "trackingUrls",
    "message"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "get_order",
    "arguments": {
      "orderId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    }
  }
}
```

Success response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"id\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"status\":\"processing\",\"method\":\"x402\",\"price\":{\"currency\":\"USD\",\"subtotal\":100,\"shipping\":400,\"tax\":50,\"total\":550},\"quantity\":2,\"createdAt\":1893456000000,\"paidAt\":1893456060000,\"paymentStatus\":\"paid\",\"fulfillmentStatus\":\"accepted\",\"cryptoPayment\":null,\"transaction\":\"0x5555555555555555555555555555555555555555555555555555555555555555\",\"network\":\"eip155:8453\",\"trackingUrls\":[],\"message\":\"Thank you! Your order is with our print partner.\"}"
      }
    ],
    "structuredContent": {
      "id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "status": "processing",
      "method": "x402",
      "price": {
        "currency": "USD",
        "subtotal": 100,
        "shipping": 400,
        "tax": 50,
        "total": 550
      },
      "quantity": 2,
      "createdAt": 1893456000000,
      "paidAt": 1893456060000,
      "paymentStatus": "paid",
      "fulfillmentStatus": "accepted",
      "cryptoPayment": null,
      "transaction": "0x5555555555555555555555555555555555555555555555555555555555555555",
      "network": "eip155:8453",
      "trackingUrls": [],
      "message": "Thank you! Your order is with our print partner."
    }
  }
}
```

Common error response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"error\":{\"httpStatus\":404,\"message\":\"Not found.\"}}"
      }
    ],
    "structuredContent": {
      "error": {
        "httpStatus": 404,
        "message": "Not found."
      }
    },
    "isError": true
  }
}
```

### list_payment_assets

Public: search Base ERC-20 and ready Solana SPL / compatible Token-2022 payment candidates by name, ticker or exact contract/mint. No market-cap limit. Results are paginated; pass nextOffset as offset. Call quote_crypto_payment to verify the token and obtain a fresh price. Solana needs the merchant receiving account initialized.

Input JSON Schema:

```json
{
  "type": "object",
  "properties": {
    "search": {
      "type": "string",
      "maxLength": 100,
      "default": ""
    },
    "network": {
      "type": "string",
      "enum": [
        "eip155:8453",
        "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
      ]
    },
    "offset": {
      "type": "integer",
      "minimum": 0,
      "maximum": 20000,
      "default": 0
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "default": 50
    }
  },
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output JSON Schema (structuredContent):

```json
{
  "type": "object",
  "properties": {
    "network": {
      "type": "string"
    },
    "priceStatus": {
      "type": "string",
      "enum": [
        "available",
        "unavailable"
      ]
    },
    "rankingSource": {
      "type": "string"
    },
    "catalogUpdatedAt": {
      "type": "string"
    },
    "catalogSource": {
      "type": "string",
      "enum": [
        "bundled",
        "refreshed"
      ]
    },
    "catalogStale": {
      "type": "boolean"
    },
    "assets": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "rankId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "symbol": {
            "type": "string"
          },
          "asset": {
            "type": "string"
          },
          "decimals": {
            "type": "integer"
          },
          "transferMethod": {
            "type": "string",
            "enum": [
              "eip3009",
              "permit2",
              "spl"
            ]
          },
          "network": {
            "type": "string"
          },
          "tokenProgram": {
            "type": "string"
          },
          "rank": {
            "type": [
              "number",
              "null"
            ]
          },
          "wrapped": {
            "type": "boolean"
          },
          "stable": {
            "type": "boolean"
          },
          "availability": {
            "type": "string",
            "enum": [
              "quote_required",
              "receiving_ready"
            ]
          },
          "priceChecked": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "rankId",
          "name",
          "symbol",
          "asset",
          "decimals",
          "transferMethod",
          "network"
        ],
        "additionalProperties": false
      }
    },
    "networks": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "total": {
      "type": "integer"
    },
    "offset": {
      "type": "integer"
    },
    "nextOffset": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ]
    },
    "solanaPayTo": {
      "type": [
        "string",
        "null"
      ]
    }
  },
  "required": [
    "network",
    "priceStatus",
    "rankingSource",
    "assets",
    "networks",
    "total",
    "offset",
    "nextOffset",
    "solanaPayTo"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "list_payment_assets",
    "arguments": {}
  }
}
```

Success response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"network\":\"eip155:8453\",\"priceStatus\":\"available\",\"rankingSource\":\"CoinGecko token catalog; no market-cap restriction\",\"assets\":[{\"id\":\"usd-coin\",\"rankId\":\"usd-coin\",\"name\":\"USDC\",\"symbol\":\"USDC\",\"asset\":\"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\"decimals\":6,\"transferMethod\":\"eip3009\",\"network\":\"eip155:8453\",\"rank\":6}],\"networks\":[\"eip155:8453\"],\"total\":1,\"offset\":0,\"nextOffset\":null,\"solanaPayTo\":\"87RFu8CtatoJe2dAys91WxT6bCGw9rzmnGzZ8HwsAzye\"}"
      }
    ],
    "structuredContent": {
      "network": "eip155:8453",
      "priceStatus": "available",
      "rankingSource": "CoinGecko token catalog; no market-cap restriction",
      "assets": [
        {
          "id": "usd-coin",
          "rankId": "usd-coin",
          "name": "USDC",
          "symbol": "USDC",
          "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
          "decimals": 6,
          "transferMethod": "eip3009",
          "network": "eip155:8453",
          "rank": 6
        }
      ],
      "networks": [
        "eip155:8453"
      ],
      "total": 1,
      "offset": 0,
      "nextOffset": null,
      "solanaPayTo": "87RFu8CtatoJe2dAys91WxT6bCGw9rzmnGzZ8HwsAzye"
    }
  }
}
```

Common error response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"error\":{\"httpStatus\":503,\"message\":\"Live crypto pricing is temporarily unavailable.\"}}"
      }
    ],
    "structuredContent": {
      "error": {
        "httpStatus": 503,
        "message": "Live crypto pricing is temporarily unavailable."
      }
    },
    "isError": true
  }
}
```

### quote_crypto_payment

Lock an unpaid order in a listed token for up to 60 seconds. Obtain approval of this exact amount and include cryptoQuoteId in the payment retry.

Input JSON Schema:

```json
{
  "type": "object",
  "properties": {
    "orderId": {
      "type": "string",
      "pattern": "^[a-zA-Z0-9_-]{1,100}$"
    },
    "tokenId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "guestToken": {
      "type": "string",
      "pattern": "^ppg_[a-f0-9]{64}$",
      "description": "Private guest credential; omit when Authorization Bearer is present."
    }
  },
  "required": [
    "orderId",
    "tokenId"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output JSON Schema (structuredContent):

```json
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "rankId": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "symbol": {
      "type": "string"
    },
    "asset": {
      "type": "string"
    },
    "decimals": {
      "type": "integer"
    },
    "transferMethod": {
      "type": "string",
      "enum": [
        "eip3009",
        "permit2",
        "spl"
      ]
    },
    "network": {
      "type": "string"
    },
    "tokenProgram": {
      "type": "string"
    },
    "rank": {
      "type": [
        "number",
        "null"
      ]
    },
    "wrapped": {
      "type": "boolean"
    },
    "stable": {
      "type": "boolean"
    },
    "availability": {
      "type": "string",
      "enum": [
        "quote_required",
        "receiving_ready"
      ]
    },
    "priceChecked": {
      "type": "boolean"
    },
    "tokenId": {
      "type": "string"
    },
    "amount": {
      "type": "string",
      "pattern": "^\\d+$"
    },
    "displayAmount": {
      "type": "string"
    },
    "usdTotal": {
      "type": "integer",
      "minimum": 0,
      "description": "Integer USD cents"
    },
    "usdPrice": {
      "type": "string"
    },
    "priceAsOf": {
      "type": "integer",
      "minimum": 0,
      "description": "Unix time in milliseconds"
    },
    "source": {
      "type": "string"
    },
    "payTo": {
      "type": "string"
    },
    "quotedAt": {
      "$ref": "#/properties/priceAsOf"
    },
    "validUntil": {
      "$ref": "#/properties/priceAsOf"
    }
  },
  "required": [
    "id",
    "rankId",
    "name",
    "symbol",
    "asset",
    "decimals",
    "transferMethod",
    "network",
    "tokenId",
    "amount",
    "displayAmount",
    "usdTotal",
    "usdPrice",
    "priceAsOf",
    "source",
    "payTo",
    "quotedAt",
    "validUntil"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "quote_crypto_payment",
    "arguments": {
      "orderId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "tokenId": "usd-coin"
    }
  }
}
```

Success response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"id\":\"55555555-5555-4555-8555-555555555555\",\"rankId\":\"usd-coin\",\"name\":\"USDC\",\"symbol\":\"USDC\",\"asset\":\"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\"decimals\":6,\"transferMethod\":\"eip3009\",\"network\":\"eip155:8453\",\"rank\":6,\"tokenId\":\"usd-coin\",\"amount\":\"5500000\",\"displayAmount\":\"5.5\",\"usdTotal\":550,\"usdPrice\":\"1\",\"priceAsOf\":1893456000000,\"source\":\"USDC checkout denomination\",\"payTo\":\"0x1111111111111111111111111111111111111111\",\"quotedAt\":1893456000000,\"validUntil\":1893456060000}"
      }
    ],
    "structuredContent": {
      "id": "55555555-5555-4555-8555-555555555555",
      "rankId": "usd-coin",
      "name": "USDC",
      "symbol": "USDC",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "decimals": 6,
      "transferMethod": "eip3009",
      "network": "eip155:8453",
      "rank": 6,
      "tokenId": "usd-coin",
      "amount": "5500000",
      "displayAmount": "5.5",
      "usdTotal": 550,
      "usdPrice": "1",
      "priceAsOf": 1893456000000,
      "source": "USDC checkout denomination",
      "payTo": "0x1111111111111111111111111111111111111111",
      "quotedAt": 1893456000000,
      "validUntil": 1893456060000
    }
  }
}
```

Common error response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"error\":{\"httpStatus\":409,\"message\":\"This order is not waiting for a crypto payment.\"}}"
      }
    ],
    "structuredContent": {
      "error": {
        "httpStatus": 409,
        "message": "This order is not waiting for a crypto payment."
      }
    },
    "isError": true
  }
}
```

### get_checkout_options

Public account and guest authentication instructions.

Input JSON Schema:

```json
{
  "type": "object",
  "properties": {},
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output JSON Schema (structuredContent):

```json
{
  "type": "object",
  "properties": {
    "account": {
      "type": "object",
      "properties": {
        "signInUrl": {
          "type": "string",
          "format": "uri"
        },
        "firebaseConfigUrl": {
          "type": "string",
          "format": "uri"
        },
        "instructions": {
          "type": "string"
        }
      },
      "required": [
        "signInUrl",
        "firebaseConfigUrl",
        "instructions"
      ],
      "additionalProperties": false
    },
    "guest": {
      "type": "object",
      "properties": {
        "endpoint": {
          "type": "string"
        },
        "method": {
          "type": "string",
          "const": "POST"
        },
        "mcpTool": {
          "type": "string",
          "const": "start_guest_checkout"
        },
        "accountRequired": {
          "type": "boolean",
          "const": false
        },
        "paymentMethods": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "card",
              "x402"
            ]
          }
        }
      },
      "required": [
        "endpoint",
        "method",
        "mcpTool",
        "accountRequired",
        "paymentMethods"
      ],
      "additionalProperties": false
    },
    "guideUrl": {
      "type": "string",
      "format": "uri"
    }
  },
  "required": [
    "account",
    "guest",
    "guideUrl"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "get_checkout_options",
    "arguments": {}
  }
}
```

Success response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"account\":{\"signInUrl\":\"https://photoprintagent.com/agents\",\"firebaseConfigUrl\":\"https://photoprintagent.com/firebase-config.json\",\"instructions\":\"Use a verified Firebase ID token with POST /api/agent-keys or create_agent_key. See the account login recipe above.\"},\"guest\":{\"endpoint\":\"/api/guest-sessions\",\"method\":\"POST\",\"mcpTool\":\"start_guest_checkout\",\"accountRequired\":false,\"paymentMethods\":[\"card\",\"x402\"]},\"guideUrl\":\"https://photoprintagent.com/docs/mcp\"}"
      }
    ],
    "structuredContent": {
      "account": {
        "signInUrl": "https://photoprintagent.com/agents",
        "firebaseConfigUrl": "https://photoprintagent.com/firebase-config.json",
        "instructions": "Use a verified Firebase ID token with POST /api/agent-keys or create_agent_key. See the account login recipe above."
      },
      "guest": {
        "endpoint": "/api/guest-sessions",
        "method": "POST",
        "mcpTool": "start_guest_checkout",
        "accountRequired": false,
        "paymentMethods": [
          "card",
          "x402"
        ]
      },
      "guideUrl": "https://photoprintagent.com/docs/mcp"
    }
  }
}
```

Common error response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"error\":{\"httpStatus\":503,\"message\":\"Service temporarily unavailable.\"}}"
      }
    ],
    "structuredContent": {
      "error": {
        "httpStatus": 503,
        "message": "Service temporarily unavailable."
      }
    },
    "isError": true
  }
}
```

### create_agent_key

Mint a 90-day account key using a verified Firebase ID token in Authorization. Guests and existing agent keys cannot mint keys.

Input JSON Schema:

```json
{
  "type": "object",
  "properties": {},
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Output JSON Schema (structuredContent):

```json
{
  "type": "object",
  "properties": {
    "key": {
      "type": "string"
    },
    "id": {
      "type": "string"
    },
    "expiresAt": {
      "type": "integer",
      "minimum": 0,
      "description": "Unix time in milliseconds"
    }
  },
  "required": [
    "key",
    "id",
    "expiresAt"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```

Request:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "create_agent_key",
    "arguments": {}
  }
}
```

Success response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"key\":\"ppa_0000000000000000000000000000000000000000000000000000000000000000\",\"id\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"expiresAt\":1901232000000}"
      }
    ],
    "structuredContent": {
      "key": "ppa_0000000000000000000000000000000000000000000000000000000000000000",
      "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "expiresAt": 1901232000000
    }
  }
}
```

Common error response:

```json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"error\":{\"httpStatus\":403,\"message\":\"Use a verified Firebase ID token to manage keys. See /docs/mcp.\"}}"
      }
    ],
    "structuredContent": {
      "error": {
        "httpStatus": 403,
        "message": "Use a verified Firebase ID token to manage keys. See /docs/mcp."
      }
    },
    "isError": true
  }
}
```

## Binary upload response JSON Schema

Returned by POST /api/photos (HTTP 201), outside the JSON-RPC envelope.

```json
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "width": {
      "type": "integer"
    },
    "height": {
      "type": "integer"
    },
    "quality": {
      "type": "string",
      "enum": [
        "high",
        "reduced"
      ]
    },
    "sourceFormat": {
      "type": "string"
    },
    "sourceImages": {
      "type": "integer",
      "exclusiveMinimum": 0
    },
    "expiresAt": {
      "type": "integer",
      "minimum": 0,
      "description": "Unix time in milliseconds"
    }
  },
  "required": [
    "id",
    "width",
    "height",
    "quality",
    "expiresAt"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
```
