{"openapi":"3.1.0","info":{"title":"BAPECLUB Public API","version":"1.1.0","summary":"Read-focused HTTP surface for BAPECLUB: droplist, magazine, pre-orders, fit submissions, club subscription.","description":"Public API for bapeclub.app. Reads are anonymous. Writes require no explicit auth today\nbut enforce cookie/IP-based rate limits and rely on the web UI for human verification;\nprogrammatic agent auth is on the roadmap.\n\nDiscovery surfaces:\n  - /.well-known/agent.json              capability + content-signals manifest\n  - /.well-known/ai-plugin.json          OpenAI plugin manifest\n  - /.well-known/mcp.json                Claude Desktop-shape MCP pointer\n  - /.well-known/mcp/server-card.json    SEP-2127 MCP server card (mirrors /api/mcp tool list)\n  - /.well-known/api-catalog             RFC 9727 catalog index\n  - /api/mcp                             MCP JSON-RPC endpoint (tools/list, tools/call, resources/list)\n  - /sitemap.xml                         URL sitemap (drops + releases + magazine + icons)\n\nShipping-tier rate tables (used by /api/preorders):\n  Worldwide (international):\n    standard  -- Japan Post ePacket  -- 7-14 days -- base $12 + $8/kg\n    express   -- FedEx International -- 3-6 days  -- base $22 + $15/kg\n    priority  -- DHL Express         -- 2-4 days  -- base $32 + $20/kg\n    (plus $3 authentication fee on worldwide)\n  Taiwan (taiwan-bank):\n    standard  -- 7-11 / Black Cat    -- 2-3 days  -- flat $5 up to 5kg\n    express   -- Black Cat Express   -- 1-2 days  -- flat $7 up to 5kg\n    (no priority tier; no authentication fee)\nCanonical rate source: apps/bapeclub/lib/pricing.ts (WORLDWIDE_SHIPPING_RATES, TAIWAN_SHIPPING_RATES).\n\nVersioning and deprecation policy:\n  - Current version: 1.1.0 (semver, tracked in info.version). Changes within a major are\n    additive; breaking changes bump the major version.\n  - When a breaking version ships it is published under the /v1/ URL prefix (e.g.\n    /v1/api/droplist) with the current surface kept on the unprefixed path for at least\n    90 days. Nothing changes without a documented announcement in this spec.\n  - Deprecations: endpoints slated for removal are marked \"deprecated\": true in this\n    document for at least 90 days and every response from a deprecated endpoint carries\n    a Deprecation response header (plus Sunset with the removal date once scheduled).\n  - Clients MUST pin the version they integrate against (read info.version and follow\n    the documentation of that version, never assume \"latest\").\n\nErrors:\n  - Every 4xx/5xx response body follows RFC 9457 (application/problem+json) — see the\n    ErrorResponse schema. Each error carries a stable machine-readable `code` (for\n    example RATE_LIMITED, INVALID_ITEM_ID, NOT_FOUND), a human `title`/`detail`, and a\n    `docs` pointer. Agents should key logic off `code`, never off the HTTP status alone.\n\nRate limits:\n  - All /api/* responses carry the RFC 9740 RateLimit header fields (RateLimit-Limit,\n    RateLimit-Reset, RateLimit-Policy) emitted by the edge with per-session advisory\n    bounds. Route handlers enforce stricter per-route ceilings, declared per-operation\n    via the `x-rate-limit` extension.\n  - 429 responses additionally include a Retry-After header (seconds). Agents should\n    pause and retry after Retry-After, backing off on repeated 429s.","contact":{"name":"BAPECLUB","email":"contact@bapeclub.co","url":"https://bapeclub.app"},"license":{"name":"BAPECLUB Editorial License","url":"https://bapeclub.app/llms.txt"}},"servers":[{"url":"https://bapeclub.app","description":"Production"}],"x-versioning-policy":{"current":"1.1.0","scheme":"semver (major.minor.patch); breaking changes bump the major version","url_path_versioning":"/v1/","url_path_note":"Breaking versions publish under /v1/; the current surface stays on the unprefixed path for at least 90 days after a breaking release.","deprecation":{"signal_headers":["Deprecation","Sunset"],"min_notice_days":90,"spec_marking":"deprecated: true"},"pinning":"Clients MUST pin info.version and follow that version, never assume latest."},"x-rate-limits":{"header_fields":["RateLimit-Limit","RateLimit-Reset","RateLimit-Policy"],"retry_after_on_429":true,"per_route_extension":"x-rate-limit","note":"Edge emits advisory per-session bounds; route handlers enforce per-route ceilings."},"tags":[{"name":"Droplist","description":"This week’s BAPE drops + voting."},{"name":"Pre-orders","description":"Order a drop for ship-from-Taipei fulfillment."},{"name":"Magazine","description":"Editorial articles."},{"name":"Club","description":"Email capture."},{"name":"Submissions","description":"Community-submitted fits."},{"name":"Media","description":"OG images and share cards."},{"name":"Agent","description":"Agent-facing discovery + MCP transport."},{"name":"Sitemap","description":"URL sitemap."}],"paths":{"/api/droplist":{"get":{"tags":["Droplist"],"summary":"Get current-week droplist","description":"Returns the current ISO week’s droplist including ranked vote counts and whether the caller’s cookie has voted for each item.","operationId":"getDroplist","responses":{"200":{"description":"Current droplist snapshot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DroplistSnapshot"}}}}},"x-rate-limit":{"requests_per_minute":60}},"post":{"tags":["Droplist"],"summary":"Cast a vote","description":"Records a vote for the given itemId. One vote per (cookie, item, week). Sets the voter cookie if absent.","operationId":"voteDrop","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["itemId"],"properties":{"itemId":{"type":"string","description":"Droplist item id (e.g. drop_shark_hoodie_green).","minLength":1}}}}}},"responses":{"200":{"description":"Vote recorded (or already-voted).","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"reason":{"type":"string","nullable":true},"weekKey":{"type":"string"},"voteCount":{"type":"integer"}}}}}},"400":{"description":"Malformed body / missing itemId.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown itemId for the current week.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"x-sensitive-fields":["Set-Cookie"]}},"/api/preorders":{"post":{"tags":["Pre-orders"],"summary":"Place a pre-order","description":"Creates a reservation for a drop. Per-region payment routing (PayPal for worldwide,\nTW bank transfer for Taiwan). Returns an order code formatted ORD-XXXXX plus a `priced`\nobject echoing the resolved courier, ETA label, and the selected shipping tier.\n\nThe optional `shippingTier` enum controls courier selection:\n  - international region:  standard | express | priority\n  - taiwan-bank region:    standard | express  (priority falls back to standard)\n\nRate tables are defined in apps/bapeclub/lib/pricing.ts (WORLDWIDE_SHIPPING_RATES,\nTAIWAN_SHIPPING_RATES) and summarised in the top-level info.description.","operationId":"reservePreorder","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreorderRequest"}}}},"responses":{"200":{"description":"Order reserved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreorderResponse"}}}},"400":{"description":"Invalid body / missing address or phone for Taiwan region.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Unknown itemId for the current week.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Postgres not configured / db error.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"x-sensitive-fields":["name","email","phone","address"]}},"/api/mcp":{"post":{"tags":["Agent"],"summary":"MCP JSON-RPC endpoint","description":"Model Context Protocol JSON-RPC 2.0 transport. Supported methods:\n  - initialize           returns protocol version + capabilities\n  - tools/list           returns the registered tool catalogue\n  - tools/call           invokes a tool by name\n  - resources/list       lists agent-readable resources\n  - resources/read       reads a resource by URI\n  - notifications/initialized (204, no body)\n\nTool catalogue mirrors /.well-known/mcp/server-card.json. Read tools:\nget_droplist, list_articles, get_article, list_cultural_moments,\nshop_search, search_drops, get_drop. Write tools (vote_for_drop,\nreserve_preorder, submit_fit) are stubs that return deferred:true +\na webUrl so agents can redirect the human user to the web flow.","operationId":"mcpJsonRpc","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"oneOf":[{"type":"integer"},{"type":"string"}]},"method":{"type":"string","example":"tools/list"},"params":{"type":"object"}}}}}},"responses":{"200":{"description":"JSON-RPC 2.0 response."},"204":{"description":"Notification acknowledged silently."},"400":{"description":"Invalid JSON-RPC envelope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"405":{"description":"GET not supported — use POST.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"x-discovery":{"serverCard":"https://bapeclub.app/.well-known/mcp/server-card.json","pointer":"https://bapeclub.app/.well-known/mcp.json"}}},"/sitemap.xml":{"get":{"tags":["Sitemap"],"summary":"URL sitemap","description":"Sitemap-0.9 XML document listing home, magazine, vote, drops, releases, magazine articles, and cultural-moment subject pages.","operationId":"getSitemap","responses":{"200":{"description":"Sitemap XML.","content":{"application/xml":{"schema":{"type":"string"}},"text/xml":{"schema":{"type":"string"}}}}}}},"/api/club/subscribe":{"post":{"tags":["Club"],"summary":"Subscribe to the club mailing list","operationId":"subscribeClub","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","source"],"properties":{"email":{"type":"string","format":"email","maxLength":254},"source":{"type":"string","enum":["landing","magazine","drop","ops"]}}}}}},"responses":{"200":{"description":"Subscribed.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"persisted":{"type":"boolean"}}}}}},"400":{"description":"Invalid email.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Subscribers table not migrated yet.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"x-sensitive-fields":["email"]}},"/api/submissions":{"post":{"tags":["Submissions"],"summary":"Submit a community fit","description":"Multipart form upload: image + caption + submitter email/IG handle. Rate-limited to 3/IP/24h.","operationId":"submitFit","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["image","caption","name","email"],"properties":{"image":{"type":"string","format":"binary"},"caption":{"type":"string","minLength":3,"maxLength":500},"name":{"type":"string","minLength":1,"maxLength":120},"email":{"type":"string","format":"email","maxLength":160},"igHandle":{"type":"string","maxLength":60},"linkedDropId":{"type":"string","maxLength":200}}}}}},"responses":{"200":{"description":"Submission queued for moderation.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"submissionId":{"type":"integer"}}}}}},"400":{"description":"Invalid body / missing image.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Image too large.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Unsupported image content-type.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit reached.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"x-rate-limit":{"requests_per_day_per_ip":3},"x-sensitive-fields":["name","email","igHandle"]}},"/api/og":{"get":{"tags":["Media"],"summary":"Open Graph share card (PNG)","description":"1200x630 PNG summarising the current issue.","operationId":"getOgCard","responses":{"200":{"description":"Share card image.","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}}}}}},"components":{"schemas":{"DroplistItem":{"type":"object","required":["id","title","brand","officialImageUrl","sourceUrl","voteCount"],"properties":{"id":{"type":"string","example":"drop_shark_hoodie_green"},"title":{"type":"string"},"brand":{"type":"string","example":"A BATHING APE"},"colorway":{"type":"string","nullable":true},"officialImageUrl":{"type":"string","format":"uri"},"sourceUrl":{"type":"string","format":"uri"},"releaseDate":{"type":"string","description":"ISO date (YYYY-MM-DD), may be empty."},"shopifyHandle":{"type":"string","nullable":true},"voteCount":{"type":"integer","minimum":0},"alreadyVoted":{"type":"boolean"}}},"DroplistSnapshot":{"type":"object","required":["weekKey","opensAt","closesAt","items","grandTotal"],"properties":{"weekKey":{"type":"string","example":"2026-W16"},"opensAt":{"type":"string","format":"date-time"},"closesAt":{"type":"string","format":"date-time"},"sourceUrl":{"type":"string","format":"uri"},"grandTotal":{"type":"integer","minimum":0},"items":{"type":"array","items":{"$ref":"#/components/schemas/DroplistItem"}}}},"PreorderRequest":{"type":"object","required":["itemId","name","email","region"],"properties":{"itemId":{"type":"string","minLength":1},"quantity":{"type":"integer","minimum":1,"maximum":3,"default":1},"name":{"type":"string","minLength":1,"maxLength":120},"email":{"type":"string","format":"email","maxLength":160},"phone":{"type":"string","maxLength":40,"nullable":true},"region":{"type":"string","enum":["international","taiwan-bank"],"description":"Fulfillment region. `international` routes to PayPal payment + worldwide courier tiers. `taiwan-bank` routes to TW bank transfer + Taiwan courier tiers."},"shippingTier":{"type":"string","enum":["standard","express","priority"],"default":"standard","description":"Courier tier. `standard` on both regions. `express` adds a faster courier. `priority` is worldwide-only (DHL Express); picking `priority` on `taiwan-bank` falls back to `standard`."},"address":{"type":"string","maxLength":500,"nullable":true},"notes":{"type":"string","maxLength":500,"nullable":true}}},"PricedOffer":{"type":"object","required":["unitPriceUsd","unitPriceTwd","shippingUsd","shippingTwd","totalUsd","totalTwd","shippingCourier","shippingEtaLabel","shippingTier"],"description":"Server-resolved pricing echo. Always returned on successful /api/preorders so callers can show the finalised courier + ETA + totals the server committed to.","properties":{"unitPriceUsd":{"type":"integer","minimum":0},"unitPriceTwd":{"type":"integer","minimum":0},"shippingUsd":{"type":"integer","minimum":0},"shippingTwd":{"type":"integer","minimum":0},"totalUsd":{"type":"integer","minimum":0},"totalTwd":{"type":"integer","minimum":0},"shippingCourier":{"type":"string","example":"Japan Post ePacket"},"shippingEtaLabel":{"type":"string","example":"7-14 days"},"shippingTier":{"type":"string","enum":["standard","express","priority"]}}},"PreorderResponse":{"type":"object","required":["ok","orderCode"],"properties":{"ok":{"type":"boolean"},"orderCode":{"type":"string","example":"ORD-A7K3F"},"priced":{"$ref":"#/components/schemas/PricedOffer"}}},"ErrorResponse":{"type":"object","required":["type","title","status"],"properties":{"type":{"type":"string","description":"RFC 9457 error type URI (or short urn) identifying the problem class.","example":"urn:error:rate_limit"},"title":{"type":"string","description":"Short, human-readable summary of the problem."},"status":{"type":"integer","description":"HTTP status code."},"detail":{"type":"string","description":"Human-readable explanation, safe to show end users."},"instance":{"type":"string","format":"uri-reference","description":"URI identifying the specific failing request."},"code":{"type":"string","description":"Stable machine-readable error code agents can key logic off (e.g. RATE_LIMITED, INVALID_ITEM_ID)."},"docs":{"type":"string","format":"uri","description":"Where to find help for this error class."}}}}}}