{
  "openapi": "3.1.0",
  "info": {
    "title": "Price Discussion (PD) public API",
    "version": "1.0.0",
    "description": "PD's stable public reads, as JSON over HTTPS. No key, no signup, open to every website (CORS: *, never with credentials). Prices are ETH, token ids are 1-based, times are UTC. Poll no faster than once a minute and cache on your side. The human manual is https://pricediscussion.com/docs/api; agents can also use the MCP server at https://mcp.pricediscussion.com (https://pricediscussion.com/docs/mcp). For anything tokenized the Ethereum chain is the source of truth: https://pricediscussion.com/docs/building-on-pd. Fields not described here may change.",
    "contact": { "name": "Price Discussion", "url": "https://discord.gg/4tJr2txNCX" }
  },
  "servers": [{ "url": "https://pricediscussion.com" }],
  "tags": [
    { "name": "Catalog" },
    { "name": "Projects and Outputs" },
    { "name": "People" },
    { "name": "Activity and market" },
    { "name": "Soundtracks" },
    { "name": "Stickers, keychains, $PRICE, Exchange" },
    { "name": "Agents" }
  ],
  "components": {
    "parameters": {
      "slug": { "name": "slug", "in": "path", "required": true, "description": "A Project's slug: lowercase letters, digits, '-' and '_' (the part after /art/ in its page URL).", "schema": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_-]{1,49}$" } },
      "address": { "name": "address", "in": "path", "required": true, "description": "A 0x… Ethereum address.", "schema": { "type": "string", "pattern": "^0x[0-9a-fA-F]{40}$" } }
    },
    "schemas": {
      "Error": { "type": "object", "properties": { "error": { "type": "string" }, "code": { "type": "string" } } }
    }
  },
  "paths": {
    "/api/home": {
      "get": {
        "tags": ["Catalog"],
        "summary": "The platform at a glance",
        "description": "stats (projects, minted, volume_eth, primary_eth, secondary_eth, top_projects); uploads — every Project, newest first (slug, title, project_no, max_supply, minted_count, artist_minted_count, sold_out_at, artist_minted_out, uploaded_at); minting_now — the same fields for what is still minting.",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/stats": {
      "get": {
        "tags": ["Catalog"],
        "summary": "Platform totals",
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "type": "object", "properties": {
              "total_projects": { "type": "integer" }, "total_minted": { "type": "integer" }, "total_holders": { "type": "integer" },
              "total_volume_eth": { "type": "string" }, "primary_volume_eth": { "type": "string" }, "secondary_volume_eth": { "type": "string" },
              "active_artists": { "type": "integer" }, "artists_in_cooldown": { "type": "integer" }
            } } } }
          }
        }
      }
    },
    "/api/search": {
      "get": {
        "tags": ["Catalog"],
        "summary": "What the app's search finds",
        "parameters": [{ "name": "q", "in": "query", "required": true, "schema": { "type": "string" } }],
        "description": "Short lists of matches: projects, users, artworks, soundtracks, traits.",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/project/{slug}/outputs": {
      "get": {
        "tags": ["Projects and Outputs"],
        "summary": "One Project and its minted Outputs",
        "parameters": [{ "$ref": "#/components/parameters/slug" }],
        "description": "project_id, total, artist_minted, artist_address, contract_address, colorway, price_sprite, soundtrack, traits, subtraits, stats, and outputs[] (token_id, owner, owner_handle, list_price_eth, mint_ts, last_sale_ts).",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/project/{slug}/floor": {
      "get": {
        "tags": ["Projects and Outputs"],
        "summary": "A Project's floor (lowest live listing)",
        "parameters": [{ "$ref": "#/components/parameters/slug" }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "project_id": { "type": "string" }, "floor_eth": { "type": ["number", "null"] } } } } } } }
      }
    },
    "/api/output/{slug}-{id}": {
      "get": {
        "tags": ["Projects and Outputs"],
        "summary": "One Output, completely",
        "parameters": [
          { "$ref": "#/components/parameters/slug" },
          { "name": "id", "in": "path", "required": true, "description": "Token id, 1-based. The path is e.g. /api/output/kiki-7", "schema": { "type": "integer", "minimum": 1 } }
        ],
        "description": "owner, minter, minted_at, list_price_eth, last_sale_eth, symbol, traits (artist traits + PD platform traits: PriceDay, Sun, Moon, Rising, Fate…), fingerprint (measured from the pixels), history[] (every recorded event with prices). The artwork image: https://pricediscussion.com/preview/{slug}/{id}.v2.png",
        "responses": { "200": { "description": "OK" }, "400": { "description": "Not a minted Output" } }
      }
    },
    "/api/user/{address}": {
      "get": {
        "tags": ["People"],
        "summary": "A public profile",
        "parameters": [{ "$ref": "#/components/parameters/address" }],
        "description": "address, handle, ens_name, user_number, price_rank, price_score, price_streak, price_sprite, price_sprite_resolved (the parts the PriceSprite face is drawn from), sigil_forged_at, follower_count, following_count, profile_hex, owned_projects, volume_spent_eth — only what anyone sees on the profile page.",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/user/by-handle/{handle}": {
      "get": {
        "tags": ["People"],
        "summary": "A public profile by @name",
        "parameters": [{ "name": "handle", "in": "path", "required": true, "description": "The @name without the @", "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Same shape as /api/user/{address}" } }
      }
    },
    "/api/user/{address}/outputs": {
      "get": {
        "tags": ["People"],
        "summary": "What a wallet holds",
        "parameters": [{ "$ref": "#/components/parameters/address" }],
        "description": "total, and holdings[] (slug, token_id, list_price_eth, mint_ts, acquired_ts, last_sale_ts).",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/artist/{address}": {
      "get": {
        "tags": ["People"],
        "summary": "An artist's Projects and cooldown",
        "parameters": [{ "$ref": "#/components/parameters/address" }],
        "description": "projects[] (id, title, minted_count, max_supply, floor_price_eth, volume_eth, uploaded_at, sold_out_at), total_volume_eth, cooldown_active, cooldown_days_remaining. A wallet shows as an artist only once its first Project is on PD.",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/artists": {
      "get": {
        "tags": ["People"],
        "summary": "Every artist on PD",
        "description": "artists[] (name, handle, address, status: active | cooldown).",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/feed": {
      "get": {
        "tags": ["Activity and market"],
        "summary": "The event feed, newest first",
        "parameters": [
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } },
          { "name": "project_id", "in": "query", "description": "One Project's slug", "schema": { "type": "string" } },
          { "name": "address", "in": "query", "description": "Only events this wallet is on either side of", "schema": { "type": "string" } },
          { "name": "types", "in": "query", "description": "Comma list of MINT, LIST, SALE, XFER, OFFER, OFFER_WITHDRAWN, OFFER_DECLINED", "schema": { "type": "string" } },
          { "name": "order", "in": "query", "schema": { "type": "string", "enum": ["time", "price"] } },
          { "name": "dir", "in": "query", "schema": { "type": "string", "enum": ["desc", "asc"] } }
        ],
        "description": "events[] (id, type, project_id, token_id as {slug}-{id}, from_address, to_address, from_handle, to_handle, price_eth, timestamp).",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/marketplace": {
      "get": {
        "tags": ["Activity and market"],
        "summary": "The market right now",
        "description": "listings[] (slug, token_id, price_eth), recent events, and stats (listed, offers, volume_eth, top_projects, top_sales).",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/priceday/{n}": {
      "get": {
        "tags": ["Activity and market"],
        "summary": "One finished PriceDay",
        "parameters": [{ "name": "n", "in": "path", "required": true, "schema": { "type": "integer", "minimum": 1 } }],
        "description": "number, date_label, minted, uploaded, biggestSale, tallies (mints, sales, volume_eth, listings, offers, uploads), story.",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/gas": {
      "get": {
        "tags": ["Activity and market"],
        "summary": "Live Ethereum gas",
        "description": "baseFeeGwei, standardGwei, fastGwei, rapidGwei, ethUsd, blockNumber.",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/soundtracks": {
      "get": {
        "tags": ["Soundtracks"],
        "summary": "Every Project's soundtrack",
        "description": "Soundtracks on PD are YouTube playlists (usually mixes). soundtracks[]: slug, title, playlist_id, url, embed_url (drop into an iframe), plays (PD Plays). Newest Project first.",
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "soundtracks": { "type": "array", "items": { "type": "object", "properties": {
              "slug": { "type": "string" }, "title": { "type": "string" }, "playlist_id": { "type": "string" },
              "url": { "type": "string" }, "embed_url": { "type": "string" }, "plays": { "type": "integer" }
            } } } } } } }
          }
        }
      }
    },
    "/api/soundtrack/{slug}": {
      "get": {
        "tags": ["Soundtracks"],
        "summary": "One Project's soundtrack facts",
        "parameters": [{ "$ref": "#/components/parameters/slug" }],
        "description": "info (kind: playlist | video, tracks, playable, runtimeSec, maker) and plays. info is null when the Project has no soundtrack.",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/stickers/market": {
      "get": {
        "tags": ["Stickers, keychains, $PRICE, Exchange"],
        "summary": "Every sticker sheet's market",
        "parameters": [{ "name": "summary", "in": "query", "required": true, "schema": { "type": "string", "enum": ["1"] } }],
        "description": "sheets{sheet_id: {floor, listed, best_offer, last, volume, sales, peels}}, on_chain.",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/keychains/market": {
      "get": {
        "tags": ["Stickers, keychains, $PRICE, Exchange"],
        "summary": "Keychains for sale, or the keychain contract",
        "parameters": [
          { "name": "listed", "in": "query", "description": "1 = keychains for sale", "schema": { "type": "string", "enum": ["1"] } },
          { "name": "meta", "in": "query", "description": "1 = the contract and its royalty receiver", "schema": { "type": "string", "enum": ["1"] } }
        ],
        "description": "Pass exactly one of listed=1 or meta=1. Keychains are numbered, never named.",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/keychains/{address}": {
      "get": {
        "tags": ["Stickers, keychains, $PRICE, Exchange"],
        "summary": "The keychains a wallet holds",
        "parameters": [{ "$ref": "#/components/parameters/address" }],
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/price/{address}": {
      "get": {
        "tags": ["Stickers, keychains, $PRICE, Exchange"],
        "summary": "A wallet's $PRICE balance",
        "parameters": [{ "$ref": "#/components/parameters/address" }],
        "description": "address, token_address, balance_wei, balance_formatted, decimals. $PRICE is official PD digital merch (ERC-20 0x173a012c7c8ca3cfb531dcad84a40c53dbe74638 on Ethereum mainnet); it carries no rights, revenue, perks or access.",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/exchange": {
      "get": {
        "tags": ["Stickers, keychains, $PRICE, Exchange"],
        "summary": "The Exchange board — open trades anyone can fill",
        "parameters": [{ "name": "board", "in": "query", "required": true, "schema": { "type": "string", "enum": ["1"] } }],
        "description": "board[] — each trade's proposer_address, give (Outputs, sticker sheets, keychains), give_eth, get, get_eth, end_time.",
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/exchange/settled": {
      "get": {
        "tags": ["Stickers, keychains, $PRICE, Exchange"],
        "summary": "Exchange trades settled since a moment",
        "parameters": [{ "name": "since", "in": "query", "required": true, "description": "Unix seconds", "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/api/agent/offer": {
      "post": {
        "tags": ["Agents"],
        "summary": "Prepare or submit an offer signed by the bidder's own wallet",
        "description": "action=prepare → the unsigned Seaport order, EIP-712 typed_data for eth_signTypedData_v4, and any wrap/approve transactions needed first. action=submit with the signed order → booked on PD's offers book after the same server check every app offer passes. PD never holds a key. See https://pricediscussion.com/docs/mcp#acting-for-someone.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["action", "slug", "scope"], "properties": {
            "action": { "type": "string", "enum": ["prepare", "submit"] },
            "slug": { "type": "string" },
            "scope": { "type": "string", "enum": ["item", "collection", "trait"] },
            "tokenId": { "type": "string", "description": "item offers" },
            "category": { "type": "string", "description": "trait offers: the trait name" },
            "value": { "type": "string", "description": "trait offers: the trait value" },
            "priceEth": { "type": "string", "description": "prepare: the offer in ETH (paid as WETH)" },
            "bidder": { "type": "string", "description": "prepare: the bidder's 0x… address" },
            "durationSec": { "type": "integer", "description": "prepare: how long it stays open (default 30 days)" },
            "order": { "type": "object", "description": "submit: the prepared order with its signature filled in" }
          } } } }
        },
        "responses": { "200": { "description": "OK" }, "400": { "description": "Refused — the error says why", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } }
      }
    }
  }
}
