FUCK PARSER API Взять ключ

Документация

Адрес API: https://api.fuck-api.xyz. Фильтры каждой площадки и сборщик скрипта на страницах площадок.

Ключ

Ключ выдаёт @fuckparserbot, кнопка «🔑 API». Он нужен в заголовке X-API-Key везде, кроме GET /v1/health. «♻️ Перевыпустить ключ» сразу выключает старый.

HEADERS = {"X-API-Key": "fp_ВАШ_КЛЮЧ"}

Задача

  • POST /v1/tasks только создаёт задачу и возвращает её task.id. Объявлений в этом ответе нет. Повтор с теми же фильтрами, пока задача жива, вернёт тот же id.
  • GET /v1/tasks/{id}/ads отдаёт объявления: только новые с прошлого запроса. Новую задачу не запускает.
  • Ответ потерялся: повтори с repeat=1, придёт та же порция.
  • Задача живёт 30 минут или до limit объявлений.
  • Нет GET к задаче 3 минуты: она останавливается сама, на случай если скрипт упал.
  • Закончилась: task.status станет done, причина в task.reason. Забирай остаток, пока ads не придёт пустым.
  • Задача одна за раз: другая получит 409 task_running, номер идущей в error.task. Остановить: POST /v1/stop или DELETE /v1/tasks/{id}.
  • Без подписки задача не стартует, придёт notice.
  • Память с ботом общая: что ты получил в боте, через API не придёт, и наоборот.
statusЧто значит
startingПоиск запускается.
runningПоиск идёт.
doneЗадача закончилась, причина в reason.
reasonПочему закончилась
limitНабрано limit объявлений.
timeВышло время.
idleНе было GET к задаче.
stoppedОстановлена: POST /v1/stop, DELETE /v1/tasks/{id} или «⏹ Остановить задачу» в боте.
panelОстановлена в панели.
failedСбой сервера или площадка не ответила.
interruptedБот перезапускался. Остаток можно забрать.
refusedНе стартовала: нет подписки, бан или слишком много разделов.
bannedAPI закрыт.
offAPI выключен.

Параметры

ПараметрГдеЧто этоЕсли не задать
platformтело POST /v1/tasksПлощадка из GET /v1/platforms.нужен всегда
filtersтело POST /v1/tasksФильтры площадки.умолчания площадки
limitтело POST /v1/tasksОбъявлений за задачу, от 1 до 1000.1000
waitGET /v1/tasks/{id}/adsСколько секунд ждать новое, от 0 до 25.0
repeatGET /v1/tasks/{id}/ads1: прошлая порция ещё раз.нет

Другие поля и параметры, неизвестный фильтр, вариант не из списка, число вне пределов, фильтр чужой страны, limit больше 1000: ответ 400, поле в error.field. Пустое значение ("", null, []) значит «фильтра нет». Страну (gate: true в схеме) задавай первой, фильтр с countries работает только в этих странах.

Адреса

ЗапросЧто делает
POST /v1/tasksСоздать и запустить задачу: {"task": {...}}.
GET /v1/tasks/{id}/adsНовое с прошлого запроса: {"task": {...}, "ads": [...]}.
GET /v1/tasks/{id}Одна задача.
DELETE /v1/tasks/{id}Остановить эту задачу.
POST /v1/stopОстановить идущую задачу: {"stopped": [...]}.
GET /v1/tasksТвои задачи за 3 дня: {"tasks": [...]}.
GET /v1/platformsПлощадки и их страны.
GET /v1/platforms/{key}Фильтры площадки: вид, варианты, пределы, примеры.
GET /v1/platforms/{key}/filters/{name}?parent=Подразделы дерева. Без parent верхние разделы.
GET /v1/platforms/{key}/places?q=Код города по названию, от двух букв. Страну передай рядом.
GET /v1/meНачало ключа, доступ, лимиты, идущая задача.
GET /v1/healthЖив ли API, без ключа.
GET /v1/ads
POST /v1/ads
Старый способ одним запросом.

Значения фильтров

Вид (kind)Как писатьПример
Один вариантстрока с кодом"published": "1h"
Несколько вариантовсписок кодов"condition": ["new", "used"]
Разделыкод или список кодов"category": ["1307", "1312"]
Диапазон[от, до] или строка «от-до»"price": [100, 500]
Числочисло"seller_ads": 5
Да или нетtrue или false"with_phone": true
Текстстрока"query": "iphone 13"
Местострока с кодом места"location": "3331"
  • Диапазон: 100- от 100, -500 до 500, 0-0 ровно ноль. Одно число без дефиса фильтры читают по-разному, смотри пример у фильтра.
  • Разделы открываются по веткам: без parent верхние, дальше код раздела с more: true. Адрес в схеме с ?parent={country}: первым идёт код страны.
  • Место пишется кодом из GET /v1/platforms/{key}/places?q=.
  • В тексте лишние пробелы схлопываются, длина до max_length.

Ответ

Ответ GET /v1/tasks/{id}/ads: задача и новые объявления. Время в UTC.

