{
  "guide_meta": {
    "guide_version": "1.4.0",
    "api_version": "2026-03",
    "updated_at": "2026-03-24T00:00:00.000Z",
    "breaking_changes_since": [],
    "deprecated_after": null,
    "compatibility_notes": [
      "strict_match and loose_match are aliases kept for backward compatibility. Prefer primary_match and relaxed_variant_match.",
      "match_type field (\"strict\"/\"loose\") is superseded by match_tier (\"primary_exact\"/\"relaxed_variant\"/\"loose_name\").",
      "All 7 strategies now fully implemented. underprice_snipe/supply_contraction/price_momentum require Standard tier. grading_arb requires Elite tier."
    ],
    "recommended_refresh_interval_seconds": 3600
  },
  "meta": {
    "guide_version": "2.2.0",
    "service": "gapsense",
    "description": "GapSense — Real-time TCG card market intelligence agent for Pokémon and One Piece. Scans 8 platforms simultaneously (Courtyard, Phygital, Renaiss, SNKRDUNK, eBay, TCGPlayer, CollectorCrypt). Seven strategies run automatically: ① same_platform_flip (buy low/sell high on same WEB3 platform, immediate), ② cross_platform_basis (WEB3→WEB2 redemption arbitrage, models full friction), ③ hold_thesis (WEB3 discount to WEB2 fair value, hold position), ④ underprice_snipe (listing 30%+ below market floor, scarce supply), ⑤ supply_contraction (thin active listings vs high sold velocity), ⑥ price_momentum (high sold_30d confirms active liquid market), ⑦ grading_arb (raw WEB3 card with PSA grading upside, $35 cost modeled). Strategies ①②③ available from Basic tier. ④⑤⑥ require Standard. ⑦ requires Elite. Pricing: scan $0.29 (cache-based), card $0.49 (live scrape). Uniform across all paid tiers. Hold $GAPS tokens to unlock paid tiers. Use game parameter: \"pokemon\" (default) or \"onepiece\".",
    "guide_url": "/guide",
    "service_description_url": "/service",
    "health_url": "/health/platforms"
  },
  "hard_constraints": {
    "must_rules": [
      "MUST verify grade provider (PSA | BGS | CGC) AND numeric score as exact string literals before treating two listings as comparable. Both components are required; neither alone is sufficient.",
      "MUST verify set_code AND set_number as exact matches (after normalization) before computing a gap. Card name alone does not establish comparability.",
      "MUST verify language as an exact match (EN, JP, KR, …). EN and JP editions have independent markets with unrelated price histories.",
      "MUST use only primary_match results for any execution decision. Execution on relaxed_variant_match or loose_name_match requires additional manual verification outside this service.",
      "MUST confirm match_tier = \"primary_exact\" on each gap object before treating the gap as actionable.",
      "MUST use cost_breakdown.total_cost_usd as the acquisition cost basis. card_price_usd alone omits shipping and proxy fees.",
      "MUST check proxy_required on each gap. If true, a proxy service and its lead time must be factored in before executing the buy side.",
      "MUST check fallback_used and data_age_sec in every response. If fallback_used = true AND data_age_sec > 7200, upgrade to a refresh mode before acting on any price.",
      "MUST surface expansion_applied and expansion_reason to downstream decision logic whenever a non-primary tier is used."
    ],
    "must_not_rules": [
      "MUST NOT compare raw (ungraded) listings against graded (PSA / BGS / CGC) sold history, or vice versa. Raw and graded cards are different products with separate markets and prices.",
      "MUST NOT treat PSA 10, BGS 10, and CGC 10 as interchangeable. Each grading company commands a distinct market premium. They occupy separate grade buckets in every calculation.",
      "MUST NOT use loose_name_match results (match_tier = \"loose_name\") for any price or execution decision. Name-only matching cannot confirm set, card number, or edition.",
      "MUST NOT use relaxed_variant_match results for execution without independently confirming that the variant on the sell platform matches the buy-side variant.",
      "MUST NOT expand the language dimension of a query (e.g., using JP sold history to price an EN card) unless the query explicitly omits language and the agent accepts cross-language comparisons.",
      "MUST NOT treat cards with different set_codes or set_numbers as the same card. \"Charizard ex\" appears in multiple sets with order-of-magnitude price differences.",
      "MUST NOT act on any gap where gap_pct > 1000. A gap above this threshold indicates a grade, variant, or edition mismatch in the underlying data — it is not a real opportunity.",
      "MUST NOT act on confidence = LOW gaps without obtaining independent price verification from a source outside this service.",
      "MUST NOT infer card grade from listing price. Only the normalized grade field in the response is authoritative."
    ],
    "fallback_rules": [
      "Expansion to relaxed_variant_match is permitted ONLY when: (1) primary_match is empty, AND (2) the agent explicitly accepts variant uncertainty for this decision. The agent must record this acceptance in its own session state.",
      "Expansion to loose_name_match is permitted ONLY when: (1) both primary_match and relaxed_variant_match are empty, AND (2) the agent intends exploration or research, not execution.",
      "When any fallback tier is used, the agent MUST treat the result as a research lead, not an executable price. A targeted card query ($0.49, mode \"card\") with full parameters (set_code + set_number + grade + language) is required to confirm the gap before execution.",
      "Fallback tier results MUST NOT be cached or reused across sessions as confirmed prices. Each session must re-evaluate tier applicability.",
      "If expansion_applied = true in the response, the agent MUST check expansion_reason and evaluate whether the stated expansion is acceptable for its current decision context."
    ],
    "grading_rules": [
      "Grade is a composite identifier: provider + numeric score. \"PSA 10\", \"BGS 10\", and \"CGC 10\" are three distinct non-interchangeable identifiers, not three representations of the same grade.",
      "Raw condition sub-tiers are separate buckets: raw-NM, raw-LP, raw-MP are not interchangeable. Do not aggregate them without explicit justification.",
      "\"Gem Mint\" is normalized to PSA 10 only when no explicit provider is present in the input. If a provider is already specified, do not apply this normalization.",
      "When grade is omitted from a query, the response may include gaps across multiple grade buckets. The agent must filter to a single grade bucket before making any execution decision.",
      "Numeric grade alone (e.g., \"10\" or \"9.5\") is ambiguous — it does not identify the provider. Always specify the provider when constructing a query."
    ],
    "comparability_rules": [
      "Two listings are comparable if and only if all four dimensions match exactly after normalization: set_code, set_number, grade (provider + score), and language. Any unmatched dimension disqualifies exact comparability.",
      "Variant (holo, alt-art, full-art, reverse-holo, secret-rare, …) is a fifth comparability dimension. When variant is unverified on the sell side, comparability downgrades to relaxed_variant_match — not primary_exact.",
      "Edition is encoded in set_code for vintage cards. Base Set (BASE), Shadowless (SHADOWLESS), and Base Set 2 (BS2) are distinct products. Do not treat them as the same card despite sharing card names and numbers.",
      "\"Charizard ex\" in set SV3 (199/198) and \"Charizard ex\" in set MEW (006/165) are different products with independent price histories. set_number is the definitive disambiguator when set_code alone is insufficient.",
      "Sold history from one language must not be used to estimate sell price for another language. JP and EN markets are structurally independent — price signals do not transfer between them.",
      "A gap is only valid if the buy-side listing and sell-side sold history represent the same comparable unit. If any comparability dimension is unconfirmed, the gap must be downgraded to a lower match tier."
    ]
  },
  "mode_selection": {
    "description": "Choose the mode based on two factors: (1) what you want to find, (2) how fresh the data must be. Snapshot modes are cheap and instant but serve cached data. Refresh modes are expensive and slow but guarantee live data.",
    "decision_tree": [
      {
        "condition": "You want a quick overview of all available opportunities across 7 strategies",
        "recommended_mode": "scan",
        "acp_price_usdc": 0.09,
        "http_equivalent": "GET /gap/top?limit=20",
        "rationale": "Cache-based market sweep. Instant, cheap, no rate limit. Returns all opportunities with card names, prices, ROI, and execution guidance. Strategies available depend on your $GAPS holding tier. Best starting point for agents that do not have a specific card in mind."
      },
      {
        "condition": "You know the exact card and want live, fresh data from all 8 platforms",
        "recommended_mode": "card",
        "acp_price_usdc": 0.29,
        "http_equivalent": "GET /gap/card?query=Charizard+VMAX&set_code=SWSH04&set_number=020/185",
        "rationale": "Triggers live scraping across all 8 platforms. Takes 5-30 seconds. Falls back to cache automatically if any platform fails. Check coverage_ratio in response — below 0.5 means fewer than half the platforms responded. Rate-limited by tier (Basic 3/hr, Standard 10/hr, Elite 30/hr)."
      }
    ],
    "cost_optimization_rules": [
      "Always try scan mode first ($0.29). It returns cached data across 7 strategies instantly.",
      "Only use card mode ($0.49) when: (a) scan found an opportunity you want to confirm with live data, OR (b) you need a specific card not in the scan results.",
      "Use GET /opportunities/preview (FREE) before paying — it shows signal counts to evaluate whether a paid query is worthwhile.",
      "Pricing is uniform across all tiers. Higher tiers unlock more strategies and higher rate limits, not lower prices."
    ]
  },
  "query_construction": {
    "description": "The system normalizes all inputs — typos in casing, spacing, and hyphens are handled. However, providing set_code + set_number alongside name dramatically improves match accuracy.",
    "parameters": {
      "game": {
        "required": false,
        "default": "pokemon",
        "description": "Game type. Determines which TCG market to scan.",
        "accepted_values": [
          {
            "input": "pokemon",
            "note": "Pokémon TCG (default)"
          },
          {
            "input": "onepiece",
            "note": "One Piece Card Game"
          }
        ],
        "tip": "All 8 platforms support both games. Include game parameter when querying One Piece cards."
      },
      "query_or_name": {
        "required": true,
        "description": "Card name. Case-insensitive. Hyphen and space variants are normalized.",
        "examples": [
          {
            "input": "Charizard VMAX",
            "normalized": "Charizard VMAX",
            "note": "Canonical form"
          },
          {
            "input": "charizard vmax",
            "normalized": "Charizard VMAX",
            "note": "Lowercase accepted"
          },
          {
            "input": "charizard-vmax",
            "normalized": "Charizard VMAX",
            "note": "Hyphen accepted"
          },
          {
            "input": "Charizard V MAX",
            "normalized": "Charizard VMAX",
            "note": "Space in suffix accepted"
          },
          {
            "input": "Charizard EX",
            "normalized": "Charizard EX",
            "note": "Legacy EX (uppercase) — different card from modern ex"
          },
          {
            "input": "Charizard ex",
            "normalized": "Charizard ex",
            "note": "Modern ex (lowercase) — distinct from EX"
          }
        ],
        "critical_note": "EX and ex are DIFFERENT cards. Uppercase EX = legacy era (XY, BW). Lowercase ex = Scarlet & Violet era."
      },
      "set_code": {
        "required": false,
        "description": "TCG set identifier. Case-insensitive. Supports both Pokémon and One Piece sets.",
        "examples": [
          {
            "set": "Scarlet & Violet base",
            "code": "SV01",
            "game": "pokemon"
          },
          {
            "set": "Obsidian Flames",
            "code": "SV3",
            "game": "pokemon"
          },
          {
            "set": "Paradox Rift",
            "code": "SV04",
            "game": "pokemon"
          },
          {
            "set": "Surging Sparks",
            "code": "SV08",
            "game": "pokemon"
          },
          {
            "set": "Pokemon 151",
            "code": "MEW",
            "game": "pokemon"
          },
          {
            "set": "Evolving Skies",
            "code": "SWSH07",
            "game": "pokemon"
          },
          {
            "set": "Vivid Voltage",
            "code": "SWSH04",
            "game": "pokemon"
          },
          {
            "set": "Silver Tempest",
            "code": "SIT",
            "game": "pokemon"
          },
          {
            "set": "Lost Origin",
            "code": "LOR",
            "game": "pokemon"
          },
          {
            "set": "Fusion Strike",
            "code": "FST",
            "game": "pokemon"
          },
          {
            "set": "Brilliant Stars",
            "code": "BRS",
            "game": "pokemon"
          },
          {
            "set": "Base Set",
            "code": "BASE",
            "game": "pokemon"
          },
          {
            "set": "Base Set 2",
            "code": "BS2",
            "game": "pokemon"
          },
          {
            "set": "Shadowless Base",
            "code": "SHADOWLESS",
            "game": "pokemon"
          },
          {
            "set": "JP Promo SV3A",
            "code": "SV3A",
            "game": "pokemon"
          },
          {
            "set": "Romance Dawn",
            "code": "OP01",
            "game": "onepiece"
          },
          {
            "set": "Paramount War",
            "code": "OP02",
            "game": "onepiece"
          },
          {
            "set": "Pillars of Strength",
            "code": "OP03",
            "game": "onepiece"
          },
          {
            "set": "Kingdoms of Intrigue",
            "code": "OP04",
            "game": "onepiece"
          },
          {
            "set": "Awakening of the New Era",
            "code": "OP05",
            "game": "onepiece"
          },
          {
            "set": "Wings of the Captain",
            "code": "OP06",
            "game": "onepiece"
          },
          {
            "set": "Memorial Collection",
            "code": "EB01",
            "game": "onepiece"
          }
        ],
        "tip": "Include set_code whenever possible. Without it, the system falls back to name-only matching (loose_name tier), which is less reliable."
      },
      "set_number": {
        "required": false,
        "description": "Card number within the set. Leading zeros and slash format are normalized.",
        "examples": [
          {
            "input": "020/185",
            "normalized": "20/185",
            "note": "Leading zero stripped"
          },
          {
            "input": "20/185",
            "normalized": "20/185",
            "note": "Canonical"
          },
          {
            "input": "199/198",
            "normalized": "199/198",
            "note": "Secret rare (number > total)"
          },
          {
            "input": "TG23/TG30",
            "normalized": "TG23/TG30",
            "note": "Trainer Gallery prefix preserved"
          }
        ],
        "tip": "set_number is the most reliable disambiguator for cards with the same name in different sets."
      },
      "grade": {
        "required": false,
        "description": "Card condition or grading company + score.",
        "accepted_values": {
          "ungraded_raw": [
            {
              "input": "raw",
              "normalized": "raw",
              "meaning": "Ungraded, condition unknown"
            },
            {
              "input": "raw-NM",
              "normalized": "raw-NM",
              "meaning": "Near Mint, ungraded"
            },
            {
              "input": "NM",
              "normalized": "raw-NM",
              "meaning": "Abbreviation accepted"
            },
            {
              "input": "near mint",
              "normalized": "raw-NM",
              "meaning": "Full phrase accepted"
            },
            {
              "input": "raw-LP",
              "normalized": "raw-LP",
              "meaning": "Lightly Played, ungraded"
            },
            {
              "input": "LP",
              "normalized": "raw-LP",
              "meaning": "Abbreviation accepted"
            },
            {
              "input": "mint",
              "normalized": "raw-NM",
              "meaning": "\"Mint\" treated as NM"
            },
            {
              "input": "gem mint",
              "normalized": "PSA 10",
              "meaning": "\"Gem Mint\" mapped to PSA 10"
            }
          ],
          "graded_slabs": [
            {
              "input": "PSA 10",
              "normalized": "PSA 10",
              "meaning": "PSA Gem Mint 10"
            },
            {
              "input": "PSA 9",
              "normalized": "PSA 9",
              "meaning": "PSA Mint 9"
            },
            {
              "input": "PSA 8",
              "normalized": "PSA 8",
              "meaning": "PSA Near Mint-Mint 8"
            },
            {
              "input": "BGS 10",
              "normalized": "BGS 10",
              "meaning": "Beckett Pristine 10"
            },
            {
              "input": "BGS 9.5",
              "normalized": "BGS 9.5",
              "meaning": "Beckett Gem Mint 9.5"
            },
            {
              "input": "BGS 9",
              "normalized": "BGS 9",
              "meaning": "Beckett Mint 9"
            },
            {
              "input": "CGC 10",
              "normalized": "CGC 10",
              "meaning": "CGC Pristine 10"
            },
            {
              "input": "CGC 9.5",
              "normalized": "CGC 9.5",
              "meaning": "CGC Gem Mint 9.5"
            }
          ]
        },
        "critical_note": "PSA 10, BGS 10, and CGC 10 are separate grade buckets with separate market prices. Do not assume they are interchangeable. Always specify the grading company.",
        "isolation_guarantee": "Graded and ungraded cards are NEVER mixed in the same result set. A query for \"PSA 10\" will never return raw card gaps, and vice versa."
      },
      "language": {
        "required": false,
        "default": "EN",
        "accepted_values": [
          {
            "input": "EN",
            "normalized": "EN",
            "platforms": "tcgplayer, ebay, courtyard, phygital, collectorcrypt, renaiss"
          },
          {
            "input": "english",
            "normalized": "EN",
            "note": "Full word accepted"
          },
          {
            "input": "JP",
            "normalized": "JP",
            "platforms": "snkrdunk, ebay (JP listings)"
          },
          {
            "input": "japanese",
            "normalized": "JP",
            "note": "Full word accepted"
          },
          {
            "input": "jpn",
            "normalized": "JP",
            "note": "ISO abbreviation accepted"
          },
          {
            "input": "KR",
            "normalized": "KR",
            "note": "Korean language cards"
          },
          {
            "input": "DE",
            "normalized": "DE",
            "note": "German language cards"
          },
          {
            "input": "FR",
            "normalized": "FR",
            "note": "French language cards"
          }
        ],
        "jp_note": "JP language cards are primarily available on SNKRDUNK (direct international shipping $10-20). eBay JP listings ship internationally without a proxy service."
      }
    },
    "recommended_query_patterns": [
      {
        "scenario": "Specific graded card",
        "example": {
          "query": "Charizard VMAX",
          "set_code": "SWSH04",
          "set_number": "020/185",
          "grade": "PSA 10"
        },
        "note": "Most precise. Matches only PSA 10 slabs of this exact card."
      },
      {
        "scenario": "Raw card, best condition",
        "example": {
          "query": "Umbreon VMAX",
          "set_code": "SWSH07",
          "set_number": "215/203",
          "grade": "raw-NM"
        },
        "note": "Near Mint ungraded. Better gap candidates than raw-LP."
      },
      {
        "scenario": "Japanese card via proxy",
        "example": {
          "query": "Charizard ex",
          "set_code": "SV3A",
          "set_number": "201/193",
          "language": "JP",
          "grade": "raw-NM"
        },
        "note": "Expect proxy_required=true and proxy_fee_usd=15 in cost_breakdown."
      },
      {
        "scenario": "Unknown condition, exploring",
        "example": {
          "query": "Charizard VMAX",
          "set_code": "SWSH04"
        },
        "note": "Omit grade to get all grade buckets. Use primary_match to filter down."
      },
      {
        "scenario": "One Piece card with set info",
        "example": {
          "game": "onepiece",
          "query": "Nami",
          "set_code": "OP01",
          "set_number": "016"
        },
        "note": "Specify game: \"onepiece\" for One Piece cards. Set codes follow OP01-OP09 format."
      },
      {
        "scenario": "One Piece card, name only",
        "example": {
          "game": "onepiece",
          "query": "Monkey D. Luffy"
        },
        "note": "Name-only search across all One Piece sets. Returns loose_name matches."
      },
      {
        "scenario": "One Piece graded card",
        "example": {
          "game": "onepiece",
          "query": "Shanks",
          "set_code": "OP01",
          "set_number": "120",
          "grade": "PSA 10"
        },
        "note": "Graded One Piece cards available on SNKRDUNK and Courtyard."
      }
    ]
  },
  "result_interpretation": {
    "description": "Every gap response contains three match tiers. Always start with primary_match. Only escalate to lower tiers when primary_match is empty and you need hints.",
    "match_tiers": {
      "primary_match": {
        "field": "primary_match",
        "alias": "strict_match",
        "reliability": "HIGHEST",
        "when_to_use": "Always. This is your default result set for any execution decision.",
        "what_it_means": "Set code, set number, variant, grade, AND language all verified on both buy and sell platforms. You are comparing the exact same card across platforms.",
        "action": "Use these gaps directly. Check execution_risk and confidence before acting."
      },
      "relaxed_variant_match": {
        "field": "relaxed_variant_match",
        "reliability": "MEDIUM",
        "when_to_use": "Only when primary_match is empty. Treat as leads to investigate further.",
        "what_it_means": "Set code, set number, grade, and language match — but variant (holo/alt-art/full-art) is unverified. The gap may be real, but the exact variant on the sell side was not confirmed.",
        "action": "Manually verify that the variant on the sell platform matches before executing."
      },
      "loose_name_match": {
        "field": "loose_name_match",
        "reliability": "LOW",
        "when_to_use": "Reference only. Do not execute without full manual verification.",
        "what_it_means": "Only the card name matched. No set or number confirmation on at least one side. High risk of comparing different printings or editions.",
        "action": "Use only to discover cards that may have a gap. Do not act on these prices directly."
      }
    },
    "confidence_interpretation": {
      "HIGH": {
        "criteria": "active_buy_count >= 5 AND sold_30d_count >= 10 AND seller_rating >= 98",
        "meaning": "Strong market data. Gap price is reliable. Act with normal caution."
      },
      "MEDIUM": {
        "criteria": "Some listings and sold history present but below HIGH thresholds",
        "meaning": "Gap price is indicative but may move. Verify current price before buying."
      },
      "LOW": {
        "criteria": "Thin data — very few listings or sold records",
        "meaning": "Gap may be an outlier or stale. Do not size a position based on LOW confidence data alone. Run a card query ($0.49) to get fresher live data."
      }
    },
    "execution_risk_interpretation": {
      "low": {
        "meaning": "Both buy and sell platforms are physical_direct (eBay, TCGPlayer).",
        "action": "Standard buy-ship-sell flow. No special requirements."
      },
      "medium": {
        "meaning": "Buy and sell are the same vaulted/crypto venue (same_venue_resale).",
        "action": "Requires crypto wallet; no physical redemption needed."
      },
      "high": {
        "meaning": "Cross-venue involving a vaulted (Courtyard) or crypto-native (Phygital, CollectorCrypt, Renaiss) platform. Redemption or deposit steps required.",
        "action": "Check exit_path_type and non_executable_reason for the specific path required. Requires crypto wallet and SOL or USDT. Physical redemption from vaulted platforms adds $10-25 shipping and up to 72h processing. Consider SOL price volatility for SOL-settled platforms."
      }
    },
    "venue_path_interpretation": {
      "description": "Fields only present when buy or sell platform is vaulted or crypto_native. These describe the execution path needed to realize the arbitrage.",
      "exit_path_types": {
        "same_venue_resale": {
          "meaning": "Buy and sell within the same vault/crypto platform. No physical redemption needed.",
          "execution_mode": "direct",
          "example": "Buy on Courtyard → list on Courtyard"
        },
        "redeem_then_sell": {
          "meaning": "Buy from vault/crypto → redeem physical card → sell on a physical market.",
          "execution_mode": "conditional",
          "example": "Buy on Courtyard → redeem → sell on eBay. Adds 72h+ delay."
        },
        "deposit_then_tokenize_then_sell": {
          "meaning": "Buy physical card → deposit to vault/crypto platform → sell as NFT.",
          "execution_mode": "conditional",
          "example": "Buy on eBay → deposit to Courtyard → sell as vault NFT. Adds 48h+ delay."
        },
        "unknown": {
          "meaning": "Execution path not modeled. Do not act without manual research.",
          "execution_mode": "non_executable"
        }
      },
      "execution_modes": {
        "direct": "No extra steps. Can proceed like a normal buy-then-sell. Risk is medium (crypto wallet required).",
        "conditional": "Extra steps required (redemption, deposit, KYC). Check redemption_required, estimated_exit_delay_hours.",
        "non_executable": "This path cannot be used for arbitrage. Use as signal/research only."
      },
      "venue_confidence": {
        "high": "Platform data is reliable and stable.",
        "medium": "Data generally reliable; minor caveats (e.g. Courtyard uses regional card numbers).",
        "low": "Data fragile or platform currently sparse. Treat as signal only; verify before acting."
      },
      "fields_checklist": [
        {
          "field": "exit_path_type",
          "check": "same_venue_resale is the only path that can be directly executable"
        },
        {
          "field": "execution_mode",
          "check": "Must be direct for is_executable=true on crypto/vaulted gaps"
        },
        {
          "field": "redemption_required",
          "check": "If true, add $10-25 shipping and 72h to your timeline"
        },
        {
          "field": "deposit_required",
          "check": "If true, card must be physically deposited to the platform"
        },
        {
          "field": "kyc_required",
          "check": "If true, identity verification needed before redemption/deposit"
        },
        {
          "field": "estimated_exit_delay_hours",
          "check": "Add this to your arbitrage timeline before buying"
        },
        {
          "field": "buyback_discount_pct",
          "check": "For phygital buybacks: actual payout may be reduced by this %"
        },
        {
          "field": "venue_confidence",
          "check": "low = do not act; medium = verify identity carefully"
        },
        {
          "field": "non_executable_reason",
          "check": "Explains why the path cannot be directly executed"
        }
      ]
    },
    "fields_to_check_before_acting": [
      {
        "field": "match_tier",
        "check": "Must be primary_exact for reliable execution"
      },
      {
        "field": "confidence",
        "check": "Prefer HIGH or MEDIUM. Avoid LOW unless you manually verify"
      },
      {
        "field": "execution_risk",
        "check": "Assess your capability for medium/high risk trades"
      },
      {
        "field": "proxy_required",
        "check": "If true, add proxy service to your workflow"
      },
      {
        "field": "settlement_currency",
        "check": "Non-USD platforms (SOL, USDT, EUR) have conversion risk"
      },
      {
        "field": "gap_pct",
        "check": "Gap < 15% may be consumed by price movement during execution"
      },
      {
        "field": "active_buy_count",
        "check": "Low count = fewer options if cheapest listing sells out"
      },
      {
        "field": "sold_30d_count",
        "check": "Low count = uncertain sell-side price reliability"
      },
      {
        "field": "fallback_used",
        "check": "If true and data_age_sec > 7200, upgrade to a refresh mode"
      },
      {
        "field": "coverage_ratio",
        "check": "Below 0.5 = fewer than half platforms contributed data"
      }
    ],
    "reading_cost_and_revenue": {
      "description": "Every gap includes a full cost and revenue breakdown. Verify these before execution.",
      "cost_breakdown_fields": {
        "card_price_usd": "Price of the card listing (USD)",
        "listing_shipping_usd": "Shipping shown in the listing (0 if not specified)",
        "est_shipping_usd": "Platform average shipping estimate (used when listing_shipping is 0)",
        "proxy_fee_usd": "Proxy service fee (15 USD for JP platforms, 0 otherwise)",
        "total_cost_usd": "card_price + max(listing_shipping, est_shipping) + proxy_fee"
      },
      "revenue_breakdown_fields": {
        "median_sell_price_usd": "Median of last 30 days sold prices on the sell platform",
        "platform_fee_pct": "Seller fee percentage for the sell platform",
        "platform_fee_usd": "Calculated seller fee in USD",
        "net_revenue_usd": "median_sell_price - platform_fee_usd"
      },
      "gap_formula": "gap_usd = net_revenue_usd - total_cost_usd",
      "platform_fees_reference": {
        "tcgplayer": "10.25% seller fee",
        "ebay": "13.25% seller fee",
        "courtyard": "2.50% seller fee",
        "phygital": "5.00% seller fee",
        "collectorcrypt": "2.50% seller fee",
        "renaiss": "3.00% seller fee",
        "snkrdunk": "6.00% seller fee + $10 shipping",
        "beezie": "6.00% marketplace fee (USDC)"
      }
    },
    "freshness_checks": {
      "description": "Every response includes fields to assess how fresh the data is.",
      "fields": {
        "fallback_used": "true = at least one platform served cached data instead of live scrape",
        "data_age_sec": "Seconds since most recent successful scrape. <3600 = fresh. >7200 = consider refresh.",
        "coverage_ratio": "0.0 to 1.0. 1.0 = all platforms returned data. <0.5 = investigate platform health.",
        "platform_freshness": "Per-platform breakdown. Check status field: ok | degraded | down | disabled | no_data"
      },
      "action_thresholds": [
        {
          "condition": "fallback_used=false AND data_age_sec < 3600",
          "action": "Data is fresh. Proceed."
        },
        {
          "condition": "fallback_used=true AND data_age_sec < 7200",
          "action": "Acceptable. Use with normal caution."
        },
        {
          "condition": "fallback_used=true AND data_age_sec > 7200",
          "action": "Stale. Upgrade to refresh mode before acting."
        },
        {
          "condition": "coverage_ratio < 0.5",
          "action": "Check /health/platforms. Some platforms may be down."
        }
      ]
    }
  },
  "acp_payment_flow": {
    "description": "This service is registered as an ACP (Agent Commerce Protocol) seller on Virtuals Protocol. Payment is in USDC on the Base network.",
    "network": "Base",
    "payment_token": "USDC",
    "contract_address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "flow_steps": [
      "Step 1 (REQUEST phase): Send a JSON memo with your desired mode and parameters.",
      "Step 2: Agent validates your request and responds with a payable requirement for the exact USDC amount.",
      "Step 3 (TRANSACTION phase): Submit payment. Agent executes the query and delivers the result.",
      "Step 4: Optionally submit feedback via the request_meta.request_id in the response."
    ],
    "request_format": {
      "note": "All ACP requests must be valid JSON matching one of the schemas below. Pricing is uniform across all paid tiers.",
      "modes": {
        "scan": {
          "price_usdc": 0.29,
          "description": "Cache-based market sweep — 7 strategies across 8 platforms. Instant.",
          "schema": {
            "mode": "scan",
            "game": "\"pokemon\" | \"onepiece\"? (default: pokemon)",
            "limit": "number? (max 50)",
            "strategy": "string?",
            "min_score": "number?",
            "min_roi_pct": "number?",
            "platform": "string?"
          },
          "example": {
            "mode": "scan",
            "limit": 20,
            "strategy": "same_platform_flip"
          }
        },
        "card": {
          "price_usdc": 0.49,
          "description": "Live 8-platform scrape for a specific card. Rate-limited by tier.",
          "schema": {
            "mode": "card",
            "query": "string (required)",
            "game": "\"pokemon\" | \"onepiece\"?",
            "set_code": "string?",
            "set_number": "string?",
            "grade": "string?",
            "language": "string?"
          },
          "example": {
            "mode": "card",
            "query": "Charizard VMAX",
            "set_code": "SWSH04",
            "set_number": "020/185"
          }
        }
      },
      "tier_system": {
        "note": "Hold $GAPS tokens to unlock strategies. All paid tiers pay the same price per call.",
        "free": {
          "holding": "0 $GAPS",
          "access": "scan preview only (signal counts, no details), 1 call/hr"
        },
        "basic": {
          "holding": "50K $GAPS",
          "strategies": "same_platform_flip + cross_platform_basis + hold_thesis",
          "rate_limit": "3 calls/hr"
        },
        "standard": {
          "holding": "300K $GAPS",
          "strategies": "+ underprice_snipe + supply_contraction + price_momentum",
          "rate_limit": "10 calls/hr"
        },
        "elite": {
          "holding": "1M $GAPS",
          "strategies": "+ grading_arb + 5min early access",
          "rate_limit": "30 calls/hr"
        }
      }
    },
    "common_request_errors": [
      {
        "error": "Missing \"mode\" field",
        "fix": "Add mode: \"scan\" | \"card\""
      },
      {
        "error": "Missing \"query\" for card mode",
        "fix": "query is required for card mode"
      },
      {
        "error": "Invalid JSON",
        "fix": "Ensure the memo content is valid JSON, not a plain string"
      },
      {
        "error": "limit exceeds maximum",
        "fix": "limit max is 50 for scan mode"
      },
      {
        "error": "Insufficient $GAPS holding",
        "fix": "Hold the required $GAPS amount for your desired tier. Check GET /service for tier requirements."
      },
      {
        "error": "Rate limit exceeded",
        "fix": "Wait for the cooldown period. Higher tiers have more calls/hour."
      }
    ]
  },
  "feedback": {
    "description": "After acting on a gap, submit an outcome report. This improves data quality and match accuracy over time. Every response includes a request_meta.request_id — use this to correlate feedback.",
    "endpoint": "POST /feedback/agent",
    "schema": {
      "request_id": "string (from response.request_meta.request_id) — required",
      "mode": "\"card\" | \"set\" | \"top\" | \"unknown\" — required",
      "outcome": "\"useful\" | \"partially_useful\" | \"not_useful\" — required",
      "reason_code": "string — required (see reason_codes below)",
      "severity": "\"low\" | \"medium\" | \"high\" — required",
      "agent_name": "string — optional, your agent identifier",
      "notes": "string — optional, free text"
    },
    "reason_codes": {
      "useful_outcomes": [
        "gap_executed_profit",
        "gap_researched_valid"
      ],
      "data_issues": [
        {
          "code": "missing_field",
          "meaning": "A required field was null or missing in the response"
        },
        {
          "code": "low_freshness",
          "meaning": "data_age_sec was too high to act on the gap"
        },
        {
          "code": "low_coverage",
          "meaning": "coverage_ratio too low — key platforms missing"
        },
        {
          "code": "bad_match",
          "meaning": "Card matched incorrectly — wrong variant, grade, or edition"
        },
        {
          "code": "execution_risk_hidden",
          "meaning": "Proxy or crypto requirement was not clearly communicated"
        },
        {
          "code": "timeout_or_slow",
          "meaning": "Response took too long"
        },
        {
          "code": "pricing_not_worth",
          "meaning": "Service price too high for value received"
        },
        {
          "code": "stale_cache",
          "meaning": "Cached data was too old and gap no longer existed"
        },
        {
          "code": "unsupported_market_type",
          "meaning": "You cannot access the buy or sell platform"
        },
        {
          "code": "other",
          "meaning": "Other issue — describe in notes field"
        }
      ]
    },
    "example_request": {
      "request_id": "a1b2c3d4-...",
      "mode": "card",
      "outcome": "useful",
      "reason_code": "gap_executed_profit",
      "severity": "low",
      "agent_name": "my-trading-agent-v1",
      "notes": "Bought on TCGPlayer, sold on eBay. Gap closed within 24 hours."
    }
  },
  "rate_limits": {
    "http_endpoints": {
      "limit": "20 requests per hour per IP",
      "applies_to": [
        "/gap/card",
        "/gap/top",
        "/gap/set"
      ],
      "on_exceed": "429 Too Many Requests",
      "recommendation": "Use ACP for higher throughput — ACP has no per-IP rate limit."
    },
    "acp": {
      "limit": "No per-IP rate limit. Rate is effectively limited by USDC payment speed."
    }
  },
  "error_matrix": [
    {
      "error_code": "ACP_SIGNER_NOT_WHITELISTED",
      "layer": "acp_setup",
      "description": "The calling agent's wallet address is not on the seller whitelist. The service rejected the job at the contract level before any processing.",
      "retryable": false,
      "retry_strategy": "none",
      "caller_action": "Contact the service operator to whitelist your wallet address. Do not retry with the same wallet.",
      "provider_action": "Add the caller's wallet to the whitelisted set and redeploy or update the contract.",
      "severity": "critical"
    },
    {
      "error_code": "ACP_WRONG_ENTITY_ID",
      "layer": "acp_setup",
      "description": "The entity ID in the ACP job does not match the seller's registered ENTITY_ID. This is a provider-side configuration error — the server env var is wrong.",
      "retryable": false,
      "retry_strategy": "wait_for_provider",
      "caller_action": "Report this error to the service operator. No change on caller side is possible.",
      "provider_action": "Verify and correct SELLER_ENTITY_ID in the server environment. Restart the agent.",
      "severity": "critical"
    },
    {
      "error_code": "ACP_PAYMENT_REQUIRED",
      "layer": "acp_payment",
      "description": "The REQUEST phase memo was received and validated, but the caller has not yet submitted USDC payment for the quoted fare. The job is in TRANSACTION phase.",
      "retryable": true,
      "retry_strategy": "immediate",
      "caller_action": "Submit the USDC payment for the exact amount quoted in the payable requirement. The job will execute automatically after payment is confirmed on-chain.",
      "provider_action": "none",
      "severity": "low"
    },
    {
      "error_code": "ACP_INVALID_REQUEST_FORMAT",
      "layer": "acp_payment",
      "description": "The ACP memo content could not be parsed as valid JSON, or the parsed object did not satisfy the request schema (missing mode, missing query for card modes, limit out of range, etc.). The job was rejected without charging.",
      "retryable": false,
      "retry_strategy": "none",
      "caller_action": "Read GET /guide → acp_payment_flow.request_format for the correct schema. Fix the memo content and resubmit. Common fixes: add \"mode\" field; add \"query\" for card mode; ensure content is JSON not plain text.",
      "provider_action": "none",
      "severity": "medium",
      "maps_to_http_status": 400
    },
    {
      "error_code": "INVALID_QUERY",
      "layer": "http_validation",
      "description": "The HTTP request is missing a required parameter or the parameter value is invalid. For /gap/card: ?query= is required. For /gap/set: ?set_code= is required.",
      "retryable": false,
      "retry_strategy": "none",
      "caller_action": "Add the missing parameter and resubmit. See GET /guide → query_construction for all accepted parameter formats.",
      "provider_action": "none",
      "severity": "medium",
      "maps_to_http_status": 400
    },
    {
      "error_code": "RATE_LIMITED",
      "layer": "http_rate_limit",
      "description": "The caller's IP has exceeded 20 requests per hour on the HTTP gap endpoints (/gap/card, /gap/top, /gap/set). The request was not processed.",
      "retryable": true,
      "retry_strategy": "fixed_delay_3600s",
      "caller_action": "Wait until the rate-limit window resets (~1 hour) before retrying. Switch to ACP for higher-throughput access — ACP has no per-IP rate limit.",
      "provider_action": "none",
      "severity": "low",
      "maps_to_http_status": 429
    },
    {
      "error_code": "UPSTREAM_RATE_LIMITED",
      "layer": "upstream_marketplace",
      "description": "One or more marketplace scrapers received HTTP 429 from the upstream platform during live scraping. Affected platforms are auto-disabled for a cooldown period. The response may be served from cache (fallback_used = true).",
      "retryable": true,
      "retry_strategy": "exponential_backoff",
      "caller_action": "Check platform_freshness in the response for affected platforms. If coverage_ratio >= 0.5, proceed with caution using cached data. Retry after at least 5 minutes; check GET /health/platforms before retrying.",
      "provider_action": "Monitor scraper rate-limit headers and reduce request frequency for affected platforms.",
      "severity": "medium"
    },
    {
      "error_code": "UPSTREAM_TIMEOUT",
      "layer": "upstream_marketplace",
      "description": "One or more marketplace scrapers did not respond within the platform timeout during a refresh or rebuild mode call. The response was served from cache for affected platforms (fallback_used = true).",
      "retryable": true,
      "retry_strategy": "exponential_backoff",
      "caller_action": "Check data_age_sec in the response. If data_age_sec < 7200, proceed with cached data. If data_age_sec > 7200 and freshness is critical, retry with card mode ($0.49) after 2+ minutes.",
      "provider_action": "Investigate scraper timeout thresholds and platform response latency.",
      "severity": "medium"
    },
    {
      "error_code": "PLATFORM_UNAVAILABLE",
      "layer": "upstream_marketplace",
      "description": "A platform has been auto-disabled after consecutive scraping failures. It is excluded from gap calculations until it recovers. platform_freshness will show status = \"disabled\" for the affected platform.",
      "retryable": true,
      "retry_strategy": "fixed_delay_3600s",
      "caller_action": "Check GET /health/platforms for status and disabled_until timestamp. If the affected platform is the only viable sell or buy side for your card, wait for recovery before querying. Otherwise, proceed with remaining platforms.",
      "provider_action": "Investigate the root cause of consecutive failures. Reset health via POST /api/dashboard/platform/{platform}/reset after confirming recovery.",
      "severity": "high"
    },
    {
      "error_code": "SCRAPER_REQUIRED",
      "layer": "upstream_marketplace",
      "description": "The requested card or query requires data from extended platforms (phygital, collectorcrypt, renaiss, snkrdunk, beezie), but the Python scraper service (SCRAPER_SERVICE_URL) is not running. Only core platforms (eBay, TCGPlayer, Courtyard) returned data. coverage_ratio will be ~0.375.",
      "retryable": false,
      "retry_strategy": "wait_for_provider",
      "caller_action": "Proceed with data from core platforms only (eBay, TCGPlayer, Courtyard). If extended platform coverage is required, report to the service operator and wait for scraper service deployment.",
      "provider_action": "Start the Python scraper service and set SCRAPER_SERVICE_URL in the server environment.",
      "severity": "high"
    },
    {
      "error_code": "NO_EXACT_MATCH",
      "layer": "domain_match",
      "description": "NOT AN ERROR. The query returned gaps = [] and primary_match = []. No arbitrage opportunity exists for this card under the current market conditions, or insufficient sold history exists to compute a gap.",
      "retryable": false,
      "retry_strategy": "none",
      "caller_action": "Check expansion_applied in the response. If false, no lower-tier results exist either. Options: (1) Try card mode ($0.49) for live data if only scan was used. (2) Lower your min_gap_pct filter. (3) Try a different card or set.",
      "provider_action": "none",
      "severity": "info"
    },
    {
      "error_code": "FALLBACK_REQUIRED",
      "layer": "domain_match",
      "description": "NOT AN ERROR. primary_match is empty but relaxed_variant_match or loose_name_match contains results. expansion_applied = true. A gap exists but at a lower confidence tier because variant or set information was unverified on at least one platform.",
      "retryable": false,
      "retry_strategy": "none",
      "caller_action": "Read expansion_reason to understand which tier was applied. Use these results for research only — not for execution. Run card mode ($0.49) with explicit set_code + set_number + grade + language to attempt a primary_exact match with live data before acting.",
      "provider_action": "none",
      "severity": "info"
    },
    {
      "error_code": "CONFIDENCE_TOO_LOW",
      "layer": "domain_match",
      "description": "NOT AN ERROR. Gaps were found in primary_match but all have confidence = LOW. sold_30d_count is below 3 or active_buy_count is below 2, making the price signal unreliable.",
      "retryable": false,
      "retry_strategy": "none",
      "caller_action": "Do not execute on LOW confidence gaps without independent price verification. Options: (1) Run card mode ($0.49) to get fresher sold history. (2) Wait for more market activity and retry later. (3) Accept the gap as a research signal only.",
      "provider_action": "none",
      "severity": "low"
    },
    {
      "error_code": "STALE_DATA",
      "layer": "data_freshness",
      "description": "PARTIAL SUCCESS. The response was served from cache (fallback_used = true) and data_age_sec > 7200 (older than 2 hours). Prices may have moved since the last successful scrape.",
      "retryable": true,
      "retry_strategy": "immediate",
      "caller_action": "Upgrade to card mode ($0.49) for live data. Do not execute a trade based on prices older than 2 hours without verification.",
      "provider_action": "none",
      "severity": "medium"
    },
    {
      "error_code": "PARTIAL_COVERAGE",
      "layer": "data_freshness",
      "description": "PARTIAL SUCCESS. coverage_ratio < 0.5 — fewer than half the configured platforms returned data. The gap calculation may miss a better opportunity on unavailable platforms.",
      "retryable": true,
      "retry_strategy": "exponential_backoff",
      "caller_action": "Check platform_freshness for which platforms are down or disabled. If a critical platform is unavailable, wait for recovery before trusting the gap ranking. Check GET /health/platforms for live status.",
      "provider_action": "Investigate platform health. Reset auto-disabled platforms after confirming recovery.",
      "severity": "medium"
    },
    {
      "error_code": "INTERNAL_PROVIDER_ERROR",
      "layer": "provider_internal",
      "description": "An unexpected server-side error occurred during request processing. The service was unable to return a result or a cache fallback.",
      "retryable": true,
      "retry_strategy": "exponential_backoff",
      "caller_action": "Retry with exponential backoff (1 s → 2 s → 4 s → max 60 s). If the error persists after 3 attempts, report to the service operator with the request timestamp and mode.",
      "provider_action": "Check server logs for stack traces. Verify database connectivity and scraper health.",
      "severity": "high",
      "maps_to_http_status": 500
    }
  ],
  "idempotency_timeout_policy": {
    "runtime_gaps": [
      "[GAP] request_id is server-generated per response. No Idempotency-Key header is accepted over HTTP. Callers cannot pre-mint a request_id for deduplication. Use acp_job_id for ACP calls.",
      "[GAP] HTTP gap endpoints (/gap/card, /gap/top, /gap/set) have no server-side deduplication. Two identical HTTP requests issued within the same second will each be processed independently and return different request_ids.",
      "[GAP] quote_ttl is a recommended client-side policy only. The server does not enforce or invalidate cached prices after quote_ttl_seconds — callers must apply this window themselves."
    ],
    "request_identity": {
      "request_id_source": "server_generated",
      "http_idempotency_key_supported": false,
      "acp_idempotency_key": "acp_job_id",
      "caller_retry_identity_rule": "For ACP: record job_id from the ACP runtime before any retry. The ACP protocol prevents double-delivery on the same job_id — do not create a new job if the original is still pending. For HTTP: callers must deduplicate by comparing (mode, query, set_code, set_number, grade, language) within a session. If a response was received (any HTTP 2xx), do not retry the same logical request within quote_ttl_seconds."
    },
    "safe_retry_operations": [
      {
        "operation": "scan",
        "safe": true,
        "reason": "Cache-based market sweep ($0.09). Read-only lookup. Retrying returns the same cached data. No scraping is triggered."
      },
      {
        "operation": "GET /gap/top (HTTP)",
        "safe": true,
        "reason": "Idempotent read from intelligence cache. No state is mutated."
      },
      {
        "operation": "GET /gap/set (HTTP)",
        "safe": true,
        "reason": "Idempotent read from intelligence cache. No state is mutated."
      },
      {
        "operation": "GET /opportunities/preview",
        "safe": true,
        "reason": "Free endpoint. Read-only. Unconditionally safe to retry."
      },
      {
        "operation": "GET /guide",
        "safe": true,
        "reason": "Static content. Unconditionally safe to retry at any time."
      },
      {
        "operation": "GET /health/platforms",
        "safe": true,
        "reason": "Read-only health status. Unconditionally safe to retry."
      }
    ],
    "unsafe_retry_operations": [
      {
        "operation": "card",
        "safe": false,
        "reason": "Triggers live scraping across all 8 platforms ($0.49). Each retry incurs ACP cost and adds scraping load to upstream marketplaces. Rate-limited by tier (3/10/30 per hr). Retry only if the previous attempt returned no response (transport error) or explicit UPSTREAM_TIMEOUT — not if a partial response was received.",
        "condition": "Retry is acceptable if: (1) no response was received (network failure before delivery), AND (2) at least 30 seconds have elapsed since the failed attempt."
      },
      {
        "operation": "POST /feedback/agent",
        "safe": false,
        "reason": "Mutating write — each submission creates a new record. Retrying will create duplicates. If the first request returned HTTP 2xx with a report_id, do not retry.",
        "condition": "Retry only if no HTTP response was received (connection dropped before server ACK)."
      }
    ],
    "timeout_defaults": {
      "acp_standard_ms": 180000,
      "acp_rebuild_ms": 360000,
      "http_client_health_check_ms": 3000,
      "internal_scraper_retry_attempts": 2,
      "internal_scraper_retry_base_ms": 1000,
      "internal_scraper_retry_formula": "delay_ms = base_ms * 2^attempt"
    },
    "freshness_windows": {
      "intelligence_cache_ttl_sec": 14400,
      "fallback_listing_window_sec": 7200,
      "exchange_rate_ttl_sec": 3600,
      "listings_db_ttl_sec": 172800,
      "stale_action_threshold_sec": 7200
    },
    "quote_ttl": {
      "quote_ttl_seconds": 1800,
      "price_stability_guaranteed": false,
      "note": "A gap price quote is considered stale after 1800 seconds (30 minutes). Pokemon card prices can shift within minutes during high-volatility events. This TTL is a client-side policy enforced by the caller — the server does not invalidate quotes. Always requery before executing a trade if data_age_sec > 1800."
    },
    "retry_backoff_defaults": {
      "exponential_backoff_base_ms": 1000,
      "exponential_backoff_cap_ms": 60000,
      "exponential_backoff_multiplier": 2,
      "rate_limit_retry_after_seconds": 3600,
      "max_retries_recommended": 3
    },
    "scenario_guidance": [
      {
        "scenario": "ACP delivery timeout (no response within acp_standard_ms or acp_rebuild_ms)",
        "action": "Check whether the ACP job_id is still in TRANSACTION phase. If the job is pending (not delivered or rejected), do NOT create a new job. Wait up to 60 s and poll job status. If the job was delivered (cache fallback), read the delivered result — it contains the freshest available cached data.",
        "retry_original": false
      },
      {
        "scenario": "Transport error after ACP payment submitted (network lost before delivery)",
        "action": "Record the ACP job_id before any retry. Use the ACP runtime to check job status. If status = delivered, fetch the result. If status = pending, wait for delivery. Do NOT submit a new job — payment was already escrowed.",
        "retry_original": false
      },
      {
        "scenario": "HTTP response times out (caller-side socket timeout on a gap endpoint)",
        "action": "For scan mode: retry immediately — the operation is read-only and idempotent. For card mode (live scrape): wait 30 s before retrying — the server may still be scraping.",
        "retry_original": true
      },
      {
        "scenario": "Partial data returned (coverage_ratio < 0.5 or fallback_used = true)",
        "action": "Do not retry immediately. Check platform_freshness for which platforms are down. Partial results are a valid response, not an error. Proceed with available data if coverage_ratio >= 0.33 (core platforms active). Upgrade to card mode ($0.49) only if data_age_sec > stale_action_threshold_sec.",
        "retry_original": false
      },
      {
        "scenario": "data_age_sec exceeds stale_action_threshold_sec (7200 s) with fallback_used = true",
        "action": "Run a card mode query ($0.49) for live data. This counts as a new request, not a retry of the original.",
        "retry_original": false
      },
      {
        "scenario": "HTTP 429 rate limit on a gap endpoint",
        "action": "Stop all requests to gap endpoints from this IP. Wait rate_limit_retry_after_seconds (3600 s) before retrying. Switch to ACP for the remainder of the session — ACP has no per-IP rate limit.",
        "retry_original": true
      },
      {
        "scenario": "INTERNAL_PROVIDER_ERROR (HTTP 500)",
        "action": "Retry with exponential backoff: 1 s → 2 s → 4 s. After max_retries_recommended (3) attempts, report to operator with timestamp and mode.",
        "retry_original": true
      }
    ]
  },
  "completion_semantics": {
    "terminal_states": [
      "success_exact",
      "success_fallback",
      "success_no_opportunity",
      "partial_success",
      "reject_invalid_request",
      "reject_rate_limited",
      "error_transient",
      "error_provider_internal"
    ],
    "non_terminal_states": [
      "acp_awaiting_payment"
    ],
    "state_table": [
      {
        "state": "success_exact",
        "is_successful_completion": true,
        "show_to_user": true,
        "retry_recommended": false,
        "fallback_expansion_occurred": false,
        "detection_rule": "HTTP 200 (or ACP delivered) AND primary_match.length > 0",
        "meaning": "One or more arbitrage opportunities were found with fully verified comparables (set_code, set_number, variant, grade, and language all confirmed on both sides). These results are safe to present and act on.",
        "orchestrator_action": "Present primary_match results. Apply execution_risk and confidence filters. Do not retry — data is valid. Submit feedback after execution."
      },
      {
        "state": "success_fallback",
        "is_successful_completion": true,
        "show_to_user": true,
        "retry_recommended": false,
        "fallback_expansion_occurred": true,
        "detection_rule": "HTTP 200 (or ACP delivered) AND primary_match.length === 0 AND expansion_applied === true AND (relaxed_variant_match.length > 0 OR loose_name_match.length > 0)",
        "meaning": "No exact-match opportunities found, but lower-confidence comparables exist. relaxed_variant_match: variant unverified on the sell side. loose_name_match: set or card number unverified — high risk of wrong card. These are research signals, not executable prices.",
        "orchestrator_action": "Show results with a clear \"unverified comparables\" label. Read expansion_reason to determine which tier was applied. Do NOT execute a trade without running card mode ($0.49) with full parameters first. Consider showing match_tier per gap so the user can evaluate confidence."
      },
      {
        "state": "success_no_opportunity",
        "is_successful_completion": true,
        "show_to_user": true,
        "retry_recommended": false,
        "fallback_expansion_occurred": false,
        "detection_rule": "HTTP 200 (or ACP delivered) AND gaps.length === 0 AND expansion_applied === false AND coverage_ratio >= 0.5 AND (data_age_sec === null OR data_age_sec < 7200)",
        "meaning": "The query was valid and fresh data was retrieved across sufficient platforms, but no arbitrage gap meets the minimum thresholds (gap_usd >= $2, gap_pct <= 1000%). This is not an error — it is a legitimate market outcome. The card price is currently balanced across platforms.",
        "orchestrator_action": "Inform the user that no opportunity was found for this card right now. Suggest trying a different grade, a different set, or checking again later. Do not retry immediately — market conditions will not change within minutes."
      },
      {
        "state": "partial_success",
        "is_successful_completion": false,
        "show_to_user": true,
        "retry_recommended": true,
        "fallback_expansion_occurred": false,
        "detection_rule": "HTTP 200 (or ACP delivered) AND at least one of: (a) coverage_ratio < 0.5, (b) fallback_used === true AND data_age_sec > 7200, (c) platform_freshness contains one or more status = \"down\" or \"disabled\" entries for platforms that would materially affect the result",
        "meaning": "Data was returned but is incomplete or stale. Gaps may be present but key platforms are missing (coverage_ratio < 0.5) or price data is older than 2 hours. The result may be actionable with caveats, but a gap that appears here might close or be larger if full data were available.",
        "orchestrator_action": "Show results with a \"data may be incomplete\" caveat. Check platform_freshness to identify which platforms are missing. If coverage_ratio < 0.33 (only core platforms), state that EU/JP/crypto platforms are unavailable. Upgrade to a refresh mode if data_age_sec > 7200 and freshness is critical. Retry after checking GET /health/platforms."
      },
      {
        "state": "reject_invalid_request",
        "is_successful_completion": false,
        "show_to_user": false,
        "retry_recommended": false,
        "fallback_expansion_occurred": false,
        "detection_rule": "HTTP 400 OR ACP job rejected with format/schema error OR missing required parameter (?query=, ?set_code=, mode field, JSON parse failure)",
        "meaning": "The request was malformed. The provider rejected it before any processing. No cost was incurred (ACP: no payment was charged). This is unambiguously a caller-side error.",
        "orchestrator_action": "Do not retry with the same input. Read the error body and cross-reference error_matrix entry INVALID_QUERY or ACP_INVALID_REQUEST_FORMAT for the specific fix. Correct the request parameters and resubmit."
      },
      {
        "state": "reject_rate_limited",
        "is_successful_completion": false,
        "show_to_user": false,
        "retry_recommended": true,
        "fallback_expansion_occurred": false,
        "detection_rule": "HTTP 429",
        "meaning": "The caller's IP has exceeded 20 requests per hour on the HTTP gap endpoints. No processing occurred. Switch to ACP for higher-throughput access.",
        "orchestrator_action": "Stop all HTTP gap requests from this session. Wait retry_after_seconds (3600) before retrying over HTTP. Switch to ACP immediately if further queries are needed within the hour."
      },
      {
        "state": "acp_awaiting_payment",
        "is_successful_completion": false,
        "show_to_user": false,
        "retry_recommended": false,
        "fallback_expansion_occurred": false,
        "detection_rule": "ACP job is in REQUEST phase — payable requirement created, payment not yet confirmed",
        "meaning": "The REQUEST memo was valid. The service has quoted the fare and is waiting for the caller to submit USDC payment on-chain. This is a protocol handshake state, not a completion. No result has been delivered yet.",
        "orchestrator_action": "Submit the quoted USDC payment. Do not create a new job — use the existing job_id. Once payment is confirmed on-chain, the service will execute and deliver the result automatically (TRANSACTION phase)."
      },
      {
        "state": "error_transient",
        "is_successful_completion": false,
        "show_to_user": false,
        "retry_recommended": true,
        "fallback_expansion_occurred": false,
        "detection_rule": "Response indicates upstream failure AND a cache fallback result was NOT delivered. Signals: UPSTREAM_RATE_LIMITED, UPSTREAM_TIMEOUT, PLATFORM_UNAVAILABLE when coverage_ratio = 0 (no platforms responded at all).",
        "meaning": "One or more upstream marketplaces failed and no usable cached data was available to serve as fallback. This is a temporary provider-side condition — the platform or scraper is recovering.",
        "orchestrator_action": "Retry with exponential backoff (1 s → 2 s → 4 s, cap 60 s). Check GET /health/platforms before retrying. After 3 failed retries, surface a \"service temporarily unavailable\" message to the user and cease retrying until platform health recovers."
      },
      {
        "state": "error_provider_internal",
        "is_successful_completion": false,
        "show_to_user": false,
        "retry_recommended": true,
        "fallback_expansion_occurred": false,
        "detection_rule": "HTTP 500 OR ACP job rejected with \"Internal error\" message OR ACP_WRONG_ENTITY_ID error code",
        "meaning": "An unhandled server-side error occurred. This is a provider fault, not a caller error. The provider must investigate.",
        "orchestrator_action": "Retry up to max_retries_recommended (3) times with exponential backoff. If all retries fail, log the request timestamp and mode, and report to the service operator. Do not surface raw error details to end users."
      }
    ],
    "detection_algorithm": [
      "1. If no response received (network/transport failure before any bytes): → error_transient",
      "2. If HTTP 400 OR ACP reject with schema error: → reject_invalid_request",
      "3. If HTTP 429: → reject_rate_limited",
      "4. If HTTP 500 OR ACP reject with \"Internal error\": → error_provider_internal",
      "5. If ACP job in REQUEST phase (payable requirement created, not yet paid): → acp_awaiting_payment",
      "6. If HTTP 200 / ACP delivered AND primary_match.length > 0: → success_exact",
      "7. If HTTP 200 / ACP delivered AND primary_match.length === 0 AND expansion_applied === true AND (relaxed_variant_match.length > 0 OR loose_name_match.length > 0): → success_fallback",
      "8. If HTTP 200 / ACP delivered AND gaps.length === 0 AND expansion_applied === false AND coverage_ratio >= 0.5 AND data_age_sec < 7200: → success_no_opportunity",
      "9. If HTTP 200 / ACP delivered AND (coverage_ratio < 0.5 OR (fallback_used === true AND data_age_sec > 7200)): → partial_success",
      "10. Fallback (HTTP 200 / ACP delivered but no rule matched above): → partial_success"
    ],
    "error_code_to_state": {
      "INVALID_QUERY": "reject_invalid_request",
      "ACP_INVALID_REQUEST_FORMAT": "reject_invalid_request",
      "ACP_SIGNER_NOT_WHITELISTED": "reject_invalid_request",
      "ACP_PAYMENT_REQUIRED": "acp_awaiting_payment",
      "ACP_WRONG_ENTITY_ID": "error_provider_internal",
      "RATE_LIMITED": "reject_rate_limited",
      "UPSTREAM_RATE_LIMITED": "error_transient",
      "UPSTREAM_TIMEOUT": "error_transient",
      "PLATFORM_UNAVAILABLE": "partial_success",
      "SCRAPER_REQUIRED": "partial_success",
      "NO_EXACT_MATCH": "success_no_opportunity",
      "FALLBACK_REQUIRED": "success_fallback",
      "CONFIDENCE_TOO_LOW": "success_exact",
      "STALE_DATA": "partial_success",
      "PARTIAL_COVERAGE": "partial_success",
      "INTERNAL_PROVIDER_ERROR": "error_provider_internal"
    }
  },
  "platform_availability": {
    "always_available": [
      "tcgplayer",
      "ebay",
      "courtyard"
    ],
    "requires_scraper_service": [
      "phygital",
      "collectorcrypt",
      "renaiss",
      "snkrdunk",
      "beezie"
    ],
    "check_live_status": "GET /health/platforms",
    "note": "If the Python scraper service is not running, 5 of 8 platforms return no data. In this case coverage_ratio will be around 0.375 (3 of 8). The 3 core platforms (eBay, TCGPlayer, Courtyard) are sufficient for US/crypto gap detection."
  },
  "example_workflows": [
    {
      "name": "Find the best opportunity right now (no specific card in mind)",
      "steps": [
        "1. Call GET /opportunities/preview (FREE) to see total signal counts across 7 strategies",
        "2. If total_opportunities > 0, purchase a scan ($0.29): { \"mode\": \"scan\", \"limit\": 20 }",
        "3. Filter results: primary_match only, execution_risk != \"high\" (unless you have crypto setup)",
        "4. Sort by gap_pct or score descending",
        "5. For the top result, purchase a card query ($0.49) with exact set_code + set_number + grade for live confirmation",
        "6. Check buy_from.url to verify listing still exists",
        "7. Execute: buy on buy_from.platform, sell on sell_to.platform",
        "8. Submit feedback with POST /feedback/agent"
      ]
    },
    {
      "name": "Research a specific card you already have in mind",
      "steps": [
        "1. Purchase a card query ($0.49): { \"mode\": \"card\", \"query\": \"Charizard VMAX\", \"set_code\": \"SWSH04\" }",
        "2. If primary_match is empty: check expansion_reason. If relaxed_variant_match has results, manually verify variant.",
        "3. Check confidence. If LOW, consider waiting or re-querying later.",
        "4. Compare gap_pct against your minimum threshold (recommend >= 15% to buffer execution friction)",
        "5. Check proxy_required. If true, factor in 2-3 week delivery and proxy setup.",
        "6. Proceed or skip based on execution_risk assessment."
      ]
    },
    {
      "name": "Monitor WEB3 same-platform flip opportunities",
      "steps": [
        "1. Purchase a scan ($0.29): { \"mode\": \"scan\", \"strategy\": \"same_platform_flip\" }",
        "2. Filter by spread_pct >= 20% and convergence_score >= 60",
        "3. Check platform fees (courtyard 5%, phygital 2.5%) are factored into net_profit_usd",
        "4. For best result, purchase card query ($0.49) to confirm with live data",
        "5. Execute on the same WEB3 platform — buy low listing, relist at median"
      ]
    }
  ],
  "common_agent_mistakes": [
    {
      "mistake": "Using loose_name_match results for execution",
      "consequence": "High risk of buying the wrong card. Different printings have wildly different values.",
      "fix": "Only execute on primary_match results."
    },
    {
      "mistake": "Ignoring fallback_used=true with old data_age_sec",
      "consequence": "Gap may no longer exist. Price data is stale.",
      "fix": "If fallback_used=true AND data_age_sec > 7200, run a card query ($0.49) for live data before acting."
    },
    {
      "mistake": "Treating PSA 10 and BGS 10 as the same grade",
      "consequence": "Different markets, different prices. Could result in incorrect gap calculation.",
      "fix": "Always specify the grading company explicitly. They are separate buckets."
    },
    {
      "mistake": "Ignoring execution_risk=high for crypto/vaulted platforms",
      "consequence": "Requires crypto wallet, gas fees, SOL price exposure, and vault redemption costs.",
      "fix": "Only include high-risk platforms if your agent is set up for crypto transactions."
    },
    {
      "mistake": "Not accounting for proxy_fee_usd on JP platform buys",
      "consequence": "Underestimating total cost. $15 proxy fee can eliminate the gap on cheaper cards.",
      "fix": "Always read cost_breakdown.proxy_fee_usd. It is already included in cost_usd."
    },
    {
      "mistake": "Acting on gap_pct < 10% without buffer",
      "consequence": "Price movement, shipping delays, or small listing errors can eliminate the margin.",
      "fix": "Require gap_pct >= 15% minimum, or gap_usd >= $20 for high-value cards."
    },
    {
      "mistake": "Excessive card queries when scan is sufficient",
      "consequence": "$0.49 per card query, rate-limited by tier. Scan ($0.29) covers the same data from cache.",
      "fix": "Use scan mode for market overview. Only use card mode for specific cards needing live confirmation."
    },
    {
      "mistake": "Dismissing all crypto/vaulted gaps as non-executable",
      "consequence": "Same-venue resale opportunities (e.g. buy cheap on Courtyard, relist on Courtyard) are directly executable and can have HIGH confidence. Blanket-blocking by market_type removes real opportunities.",
      "fix": "Check execution_mode: \"direct\" same-venue gaps (exit_path_type=\"same_venue_resale\") are actionable. Only \"conditional\" or \"non_executable\" gaps require extra steps. Use is_executable=true as the gating field, not market_type."
    }
  ],
  "hold_signals": {
    "description": "Hold Signals is a separate module from arbitrage gap detection. It scores cards for \"hold attractiveness\" based on current observable market structure — NOT price predictions. Hold results are never mixed with arbitrage gap results.",
    "endpoints": {
      "hold_top": {
        "path": "GET /hold/top",
        "params": "limit (default 20), min_hold_score (0–100), max_price_usd",
        "description": "Rank the best hold candidates across the full tracked universe (HOT + WARM cards). Uses 4h intelligence cache — no extra scraping cost."
      },
      "hold_set": {
        "path": "GET /hold/set?set_code=SWSH07",
        "params": "set_code (required), limit, min_hold_score, max_price_usd",
        "description": "Rank hold candidates within one set. Cache-based."
      },
      "hold_card": {
        "path": "GET /hold/card?query=Umbreon+VMAX&set_code=SWSH07",
        "params": "query (required), set_code, set_number, grade, language, min_hold_score, max_price_usd",
        "description": "Evaluate one exact card identity. Performs a live scrape."
      }
    },
    "scoring_contract": {
      "formula": "hold_score = 0.25×momentum + 0.20×liquidity + 0.20×scarcity + 0.20×stability + 0.15×catalyst",
      "range": "0–100 integer. Higher is more attractive for holding.",
      "sub_scores": {
        "momentum_score": "How far the market sell median sits above the current cheapest active listing. Rewards positive spread breadth across multiple sell platforms.",
        "liquidity_score": "Depth of market — enough sold history and listing depth to exit cleanly. Penalises cards with fewer than 3 sold records or fewer than 2 active listings.",
        "scarcity_score": "Supply tightness: few active listings + high sell-through velocity. A card can be both scarce and liquid (quick turnover of rare supply).",
        "stability_score": "Data quality and noise control. Deducts for anomalous prices, auction traps, reference-only comps, and identity contamination. Rewards HIGH-confidence data.",
        "catalyst_score": "Structural signals: HOT_CARDS tier membership (+70), WARM_CARDS (+40), or primary-exact match breadth. Conservative 15% weight."
      },
      "signal_confidence_rules": {
        "HIGH": "hold_score ≥ 60 AND stability ≥ 55 AND liquidity ≥ 35 AND ≥1 executable gap",
        "MEDIUM": "hold_score ≥ 40 AND stability ≥ 35",
        "LOW": "data sparse, noisy, or mostly reference-only"
      }
    },
    "separation_from_arbitrage": [
      "Hold endpoints (/hold/*) are entirely separate from gap endpoints (/gap/*).",
      "Hold scores are NOT gap percentages — a card with a high hold_score may have no current arb gap.",
      "Do NOT filter hold results using is_executable — the hold engine has its own confidence model.",
      "Hold signals use gap data as input but produce a distinct output schema (HoldResult, not CardGap)."
    ],
    "reading_hold_results": {
      "hold_score": "Composite 0–100. Treat ≥ 60 as noteworthy, ≥ 75 as strong.",
      "signal_confidence": "HIGH/MEDIUM/LOW — confidence in the data foundation, not in price direction.",
      "watch_reasons": "Positive signals: what makes this card attractive.",
      "risk_flags": "Reasons for caution: anomalous prices, thin liquidity, etc.",
      "price_context": "lowest_ask_usd, median_sell_usd, best_gap_pct — current observable prices.",
      "executable_gap_count": "Number of is_executable=true arbitrage gaps found. 0 does not mean the card is not worth holding — it may still have strong momentum/scarcity."
    }
  },
  "pack_ev_monitor": {
    "description": "Pack EV Monitor tracks expected value (EV) for WEB3 claw machines, gacha packs, and vending machines. These platforms have a fixed card pool that depletes as users pull — cards don't return to the pool when resold. The monitor tells you which packs are EV-positive before you pull.",
    "endpoint": {
      "path": "GET /pack-ev",
      "auth": "Free — no authentication required",
      "params": {
        "platform": "phygital | renaiss | courtyard | all (default: all)",
        "category": "pokemon | onepiece | baseball | all (default: all)",
        "signal": "strong_positive | positive | breakeven | negative | all (default: all)"
      },
      "example": "GET /pack-ev?signal=strong_positive&category=pokemon"
    },
    "platforms": {
      "phygital": {
        "mechanic": "Claw Machine — fixed pool, no refill. Pool depletes as cards are pulled.",
        "ev_source": "Platform-provided EV (actual remaining pool) + rarity tier theoretical EV. Cross-verifiable.",
        "key_fields": [
          "platform_ev_usd",
          "theoretical_ev_usd",
          "pool_depletion",
          "rarity_tiers"
        ]
      },
      "renaiss": {
        "mechanic": "Gacha — per-pack EV updated by platform, USDT pricing on BNB chain.",
        "ev_source": "Platform-provided expectedValueInUsd. Cannot be independently verified from public data.",
        "key_fields": [
          "platform_ev_usd",
          "price_usdt",
          "buyback_pct",
          "stage"
        ]
      },
      "courtyard": {
        "mechanic": "Vending Machine — fixed-odds rarity buckets with value ranges. Currently baseball only.",
        "ev_source": "Odds buckets publicly available. Theoretical EV independently computed from odds × midpoints.",
        "key_fields": [
          "theoretical_ev_usd",
          "price_usd",
          "odds_buckets",
          "buyback_ratio",
          "inventory_count"
        ]
      }
    },
    "signal_thresholds": {
      "strong_positive": "ev_edge_pct >= 5%",
      "positive": "ev_edge_pct >= 1%",
      "breakeven": "ev_edge_pct >= -1%",
      "negative": "ev_edge_pct < -1%"
    },
    "caveats": [
      "Phygitals and Courtyard EV can be verified from raw odds/tier data. Renaiss EV is platform-provided only.",
      "Buyback guarantees (85-90%) provide a floor on exit value, but actual resale on WEB2 may be higher.",
      "Courtyard currently has only baseball vending machines — Pokémon machines will auto-detect when launched.",
      "EV is a statistical expectation over many pulls. Individual pull outcomes vary widely."
    ]
  }
}