PunkPredictor API Documentation

PunkPredictor: The most accurate valuation tool for CryptoPunks. Find deals and make informed decisions.

Welcome to the PunkPredictor API documentation. PunkPredictor is the most accurate AI-powered valuation model for CryptoPunks, providing real-time price predictions, historical valuations, and market analytics for NFT collectors and investors.

Our advanced machine learning algorithms analyze multiple factors including rarity, attributes, historical sales data, and market trends to deliver precise valuations that give you an edge in the CryptoPunk market.

Base URL: https://api.punkpredictor.xyz
API Structure: All endpoints are direct API calls to https://api.punkpredictor.xyz/ENDPOINT_NAME. You can query by punk ID (0-9999) for most endpoints that require specific punk data.
Agent-native integration: x402 paid per-call endpoints are documented separately at /agents-docs.

Key Features

As the most accurate valuation tool for CryptoPunks, PunkPredictor helps you find deals and make informed decisions with these features:

  • Accurate Price Predictions: Get current estimated values for any CryptoPunk with our model
  • Historical Valuations: Access historical price data to track value changes over time
  • Market Analytics: Monitor floor price changes and average valuation trends
  • Attribute-Based Analysis: Understand how specific attributes impact punk valuations

Authentication

All API requests require an API key that should be included in the request body. PunkPredictor offers exclusive membership access with limited availability to ensure premium service quality.

To get your API key:

  1. Purchase a subscription to PunkPredictor
  2. Once your payment is confirmed, the site will be unlocked
  3. Click on the key icon located in the bottom right corner of the screen
  4. Your unique API key will be displayed and can be copied for use in your applications

Your API key is automatically generated when you subscribe to our service and can be accessed at any time through the key icon after logging in.

Note: Keep your API key secure and do not share it publicly. All API keys have rate limits to ensure service stability.

Rate Limits

The API is rate limited to 30 requests per minute per API key. If you exceed this limit, you'll receive a 429 Too Many Requests response.

Our rate limits are designed to ensure fair usage and optimal performance for all members. For applications requiring higher limits, please contact our support team.

Response Status Codes

Status Code Description
200 OK Request successful
400 Bad Request Invalid parameters or missing required fields
401 Unauthorized Invalid or missing API key
429 Too Many Requests Rate limit exceeded
500 Server Error Internal server error

API Endpoints

POST

Predict the Price of a Punk Today

/predict_price_of_a_punk_today

Get the current predicted price for a specific CryptoPunk.

Parameters

Parameter Type Description
id_punk Required integer The ID of the CryptoPunk (0-9999)
api_key Required string Your API key for authentication

Python Example

Python
import requests

# Base URL
BASE_URL = 'https://api.punkpredictor.xyz'
api_key = 'YOUR_API_KEY'

def predict_price_of_punk(id_punk, api_key):
    url = f"{BASE_URL}/predict_price_of_a_punk_today"
    data = {
        "id_punk": id_punk,
        "api_key": api_key
    }
    response = requests.post(url, json=data)
    return response.json()

# Example Usage
result = predict_price_of_punk(1234, api_key)
print(result)

Try it

Response

200 OK
POST

Get Punk Historical Valuations

/get_punk_history

Retrieve historical price valuations for a specific CryptoPunk.

Parameters

Parameter Type Description
punk_id Required integer The ID of the CryptoPunk (0-9999)
api_key Required string Your API key for authentication

Python Example

Python
import requests

# Base URL
BASE_URL = 'https://api.punkpredictor.xyz'
api_key = 'YOUR_API_KEY'

def get_punk_history(punk_id, api_key):
    url = f"{BASE_URL}/get_punk_history"
    data = {
        "punk_id": punk_id,
        "api_key": api_key
    }
    response = requests.post(url, json=data)
    return response.json()

# Example Usage
result = get_punk_history(1234, api_key)
print(result)

Try it

Response

200 OK
POST

Get Punk Sales History

/get_punksales

Retrieve sales history for a specific CryptoPunk including floor price context and metadata.

Parameters