{
  "task": {
    "id": "t_k3v9q2m7xw4p",
    "platform": "gumtree",
    "status": "running",
    "found": 1,
    "handed": 1,
    "limit": 200,
    "started_at": "2026-10-20T20:49:52Z",
    "ends_at": "2026-10-20T21:19:52Z"
  },
  "ads": [
    {
      "id": "2384712650",
      "title": "iPhone 13 128 GB",
      "price": 340,
      "currency": "EUR",
      "price_type": "Fixed",
      "url": "https://www.gumtree.com/ad/2384712650",
      "location": "Berlin",
      "published_at": "2026-10-20T20:46:06Z",
      "seller_name": "Marek",
      "seller_type": "Private",
      "seller_ads": 2,
      "seller_sales": 0,
      "phone": "+4915100000000",
      "whatsapp": "https://wa.me/4915100000000",
      "condition": "Used",
      "seen_by": 0
    }
  ]
}

Без подписки

POST /v1/tasks отвечает HTTP 200, task равен null, причина в notice. Подписка открывает все площадки, начатая задача доживает срок, даже если подписка кончилась.

{
  "task": null,
  "notice": {
    "code": "no_subscription",
    "message": "No subscription."
  }
}
codemessageЧто делать
no_subscriptionNo subscription.Оформи подписку в боте.
no_subscriptionSubscription expired.Продли подписку в боте.

Задача

ПолеЧто это
idt_ и 12 знаков.
platformПлощадка.
statusstarting, running или done.
reasonПочему закончилась, только у done.
foundСколько нашла.
handedСколько ты забрал.
limitПотолок объявлений.
started_atКогда запустилась.
ends_atКогда закончится, у законченной: когда закончилась.

POST /v1/tasks, GET /v1/tasks/{id}, DELETE /v1/tasks/{id} и POST /v1/stop добавляют filters, asked_at и ended_at.

Объявление

Пустых полей нет: нет телефона, нет и phone. Порядок в порции: в каком нашли.

ПолеТипЧто это
idстрокаНомер на площадке, есть всегда.
titleстрокаЗаголовок.
priceчислоЦена в валюте объявления.
currencyстрокаEUR, PLN, USD и другие.
price_typeстрокаFixed, Negotiable, On request, Free, Swap, Reserved, Auction, Auction/Buy now.
urlстрокаОбъявление.
chat_urlстрокаЧат с продавцом, если у площадки свой чат.
categoryстрокаРаздел, например Electronics / Mobile Phones.
locationстрокаМесто, как на площадке.
published_atстрокаКогда вышло, ISO 8601, UTC.
seller_nameстрокаИмя продавца.
seller_typeстрокаPrivate, Commercial, Business, Dealer, Shop, PRO shop.
seller_idстрокаНомер продавца на площадке.
seller_urlстрокаПрофиль продавца.
seller_adsчислоОбъявлений у продавца сейчас.
seller_total_adsчислоОбъявлений у продавца всего.
seller_salesчислоПродаж у продавца. 0 значит ноль, а не «неизвестно».
seller_purchasesчислоПокупок у продавца.
seller_sold_itemsчислоСчётчик проданного у Wallapop, не равен seller_sales.
seller_reviewsчислоОтзывов у продавца.
seller_ratingстрокаОценка, как у площадки: 88% или 4.7/5.
seller_registered_atстрокаКогда продавец завёл аккаунт, ISO 8601, у части площадок только YYYY-MM или YYYY.
seller_reply_rateстрокаКак часто отвечает, например 81%.
seller_followersчислоПодписчиков у продавца.
seller_cityстрокаГород продавца, если площадка отдаёт его отдельно.
phoneстрокаТелефон продавца, как на площадке.
whatsappстрокаСсылка wa.me, когда есть номер.
possible_emailстрокаПочта, угаданная по нику, у площадок с проверкой почты.
email_verifiedtrue или falsetrue, если Gmail подтвердил ящик.
conditionстрокаNew, Like new, Used, Refurbished, Good, Not working, набор у площадок свой.
shippingtrue или falseЕсть доставка.
buy_nowtrue или falseМожно купить сразу.
promotedtrue или falseПродавец платил за место в ленте.
imageстрокаГлавное фото.
descriptionстрокаТекст объявления.
seen_byчислоСкольким другим людям оно уже пришло.

Свои поля некоторых площадок (brand, model, year, mileage) приходят как есть.

Ошибки

Код 4xx или 5xx, field есть, когда виновато поле. no_subscription не ошибка, а 200 с notice.

