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.

    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"]
    }

    POST /api/quote/calculate

    Returns an itemized estimate for one or more formats. Request and response are JSON.

    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,
      "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 at least one USB drive, matching checkout. Leave it out and the response says how many drives it assumed (see below).
    • 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, "unitPrice": 14.99, "subtotal": 14.99 },
      "returnShipping": { "price": 12.49 },
      "mediaSubtotal": 597.34,
      "total": 624.82,
      "turnaround": { "minDays": 14, "maxDays": 21, "description": "Processing typically takes 2–3 weeks…" },
      "notes": ["FHD/UHD video orders include at least one USB drive ($14.99) …", "…"],
      "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": 1.42, "taxableSubtotal": 14.99, "ratePercent": 9.5,
        "basis": "Combined sales-tax rate for ZIP code 90210 (California, Los Angeles County, …), applied to the taxable portion of the quote ($14.99).",
        "isEstimate": true
      },
      "inboundShipping": {
        "options": [
          { "id": "own_box", "name": "Ship in your own box", "price": 0, "estimatedTotal": 626.24,
            "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": 638.73,
            "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": 626.24
    }

    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, 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": 327.38, "delta": 114.99,
              "usb": { "quantity": 1, "unitPrice": 14.99, "subtotal": 14.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": 427.38, "delta": 214.99,
              "usb": { "quantity": 1, "unitPrice": 14.99, "subtotal": 14.99 },
              "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": 14.99, "total": 227.38 }
      },
      "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 USB drive costs $14.99 (total $227.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.
    • estimatedTotaltotal 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 },
      "prefill": { "selectedCategories": ["videotapes", "film", "photos"], "quantities": { … }, "usbQty": 1, "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/compare

    Same request body as /calculate. 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": 405.37, "delta": 114.99, "usb": { "quantity": 1, "unitPrice": 14.99, "subtotal": 14.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": 505.37, "delta": 214.99, "usb": { "quantity": 1, "unitPrice": 14.99, "subtotal": 14.99 }, "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 by the API. A personal discount code is issued only when a customer saves their quote with their contact details at https://www.capture.com/free-quote.
    • 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. POST /api/quote/calculate and POST /api/quote/compare share a limit of 60 requests per minute per IP and return 429 beyond that.
    • Identify yourself with a descriptive User-Agent and, where possible, the source field.
    • No personal data is accepted or stored by these endpoints. Stored quotes contain only the requested items, the priced result and your User-Agent.
    • 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, 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.
    • All four tools publish an outputSchema in tools/list; structuredContent always validates against it (for quotes: lines, USB, 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 four 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"];
    // get_digitization_quote takes the same { items, usbDrives, postalCode } input as the HTTP API;
    // compare_digitization_options takes { items, usbDrives };
    // retrieve_digitization_quote takes { quoteId } and returns the stored quote

    Discovery