Parameter Type Description
punk_id Required integer The ID of the CryptoPunk (0-9999)
api_key Required string Your API key for authentication

Python Example

Python
import requests

# Base URL
BASE_URL = 'https://api.punkpredictor.xyz'
api_key = 'YOUR_API_KEY'

def get_punk_sales(punk_id, api_key):
    url = f"{BASE_URL}/get_punksales"
    data = {
        "punk_id": punk_id,
        "api_key": api_key
    }
    response = requests.post(url, json=data)
    return response.json()

# Example Usage
result = get_punk_sales(1234, api_key)
print(result)

Try it

Response

200 OK
POST

Get Punk Traits

/get_punk_traits

Retrieve traits for one or more CryptoPunks by their IDs.

Parameters

Parameter Type Description
punk_ids Required array Array of CryptoPunk IDs (0-9999)
api_key Required string Your API key for authentication

Python Example

Python
import requests

# Base URL
BASE_URL = 'https://api.punkpredictor.xyz'
api_key = 'YOUR_API_KEY'

def get_punk_traits(punk_ids, api_key):
    url = f"{BASE_URL}/get_punk_traits"
    data = {
        "punk_ids": punk_ids,
        "api_key": api_key
    }
    response = requests.post(url, json=data)
    return response.json()

# Example Usage
result = get_punk_traits([1234, 5678, 9999], api_key)
print(result)

Try it

Response

200 OK
POST

Get Sales by Specific Traits

/get_sales_by_specific_traits

Retrieve sales data for CryptoPunks that have ALL specified traits (AND operation).

Parameters

Parameter Type Description
traits Required array Array of trait names to filter by
page integer Page number for pagination (default: 1)
limit integer Number of results per page (default: 5, max: 1000)
api_key Required string Your API key for authentication

Python Example

Python
import requests

# Base URL
BASE_URL = 'https://api.punkpredictor.xyz'
api_key = 'YOUR_API_KEY'

def get_sales_by_traits(traits, api_key, page=1, limit=5):
    url = f"{BASE_URL}/get_sales_by_specific_traits"
    data = {
        "traits": traits,
        "page": page,
        "limit": limit,
        "api_key": api_key
    }
    response = requests.post(url, json=data)
    return response.json()

# Example Usage
result = get_sales_by_traits(["3D Glasses", "Beanie"], api_key)
print(result)

Try it

Response

200 OK
POST

Active Listings

/get_punks_for_sale

Retrieve all current active listings from the marketplace, sorted by price. Useful for finding the lowest priced Punks.

Parameters

No parameters required.

Response Format

Returns a JSON array of active listings.

JSON
[
  {
    "Punk ID": "8888",
    "ETH Price": 24.5,
    "USD Price": 45120.50,
    "source": "Punks Marketplace"
  },
  ...
]

Try it out

Response

200 OK
GET

Lending Data

/get_lending_data

Retrieve lending data for CryptoPunk-backed loans across all major platforms (Gondi, NFTfi, Blur/Blend, Arcade). Supports filtering by punk ID, lender, borrower, status, and platform.

Parameters

Parameter Type Description
X-API-Key Required string Your API key for authentication (sent in header)
punk_id integer Optional. Filter loans for a specific CryptoPunk ID (0-9999)
lender string Optional. Filter by lender address or ENS name (partial match supported)
borrower string Optional. Filter by borrower address or ENS name (partial match supported)
status string Optional. Filter by loan status: "active", "repaid", or "defaulted"
platform string Optional. Filter by platform: "gondi", "nftfi", "blur" (or "blend"), "arcade"
page integer Optional. Page number for pagination (default: 1)
limit integer Optional. Results per page (default: 100, max: 500)

Response Format

Returns paginated loan data with aggregate statistics for filtered results:

