Reverse-engineering complet al API-ului 999.md, executat în martie-aprilie 2026 pentru a alimenta funcții verificare concurenți (Verificare concurenți) + Indicele 999 (Indicele 999) + cuvinte căutate (Demand Signal).
Insight central: 999.md folosește GraphQL ca partea de server pentru partea vizibilă-ul SPA. Endpoint-ul
https://999.md/graphqle PUBLIC pentru queries read (fără auth necesară), cu rate limit ~50 req/min/IP.
De ce reverse-engineering și nu API oficial
999.md nu publică API documentat oficial pentru terți. Există Partners API privat (folosit de TopAuto, AUTOPLAZA, Imona), dar accesul cere contract enterprise + revenue sharing. Pentru tools de productivity ca Bravin, alternativa era:
- Scrape HTML — fragil, lent, transfer mare. Renunțat.
- Reverse GraphQL — endpoint public read, rate-limited, schema introspection-friendly. Ales.
- Cumpără Partners API — costos ($1.000+/lună), overkill pentru doar vizualizare.
Pașii descoperirii (replicabili)
Pas 1: Network inspection în Chrome DevTools
Deschis 999.md, calculator promovare → Network → filtrare XHR/Fetch. Observat că toate operațiile (search, listing detail, conectare) merg prin POST /graphql cu body JSON:
{
"operationName": "SearchListings",
"variables": { "category": "transport", "limit": 30 },
"query": "query SearchListings($category: String!, $limit: Int!) { ... }"
}
Pas 2: Capture queries via mitmproxy
Pornit mitmproxy pe MacBook, navigat 999.md timp de 2 ore (toate categorii, search, conectare, mesaje). Capturat 147 cereri unice către /graphql.
mitmproxy --mode regular --listen-port 8080 --save-stream-file 999md.dump
Pas 3: Deduplicare queries
Din 147 cereri → 90 queries unice (după deduplication pe câmp operationName). Restul = repetiții cu parametri diferiți.
cat 999md.dump | jq '.operationName' | sort -u | wc -l
# → 90
Pas 4: Introspection pentru schema completă
curl -X POST https://999.md/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { queryType { fields { name args { name type { name kind } } type { name kind } } } } }"}' \
> schema.json
Schema completă: 1.247 type-uri, 150 queries declarate, 40 mutations. Din 150 queries, 60 sunt internal/admin, 90 sunt utile public.
Pas 5: Validare și clasificare
Toate cele 90 queries categorizate:
- 38 queries listing search (per categorie + filtre)
- 15 queries listing detail (full info, photos, user, reviews)
- 12 queries category/subcategory navigation
- 10 queries user public profile (alte anunțuri, rating)
- 8 queries pricing/promotions (booster prices, top placement cost)
- 5 queries macro stats (trending categories, hot keywords)
- 2 queries miscellaneous (sitemap, breadcrumb)
Top 10 queries cele mai utile
1. SearchListings (cel mai folosit)
query SearchListings(
$category: String!,
$subcategory: String,
$minPrice: Float,
$maxPrice: Float,
$location: String,
$limit: Int = 30,
$offset: Int = 0,
$orderBy: String = "reseted_desc"
) {
listings(
category: $category,
subcategory: $subcategory,
minPrice: $minPrice,
maxPrice: $maxPrice,
location: $location,
limit: $limit,
offset: $offset,
orderBy: $orderBy
) {
id
title
titleRu
price
currency
photos { url thumbnail }
user { id name isCompany }
publishedAt
resetedAt
boosterUntil
topPlacement
location
}
}
Insight: parametrul orderBy: "reseted_desc" confirmă că ranking-ul e literalmente sortare după reseted_at (timpul ultimei republicări). Vezi blog /blog/algoritm-999md-explicat.
2. ListingById
query ListingById($id: ID!) {
listing(id: $id) {
id
title
titleRu
description
descriptionRu
price
currency
photos { url width height }
user {
id name phone email
registeredAt totalListings averageRating
}
location { city street latitude longitude }
attributes {
key value valueRu
}
viewsCount
contactsCount
favoritesCount
publishedAt
resetedAt
expiresAt
}
}
Folosit pentru: verificare cont audit propriu (vezi viewsCount/contactsCount = rentabilitate per anunț).
3. CategoryStats
query CategoryStats($category: String!, $period: String = "30d") {
categoryStats(category: $category, period: $period) {
totalListings
activeListings
newPerHour
velocityPerCategory
avgPrice
avgPriceTrend
topUsers { id name listingCount }
}
}
Folosit pentru: Indicele 999 Indicele 999 (calculul viteză 224/h Auto, 223/h Real-estate, etc).
4. UserPublicProfile
query UserPublicProfile($userId: ID!) {
user(id: $userId) {
id
name
avatar
isCompany
companyName
registeredAt
totalListings
activeListings
averageRating
reviewsCount
listings(limit: 50) {
id title price photos { url } resetedAt
}
}
}
Folosit pentru: verificare concurenți verificare concurenți — vezi câte anunțuri are AUTOPLAZA, când le republică, ce categorii.
5. TrendingKeywords
query TrendingKeywords($period: String = "7d", $limit: Int = 50) {
trendingKeywords(period: $period, limit: $limit) {
keyword
keywordRu
searchCount
growthPercent
relatedCategories
}
}
Folosit pentru: cuvinte căutate Demand Signal — "Ce caută moldovenii AZI pe 999.md".
6-10. Restul (sumarizat)
BoosterPrices— costul booster per categorie (variabil!)TopPlacementOptions— opțiuni top placement + prețCategoryHierarchy— arbore complet 23 categorii + 310 subcategoriiLocationsList— toate orașele/raioanele MD cu listingsRecentReviews— review-uri publice useri (pentru trust score)
Vezi listă completă 90 queries în GitHub → (public)
Rate limiting și etichetă
Simpals nu publică limite explicit, dar empiric:
- ~50 req/min/IP = OK
- >100 req/min/IP = HTTP 429 Throttled
- >500 req/min/IP = IP ban temporar (1-24h)
Best practice Bravin:
- Cache agresiv — listings 5 min, categories 1h, user profiles 30 min
- Throttle automat —
p-throttlela 30 req/min ca safety margin - Backoff exponential — la 429, retry la 30s, 60s, 120s, 240s
- User-Agent identificat —
Bravin-io/1.0 (https://index9.site; contact@index9.site)ca să poată Simpals contacta dacă există probleme - Cron noaptea — heavy queries (Indicele 999 daily refresh) între 03:00-05:00 când load e minim
Exemple complete
Bash / curl
curl -X POST https://999.md/graphql \
-H "Content-Type: application/json" \
-H "User-Agent: Bravin-io/1.0 (contact@index9.site)" \
-d '{
"operationName": "SearchListings",
"variables": {
"category": "transport",
"limit": 10,
"orderBy": "reseted_desc"
},
"query": "query SearchListings($category: String!, $limit: Int!, $orderBy: String!) { listings(category: $category, limit: $limit, orderBy: $orderBy) { id title price resetedAt } }"
}'
Node.js
const client = new GraphQLClient('https://999.md/graphql', {
headers: { 'User-Agent': 'Bravin-io/1.0' }
})
const query = gql`
query SearchListings($category: String!) {
listings(category: $category, limit: 30, orderBy: "reseted_desc") {
id title price resetedAt
}
}
`
const data = await client.request(query, { category: 'transport' })
console.log(data.listings)
Python
import requests
r = requests.post(
'https://999.md/graphql',
headers={'User-Agent': 'Bravin-io/1.0'},
json={
'query': '''
query SearchListings($category: String!) {
listings(category: $category, limit: 30, orderBy: "reseted_desc") {
id title price resetedAt
}
}
''',
'variables': {'category': 'transport'}
}
)
print(r.json()['data']['listings'])
Schema completă
Snapshot lunar la https://api.index9.site/999md/schema.graphql (public, free).
Diff față de luna trecută la https://api.index9.site/999md/schema.diff (public).
Avertismente
- Schema poate dispărea oricând — Simpals nu e obligat să o țină publică. Bravin păstrează cache local + alerte.
- Rate limit poate strânge — la creștere agresivă a useri Bravin, Simpals poate decide să închidă acces.
- Mutations cer auth — pentru a crea/edita/șterge anunțuri trebuie session cookie sau Partners API. Bravin folosește Partners API oficial pentru mutations (avem contract semnat din martie 2026).
Etică & legalitate
- Date publice citite: OK (aceleași afișate în browser)
- Date personale extrase: NU (telefoane non-publice, mesaje private = ilegal Lege 133 MD)
- DoS / suprasolicitare: NU (rate limit auto, cache, throttle)
- Re-publicare conținut: NU (copyright Simpals + utilizatori autori)
- Comunicare deschisă: avem contact direct cu echipa Simpals, le raportăm bug-uri schema
Următorul pas
- Citește algoritm ranking 999.md (insight central) →
- Vezi Indicele 999 live (folosește aceste queries) →
- Verificare concurenți funcție (verificare concurenți) →
- Index docs general →
- Politica de securitate →
Pentru parteneriat tehnic cu Simpals direct: contactează partners@simpals.com. Pentru integrare cu Bravin: scrie la contact@index9.site.
index9 | Chișinău, Republica Moldova | contact@index9.site | +373 22 000 999