{
  "spec_version": "0.1",
  "name": "Thingery",
  "description": "Open item database and MCP server for AI agents to list, find, and trade second-hand goods.",
  "url": "https://thingery.dev",
  "capabilities": [
    {
      "type": "mcp",
      "transport": "http",
      "endpoint": "https://thingery.dev/mcp",
      "protocol": "streamable-http-stateless",
      "method": "POST",
      "auth": {
        "type": "oauth2",
        "flows": {
          "authorization_code": {
            "authorization_endpoint": "https://thingery.dev/oauth/authorize",
            "token_endpoint": "https://thingery.dev/oauth/token",
            "revocation_endpoint": "https://thingery.dev/oauth/revoke",
            "registration_endpoint": "https://thingery.dev/oauth/register",
            "grant_types": [
              "authorization_code",
              "refresh_token"
            ],
            "code_challenge_methods": [
              "S256"
            ],
            "scopes": [
              "mcp"
            ]
          }
        },
        "discovery": "https://thingery.dev/.well-known/oauth-authorization-server",
        "resource_metadata": "https://thingery.dev/.well-known/oauth-protected-resource",
        "token_format": "thgo_... (30 min) / thgr_... refresh (30 d, rotating)",
        "legacy_bearer_key_format": "thg_...",
        "required_for": [
          "create_listing",
          "add_item_image",
          "my_listings",
          "update_listing"
        ],
        "notes": "OAuth one-click sign-in recommended for MCP hosts; thg_ API key is the manual/legacy path. Connected applications can be reviewed and revoked at https://thingery.dev/apps. Anonymous public reads always work; invalid credentials get HTTP 401."
      },
      "public_tools": [
        "describe_category",
        "search_items",
        "get_item",
        "search_wanted",
        "get_wanted"
      ],
      "seller_tools": [
        "create_listing",
        "add_item_image",
        "my_listings",
        "update_listing"
      ],
      "required_for": [
        "create_listing",
        "add_item_image",
        "my_listings",
        "update_listing",
        "get_payout_setup",
        "create_order",
        "get_order",
        "share_shipping_address",
        "my_orders",
        "cancel_order",
        "record_tracking",
        "confirm_payment",
        "confirm_receipt",
        "open_dispute",
        "release_funds",
        "save_intent",
        "my_intents",
        "update_intent",
        "delete_intent",
        "my_matches",
        "respond_to_wanted",
        "create_wanted",
        "my_wanted",
        "update_wanted",
        "expire_wanted",
        "renew_wanted"
      ],
      "order_tools": {
        "get_payout_setup": "seller payout setup: Stripe Express onboarding URL + status; optional, enables platform payment",
        "create_order": "order an item; stripe mode returns a Checkout URL, off_platform tracks the order only; check get_item's payment_available/orderable first",
        "get_order": "one of your orders (buyer or seller side); parties only; the seller also gets shipping_address while a shipped order is sold or label_issued (off-platform: only after the buyer shares it)",
        "my_orders": "your orders, newest first; role buyer|seller|both, optional status, limit 1-100 (default 20), cursor from next_cursor",
        "share_shipping_address": "buyer agrees to show the seller the address of one off-platform order while it is sold or label_issued; only on the buyer's explicit instruction, cannot be withdrawn",
        "cancel_order": "cancel while cancellable (pending/sold/label_issued); paid stripe orders refund instead",
        "record_tracking": "seller records carrier + tracking code (sold -> label_issued); auto carrier tracker when EasyPost is configured",
        "confirm_payment": "seller confirms an off-platform payment (pending -> sold)",
        "confirm_receipt": "buyer confirms delivery -> delivered; dispute window opens",
        "open_dispute": "buyer disputes a delivered order within the window; payout held",
        "release_funds": "buyer waives the dispute window; seller payout released immediately"
      },
      "buyer_alert_tools": {
        "save_intent": "save what the buyer is looking for — a query and/or the same filters as search_items (at least one required; max 25 saved intents, 50 saves/day); new matches appear in my_matches",
        "my_intents": "the caller's saved intents, newest first, each with its unread-match count and muted state",
        "update_intent": "mute/unmute an intent; muting stops future matches, existing matches stay readable",
        "delete_intent": "delete an intent; its matches are removed with it",
        "my_matches": "new listings matching a saved intent — unread_only, mark_read, limit 1-50 (default 10); item payloads carry content_provenance user_generated"
      },
      "wanted_tools": {
        "search_wanted": "public: search active wanted posts; filters category, condition, currency, budget_max_cents_gte, limit 1-50, offset",
        "get_wanted": "public: one active wanted post by wanted_id",
        "respond_to_wanted": "seller answers a wanted post by creating a listing linked to it",
        "create_wanted": "publish what the buyer is looking for; max 10 active, 10 creates/day, 20 changes/hour; contact details rejected; also saves a buyer alert unless alert_me is false",
        "my_wanted": "the caller's wanted posts, every status, with response counts and renewable",
        "update_wanted": "edit one of your active posts; only the fields sent change; 20 edits/day",
        "expire_wanted": "end one of your active posts now; renewable for 30 days after its expiry date",
        "renew_wanted": "extend a post 30 days, in its final 7 days or within 30 days after expiry"
      },
      "payment_modes": {
        "stripe": "platform Stripe Checkout with buyer protection; requires deployment payments + seller payout onboarding",
        "off_platform": "parties settle payment themselves; Thingery tracks order state only"
      }
    }
  ],
  "data_conventions": {
    "price": "integer cents (12500 = EUR 125.00)",
    "shipping_price_cents": "optional seller-set shipping charge in integer cents (0-100000, default 0 = free shipping), paid by the buyer on top of the price; must be 0 on local pickup listings; total_price_cents on listing and order output is what the buyer pays",
    "condition_scale": [
      "new",
      "like_new",
      "good",
      "fair",
      "poor"
    ],
    "shipping_tiers": [
      "locker",
      "parcel",
      "bulky",
      "local",
      "unknown"
    ],
    "item_ids": "UUID v4",
    "attributes": "flexible JSONB object of category-specific fields; declared definitions validate types and required keys, unknown keys pass through and remain searchable (attribute filters match any key); discover per category via describe_category, whose undeclared_in_use section lists undeclared keys observed on active listings (item/seller counts, top 20 — search hints, not validated fields); writes capped at 64 entries, 128-char keys, 500-char string values (invalid_attributes beyond)",
    "order_states": [
      "pending",
      "sold",
      "label_issued",
      "in_transit",
      "delivered",
      "disputed",
      "payout_pending",
      "paid_out",
      "cancelled",
      "refunded"
    ],
    "order_deadlines": "server-enforced: unpaid cancels (24h default), dispute window after delivery (48h default, pickups auto-confirm 14d), never-shipped disputes auto-refund (72h)"
  },
  "rate_limits": {
    "authenticated_per_minute": 120,
    "anonymous_per_minute": 30
  },
  "docs": {
    "llms_txt": "https://thingery.dev/llms.txt",
    "github": "https://github.com/thingery/thingery"
  }
}