JSON
{
  "data": [
    {
      "token_id": 1234,
      "platform": "gondi",
      "lender": "0x1234...",
      "lender_ens": "lender.eth",
      "borrower": "0x5678...",
      "borrower_ens": "borrower.eth",
      "principal": 5.0,
      "principal_symbol": "WETH",
      "apr": 12.5,
      "effective_apr": 15.2,
      "duration_days": 30,
      "LTV": 0.65,
      "status": "active",
      "type": "initiated",
      "date": "2024-01-15",
      "end_date": "2024-02-14"
    }
  ],
  "pagination": {
    "total": 10258,
    "page": 1,
    "limit": 100,
    "pages": 103
  },
  "filteredStats": {
    "totalLoans": 4445,
    "avgLTV": 58.47,
    "avgDuration": 56.03,
    "avgAPR": 15.24,
    "defaultRate": 2.43,
    "activeLoans": 198
  }
}

Python Example

Python
import requests

# Base URL
BASE_URL = 'https://api.punkpredictor.xyz'
api_key = 'YOUR_API_KEY'

def get_lending_data(api_key, punk_id=None, lender=None, borrower=None, 
                     status=None, platform=None):
    url = f"{BASE_URL}/get_lending_data"
    params = {}
    if punk_id is not None:
        params['punk_id'] = punk_id
    if lender:
        params['lender'] = lender
    if borrower:
        params['borrower'] = borrower
    if status:
        params['status'] = status
    if platform:
        params['platform'] = platform
    
    headers = {
        'X-API-Key': api_key,
        'Content-Type': 'application/json'
    }
    response = requests.get(url, headers=headers, params=params)
    return response.json()

# Example: Get all Gondi loans
result = get_lending_data(api_key, platform='gondi')
print(f"Found {result['filteredStats']['totalLoans']} Gondi loans")

# Example: Get loans by lender ENS
result = get_lending_data(api_key, lender='tatsu.eth')
print(result)

# Example: Get active loans for a specific punk
result = get_lending_data(api_key, punk_id=1234, status='active')
print(result)

Try it

Response

200 OK
GET

Lending Pre-computed Analytics

/lending/analytics

Retrieve pre-computed analytics for CryptoPunk lending including monthly charts, risk curves, yield curves, DTD shock analysis, and top lenders/borrowers. Supports the same filters as /get_lending_data for filtered analytics.

Parameters

Parameter Type Description
X-API-Key Required string Your API key for authentication (sent in header)
punk_id integer Optional. Filter analytics for a specific CryptoPunk ID
lender string Optional. Filter by lender address or ENS name
borrower string Optional. Filter by borrower address or ENS name
status string Optional. Filter by loan status
platform string Optional. Filter by platform

Response Format

Returns pre-computed analytics data optimized for chart rendering:

JSON
{
  "success": true,
  "analytics": {
    "summary": {
      "totalLoans": 10258,
      "avgLTV": 58.72,
      "avgDuration": 56.03,
      "avgAPR": 15.78,
      "defaultRate": 2.65
    },
    "monthlyCharts": {
      "ltv": [{"month": "2024-01", "average": 0.58, "count": 150}],
      "duration": [{"month": "2024-01", "average": 45.2, "count": 150}],
      "apr": [{"month": "2024-01", "average": 15.5, "count": 150}],
      "platform": [{"month": "2024-01", "gondi": 80, "nftfi": 50, "blur": 20}],
      "defaultRate": [{"month": "2024-01", "total": 150, "defaults": 3, "defaultRate": 2.0}],
      "uniqueLenders": [{"month": "2024-01", "uniqueLenders": 45}]
    },
    "riskCurves": {
      "gondi": {
        "40-60%": {"30-60d": {"defaultRate": 1.2, "loanCount": 156}}
      }
    },
    "yieldCurve": {
      "total": [{"duration": "0-7d", "avgAPR": 18.5, "count": 50}],
      "byPlatform": {...}
    },
    "dtdShock": [
      {"label": "-20%", "drop": -20, "principalAtRisk": 150.5, "loanCount": 45}
    ],
    "topLenders": [...],
    "topBorrowers": [...]
  }
}

Python Example

Python
import requests

BASE_URL = 'https://api.punkpredictor.xyz'
api_key = 'YOUR_API_KEY'