{
  "error": {
    "code": "invalid_filter",
    "message": "price: must be from 0 to 100000000.",
    "field": "price"
  }
}
HTTPcodeКогдаЧто делать
400bad_requestТело не JSON, лишнее поле или параметр, limit или wait вне пределов, в q меньше двух букв.Поле в error.field.
400unknown_platformНет такой площадки, или она закрыта.GET /v1/platforms.
400unknown_filterУ площадки нет такого фильтра или дерева.Фильтры на странице площадки.
400invalid_filterНет такого варианта, число вне пределов, фильтр чужой страны, много разделов.Фильтр в error.field.
400place_unknownКод места не найден.Код из GET /v1/platforms/{key}/places?q=.
401no_keyНет X-API-Key.Добавь ключ.
401bad_keyКлюч неверный или перевыпущен.Свежий ключ в боте. 10 неверных за 10 минут закрывают адрес на час.
403bannedAPI закрыт.Поддержка бота.
404not_foundНет такой задачи (чужая тоже 404) или адреса.Свои задачи: GET /v1/tasks.
405bad_methodМетод не подходит к адресу.Таблица адресов.
409task_runningЗадач уже идёт столько, сколько можно разом. Номер идущей в error.task.POST /v1/stop или те же фильтры.
409busy_taskЗадачу сейчас читает другой запрос.Повтори через секунду.
413too_largeТело больше 16 КБ.Меньше фильтров.
429rate_limitedМного запросов с ключа или адреса, или частый поиск города.Жди Retry-After секунд.
429slow_downМного запросов ключа разом, частый запуск задач, или адрес закрыт за перебор ключей.Жди Retry-After секунд.
500internalСбой сервера.Повтори с repeat=1. Не проходит: X-Request-Id в поддержку.
503busyСервер перегружен.Жди Retry-After секунд.
503updatingБот перезапускается, найденное можно забирать.Жди Retry-After секунд.
503unavailableAPI выключен, или площадка не ответила на поиск города.Жди Retry-After секунд.

Заголовки: X-Request-Id в каждом ответе, X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset в ответах с ключом, Retry-After в 429 и 503. Больше 50 запросов за 10 секунд с адреса режет Cloudflare: 429 с HTML вместо JSON.

Лимиты

ЧтоСколько
Запросов в минуту на ключ60, подряд до 20
Запросов ключа одновременно3
Задач одновременно1
Жизнь задачи30 минут или limit объявлений
Нет GET к задачеконец через 3 минуты
limitдо 1000
waitдо 25 секунд
Поиск города10 в минуту
Тело запроса16 КБ
Неверный ключ10 за 10 минут закрывают адрес на час

Скрипт

Тот же, что собирает страница площадки: создаёт задачу, забирает новое, повторяет потерянный ответ, ждёт Retry-After, выходит на done и notice.

import time

import requests

API = "https://api.fuck-api.xyz"
HEADERS = {"X-API-Key": "fp_ВАШ_КЛЮЧ"}
BODY = {
    "platform": "gumtree",
    "filters": {
        "query": "iphone 13",
        "price": [100, 500],
        "with_phone": True,
    },
    "limit": 200,
}


def show(ad):
    print(ad.get("title"), ad.get("price"), ad.get("currency", ""), ad.get("phone", "без телефона"), ad.get("url"))


def ask(method, path, **given):
    while True:
        try:
            response = requests.request(method, f"{API}{path}", headers=HEADERS, timeout=40, **given)
        except requests.RequestException:
            return None

        if response.status_code not in (429, 503):
            return response

        time.sleep(int(response.headers.get("Retry-After", 5)))


def lost(response):
    return response is None or response.status_code >= 500


def main():
    response = ask("POST", "/v1/tasks", json=BODY)

    while lost(response):
        time.sleep(5)
        response = ask("POST", "/v1/tasks", json=BODY)

    answer = response.json()

    if response.status_code != 200:
        print(answer["error"]["code"], answer["error"]["message"])
        return

    if "notice" in answer:
        print(answer["notice"]["message"])
        return

    path = f"/v1/tasks/{answer['task']['id']}/ads"
    repeat = False

    while True:
        response = ask("GET", path, params={"wait": 20, "repeat": 1} if repeat else {"wait": 20})

        if lost(response):
            repeat = True
            time.sleep(5)
            continue

        answer = response.json()

        if response.status_code != 200:
            print(answer["error"]["code"], answer["error"]["message"])
            return

        repeat = False

        for ad in answer["ads"]:
            show(ad)

        if answer["task"]["status"] == "done" and not answer["ads"]:
            print("Задача закончилась:", answer["task"].get("reason"))
            return

        time.sleep(5)


main()

Остановить задачу и посмотреть свои

import requests

API = "https://api.fuck-api.xyz"
HEADERS = {"X-API-Key": "fp_ВАШ_КЛЮЧ"}

print(requests.post(f"{API}/v1/stop", headers=HEADERS, timeout=30).json())
print(requests.get(f"{API}/v1/tasks", headers=HEADERS, timeout=30).json())
print(requests.get(f"{API}/v1/me", headers=HEADERS, timeout=30).json())

Найти код города

import requests

API = "https://api.fuck-api.xyz"
HEADERS = {"X-API-Key": "fp_ВАШ_КЛЮЧ"}
PARAMS = {
    "q": "Berlin",
}

answer = requests.get(f"{API}/v1/platforms/kleinanzeigen/places", params=PARAMS, headers=HEADERS, timeout=40).json()

if "error" in answer:
    print(answer["error"]["message"])

for place in answer.get("places", []):
    print(place["id"], place["name"], place.get("region", ""))
{"places": [{"id": "3331", "name": "Berlin", "region": "Berlin"}]}