{
  "openapi": "3.1.0",
  "info": {
    "title": "Korea Sets order requests",
    "version": "0.2.0",
    "description": "Read the open offer (sets, Korean promo dates, estimated delivered totals) and send an order request. No payment at request time: a person emails the confirmed delivered total and buys only after the customer accepts. Totals are estimates until confirmed; never present them as reserved or confirmed. Our checked promises and their assumptions are in GET /api/order guarantees. Early pilot · No order has shipped yet. Korean prices were read on Olive Young Korea's own pages but not yet confirmed by a person, and most are promo prices with an end date (promo.ends_at, KST), so every total here is an estimate until a person confirms it."
  },
  "servers": [
    {
      "url": "https://chainshift-preview.vercel.app"
    }
  ],
  "paths": {
    "/api/order": {
      "get": {
        "operationId": "getOffer",
        "summary": "Open offer with sets, promo status and estimated totals",
        "description": "Computed at request time: an ended promo shows requote_needed with no amount. Each read returns a trace_id: send it back in the order request so the read and the request link up.",
        "parameters": [
          {
            "name": "offer",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "kr-sets"
              ]
            }
          },
          {
            "name": "trace_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^it_[0-9a-f]{24}$",
              "description": "the trace_id an earlier answer returned (it_ + 24 hex characters; GET /api/order returns one on every read, clarify_request too), so the steps of one request link up; not personal data. Leave it out if you have none."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "offer",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "404": {
            "description": "unknown offer"
          }
        }
      }
    },
    "/api/order/intake": {
      "post": {
        "operationId": "sendOrderRequest",
        "summary": "Send an order request or a notify request (no payment)",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\"?[A-Za-z0-9._:-]{8,128}\"?$"
            },
            "description": "optional retry key for this one request (8-128 letters, digits, . _ : -; a UUID works), in the body as idempotency_key or as the Idempotency-Key header. The same key with the same request returns the same order_id (watch_id for notify), so a retry after a timeout does not make a second request. Without it, trace_id is used as the key when sent. The response field idempotency.basis says which key was used."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "ship_country",
                  "contact_email"
                ],
                "description": "Send bundle_sku + quantity for one set, or items for several sets in one parcel. Never send names, street addresses, phone numbers or payment details.",
                "properties": {
                  "offer": {
                    "type": "string",
                    "enum": [
                      "kr-sets"
                    ]
                  },
                  "bundle_sku": {
                    "type": "string",
                    "enum": [
                      "CS-KR-BANILACO-CLEANITZERO-DUO-JOAN",
                      "CS-KR-HUXLEY-PERFUME-HANDCREAM-MONCHHICHI",
                      "CS-KR-MEDIHEAL-HYPERGEL-8P1",
                      "CS-KR-VT-REEDLESHOT100-POKEMON"
                    ]
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "bundle_sku",
                        "quantity"
                      ],
                      "properties": {
                        "bundle_sku": {
                          "type": "string",
                          "enum": [
                            "CS-KR-BANILACO-CLEANITZERO-DUO-JOAN",
                            "CS-KR-HUXLEY-PERFUME-HANDCREAM-MONCHHICHI",
                            "CS-KR-MEDIHEAL-HYPERGEL-8P1",
                            "CS-KR-VT-REEDLESHOT100-POKEMON"
                          ]
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1
                        }
                      }
                    }
                  },
                  "ship_country": {
                    "type": "string",
                    "enum": [
                      "CA",
                      "JP",
                      "US"
                    ]
                  },
                  "ship_region": {
                    "type": "string",
                    "maxLength": 40,
                    "description": "state/province/prefecture name only"
                  },
                  "contact_email": {
                    "type": "string",
                    "format": "email"
                  },
                  "language": {
                    "type": "string",
                    "description": "reply language tag, e.g. en, ja, es"
                  },
                  "request": {
                    "type": "string",
                    "enum": [
                      "order",
                      "notify"
                    ],
                    "default": "order"
                  },
                  "client_ref": {
                    "type": "string",
                    "maxLength": 64
                  },
                  "idempotency_key": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9._:-]{8,128}$",
                    "description": "optional retry key for this one request (8-128 letters, digits, . _ : -; a UUID works), in the body as idempotency_key or as the Idempotency-Key header. The same key with the same request returns the same order_id (watch_id for notify), so a retry after a timeout does not make a second request. Without it, trace_id is used as the key when sent. The response field idempotency.basis says which key was used."
                  },
                  "trace_id": {
                    "type": "string",
                    "pattern": "^it_[0-9a-f]{24}$",
                    "description": "the trace_id an earlier answer returned (it_ + 24 hex characters; GET /api/order returns one on every read, clarify_request too), so the steps of one request link up; not personal data. Leave it out if you have none."
                  },
                  "provenance": {
                    "type": "object",
                    "description": "Optional provenance: where each value came from, keyed by bundle_sku or items, quantity and ship_country, each one of just_confirmed (the user said or confirmed it in this conversation), known_before, assumed or unknown. The request is still received; any value not just_confirmed comes back in unconfirmed with questions (kind confirm): ask the user before our email arrives.",
                    "additionalProperties": false,
                    "properties": {
                      "bundle_sku": {
                        "type": "string",
                        "enum": [
                          "just_confirmed",
                          "known_before",
                          "assumed",
                          "unknown"
                        ]
                      },
                      "items": {
                        "type": "string",
                        "enum": [
                          "just_confirmed",
                          "known_before",
                          "assumed",
                          "unknown"
                        ]
                      },
                      "quantity": {
                        "type": "string",
                        "enum": [
                          "just_confirmed",
                          "known_before",
                          "assumed",
                          "unknown"
                        ]
                      },
                      "ship_country": {
                        "type": "string",
                        "enum": [
                          "just_confirmed",
                          "known_before",
                          "assumed",
                          "unknown"
                        ]
                      }
                    }
                  },
                  "condition": {
                    "type": "object",
                    "additionalProperties": false,
                    "description": "Optional on request \"notify\": condition = {sku, ship_to, max_delivered_total_usd, min_hours_valid, until}. sku and ship_to repeat bundle_sku and ship_country (leave them out or send the same values). max_delivered_total_usd: a number in USD with up to 2 decimals, matched against sets[].delivered_total_per_unit[ship_to].amount (one set, shipping and duties included). min_hours_valid: whole hours from 1 to 72; matches only while the published quote (sets[].quote_valid_until) still holds at least that long. until: a date (YYYY-MM-DD, Korea time) up to 90 days ahead; without it the request lasts 30 days. Without max_delivered_total_usd or min_hours_valid it is a plain notify (emailed when the set can be ordered again). A sold-out set or an ended promo never matches. Each new quote_id that matches is checked by a person before anything is sent; the email states the quote_id and the total, which stays an estimate until a person confirms it. We email you when it matches. Nothing is bought automatically.",
                    "properties": {
                      "sku": {
                        "type": "string",
                        "enum": [
                          "CS-KR-BANILACO-CLEANITZERO-DUO-JOAN",
                          "CS-KR-HUXLEY-PERFUME-HANDCREAM-MONCHHICHI",
                          "CS-KR-MEDIHEAL-HYPERGEL-8P1",
                          "CS-KR-VT-REEDLESHOT100-POKEMON"
                        ]
                      },
                      "ship_to": {
                        "type": "string",
                        "enum": [
                          "CA",
                          "JP",
                          "US"
                        ]
                      },
                      "max_delivered_total_usd": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 1000,
                        "multipleOf": 0.01
                      },
                      "min_hours_valid": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 72
                      },
                      "until": {
                        "type": "string",
                        "format": "date"
                      }
                    }
                  },
                  "options": {
                    "type": "object",
                    "description": "choices for sets that have them, keyed by bundle_sku; e.g. {\"<bundle_sku>\": {\"scent\": \"Cotton Haze\"}}; values listed in offer.json sets[].options",
                    "additionalProperties": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string"
                      }
                    }
                  },
                  "agent": {
                    "oneOf": [
                      {
                        "type": "string",
                        "maxLength": 80
                      },
                      {
                        "type": "object",
                        "additionalProperties": false,
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "operator": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          }
                        }
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "received (order_id, request_summary, quote_valid_until, status_url) or watching (notify); with provenance also unconfirmed, and questions (kind confirm) for values not just_confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid or extra fields (bad_idempotency_key · idempotency_key_mismatch when the header and the body key differ · bad_provenance)"
          },
          "403": {
            "description": "agents only"
          },
          "409": {
            "description": "not open, closed or limit reached"
          },
          "413": {
            "description": "body_too_large (over 16384 bytes)"
          },
          "503": {
            "description": "requests not open on this server yet"
          }
        }
      }
    },
    "/api/order/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Service status: status (ok or degraded), version, api_version, time, checks",
        "description": "No secrets: checks only says whether the catalog loads and whether order requests are open.",
        "responses": {
          "200": {
            "description": "status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "version",
                    "api_version",
                    "time"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "degraded"
                      ]
                    },
                    "version": {
                      "type": "string"
                    },
                    "api_version": {
                      "type": "string"
                    },
                    "time": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "checks": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/order/changes.json": {
      "get": {
        "operationId": "getChanges",
        "summary": "Restocks, sell-outs and Korean price changes",
        "responses": {
          "200": {
            "description": "changes feed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/api/order/v1/products": {
      "get": {
        "operationId": "listProducts",
        "summary": "Products (sets) with search and filters",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ship_country",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "CA",
                "JP",
                "US"
              ]
            }
          },
          {
            "name": "max_total",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            },
            "description": "delivered total cap in USD for ship_country (default US)"
          },
          {
            "name": "korea_only",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "only sets whose same configuration we did not find at Olive Young US"
          }
        ],
        "responses": {
          "200": {
            "description": "products",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/api/order/v1/products/{product_id}": {
      "get": {
        "operationId": "getProduct",
        "summary": "One product with its offer",
        "parameters": [
          {
            "name": "product_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "product",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "404": {
            "description": "unknown product"
          }
        }
      }
    },
    "/api/order/v1/offers/{sku}": {
      "get": {
        "operationId": "getOfferBySku",
        "summary": "Offer for one set: availability (unverified · requote_needed · sold_out), delivered totals, shipping",
        "parameters": [
          {
            "name": "sku",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "CS-KR-BANILACO-CLEANITZERO-DUO-JOAN",
                "CS-KR-HUXLEY-PERFUME-HANDCREAM-MONCHHICHI",
                "CS-KR-MEDIHEAL-HYPERGEL-8P1",
                "CS-KR-VT-REEDLESHOT100-POKEMON"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "offer",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "404": {
            "description": "unknown offer"
          }
        }
      }
    },
    "/api/order/v1/checkout-sessions": {
      "post": {
        "operationId": "createCheckoutSession",
        "summary": "Start a quote request (no payment): returns an estimate, a prefilled order link and the request to send",
        "description": "POST /api/order/v1/checkout-sessions with \"clarify\": true, or the MCP tool clarify_request, returns status needs_info, what is missing and up to 2 questions in the user's language (en, ko, ja) when no set or delivery country is given, a budget amount has no currency, or a budget does not say whether shipping and duties are included. If the user named only a city, send it as city: a known city fills the country (filled), an unknown one is asked. Ask those questions; do not guess the country, the currency or the budget basis. Approval before payment is never asked: nothing is charged until the user approves the final total, and defaults says so. Every answer carries trace_id: send it back on the next call (clarify, checkout session, quote request) so the steps of one request link up.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "items",
                  "ship_country"
                ],
                "additionalProperties": false,
                "properties": {
                  "agent": {
                    "type": "object",
                    "description": "optional: your agent name and operator"
                  },
                  "sku": {
                    "type": "string",
                    "enum": [
                      "CS-KR-BANILACO-CLEANITZERO-DUO-JOAN",
                      "CS-KR-HUXLEY-PERFUME-HANDCREAM-MONCHHICHI",
                      "CS-KR-MEDIHEAL-HYPERGEL-8P1",
                      "CS-KR-VT-REEDLESHOT100-POKEMON"
                    ],
                    "description": "one set instead of items"
                  },
                  "bundle_sku": {
                    "type": "string",
                    "enum": [
                      "CS-KR-BANILACO-CLEANITZERO-DUO-JOAN",
                      "CS-KR-HUXLEY-PERFUME-HANDCREAM-MONCHHICHI",
                      "CS-KR-MEDIHEAL-HYPERGEL-8P1",
                      "CS-KR-VT-REEDLESHOT100-POKEMON"
                    ],
                    "description": "same as sku"
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "with sku"
                  },
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "required": [
                        "sku",
                        "quantity"
                      ],
                      "properties": {
                        "sku": {
                          "type": "string",
                          "enum": [
                            "CS-KR-BANILACO-CLEANITZERO-DUO-JOAN",
                            "CS-KR-HUXLEY-PERFUME-HANDCREAM-MONCHHICHI",
                            "CS-KR-MEDIHEAL-HYPERGEL-8P1",
                            "CS-KR-VT-REEDLESHOT100-POKEMON"
                          ]
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1
                        }
                      }
                    }
                  },
                  "ship_country": {
                    "type": "string",
                    "enum": [
                      "CA",
                      "JP",
                      "US"
                    ]
                  },
                  "city": {
                    "type": "string",
                    "description": "city the user named when no ship_country was said; a known city sets the country"
                  },
                  "clarify": {
                    "type": "boolean",
                    "description": "true: if what or country is missing (or a budget has no currency or basis, a wait has no price, a date is not a date), answer 200 with status needs_info and up to 2 questions instead of an error"
                  },
                  "category": {
                    "type": "string",
                    "description": "kind of product when no set was named (with clarify)"
                  },
                  "budget": {
                    "type": "object",
                    "properties": {
                      "max": {
                        "type": "object",
                        "properties": {
                          "amount": {
                            "type": "number"
                          },
                          "currency": {
                            "type": "string"
                          }
                        }
                      },
                      "basis": {
                        "type": "string",
                        "enum": [
                          "items_only",
                          "delivered_total"
                        ]
                      }
                    }
                  },
                  "arrive_by": {
                    "type": "string",
                    "description": "YYYY-MM-DD"
                  },
                  "reference_order": {
                    "type": [
                      "string",
                      "object"
                    ],
                    "description": "earlier order id ({} = earlier order, id unknown)"
                  },
                  "trigger": {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string"
                      },
                      "threshold": {
                        "type": "object"
                      }
                    }
                  },
                  "substitution": {
                    "type": "string",
                    "enum": [
                      "none",
                      "same_product_other_size",
                      "similar",
                      "ask"
                    ]
                  },
                  "language": {
                    "type": "string",
                    "description": "en, ko or ja for question text (others get en)"
                  },
                  "provenance": {
                    "type": "object",
                    "description": "Optional provenance: an object that says where each value came from, keyed by request field (sku or items, quantity, ship_country or city, arrive_by, budget), each one of just_confirmed (the user said or confirmed it in this conversation), known_before (from memory or an earlier conversation), assumed (you inferred it) or unknown. Send it on POST /api/order/v1/checkout-sessions or the MCP tool clarify_request. We treat a value as confirmed only when it is just_confirmed: any other value, or a firm-quote field left out of provenance, comes back as status needs_info with a question (kind confirm) to ask the user as written. Without provenance nothing changes, and the answer carries provenance_note. The order request (POST /api/order/intake, MCP request_quote) also takes provenance, keyed by bundle_sku or items, quantity and ship_country only: the request is still received, and any of those not just_confirmed comes back in unconfirmed with questions (kind confirm) to ask the user before our email arrives.",
                    "additionalProperties": false,
                    "properties": {
                      "sku": {
                        "type": "string",
                        "enum": [
                          "just_confirmed",
                          "known_before",
                          "assumed",
                          "unknown"
                        ]
                      },
                      "items": {
                        "type": "string",
                        "enum": [
                          "just_confirmed",
                          "known_before",
                          "assumed",
                          "unknown"
                        ]
                      },
                      "bundle_sku": {
                        "type": "string",
                        "enum": [
                          "just_confirmed",
                          "known_before",
                          "assumed",
                          "unknown"
                        ]
                      },
                      "quantity": {
                        "type": "string",
                        "enum": [
                          "just_confirmed",
                          "known_before",
                          "assumed",
                          "unknown"
                        ]
                      },
                      "ship_country": {
                        "type": "string",
                        "enum": [
                          "just_confirmed",
                          "known_before",
                          "assumed",
                          "unknown"
                        ]
                      },
                      "city": {
                        "type": "string",
                        "enum": [
                          "just_confirmed",
                          "known_before",
                          "assumed",
                          "unknown"
                        ]
                      },
                      "arrive_by": {
                        "type": "string",
                        "enum": [
                          "just_confirmed",
                          "known_before",
                          "assumed",
                          "unknown"
                        ]
                      },
                      "budget": {
                        "type": "string",
                        "enum": [
                          "just_confirmed",
                          "known_before",
                          "assumed",
                          "unknown"
                        ]
                      }
                    }
                  },
                  "trace_id": {
                    "type": "string",
                    "pattern": "^it_[0-9a-f]{24}$",
                    "description": "the trace_id an earlier answer returned (it_ + 24 hex characters; GET /api/order returns one on every read, clarify_request too), so the steps of one request link up; not personal data. Leave it out if you have none."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "session (mode quote_request, expires in 24 hours); defaults shows approval before payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "200": {
            "description": "only with clarify: true or provenance: status needs_info, policy, missing, questions (up to 2; kind confirm = ask the user to confirm a value that was not just_confirmed), unconfirmed, filled, defaults",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid items or country, or unknown_field (a field not listed here)"
          },
          "413": {
            "description": "body_too_large (over 16384 bytes)"
          }
        }
      }
    },
    "/api/order/v1/checkout-sessions/{id}": {
      "get": {
        "operationId": "getCheckoutSession",
        "summary": "Re-read a session with a fresh estimate",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "404": {
            "description": "unknown session (or a session id whose signature does not match)"
          }
        }
      }
    }
  },
  "x-mcp-server": {
    "url": "https://chainshift-preview.vercel.app/api/order/mcp",
    "transport": "streamable-http"
  }
}