def get_lending_analytics(api_key, platform=None, status=None):
    url = f"{BASE_URL}/lending/analytics"
    params = {}
    if platform:
        params['platform'] = platform
    if status:
        params['status'] = status
    
    headers = {'X-API-Key': api_key}
    response = requests.get(url, headers=headers, params=params)
    return response.json()

# Get overall analytics
result = get_lending_analytics(api_key)
summary = result['analytics']['summary']
print(f"Total loans: {summary['totalLoans']}")
print(f"Avg LTV: {summary['avgLTV']}%")
print(f"Default rate: {summary['defaultRate']}%")

# Get Gondi-specific analytics
gondi_result = get_lending_analytics(api_key, platform='gondi')
print(f"Gondi loans: {gondi_result['analytics']['summary']['totalLoans']}")

Webhook Alerts

Webhooks deliver real-time CryptoPunk listing events to any HTTPS endpoint you own. Each POST is HMAC-SHA256 signed so you can verify it came from PunkPredictor before processing.

Webhooks are available to Trader members. Alerts are delivered instantly — the same timing as Telegram and Discord. Collector timing (90-minute delay) is not available via webhook.

Authentication: All webhook management endpoints require your session cookie (logged-in dashboard session). There is no separate API key flow for webhook CRUD — use the membership dashboard or the endpoints below from an authenticated context.

How it works

When a new CryptoPunk listing appears, PunkPredictor evaluates it against each of your active webhook subscriptions. If the listing matches your filter settings, a signed JSON POST is sent to your endpoint URL.

  • Delivery retries up to 8 times with exponential back-off (1 s → ~21 min).
  • Each delivery has a unique delivery_id for safe idempotent processing.
  • Your endpoint must return 2xx within 10 seconds to be counted as delivered.
  • A rotating signing secret is supported: when you rotate, the old secret remains valid for a brief grace window so deploys stay seamless.
Subscription filters
{
  "filter": "all" | "undervalued" | "threshold",
  "threshold_pct": 10,          // only used when filter = "threshold"
  "trait_filter": "any" | "beard" | "earring" | ...
}

filter: "all" — every new listing. "undervalued" — listings where the model predicts the punk is underpriced. "threshold" — listings where gap% ≥ threshold_pct.

GET

List Webhook Subscriptions

/membership/webhooks

Returns all webhook subscriptions for the authenticated member.

Response Format

JSON
[
  {
    "id": 7,
    "url": "https://your-server.example.com/hooks/punks",
    "is_active": true,
    "filter_json": {
      "filter": "undervalued",
      "trait_filter": "any"
    },
    "created_at": "2026-03-04T12:00:00Z",
    "delivery_count": 142,
    "last_delivery_at": "2026-03-04T17:55:01Z",
    "last_delivery_status": "delivered"
  }
]
POST

Create Webhook Subscription

/membership/webhooks

Creates a new webhook subscription. Returns the subscription object including the one-time secret — store it immediately, it will not be shown again.

Request Body

FieldTypeDescription
url Required string HTTPS endpoint that will receive events
filter string "all" (default), "undervalued", or "threshold"
threshold_pct number Minimum gap % to trigger (only when filter = "threshold")
trait_filter string Trait to filter on, or "any" (default)

Response (201 Created)

JSON
{
  "id": 7,
  "url": "https://your-server.example.com/hooks/punks",
  "secret": "whsec_a1b2c3d4...",   // shown once — save this
  "is_active": true,
  "filter_json": { "filter": "undervalued", "trait_filter": "any" },
  "created_at": "2026-03-04T12:00:00Z"
}
PATCH

Update Webhook Subscription

/membership/webhooks/<id>

Update the URL, filter settings, or active state of an existing subscription. Pass only the fields you want to change.

Request Body (all optional)

FieldTypeDescription
url string New HTTPS endpoint URL
is_active boolean Enable or pause delivery
filter string "all", "undervalued", or "threshold"
threshold_pct number New gap % threshold
trait_filter string New trait filter
DELETE

Delete Webhook Subscription

/membership/webhooks/<id>

Permanently deletes the subscription and stops all future deliveries. Returns 204 No Content on success.

