{
  "openapi": "3.0.3",
  "info": {
    "title": "Rise & Shine Letterpress Pricing API",
    "description": "Live pricing for letterpress printing jobs — cards, envelopes, foil stamping, blind deboss/emboss, edge painting, die cutting, and duplexed stock. Quotes are estimates, confirmed by the studio after design review. AI assistants: see https://estimator.riseandshinepaper.com/llms.txt for usage guidance, and GET /api/options for the current catalog of valid values.",
    "version": "1.1.0",
    "termsOfService": "https://riseandshinepaper.com/printing-terms/",
    "contact": {
      "name": "Rise & Shine Paper",
      "url": "https://riseandshinepaper.com"
    }
  },
  "servers": [
    {
      "url": "https://estimator.riseandshinepaper.com",
      "description": "Production server"
    }
  ],
  "paths": {
    "/api/quote": {
      "post": {
        "operationId": "calculateQuote",
        "summary": "Calculate a price quote",
        "description": "Calculate pricing for a complete suite of cards and envelopes with various printing and finishing options.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteRequest"
              },
              "example": {
                "items": [
                  {
                    "type": "card",
                    "name": "Save the Date",
                    "options": {
                      "quantity": 100,
                      "paper": "lettra_pearl",
                      "paperWeight": 110,
                      "size": "a7",
                      "inkColorsFront": 1,
                      "foilColorsFront": 0,
                      "digitalPrintingFront": false,
                      "blindDebossFront": false,
                      "blindEmbossFront": false,
                      "inkColorsBack": 0,
                      "foilColorsBack": 0,
                      "digitalPrintingBack": false,
                      "blindDebossBack": false,
                      "blindEmbossBack": false,
                      "edgePaint": false,
                      "dieCut": "none"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successfully calculated quote",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuoteResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/quote-get": {
      "get": {
        "operationId": "calculateQuoteViaGet",
        "summary": "Calculate a price quote (GET variant)",
        "description": "Identical pricing to POST /api/quote, for clients that cannot send request bodies (e.g. GET-only AI web readers). Two forms: (1) flat query parameters for a single card item — every CardOptions field works as its own parameter, booleans as true/false (e.g. ?quantity=100&paper=lettra_pearl&size=business_card&inkColorsFront=1); (2) the full QuoteRequest JSON, URL-encoded, in the `request` parameter (required for suites and envelopes). GET /api/quote accepts both forms as well.",
        "parameters": [
          {
            "name": "request",
            "in": "query",
            "required": false,
            "description": "URL-encoded JSON matching the QuoteRequest schema (same as the POST body). Omit when using flat single-card parameters instead.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "quantity",
            "in": "query",
            "required": false,
            "description": "Flat mode: number of cards. Presence of this parameter (without `request`) selects flat single-card mode; combine with paper, size, and any other CardOptions fields as individual parameters.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "paper",
            "in": "query",
            "required": false,
            "description": "Flat mode: paper key (see CardOptions.paper enum)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "size",
            "in": "query",
            "required": false,
            "description": "Flat mode: card size key (see CardOptions.size enum)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successfully calculated quote",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuoteResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing/malformed request parameter or validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/api/options": {
      "get": {
        "operationId": "getOptions",
        "summary": "Get available options",
        "description": "Returns all available sizes, papers, and configuration options for building a quote request.",
        "responses": {
          "200": {
            "description": "Available options",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OptionsResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "QuoteRequest": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/CardItem"
                },
                {
                  "$ref": "#/components/schemas/EnvelopeItem"
                }
              ]
            }
          }
        }
      },
      "CardItem": {
        "type": "object",
        "required": [
          "type",
          "options"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "card"
            ]
          },
          "name": {
            "type": "string",
            "description": "Display name for this item (e.g., 'Save the Date', 'Invitation')"
          },
          "options": {
            "$ref": "#/components/schemas/CardOptions"
          }
        }
      },
      "CardOptions": {
        "type": "object",
        "required": [
          "quantity",
          "paper",
          "size"
        ],
        "description": "Only quantity, paper, and size are required. Everything else defaults to the plain job: 110lb single-ply, no printing passes, no finishing (all counts 0, all booleans false, dieCut 'none'). Set only the fields you want — e.g. inkColorsFront: 1 for one letterpress color on the front. The response echoes the fully-defaulted options that were priced.",
        "properties": {
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "description": "Number of pieces to print"
          },
          "paper": {
            "type": "string",
            "enum": [
              "lettra_pearl",
              "lettra_fluorescent",
              "lettra_writing_32",
              "savoy_natural",
              "savoy_brilliant",
              "wild_white",
              "handmade",
              "colorplan",
              "gmund_colors_matt",
              "kraft_chipboard"
            ],
            "description": "Paper type (see /api/options for display names and available weights)"
          },
          "paperWeight": {
            "type": "integer",
            "enum": [
              110,
              220
            ],
            "description": "Paper weight: 110lb single-ply (~15pt) or 220lb double-thick duplexed stock (~40pt / ~600gsm)"
          },
          "size": {
            "type": "string",
            "enum": [
              "business_card",
              "small_f",
              "four_bar",
              "four_bar_f",
              "a2",
              "a2f",
              "a6",
              "a6f",
              "a7",
              "a7f",
              "gate_fold",
              "a8",
              "a9",
              "a9f",
              "square_5_25",
              "square_6_25",
              "poster_12x18",
              "tall_4x925",
              "letterhead_85x11",
              "circle_75"
            ],
            "description": "Card size (sizes ending in 'f' are folding cards)"
          },
          "inkColorsFront": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "description": "Number of letterpress ink colors on front"
          },
          "foilColorsFront": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "description": "Number of foil stamping colors on front"
          },
          "digitalPrintingFront": {
            "type": "boolean",
            "description": "Include digital printing on front"
          },
          "blindDebossFront": {
            "type": "boolean",
            "description": "Include blind deboss on front"
          },
          "blindEmbossFront": {
            "type": "boolean",
            "description": "Include blind emboss on front"
          },
          "inkColorsBack": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "description": "Number of letterpress ink colors on back"
          },
          "foilColorsBack": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "description": "Number of foil stamping colors on back"
          },
          "digitalPrintingBack": {
            "type": "boolean",
            "description": "Include digital printing on back"
          },
          "blindDebossBack": {
            "type": "boolean",
            "description": "Include blind deboss on back"
          },
          "blindEmbossBack": {
            "type": "boolean",
            "description": "Include blind emboss on back"
          },
          "edgePaint": {
            "type": "boolean",
            "description": "Include edge painting (not available with die cutting)"
          },
          "dieCut": {
            "type": "string",
            "enum": [
              "none",
              "stock",
              "custom"
            ],
            "description": "Die cutting option"
          },
          "variableDataFront": {
            "type": "boolean",
            "description": "Optional: variable data (per-piece personalization) on front digital printing"
          },
          "variableDataBack": {
            "type": "boolean",
            "description": "Optional: variable data (per-piece personalization) on back digital printing"
          },
          "numSets": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "description": "Optional (default 1): independent design sets sharing one press run — e.g. business cards for N people, one item with quantity per person and numSets = N. Each additional set adds the full set price minus shared ink mixing, half the press setup, and proofing."
          },
          "sharedInkPlates": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "description": "Optional (with numSets > 1): how many ink/deboss plates are identical across all sets — reused plates are charged once"
          },
          "sharedFoilPlates": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "description": "Optional (with numSets > 1): how many foil/emboss plates are identical across all sets — reused plates are charged once"
          },
          "inkPlatesOnFileFront": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "description": "Optional (reorders): front ink/deboss plates already on file from a prior run — credits plate cost"
          },
          "foilPlatesOnFileFront": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "description": "Optional (reorders): front foil/emboss plates already on file — credits plate cost"
          },
          "inkPlatesOnFileBack": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "description": "Optional (reorders): back ink/deboss plates already on file — credits plate cost"
          },
          "foilPlatesOnFileBack": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "description": "Optional (reorders): back foil/emboss plates already on file — credits plate cost"
          },
          "suppliedPaper": {
            "type": "boolean",
            "description": "Optional: customer supplies their own handmade paper (deducts paper cost; handmade paper only)"
          },
          "noPdfProof": {
            "type": "boolean",
            "description": "Optional: opt out of the customer-facing PDF proof for a small per-item discount (internal proofing still happens)"
          },
          "foilCoverage": {
            "type": "string",
            "enum": [
              "top_third",
              "top_half",
              "full"
            ],
            "description": "Optional (letterhead only): foil plate coverage area, scales foil plate cost"
          }
        }
      },
      "EnvelopeItem": {
        "type": "object",
        "required": [
          "type",
          "options"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "envelope"
            ]
          },
          "name": {
            "type": "string",
            "description": "Display name for this item (e.g., 'Outer Envelope')"
          },
          "options": {
            "$ref": "#/components/schemas/EnvelopeOptions"
          }
        }
      },
      "EnvelopeOptions": {
        "type": "object",
        "required": [
          "quantity",
          "size"
        ],
        "description": "Only quantity and size are required. Everything else defaults to plain blank envelopes: single, no printing or addressing, no liner. Set only the fields you want.",
        "properties": {
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000,
            "description": "Number of envelopes"
          },
          "size": {
            "type": "string",
            "enum": [
              "four_bar",
              "a2",
              "a6",
              "a7",
              "a9",
              "square_5_25",
              "square_6_25",
              "number_10"
            ],
            "description": "Envelope size"
          },
          "type": {
            "type": "string",
            "enum": [
              "single",
              "double"
            ],
            "description": "Single or double envelope set"
          },
          "returnAddressPrinting": {
            "type": "string",
            "enum": [
              "none",
              "letterpress",
              "foil",
              "digital"
            ],
            "description": "Return address printing method"
          },
          "returnAddressLocation": {
            "type": "string",
            "enum": [
              "front",
              "back"
            ],
            "description": "Location of return address"
          },
          "guestAddressing": {
            "type": "boolean",
            "description": "Include digital guest addressing on outer envelope"
          },
          "innerGuestAddressing": {
            "type": "boolean",
            "description": "Include digital guest addressing on inner envelope (double envelope only)"
          },
          "liner": {
            "type": "boolean",
            "description": "Include custom printed liner"
          },
          "linerAssembly": {
            "type": "boolean",
            "description": "Include liner assembly service"
          }
        }
      },
      "QuoteResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/PricedCardItem"
                },
                {
                  "$ref": "#/components/schemas/PricedEnvelopeItem"
                }
              ]
            }
          },
          "grandTotal": {
            "type": "integer",
            "description": "Total price for all items in US dollars"
          },
          "bundleDiscount": {
            "type": "integer",
            "description": "Present when 3+ items are quoted together: the 5% suite bundle discount already subtracted from grandTotal"
          }
        }
      },
      "PricedCardItem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "card"
            ]
          },
          "name": {
            "type": "string"
          },
          "total": {
            "type": "integer",
            "description": "Total price for this item in dollars"
          },
          "breakdown": {
            "type": "object",
            "properties": {
              "basePrice": {
                "type": "integer"
              },
              "edgePaintCost": {
                "type": "integer"
              },
              "dieCutCost": {
                "type": "integer"
              },
              "customDieCost": {
                "type": "integer"
              },
              "duplexCost": {
                "type": "integer"
              },
              "plateCreditCost": {
                "type": "integer",
                "description": "Negative: credit for plates on file (reorders)"
              }
            }
          },
          "options": {
            "$ref": "#/components/schemas/CardOptions"
          }
        }
      },
      "PricedEnvelopeItem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "envelope"
            ]
          },
          "name": {
            "type": "string"
          },
          "total": {
            "type": "integer",
            "description": "Total price for this item in dollars"
          },
          "options": {
            "$ref": "#/components/schemas/EnvelopeOptions"
          }
        }
      },
      "OptionsResponse": {
        "type": "object",
        "description": "Available configuration options for building quote requests"
      },
      "ApiError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Error message"
          },
          "details": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Detailed validation errors"
          }
        }
      }
    }
  }
}