{
  "openapi": "3.1.0",
  "info": {
    "title": "WHATTA Shipment Sandbox API",
    "version": "1.0.0",
    "description": "Mock-only API. It never contacts CJ, Post Office, LOTTE, WHATTA production databases, or carrier APIs. Tracking numbers and labels are synthetic."
  },
  "servers": [{"url": "https://sandbox.whatta.co.kr"}],
  "security": [{"bearerAuth": []}],
  "paths": {
    "/api/sandbox/tenants": {
      "post": {
        "security": [],
        "summary": "Create a sandbox tenant and token",
        "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SignupRequest"}}}},
        "responses": {
          "201": {"description": "Tenant and one-time token created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SignupResponse"}}}},
          "422": {"$ref": "#/components/responses/InvalidRequest"},
          "429": {"$ref": "#/components/responses/RateLimited"}
        }
      }
    },
    "/api/sandbox/token/rotate": {
      "post": {
        "summary": "Rotate the current tenant token",
        "responses": {"201": {"description": "New one-time token"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "429": {"$ref": "#/components/responses/LifecycleRateLimited"}}
      }
    },
    "/api/sandbox/token/revoke": {
      "post": {
        "summary": "Revoke the tenant and delete its sandbox data",
        "responses": {"200": {"description": "Tenant revoked"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "429": {"$ref": "#/components/responses/LifecycleRateLimited"}}
      }
    },
    "/api/shop/orders/": {
      "post": {
        "summary": "Validate or store a fake shop order",
        "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OrderRequest"}}}},
        "responses": {"200": {"description": "Validation or duplicate"}, "400": {"$ref": "#/components/responses/InvalidRequest"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "409": {"$ref": "#/components/responses/Conflict"}}
      }
    },
    "/api/shop/shipments/": {
      "post": {
        "summary": "Validate or create a fake shipment",
        "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ShipmentRequest"}}}},
        "responses": {"200": {"description": "Identical duplicate request; returns DUPLICATE and reuses the original result"}, "201": {"description": "Mock shipment accepted"}, "202": {"description": "UNKNOWN or LABEL_PENDING; do not auto-resubmit"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "409": {"$ref": "#/components/responses/Conflict"}, "422": {"$ref": "#/components/responses/InvalidRequest"}}
      },
      "get": {
        "summary": "Read a shipment status for the authenticated tenant",
        "parameters": [{"name": "shopOrderId", "in": "query", "required": true, "schema": {"type": "string"}}],
        "responses": {"200": {"description": "Mock shipment status"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"description": "Not found in the authenticated tenant"}}
      }
    },
    "/api/shop/shipments/label/": {
      "get": {
        "summary": "Read the fake SVG label",
        "parameters": [{"name": "shopOrderId", "in": "query", "required": true, "schema": {"type": "string"}}],
        "responses": {"200": {"description": "Mock SVG label", "content": {"image/svg+xml": {"schema": {"type": "string"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "409": {"description": "Label is not ready"}, "410": {"description": "Label session expired"}}
      },
      "post": {
        "summary": "Mark a fake label as printed",
        "requestBody": {"required": true, "content": {"application/x-www-form-urlencoded": {"schema": {"type": "object", "required": ["action"], "properties": {"action": {"const": "mark_printed"}}}}}},
        "responses": {"200": {"description": "Print state recorded"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}}
      }
    }
  },
  "components": {
    "securitySchemes": {"bearerAuth": {"type": "http", "scheme": "bearer"}},
    "schemas": {
      "SignupRequest": {"type": "object", "required": ["name"], "properties": {"name": {"type": "string", "minLength": 1, "maxLength": 80, "description": "Developer alias only; do not send personal data"}}},
      "SignupResponse": {"type": "object", "properties": {"success": {"const": true}, "status": {"const": "TENANT_CREATED"}, "data": {"type": "object", "properties": {"tenantId": {"type": "string"}, "scope": {"const": "ORDER_WRITE"}, "token": {"type": "string"}, "sandboxOnly": {"const": true}}}}},
      "OrderRequest": {"type": "object", "required": ["shopOrderId", "receiver", "productName"], "properties": {"validateOnly": {"type": "boolean"}, "shopOrderId": {"type": "string"}, "receiver": {"type": "object"}, "productName": {"type": "string"}}},
      "ShipmentRequest": {"type": "object", "required": ["carrier", "shopOrderId", "idempotencyKey", "sender", "receiver", "product"], "properties": {"validateOnly": {"type": "boolean"}, "carrier": {"type": "string", "enum": ["CJ", "POST_OFFICE", "LOTTE"]}, "shopOrderId": {"type": "string"}, "idempotencyKey": {"type": "string"}, "sender": {"type": "object"}, "receiver": {"type": "object"}, "product": {"type": "object"}}}
    },
    "responses": {
      "Unauthorized": {"description": "Missing, invalid, or revoked bearer token"},
      "Forbidden": {"description": "Token scope does not permit the operation"},
      "InvalidRequest": {"description": "Invalid JSON or required field"},
      "Conflict": {"description": "Same tenant and order number with a different payload"},
      "RateLimited": {"description": "Data-plane rate limit exceeded"},
      "LifecycleRateLimited": {"description": "Rotate or revoke lifecycle rate limit exceeded"}
    }
  }
}