POST

Send Test Event

/membership/webhooks/test

Sends a synthetic punk.listed event to the specified URL using the subscription's signing secret. Use this to verify your endpoint handles the payload correctly before going live.

Request Body

FieldTypeDescription
subscription_id Required integer ID of the subscription to test

Response

JSON
{ "status": "delivered", "http_status": 200 }

Event Payload — punk.listed

Every delivery is a JSON POST with Content-Type: application/json and two custom headers:

HeaderValue
X-PP-Signature sha256=<hex-digest>
X-PP-Delivery Unique UUID for this delivery attempt
JSON
{
  "event": "punk.listed",
  "delivery_id": "d47ac10b-58cc-4372-a567-0e02b2c3d479",
  "timestamp": "2026-03-04T17:55:01Z",

  "punk_id": 1234,
  "listing_price_eth": 42.0,
  "listing_url": "https://cryptopunks.app/cryptopunks/details/1234",
  "tx_hash": "0xabc123...",
  "listed_at": "2026-03-04T17:54:58Z",

  "prediction_eth": 58.3,          // null when masked (Collector tier)
  "raw_prediction_eth": 58.3,      // null when masked
  "gap_pct": 38.8,                 // (prediction - price) / price × 100
  "is_undervalued": true,

  "traits": {
    "type": "Male",
    "count": 2,
    "accessories": ["Mole", "Smile"]
  },

  "subscription_id": 7,
  "is_subscriber": true,
  "is_test": false
}

Field reference

FieldTypeNotes
eventstringAlways "punk.listed" for listing alerts
delivery_idstring (UUID)Stable across retries — use for deduplication
timestampISO 8601When this delivery was dispatched
punk_idintegerCryptoPunk token ID (0–9999)
listing_price_ethnumberCurrent ask price in ETH
tx_hashstringOn-chain listing transaction hash
prediction_ethnumber | nullFair value estimate; null when prediction is masked
gap_pctnumberPricing gap: (prediction − price) / price × 100
is_undervaluedbooleantrue when the model considers this listing below fair value
traitsobjectPunk type, accessory count, and accessories list
is_testbooleantrue only for test deliveries sent via the /test endpoint

Signature Verification

Verify the X-PP-Signature header before processing any event. The signature is HMAC-SHA256 of the raw request body using your webhook secret.

Python
import hmac, hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_a1b2c3d4..."   # from subscription creation

@app.route("/hooks/punks", methods=["POST"])
def handle_punk_alert():
    sig_header = request.headers.get("X-PP-Signature", "")
    if not sig_header.startswith("sha256="):
        abort(400)

    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        request.get_data(),          # raw bytes — parse JSON after verification
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(sig_header[7:], expected):
        abort(403)                   # reject tampered payloads

    event = request.get_json()

    # Idempotency: skip already-processed deliveries
    delivery_id = event["delivery_id"]
    if already_processed(delivery_id):
        return "", 200

    punk_id  = event["punk_id"]
    price    = event["listing_price_eth"]
    pred     = event["prediction_eth"]
    gap      = event["gap_pct"]

    print(f"Punk #{punk_id} listed at {price} ETH  |  fair value {pred} ETH  |  gap {gap:.1f}%")
    return "", 200
Node.js
import crypto from "crypto";
import express from "express";

const app = express();
const WEBHOOK_SECRET = "whsec_a1b2c3d4...";

// Use raw body buffer — parse JSON after verification
app.post("/hooks/punks", express.raw({ type: "application/json" }), (req, res) => {
  const sigHeader = req.headers["x-pp-signature"] ?? "";
  const expected = "sha256=" + crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(req.body)          // req.body is a Buffer here
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(sigHeader), Buffer.from(expected))) {
    return res.sendStatus(403);
  }

  const event = JSON.parse(req.body);
  console.log(`Punk #${event.punk_id} listed at ${event.listing_price_eth} ETH`);
  res.sendStatus(200);
});
Important: Always verify the signature on the raw request body before parsing JSON. Parsing first can silently normalize whitespace or field order and break the digest comparison.