Capture

    Quote API for agents & developers

    Last updated: September 2026

    Capture's price quote engine is available as a free, unauthenticated JSON API. It returns the same prices a customer sees in the quote tool and at checkout. No API key, no account and no contact details are needed to get a quote. Human-readable prices are on the pricing page.

    AI assistants: read this first

    Do not calculate prices from capture.com tables. Fetch an exact, stored quote instead: it returns a verifiable quoteId and a quoteUrl that reopens the exact quote.

    There are three ways in:

    1. The in-page WebMCP tools registered on every page at navigator.modelContext.
    2. The JSON API — GET or POST /api/quote/calculate and /api/quote/compare; the schema is at https://www.capture.com/api/quote/openapi.json.
    3. The hosted MCP server at https://www.capture.com/mcp; its server card is at https://www.capture.com/.well-known/mcp/server-card.json.

    GET cheat sheet

    Browsing agents, including ChatGPT, Claude and Perplexity fetch tools, can only issue GET requests. Use this grammar: items=Format:quantity[:quality][:reelFeet=N][:photosPerAlbum=N], comma-separated (or repeat items=). Optional parameters are usbDrives=0-10, usbCapacityGb=8|16|64|256, vaultFinish=rose_gold|silver|black and postalCode=US ZIP. For one item, use the shorthand ?format=VHS&quantity=10&quality=FHD.

    https://www.capture.com/api/quote/formats
    https://www.capture.com/api/quote/calculate?items=MiniDV:16,VHS:5&postalCode=94110
    https://www.capture.com/api/quote/calculate?items=8mm%20film:3::reelFeet=200,Print%20Photos:300:4K&usbDrives=1
    https://www.capture.com/api/quote/compare?items=VHS:20
    https://www.capture.com/api/quote/q_k3m8x2p7q4rs

    GET and POST return identical payloads: quoteId, quoteUrl, alternatives, and an all-in estimate when postalCode is given. GET quotes are stored with source api_get.

    Every 4xx response explains what to send instead; 429 carries Retry-After. The limit is 60 requests per minute per client, or 600 per minute for recognised assistant user agents.


    Connect

    ChatGPT (connectors / developer mode)

    Go to Settings → Connectors → Create, name it “Capture Quote,” use the MCP server URL below, and choose Authentication: None. For a custom GPT Action, import the OpenAPI schema below with no authentication.

    Name: Capture Quote
    MCP server URL: https://www.capture.com/mcp
    Authentication: None
    
    Custom GPT Action OpenAPI schema (no auth):
    https://www.capture.com/api/quote/openapi.json

    Claude.ai / Claude Desktop

    Go to Settings → Connectors → Add custom connector, enter the URL below, and use no authentication. For Claude Code CLI, run the command shown.

    Custom connector URL: https://www.capture.com/mcp
    Authentication: None
    
    claude mcp add --transport http capture-quote https://www.capture.com/mcp

    Cursor / Windsurf / any mcp.json client

    {
      "mcpServers": {
        "capture-quote": {
          "url": "https://www.capture.com/mcp"
        }
      }
    }

    Plain curl (no MCP client)

    curl -s "https://www.capture.com/api/quote/calculate?items=MiniDV:16,VHS:5&postalCode=94110"
    
    curl -s https://www.capture.com/mcp -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

    Discovery links

    Try it

    Paste this into any assistant:

    I have 16 MiniDV tapes and 5 VHS tapes to digitize, shipping from ZIP 94110. Use Capture's quote API or MCP server (https://www.capture.com/mcp, or GET https://www.capture.com/api/quote/calculate?items=MiniDV:16,VHS:5&postalCode=94110) — do not estimate from the pricing page. Return the quoteId, the total, the all-in estimate with tax and shipping, and the quoteUrl I can open. Then ask whether I want the quote emailed to me; if I say yes, collect my first name, email and phone and call save_digitization_quote (POST /api/quote/<quoteId>/save) and tell me the discount code it returns.

    A correct answer includes a quoteId beginning with q_ that you can verify at https://www.capture.com/api/quote/<quoteId>.


    Endpoints

    GET /api/quote/formats

    Lists every format we digitize with its exact name, accepted aliases, unit, quality tiers with per-unit prices, and any minimum-charge tier. Call this first when you are unsure which format name to send. Cached for five minutes.

    GET https://www.capture.com/api/quote/formats

    Film footage — film formats carry a film object because the per-reel price depends on length: the listed price covers up to baseFeet (100 ft), each foot beyond that adds overagePerFoot ($0.20), and defaultReelFeet (50 ft, a 3" reel) is assumed when a quote item omits reelFeet. Ask the customer for reel sizes and pass reelFeet per item; the quote line's note echoes the footage used.

    {
      "format": "8mm Film", "category": "film", "unit": "reel",
      "qualities": [{ "quality": "SD", "unitPrice": 19.99, "default": true }],
      "film": {
        "baseFeet": 100, "overagePerFoot": 0.20, "defaultReelFeet": 50,
        "pricing": "Per-reel price covers up to 100 ft per reel, then $0.20 per additional foot. Reels are assumed to be 50 ft (3\") when reelFeet is omitted; …"
      },
      "aliases": ["8mmfilm", "regular8", "standard8", "8mmreel", "8mm"]
    }

    GET or POST /api/quote/calculate

    Returns an itemized estimate for one or more formats. POST accepts the JSON request shown below; GET accepts the query form in the cheat sheet. Both return the same JSON payload.

    POST https://www.capture.com/api/quote/calculate
    Content-Type: application/json
    
    {
      "items": [
        { "format": "VHS", "quantity": 12, "quality": "FHD" },
        { "format": "8mm film", "quantity": 3, "reelFeet": 200 },
        { "format": "old photos", "quantity": 300, "quality": "4K" }
      ],
      "usbDrives": 1,
      "usbCapacityGb": 256,
      "vaultFinish": "black",
      "postalCode": "90210",
      "source": "my-agent-name"
    }

    Request fields

    • items[] — required, 1–25 entries.
    • items[].format — required. Fuzzy names are accepted ("vhs tapes", "hi8", "old camcorder tapes" → best match, or a 400 listing suggestions).
    • items[].quantity — required, 1–100,000 (tapes, reels, photos, slides, albums, discs). Fractions are rounded down.
    • items[].quality — optional. Video: SD (default), FHD, UHD. Photos: 2K (default), 4K, 6K. Slides and negatives are always 4K.
    • items[].reelFeet — film only, footage per reel, 1–5,000 (3" = 50 ft, 5" = 200 ft, 7" = 400 ft). Defaults to 50. The per-reel price covers up to 100 ft; longer reels add $0.20 per extra foot (a 400 ft reel at a $19.99 base is $79.99).
    • items[].photosPerAlbum — albums and scrapbooks only, approximate photos per album (1–2,000).
    • usbDrives — optional 0–10. Cloud delivery is always included. FHD/UHD video orders automatically include the USB drive(s) the storage estimate needs, matching checkout. Leave it out and the response says how many drives it assumed (see below).
    • usbCapacityGb — optional 8 ($12.99), 16 ($14.99), 64 ($24.99) or 256 ($49.99), applied to every drive. Leave it out and the quote charges the smallest drive the files fit on. Every response carries estimatedStorageGb, recommendedUsbCapacityGb and recommendedUsbQty, computed exactly as checkout does: each tape counts as 90 minutes (SD ≈ 3 GB, FHD ≈ 8 GB, UHD ≈ 30 GB), film by footage, photos/slides/negatives per image (2K ≈ 3 MB, 4K ≈ 8 MB, 6K ≈ 20 MB), discs 4 GB and memory cards 8 GB each, with 93% of nominal capacity usable. Estimates above 256 GB recommend several 256GB drives. A capacity smaller than the recommendation is honoured but flagged in notes.
    • vaultFinish — optional colour of The Vault, the aluminum keepsake (free engraving) every USB drive ships inside, one per drive. Leave it out or send rose_gold for the included Rose Gold ($0); silver or black is a $2.99 colour upgrade per Vault. Ignored (with a note) when the quote has no USB drive. Whenever there is a drive the response carries a vault line (colour, quantity and upgrade price) next to the usb line; show them together as one item.
    • postalCode — optional 5-digit US ZIP code (ZIP+4 accepted). When present the response adds estimatedTax, inboundShipping and estimatedTotal (see below). Anything that is not a US ZIP returns 400; an optional country field is accepted but must be US. Without postalCode the response is exactly the base quote.
    • source — optional short string identifying your agent or app. Used only for aggregate usage stats.

    Response

    {
      "currency": "USD",
      "lines": [
        { "format": "VHS / S-VHS", "category": "videotapes", "quality": "FHD",
          "quantity": 12, "unit": "tape", "unitPrice": 29.99, "subtotal": 359.88 },
        { "format": "8mm Film", "category": "film", "quality": "SD",
          "quantity": 3, "unit": "reel", "unitPrice": 39.99, "subtotal": 119.97, "note": "200 ft per reel" },
        { "format": "Print Photos", "category": "photos", "quality": "4K",
          "quantity": 300, "unit": "photo", "unitPrice": 0.39, "subtotal": 117.49 }
      ],
      "usb": { "quantity": 1, "capacityGb": 256, "unitPrice": 49.99, "subtotal": 49.99 },
      "vault": { "finish": "black", "quantity": 1, "unitPrice": 2.99, "subtotal": 2.99 },
      "estimatedStorageGb": 100,
      "recommendedUsbCapacityGb": 256,
      "recommendedUsbQty": 1,
      "returnShipping": { "price": 12.49 },
      "mediaSubtotal": 597.34,
      "total": 662.81,
      "turnaround": { "minDays": 14, "maxDays": 21, "description": "Processing typically takes 2–3 weeks…" },
      "notes": ["Estimated digital files: ~100 GB (12 FHD tapes ≈ 96 GB …); a 256GB USB drive is recommended …", "…"],
      "caveats": ["Prices are estimates at current catalog rates. The final price is confirmed after we receive and count your items …", "…"],
      "quoteId": "q_k3m8x2p7q4rs",
      "source": "api",
      "country": "US",
      "catalogVersion": "7c1e2a9d04b5",
      "pricesEffectiveAt": "2026-09-18T22:00:00.000Z",
      "createdAt": "2026-09-18T22:00:00.000Z",
      "expiresAt": "2026-10-18T22:00:00.000Z",
      "quoteUrl": "https://www.capture.com/free-quote?quote=q_k3m8x2p7q4rs&utm_source=ai_agent&utm_medium=ai&utm_campaign=agent_quote_api",
      "checkoutUrl": "https://www.capture.com/setup",
      "supportUrl": "https://www.capture.com/contact",
      "generatedAt": "2026-09-18T22:00:00.000Z",
    
      // The next four fields appear only when postalCode was sent
      "postalCode": "90210",
      "estimatedTax": {
        "amount": 5.70, "taxableSubtotal": 59.98, "ratePercent": 9.5,
        "basis": "Combined sales-tax rate for ZIP code 90210 (California, Los Angeles County, …), applied to the taxable portion of the quote ($59.98).",
        "isEstimate": true
      },
      "inboundShipping": {
        "options": [
          { "id": "own_box", "name": "Ship in your own box", "price": 0, "estimatedTotal": 675.51,
            "serviceName": "Return Shipping", "description": "You pack your media in your own box …" },
          { "id": "prepaid_label", "name": "Prepaid inbound label (2-Way Shipping)", "price": 12.49, "estimatedTotal": 688.00,
            "serviceName": "2-Way Shipping (includes prepaid shipping label to send in your media)", "description": "We email you a prepaid FedEx label …" }
        ],
        "note": "Each option's estimatedTotal is the quote total plus that option's price plus estimated tax. …"
      },
      "estimatedTotal": 675.51
    }

    Quote metadata

    • quoteId — every quote is stored and can be re-read or reopened by this id.
    • expiresAt — quotes stay retrievable for 30 days; after that the id returns 410 and a fresh quote is needed.
    • catalogVersion / pricesEffectiveAt — the price-list fingerprint and the moment the quote was priced. If the catalog changes later, retrieval says so instead of re-pricing.
    • country — always US: prices are in US dollars for orders shipped within the United States, before sales tax.
    • quoteUrl, checkoutUrl, supportUrl — reopen this exact quote, start a blank order, or reach a human about it.

    When quality or USB count is omitted

    Omitting quality prices the default tier (SD video, 2K photos), but the response then carries an alternatives block: for each media family whose quality you left out, the other tiers with the full quote total if those formats were switched to it, the delta versus the returned total, the USB drive that tier requires (sized from that tier's storage estimate unless usbCapacityGb was sent), and a one-line description of what the customer gets. It is only present when at least one format with more than one tier had no quality — a request that specifies quality on every line never gets it. Omitting usbDrives adds usbAssumption: the drive count assumed (0, or 1 when HD video forces one) and the total with one more. Both cases also add a plain-language entry to notes. Use them to ask the customer a clarifying question rather than presenting the default as the only price.

    POST https://www.capture.com/api/quote/calculate
    { "items": [ { "format": "VHS", "quantity": 10 } ] }
    
    {
      "lines": [ { "format": "VHS / S-VHS", "quality": "SD", "quantity": 10, "unitPrice": 19.99, "subtotal": 199.90 } ],
      "usb": null,
      "total": 212.39,
      "alternatives": [
        {
          "family": "video",
          "formats": ["VHS / S-VHS"],
          "assumedQuality": "SD",
          "options": [
            { "quality": "FHD", "total": 362.38, "delta": 149.99,
              "usb": { "quantity": 1, "capacityGb": 256, "unitPrice": 49.99, "subtotal": 49.99 },
              "description": "Full HD (1080p): AI upscaling with de-interlacing, noise reduction and color correction; delivered on USB, so at least one drive is included." },
            { "quality": "UHD", "total": 512.37, "delta": 299.98,
              "usb": { "quantity": 2, "capacityGb": 256, "unitPrice": 49.99, "subtotal": 99.98 },
              "description": "Ultra HD (4K, 2160p): maximum AI upscaling and frame interpolation; delivered on USB, so at least one drive is included." }
          ]
        }
      ],
      "usbAssumption": {
        "quantity": 0,
        "reason": "usbDrives was not specified; assumed cloud download only (no USB drive).",
        "addOne": { "unitPrice": 24.99, "total": 237.38 }
      },
      "estimatedStorageGb": 30,
      "recommendedUsbCapacityGb": 64,
      "recommendedUsbQty": 1,
      "notes": [
        "Quality was not specified for VHS / S-VHS, so this quote uses SD. Confirm the quality tier with the customer before they order; at the other tiers the total would be FHD $327.38, UHD $427.38 (see alternatives).",
        "usbDrives was not specified; assumed cloud download only (no USB drive). Adding one 64GB USB drive costs $24.99 (total $237.38); ask the customer if they want a USB copy.",
        "…"
      ],
      "…": "…"
    }

    All-in estimate (with postalCode)

    • estimatedTax — estimated sales tax from the ZIP code alone (via Stripe Tax), applied to the taxable portion of the quote; ratePercent and basis say how it was derived and isEstimate is always true. The final tax is calculated from the full shipping address at checkout. Only some items are taxable (for example USB drives); when nothing in the quote is taxable the amount is 0 with ratePercent: null. If the tax service cannot be reached, estimatedTax and estimatedTotal are null and notes says tax could not be estimated — the itemized quote is still returned (never a hard failure).
    • inboundShipping.options[] — the inbound choices sold at checkout: own_box (customer ships their own box, price: 0; return shipping is already in total) and prepaid_label (a prepaid FedEx label, priced from the catalog as the difference between 2-Way Shipping and Return Shipping). Each option carries its own estimatedTotal. We never send boxes or packing materials.
    • estimatedTotal — total plus estimatedTax.amount, assuming the customer ships in their own box. Present it as an estimate.
    • The all-in blocks are stored with the quote, so GET /api/quote/:quoteId returns the same estimate the agent showed. Tax rates are cached for a few minutes per ZIP code, so repeated calls in one conversation are cheap; the 60 requests/minute limit still applies.

    Errors — validation problems return 400 with an error message and, for unrecognised formats, suggestions. Malformed JSON, bodies over 5 MB and non-UTF-8 payloads return 400/413/415 with the same { "error": … } shape. Never an empty 200.

    HTTP 400
    {
      "error": "Unknown format \"laserdisc\". Supported formats: Slides, Print Photos, DVD / MiniDVD, VHS / S-VHS, …",
      "suggestions": ["Slides", "Print Photos", "DVD / MiniDVD", "VHS / S-VHS", "…"]
    }

    GET /api/quote/:quoteId

    Returns a stored quote exactly as it was calculated — lines, total, notes and links are never re-priced. Alongside it you get the original request, the wizard prefill and approximations that quoteUrl uses, and a drift report: pricesChanged, human-readable priceChanges and currentPricing (the same request priced at today's rates) whenever the catalog moved since the quote was created. status carries two booleans — leadSaved and orderPlaced — so you can tell whether the customer continued on the site without the response ever exposing lead or order identifiers.

    GET https://www.capture.com/api/quote/q_k3m8x2p7q4rs
    
    {
      ...same fields as the original quote (lines, total, notes, quoteId, expiresAt, quoteUrl, …),
      "request": { "items": [ … ], "usbDrives": 1, "usbCapacityGb": 256, "vaultFinish": "black" },
      "prefill": { "selectedCategories": ["videotapes", "film", "photos"], "quantities": { … }, "usbQty": 1, "usbCapacityGb": 256, "vaultFinish": "black", "categoryQualities": { … } },
      "approximations": ["8mm Film: 200 ft reels are entered as 5" reels in the quote tool …"],
      "currentCatalogVersion": "7c1e2a9d04b5",
      "pricesChanged": false,
      "priceChanges": [],
      "currentPricing": null,
      "status": { "leadSaved": false, "orderPlaced": false }
    }
    
    HTTP 410 (expired)            HTTP 404 (unknown)
    { "error": "Quote q_… expired on 2026-10-18T22:00:00.000Z. Request a fresh quote …",
      "code": "quote_expired", "quoteId": "q_k3m8x2p7q4rs" }

    Unknown ids return 404 with code: "quote_not_found"; expired ids return 410 with code: "quote_expired". Both messages tell the agent to request a fresh quote.

    POST /api/quote/:quoteId/save

    The one endpoint that takes personal data, and the only way an agent can put a quote in the customer's inbox. Send the customer's firstName, email and phone (all required — phone is required on our own quote form too) plus optional notes for our team. It creates the same lead our quote tool creates, emails the customer the itemized quote with a personal single-use discount code (10–25% off, tiered by quote total, valid 14 days), and starts the same short follow-up sequence by email/SMS. Only call it after the customer has explicitly agreed to share those details and to receive the emailed quote and follow-ups; never with guessed or placeholder details.

    POST https://www.capture.com/api/quote/q_k3m8x2p7q4rs/save
    Content-Type: application/json
    
    {
      "firstName": "Dana",
      "email": "dana@example.com",
      "phone": "+1 555 010 1234",
      "notes": "About 20 of the tapes are PAL; wants everything before December.",
      "source": "my-assistant"
    }
    {
      "quoteId": "q_k3m8x2p7q4rs",
      "discountCode": "QUOTE10-7KQ2MZ",
      "discountPercent": 10,
      "discountExpiresAt": "2026-10-02T18:04:11.000Z",
      "emailSentTo": "dana@example.com",
      "quoteUrl": "https://www.capture.com/free-quote?quote=q_k3m8x2p7q4rs&promo=QUOTE10-7KQ2MZ&utm_source=ai_agent&utm_medium=ai&utm_campaign=agent_quote_api",
      "checkoutUrl": "https://www.capture.com/setup?promo=QUOTE10-7KQ2MZ",
      "alreadySaved": false
    }
    • quoteUrl and checkoutUrl come back with the discount code pre-applied (promo=); pass them through unchanged. Tell the customer the code, the percentage and discountExpiresAt.
    • Idempotent per quote and email: repeating the call returns the existing code with alreadySaved: true and sends no second email. A different email on the same quote is treated as a new customer.
    • Errors follow the retrieve endpoint: 404 quote_not_found, 410 quote_expired (ask for a fresh quote), 409 save_in_progress if the same quote + email is mid-save in another request (retry in a moment), and 400 validation errors that name the offending field (e.g. "phone: phone is required"). discountCode is null only if code generation failed; the email still goes out.
    • Afterwards GET /api/quote/:quoteId reports status.leadSaved: true. Nothing here places an order or charges anything.

    GET or POST /api/quote/compare

    POST accepts the same request body as /calculate; GET accepts the same query form as the cheat sheet. Instead of one quote it returns one complete total per quality tier for each media family in the request — video (SD, FHD, UHD) and photo (2K, 4K, 6K for photos, slides, negatives, albums) — so you can show the customer "SD $X / FHD $Y / UHD $Z" and let them choose.

    POST https://www.capture.com/api/quote/compare
    Content-Type: application/json
    
    {
      "items": [
        { "format": "VHS", "quantity": 10 },
        { "format": "old photos", "quantity": 250 }
      ]
    }
    • Each tier's total is a full quote: every format in that family switched to the tier, all other items priced as requested, the USB drive checkout requires for FHD/UHD video added, and return shipping included. Totals equal what /calculate returns for the same items with that quality set explicitly.
    • default marks the tier used when quality is omitted; delta is measured against that tier's total.
    • notOfferedFor lists formats in the family that do not come in that tier (film and discs are SD-only; albums start at 4K); they keep their requested or default quality in that total.
    • asRequested is the total for the body exactly as sent, with the quality actually applied to each line.

    Response

    {
      "currency": "USD",
      "asRequested": { "total": 290.38, "qualities": [
        { "format": "VHS / S-VHS", "quality": "SD" }, { "format": "Print Photos", "quality": "2K" } ] },
      "families": [
        {
          "family": "video", "formats": ["VHS / S-VHS"], "defaultQuality": "SD",
          "tiers": [
            { "quality": "SD",  "default": true,  "total": 290.38, "delta": 0,      "usb": null, "notOfferedFor": [],
              "description": "Standard definition (480p): a faithful copy of the original tape, delivered by cloud download." },
            { "quality": "FHD", "default": false, "total": 440.37, "delta": 149.99, "usb": { "quantity": 1, "capacityGb": 256, "unitPrice": 49.99, "subtotal": 49.99 }, "notOfferedFor": [],
              "description": "Full HD (1080p): AI upscaling with de-interlacing, noise reduction and color correction; delivered on USB, so at least one drive is included." },
            { "quality": "UHD", "default": false, "total": 590.36, "delta": 299.98, "usb": { "quantity": 2, "capacityGb": 256, "unitPrice": 49.99, "subtotal": 99.98 }, "notOfferedFor": [],
              "description": "Ultra HD (4K, 2160p): maximum AI upscaling and frame interpolation; delivered on USB, so at least one drive is included." }
          ]
        },
        {
          "family": "photo", "formats": ["Print Photos"], "defaultQuality": "2K",
          "tiers": [
            { "quality": "2K", "default": true,  "total": 290.38, "delta": 0,  "usb": null, "notOfferedFor": [], "description": "Good quality: …" },
            { "quality": "4K", "default": false, "total": 310.38, "delta": 20, "usb": null, "notOfferedFor": [], "description": "High quality: …" },
            { "quality": "6K", "default": false, "total": 350.38, "delta": 60, "usb": null, "notOfferedFor": [], "description": "Ultra quality: …" }
          ]
        }
      ],
      "turnaround": { "minDays": 14, "maxDays": 21, "description": "…" },
      "notes": ["Each tier total is a complete quote: …", "…"],
      "caveats": ["…"],
      "generatedAt": "2026-09-18T22:00:00.000Z"
    }

    How to present a quote

    • Show total as an estimate before sales tax, and relay the caveats — the final price is confirmed after we receive and count the items.
    • Quote the turnaround as 2–3 weeks from the day the items arrive at our facility.
    • Promo codes are never applied to a calculated quote. A personal discount code is issued only when the customer saves the quote with their contact details — on the site at https://www.capture.com/free-quote, or through POST /api/quote/:quoteId/save / save_digitization_quote once they've asked you to email it and agreed to the follow-ups.
    • If the customer shares a US ZIP code, send it as postalCode and show estimatedTotal as an all-in estimate, saying tax is estimated from the ZIP only and inbound shipping depends on the option they pick at checkout.
    • If the customer did not say which quality they want, do not present the default-tier total as the price. Use alternatives (or /api/quote/compare) to offer the tiers side by side and ask; do the same for USB drives when usbAssumption is present.
    • Send customers to quoteUrl to continue: it reopens the stored quote (/free-quote?quote=<quoteId>) in our quote tool with the quoted formats, quantities, quality tiers and USB count already filled in, so they only confirm and enter contact details. Always pass the URL through unchanged. The quote tool is slightly coarser than this API (one quality per media type, standard film reel sizes, no film quality picker); whenever that changes the reopened total, the response says so in notes — relay those; the same notes are shown to the customer on the reopened page. If prices changed after the quote was created, the page says so and shows current pricing next to the original figure. checkoutUrl is a plain link to start an order from scratch; supportUrl is where to send questions about a quote. Prices are in US dollars for orders shipped within the United States.
    • Mention expiresAt when it matters ("this quote is valid until …"). To revisit a quote later, call GET /api/quote/:quoteId (or the retrieve_digitization_quote MCP tool) rather than recalculating, so the customer sees the same numbers.

    Limits & etiquette

    • No authentication. GET and POST requests to /api/quote/calculate and /api/quote/compare, plus GET /api/quote/:quoteId and POST /api/quote/:quoteId/save, share a limit of 60 requests per minute per client, raised to 600 per minute for recognised assistant user agents, and return 429 with Retry-After beyond that.
    • Identify yourself with a descriptive User-Agent and, where possible, the source field.
    • The quoting endpoints accept and store no personal data: stored quotes contain only the requested items, the priced result and your User-Agent. Personal data enters only through POST /api/quote/:quoteId/save, with the customer's explicit consent, and is handled under our privacy policy.
    • CORS — /api/quote/* and /mcp answer OPTIONS preflight and send Access-Control-Allow-Origin: *, so browser-based agents and MCP clients can call them from any origin. Allowed request headers include Content-Type, Accept, Mcp-Session-Id and Mcp-Protocol-Version; Mcp-Session-Id and Mcp-Protocol-Version are exposed to scripts. No other capture.com routes are opened up.

    Quick start

    curl -s https://www.capture.com/api/quote/calculate \
      -H 'Content-Type: application/json' \
      -d '{"items":[{"format":"VHS","quantity":10}]}'

    MCP server (hosted)

    The same tools are served over the Model Context Protocol (Streamable HTTP, stateless, JSON responses) at https://www.capture.com/mcp. Add it as a remote MCP server in any MCP-capable client — no authentication, no session handshake beyond the standard initialize call.

    {
      "mcpServers": {
        "capture-quote": { "type": "streamable-http", "url": "https://www.capture.com/mcp" }
      }
    }
    
    # or, by hand:
    curl -s https://www.capture.com/mcp \
      -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_digitization_quote","arguments":{"items":[{"format":"VHS","quantity":10}]}}}'
    
    # compare SD / FHD / UHD totals for the same tapes:
    curl -s https://www.capture.com/mcp \
      -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"compare_digitization_options","arguments":{"items":[{"format":"VHS","quantity":10}]}}}'
    • list_digitization_formats — no arguments; returns the format catalog, including the film footage rule described above.
    • get_digitization_quote — takes { items, usbDrives, usbCapacityGb, vaultFinish, postalCode } exactly as the HTTP API and returns the stored quote (with quoteId, expiresAt, quoteUrl …, including alternatives and usbAssumption when applicable, and the all-in blocks when a postalCode was sent) as both text and structuredContent. Unknown formats come back as a tool error with suggestions.
    • compare_digitization_options — same input; returns the /api/quote/compare payload (one total per quality tier per media family) as text and structuredContent.
    • retrieve_digitization_quote — takes { quoteId } and returns the stored quote plus the price-drift report, exactly like GET /api/quote/:quoteId. Unknown or expired ids come back as a tool error with code quote_not_found or quote_expired.
    • save_digitization_quote — takes { quoteId, firstName, email, phone, notes? } and does what POST /api/quote/:quoteId/save does: creates the lead, emails the quote with its discount code, starts the follow-up sequence and returns discountCode, discountPercent, discountExpiresAt, emailSentTo, quoteUrl/checkoutUrl (code pre-applied) and alreadySaved. It is annotated readOnlyHint: false / idempotentHint: true, so well-behaved clients confirm with the user before calling it; its description also tells the model to collect consent first.
    • All five tools publish an outputSchema in tools/list; structuredContent always validates against it (for quotes: lines, USB with capacity, optional Vault line, storage estimate and recommended capacity, return shipping, totals, turnaround, notes, caveats, optional alternatives / usbAssumption / all-in blocks, quoteId, expiresAt, catalogVersion, quoteUrl, checkoutUrl, supportUrl, generatedAt, and for retrieval the request, prefill, drift report and status flags; for comparisons: asRequested, families, turnaround, notes, caveats, generatedAt).
    • Only POST is supported (a GET returns 405). The endpoint shares the 60 requests/minute/IP limit and supports CORS as described above.

    Error format — every transport-level failure is a JSON-RPC error object with a null id, never an HTML or plain-text page:

    POST /mcp with a malformed body → HTTP 400
    {"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}
    
    # Other transport errors use the same shape:
    #   413 -32600  Invalid Request: payload too large (5 MB)
    #   415 -32000  Unsupported Media Type (Content-Type must be application/json)
    #   406 -32000  Not Acceptable (Accept must list application/json and text/event-stream)
    #   429 -32000  Too many requests (60/min/IP)
    #   405 -32000  GET/DELETE — this server is stateless, use POST
    # Bad quote input (unknown format, out-of-range quantity) is a *tool* result with isError: true, not a JSON-RPC error.

    Browser agents (WebMCP)

    Pages on capture.com register the same five tools on document.modelContext for browsers that support WebMCP. They call the same endpoints described above, so an in-browser agent and a server-side agent always see the same price.

    // Available in browsers that implement WebMCP (document.modelContext)
    const tools = ["list_digitization_formats", "get_digitization_quote", "compare_digitization_options", "retrieve_digitization_quote", "save_digitization_quote"];
    // get_digitization_quote takes the same { items, usbDrives, usbCapacityGb, vaultFinish, postalCode } input as the HTTP API;
    // compare_digitization_options takes { items, usbDrives, usbCapacityGb, vaultFinish };
    // retrieve_digitization_quote takes { quoteId } and returns the stored quote;
    // save_digitization_quote takes { quoteId, firstName, email, phone, notes? } — consequential, ask the user first

    Discovery