{"openapi":"3.1.0","info":{"title":"PrintYourDuck Quote Request API","version":"1.0.0","description":"Manual quote intake API for custom 3D printing. Payment is never collected at upload. MCP metadata is available at /server.json."},"servers":[{"url":"https://printyourduck.com"}],"paths":{"/api/quote-requests":{"post":{"operationId":"submit_quote_request","summary":"Submit a manual quote request","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","country","materialPreference","quantity","rightsConfirmed","restrictedItemConfirmed","manualQuoteConfirmed","files"],"properties":{"submissionId":{"type":"string","description":"Optional client-generated idempotency key. Reuse the same value when retrying the same user-approved submission."},"name":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"},"marketRegion":{"type":"string","enum":["CANADA","UNITED_STATES","EUROPEAN_UNION","OTHER"],"description":"Optional compatibility hint. The server stores the canonical market from country."},"materialPreference":{"type":"string","enum":["PLA","PETG","TPU_FLEXIBLE","RESIN","NOT_SURE"]},"requestType":{"type":"string","enum":["ONE_OFF","SMALL_BATCH","REPEAT_ORDER"],"default":"ONE_OFF","description":"Optional request-intent hint for one-off, small-batch, or repeat-order quote review. This does not create automatic fulfillment or checkout."},"quantity":{"type":"integer","minimum":1},"desiredDeadline":{"type":"string"},"notes":{"type":"string"},"rightsConfirmed":{"type":"boolean","const":true},"restrictedItemConfirmed":{"type":"boolean","const":true},"manualQuoteConfirmed":{"type":"boolean","const":true},"userSubmissionConfirmed":{"type":"boolean","const":true,"description":"Required for agent-mediated MCP submissions to record explicit user approval. Website submissions may omit it."},"files":{"type":"array","minItems":1,"maxItems":5,"description":"Private uploaded file metadata. Maximum total upload size is 262144000 bytes.","items":{"type":"object","required":["originalFilename","fileSizeBytes","storageUrlOrKey"],"properties":{"originalFilename":{"type":"string","description":"Original STL, STEP, STP, 3MF, OBJ, or ZIP filename."},"storedFilename":{"type":"string"},"fileType":{"type":"string"},"fileSizeBytes":{"type":"integer","minimum":1,"maximum":104857600},"storageUrlOrKey":{"type":"string","description":"Private Vercel Blob key returned by /api/upload. The key must be in the quote-requests/ namespace and exist in Blob storage."},"contentType":{"type":"string"},"contentHash":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"Optional client-provided SHA-256 file metadata. Idempotent retries still need to reuse the same private Blob key."}}}}}}}}},"responses":{"201":{"description":"Quote request accepted for manual review","content":{"application/json":{"schema":{"type":"object","required":["quoteRequestId","status","message","expectedQuoteTime"],"properties":{"quoteRequestId":{"type":"string"},"status":{"type":"string","const":"manual_review_required"},"message":{"type":"string"},"expectedQuoteTime":{"type":"string"}}}}}},"400":{"description":"Invalid request"},"409":{"description":"Submission ID was reused with a different quote request payload"},"429":{"description":"Rate limited"}}}},"/api/quote-requests/{id}/status":{"get":{"operationId":"get_quote_status","summary":"Look up quote status with the quote ID and customer email","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"email","in":"query","required":true,"schema":{"type":"string","format":"email"}}],"responses":{"200":{"description":"Public-safe quote status","content":{"application/json":{"schema":{"type":"object","properties":{"quoteRequestId":{"type":"string"},"status":{"type":"string"},"marketRegion":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"expectedQuoteTime":{"type":"string"},"paymentStatus":{"type":"string"},"shippedAt":{"type":"string","format":"date-time"},"trackingAvailable":{"type":"boolean"}}}}}},"400":{"description":"Missing email"},"404":{"description":"Quote not found"},"429":{"description":"Too many status lookup requests","content":{"application/json":{"schema":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","const":"rate_limited"},"message":{"type":"string"}}}}}}}}}}}