Базовый URL https://nftpars.app/api/v1. Версия контракта 1.1.0 — машиночитаемая спецификация:
public-v1.yaml (OpenAPI 3.1). Данные передаются в JSON; время — UTC (RFC 3339, Z); суммы — целые нанотон (1 TON = 109).
Аутентификация и ключи API
Создайте приложение и ключ API в кабинете. Передавайте его в Authorization: Bearer <key>. Ключ показывается один раз и хранится только как защищённый хеш. При ротации задайте переходный период для клиентов. При утечке немедленно отзовите ключ.
Владелец приложения должен иметь действующий Telegram Premium, подтверждённый NFTPARS. Сам по себе Telegram Login не подтверждает Premium. Если проверка устареет, запросы вернут 403 PREMIUM_REQUIRED или 403 PREMIUM_VERIFICATION_STALE; данные не удаляются, а доступ восстанавливается после повторного подтверждения.
Лимиты применяются к каждому приложению (ваш план указан на странице тарифов). Каждый ответ содержит X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset (время Unix). При 429 учитывайте Retry-After.
Оплата
Для пополнения отправьте TON со своим личным комментарием (кабинет → Оплата). Средства зачисляются только после проверки в блокчейне.
Каждый запрос сначала резервирует стоимость, затем списывает оплату только за выданные результаты, освобождая остаток. Ненайденные элементы, ошибки и незавершённые задачи не оплачиваются.
Отправить Idempotency-Key в POST и запросах обновления. Повтор с тем же ключом возвращает исходный ответ (Idempotent-Replayed: true) без повторного списания. X-NFTPARS-Charged-Nano показывает стоимость запроса.
Быстрый ответ и новое наблюдение
Быстрый (по умолчанию) сразу возвращает значение из индекса со статусом freshness FRESH, STALE или PENDING; ?max_age= задаёт допустимый возраст свежих данных. Свежие данные (?refresh=true) запрашивает у индексатора новое наблюдение и возвращает 202 с задачей; опрашивайте GET /jobs/{id}или подпишитесь на job.completed вебхуки / через jobs канал WebSocket.
import time, uuid, requests
# Fresh mode: ask for a new observation. Use an Idempotency-Key so a retry never charges twice.
r = requests.get(f"{API}/gifts/SnoopDogg-534281/owner", params={"refresh": "true"},
headers={**headers, "Idempotency-Key": str(uuid.uuid4())}, timeout=15)
job = r.json()["data"]["job"] # HTTP 202
while job["status"] in ("QUEUED", "RUNNING"):
time.sleep(2)
job = requests.get(f"{API}/jobs/{job['id']}", headers=headers, timeout=15).json()["data"]
print(job["status"], job["billing"]) # only SUCCEEDED items are charged
Пакетные задачи
POST /gifts/batch с различными идентификаторами в пределах пакетного лимита вашего плана. mode: "cached" отвечает сразу; оплачиваются только найденные элементы; mode: "fresh" создаёт задачу; оплачиваются только успешные элементы.
VERIFIED — текущий владелец Telegram, подтверждённый в verified_at.
HISTORICAL — последний известный владелец Telegram; не текущий владелец.
WALLET_ONLY — подарок хранится в кошельке TON; личность в Telegram не подразумевается.
HIDDEN, UNKNOWN, CONFLICT — достоверная текущая личность не определена; при конфликте перечислены все наблюдаемые кандидаты.
Актуальность оценивается отдельно от статуса. У каждого владельца есть sources (происхождение данных) и observed_at.
Фильтры предложений
Профили фильтров ограничивают поток предложений приложения (вебхуки и offers канал WebSocket) и результаты поиска/рынка: страна, добровольно указанный гендер, коллекции, диапазон цен и номеров, статус владельца, минимальная актуальность и рыночный статус. Временные фильтры на 1, 2, 6, 24 часа или свой срок заменяют постоянный профиль и автоматически восстанавливают его по истечении срока. Страна и гендер доступны только при добровольном указании и согласии владельца; неизвестные значения исключаются, если не задано unknown.<field> = "include". Сначала проверьте покрытие.
# Two-hour temporary filter; the previous profile is restored automatically.
curl -s -X POST https://nftpars.app/api/v1/applications/$APP_ID/filters/temporary \
-H "Authorization: Bearer $NFTPARS_KEY" -H "Content-Type: application/json" \
-d '{"criteria": {"country": "RU", "gender": "female", "unknown": {"country": "exclude", "gender": "exclude"}},
"duration_minutes": 120}'
# How many indexed owners actually have reliable (self-declared, consented) attributes?
curl -s "https://nftpars.app/api/v1/filters/coverage?country=RU&gender=female" -H "Authorization: Bearer $NFTPARS_KEY"
Вебхуки
События: gift.discovered, gift.owner_changed, market.listing_created, market.listing_updated (изменения цены), market.listing_removed, market.sale, collection.updated, job.completed, job.failed. Только HTTPS. Каждая доставка подписана — X-NFTPARS-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, "t.body")> — с повторными попытками и экспоненциальной задержкой. После исчерпания попыток доставка отмечается как неудачная в журнале. Отклоняйте подписи старше 5 минут и устраняйте дубли по X-NFTPARS-Delivery.
Python
import hmac, hashlib, time
def verify(secret: str, raw_body: bytes, header: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
ts = int(parts["t"])
if abs(time.time() - ts) > tolerance: # replay protection
return False
expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
# Then de-duplicate on the X-NFTPARS-Delivery header (stable across retries).
Протокол Pusher v7 по адресу wss://nftpars.app/app/<key>. Сначала настройте каналы (в кабинете или через POST /ws/subscriptions): gift:<slug>, collection:<slug>, market:<collection>, offers (с учётом фильтров) и jobs. Приватные каналы авторизуются через POST /ws/auth с вашего сервера с ключом API. Число соединений, размер сообщений и частота ограничены; клиенты не могут публиковать события.
import Pusher from "pusher-js";
// 1) GET https://nftpars.app/api/v1/ws/config for host/key, 2) add channels via POST https://nftpars.app/api/v1/ws/subscriptions
const pusher = new Pusher(APP_KEY, {
wsHost: "nftpars.app", wsPort: 443, forceTLS: true, enabledTransports: ["ws", "wss"], cluster: "",
channelAuthorization: {
endpoint: "https://nftpars.app/api/v1/ws/auth",
headers: { Authorization: `Bearer ${KEY}` }, // server-side only — never ship keys to browsers
},
});
pusher.subscribe("private-market.PlushPepe").bind("nftpars.event", (event) => console.log(event.type, event.data));
pusher.subscribe(`private-application.${APP_ID}.offers`).bind("nftpars.event", console.log); // filtered offer stream
Справочник endpoints
GET/statusПубличный статус сервисов (без авторизации и списания)
Публичный статус сервисов (без авторизации и списания)
Аутентификация не требуется.
GET/pricingДействующие цены операций для этого приложения (без списания)
Действующие цены операций для этого приложения (без списания)
GET/gifts/{slug}
Параметр
Где
Описание
slug*
path
refresh
query
true — запросить новое наблюдение. Если кеш недостаточно свежий, возвращается задача с кодом 202.
max_age
query
В секундах; более старые данные считаются STALE.
Idempotency-Key
header
GET/gifts/{slug}/owner
Параметр
Где
Описание
slug*
path
refresh
query
true — запросить новое наблюдение. Если кеш недостаточно свежий, возвращается задача с кодом 202.
max_age
query
В секундах; более старые данные считаются STALE.
Idempotency-Key
header
GET/gifts/{slug}/history
Параметр
Где
Описание
slug*
path
cursor
query
limit
query
POST/gifts/batch
Параметр
Где
Описание
Idempotency-Key
header
GET/collections
Параметр
Где
Описание
cursor
query
limit
query
GET/collections/{collection_slug}
Параметр
Где
Описание
collection_slug*
path
GET/search
Фильтры приложения (постоянный профиль и активное временное переопределение) применяются автоматически.
Параметр
Где
Описание
q
query
collection
query
model
query
backdrop
query
symbol
query
owner_status
query
cursor
query
limit
query
GET/owners/{telegram_id}/gifts
Параметр
Где
Описание
telegram_id*
path
include_historical
query
cursor
query
limit
query
GET/wallets/{address}/gifts
Параметр
Где
Описание
address*
path
cursor
query
limit
query
GET/market/listings
Параметр
Где
Описание
collection
query
gift_slug
query
marketplace
query
status
query
price_min_nano
query
price_max_nano
query
sort
query
cursor
query
limit
query
GET/jobs/{job_id}
Параметр
Где
Описание
job_id*
path
GET/applications/{id}/usage
Параметр
Где
Описание
id*
path
from
query
to
query
GET/applications/{id}/filters
Параметр
Где
Описание
id*
path
PUT/applications/{id}/filters
Параметр
Где
Описание
id*
path
POST/applications/{id}/filters/temporaryВременный фильтр, например { "country": "RU", "gender": "female", "duration_minutes": 120 }, либо (1.1) { "profile_id": "...", "duration_minutes": 60 }, либо { "criteria": FilterCriteria, ... }. Прежняя конфигурация восстанавливается после истечения срока.
Временный фильтр, например { "country": "RU", "gender": "female", "duration_minutes": 120 }, либо (1.1) { "profile_id": "...", "duration_minutes": 60 }, либо { "criteria": FilterCriteria, ... }. Прежняя конфигурация восстанавливается после истечения срока.