ZUP API Documentation
Quick Start
1. Obține credențialele API
• Trimite cerere la contact@zup.ro
• Include în email: numele aplicației și Redirect URI-ul dorit
• Echipa ZUP va genera și trimite Client ID și Client Secret
💡 Pentru testare: folosește https://zup.ro/docs-api/oauth2-redirect
2. Testează în Swagger
• Folosește Swagger UI pentru testare
• Completează Client ID în secțiunea OAuth2
• Codul de autorizare va fi afișat pentru generarea token-ului
Cum obții credențialele API
Important: Credențialele API sunt generate manual de administratorii ZUP și nu sunt disponibile pentru auto-înregistrare.
Ce să incluzi în cererea ta:
- 1 Numele aplicației și descrierea scopului
- 2 Redirect URL pentru aplicația ta (vezi opțiunile →)
Poți include mai multe url-uri, separate prin virgula - 3 Informații de contact și detalii tehnice relevante
Opțiuni pentru Redirect URL:
Pentru aplicația ta:
Specifică URL-ul din aplicația ta unde vrei să primești codul de autorizare:
https://aplicatia-ta.com/oauth/callbackPentru testare în Swagger:
Folosește URL-ul nostru care îți afișează codul direct:
https://zup.ro/docs-api/oauth2-redirect ✓ Perfect pentru dezvoltare și testare
Procesul de autentificare OAuth2
Obținerea codului de autorizare
Redirectează utilizatorul la pagina de autorizare ZUP:
Autorizarea Client API în pagina ZUP
Utilizatorul va fi redirectat la pagina de autorizare ZUP. Atenție: utilizatorul trebuie să fie logat cu contul pentru care face autorizarea.
Important: Dacă utilizatorul nu este logat, va fi redirectat la pagina de login înainte de autorizare.
Redirectare către callback-ul aplicației
După autorizare, utilizatorul va fi redirectat către redirect_url primit în primul request, către primul dintre ele (către callback-ul aplicației tale).
Aplicația ta face schimbul codului pe token
În backend, fă un request POST la https://zup.ro/zapi/v1/oauth/token:
Utilizarea token-ului
Adaugă token-ul în header-ul Authorization pentru toate request-urile API:
Actualizarea token-ului expirat
Dacă token-ul expiră, poți folosi refresh token-ul pentru a-l actualiza:
Tip: Salvează refresh token-ul în siguranță pentru a actualiza access token-ul fără a cere utilizatorului să se autentifice din nou.
🆕 Ierarhia Categoriilor (IMPORTANT pentru CRM-uri)
Cum funcționează?
Când creați sau actualizați un anunț prin API (OAuth/ZAPI), trebuie să trimiteți DOAR category_id care reprezintă categoria finală (cea mai specifică).
Sistemul va construi automat ierarhia completă de categorii, de la categoria root până la categoria finală.
✅ Exemplu CORECT
{
"name": "Apartament 3 camere Sector 1",
"category_id": 1215, // ID-ul categoriei finale "3 camere"
"description": "...",
"county": "București",
...
}Sistemul construiește automat ierarhia în baza de date:
category1_id: 10(Imobiliare - root)category2_id: 955(Apartamente de vanzare)category3_id: 1215(3 camere - finală)category4_id: null
Cum să identificați categoria finală corectă?
O categorie finală este o categorie care NU are subcategorii. Pentru a verifica, folosiți endpoint-ul:
GET /categories/{id}/children- Dacă returnează array gol
[]→ categoria este finală (OK pentru category_id) - Dacă returnează subcategorii → categoria NU este finală (va genera eroare de validare)
Validare și Erori
Dacă trimiteți o categorie care nu este finală, veți primi o eroare de validare:
{
"message": "Validation failed",
"errors": {
"category_id": [
"Categoria 'Imobiliare' conține subcategorii. Anunțurile pot fi plasate doar în categorii finale (fără subcategorii). Vă rugăm să selectați o subcategorie specifică."
]
}
}Endpoint-uri principale
OAuth & Autentificare
POST /oauth/token - Obținerea token-ului
POST /oauth/token/refresh - Refresh token
Categorii
GET /categories - Lista categoriilor
GET /categories/{id} - Detalii categorie
GET /categories/{id}/characteristics - Caracteristici categorie
GET /categories/{id}/children - Subcategorii (pentru verificare categorie finală)
Localități & Județe
GET /locations - Județe cu localități (toate sau filtrate)
GET /locations?counties_only=true - Doar județele
GET /locations?county_name=București - Localități dintr-un județ
GET /locations?county_name=Cluj&search=floresti - Cu căutare în localități
Anunțuri
GET /anunturi - Lista anunțurilor utilizatorului
POST /anunturi - Crearea unui anunț nou (cu suport photos_urls)
POST /anunturi/batch - Creare în lot (max 100)
PUT /anunturi/{id} - Actualizarea unui anunț
POST /anunturi/{id}/command - Activare/dezactivare anunț
DELETE /anunturi/{anunt_id}/photos/{photo_id} - Stergere fotografii
Exemplu complet de integrare
// Client complet pentru API-ul ZUP - JavaScript cu token management complet
class ZupAPIClient {
constructor(clientId, clientSecret, accessToken = null, refreshToken = null) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.baseUrl = 'https://zup.ro/zapi/v1';
}
// Obține token-ul inițial din codul de autorizare
async getInitialToken(authorizationCode, redirectUri) {
try {
const response = await fetch(`${this.baseUrl}/oauth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authorizationCode,
client_id: this.clientId,
client_secret: this.clientSecret,
redirect_uri: redirectUri
})
});
if (!response.ok) {
throw new Error('Failed to get initial token');
}
const tokens = await response.json();
this.accessToken = tokens.access_token;
this.refreshToken = tokens.refresh_token;
// Salvează token-urile pentru utilizare ulterioară
localStorage.setItem('zup_access_token', tokens.access_token);
localStorage.setItem('zup_refresh_token', tokens.refresh_token);
return tokens;
} catch (error) {
console.error('Failed to get initial token:', error);
throw error;
}
}
// Actualizează token-ul folosind refresh_token
async refreshAccessToken() {
try {
const response = await fetch(`${this.baseUrl}/oauth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: this.refreshToken,
client_id: this.clientId,
client_secret: this.clientSecret
})
});
if (!response.ok) {
throw new Error('Token refresh failed');
}
const tokens = await response.json();
this.accessToken = tokens.access_token;
// Salvează noul token pentru utilizare ulterioară
localStorage.setItem('zup_access_token', tokens.access_token);
if (tokens.refresh_token) {
this.refreshToken = tokens.refresh_token;
localStorage.setItem('zup_refresh_token', tokens.refresh_token);
}
return tokens.access_token;
} catch (error) {
console.error('Failed to refresh token:', error);
throw error;
}
}
async makeRequest(endpoint, method = 'GET', data = null, retryCount = 0) {
const config = {
method: method,
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
}
};
if (data && method !== 'GET') {
config.body = JSON.stringify(data);
}
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, config);
// Dacă tokenul a expirat (401 Unauthorized), încearcă să-l reîmprospătezi
if (response.status === 401 && retryCount === 0) {
console.log('Token expirat, încerc să-l reîmprospătez...');
await this.refreshAccessToken();
// Reîncearcă request-ul cu noul token (doar o dată)
return await this.makeRequest(endpoint, method, data, 1);
}
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API Request failed:', error);
throw error;
}
}
// Exemple de endpoint-uri
async getCategories() {
return await this.makeRequest('/categories');
}
async getCounties() {
return await this.makeRequest('/locations?counties_only=true');
}
async getLocalities(countyName = null, search = null) {
const params = new URLSearchParams();
if (countyName) params.append('county_name', countyName);
if (search) params.append('search', search);
const queryString = params.toString() ? '?' + params.toString() : '';
return await this.makeRequest('/locations' + queryString);
}
async getAllLocations() {
return await this.makeRequest('/locations');
}
async getMyAds() {
return await this.makeRequest('/anunturi');
}
async getPromotedAds(limit = 12) {
return await this.makeRequest(`/anunturi/promoted?limit=${limit}`);
}
async createAd(adData) {
return await this.makeRequest('/anunturi', 'POST', adData);
}
async createAdsBatch(adsArray) {
return await this.makeRequest('/anunturi/batch', 'POST', adsArray);
}
async updateAd(adId, adData) {
return await this.makeRequest(`/anunturi/${adId}`, 'PUT', adData);
}
async activateAd(adId) {
return await this.makeRequest(`/anunturi/${adId}/command`, 'POST', { command: 'activate' });
}
async deactivateAd(adId) {
return await this.makeRequest(`/anunturi/${adId}/command`, 'POST', { command: 'deactivate' });
}
}
// Exemplu de utilizare completă cu token management complet
const clientId = 'your-client-id';
const clientSecret = 'your-client-secret';
// Scenario 1: Obținerea primului token din authorization code
async function initializeWithAuthCode() {
const zupClient = new ZupAPIClient(clientId, clientSecret);
// Obține codul de autorizare din URL (după redirect de la OAuth)
const urlParams = new URLSearchParams(window.location.search);
const authorizationCode = urlParams.get('code');
const redirectUri = 'https://aplicatia-ta.com/oauth/callback';
if (authorizationCode) {
try {
// Obține primul token
const tokens = await zupClient.getInitialToken(authorizationCode, redirectUri);
console.log('Token-uri obținute cu succes:', tokens);
// Acum poți folosi API-ul
const categories = await zupClient.getCategories();
console.log('Categorii disponibile:', categories);
// Obține județele
const counties = await zupClient.getCounties();
console.log('Județe disponibile:', counties);
// Obține localitățile din București
const localities = await zupClient.getLocalities('București', null);
console.log('Localități din București:', localities);
} catch (error) {
console.error('Eroare la obținerea token-ului:', error);
}
}
}
// Scenario 2: Utilizarea cu token-uri existente
async function useExistingTokens() {
const existingAccessToken = localStorage.getItem('zup_access_token');
const existingRefreshToken = localStorage.getItem('zup_refresh_token');
const zupClient = new ZupAPIClient(clientId, clientSecret, existingAccessToken, existingRefreshToken);
try {
// API calls - token-ul va fi refresh-at automat dacă expiră
const categories = await zupClient.getCategories();
console.log('Categorii disponibile:', categories);
// Creează un anunț nou cu fotografii din URL-uri
const newAd = {
name: 'iPhone 15 Pro',
description: 'Telefon în stare excelentă',
category_id: 3099, // Categoria finală - sistemul construiește automat ierarhia completă
county: 'București',
locality: 'Sectorul 1',
photos_urls: [
'https://example.com/iphone15-front.jpg',
'https://example.com/iphone15-back.jpg'
]
};
// Creează un anunț promovat (evidențiat)
const promotedAd = {
name: 'iPhone 15 Pro - OFERTĂ SPECIALĂ',
description: 'Telefon în stare excelentă, preț negociabil',
category_id: 3099, // Categoria finală - ierarhia se construiește automat
county: 'București',
locality: 'Sectorul 1',
is_featured: true, // Marchează anunțul ca promovat
photos_urls: [
'https://example.com/iphone15-front.jpg',
'https://example.com/iphone15-back.jpg'
]
};
const createdAd = await zupClient.createAd(newAd);
console.log('Anunț creat:', createdAd);
// Obține anunțurile promovate
const promotedAds = await zupClient.getPromotedAds(15);
console.log('Anunțuri promovate:', promotedAds);
// Creează un anunț promovat direct
const createdPromotedAd = await zupClient.createAd(promotedAd);
console.log('Anunț promovat creat:', createdPromotedAd);
// Actualizează un anunț existent pentru a-l promova
const updatedAd = await zupClient.updateAd(createdAd.id, { is_featured: true });
console.log('Anunț promovat:', updatedAd);
// Creează mai multe anunțuri într-o cerere (batch)
const multiplAds = [
{
name: 'Samsung Galaxy S24',
description: 'Nou, nout',
category_id: 3099, // Categoria finală
county: 'Cluj',
locality: 'Cluj-Napoca',
photos_urls: ['https://example.com/samsung-s24.jpg']
},
{
name: 'MacBook Pro 16"',
description: 'Pentru dezvoltatori',
category_id: 920, // Categoria finală
county: 'Iași',
locality: 'Iași',
photos_urls: ['https://example.com/macbook-pro.jpg']
}
];
const batchResult = await zupClient.createAdsBatch(multiplAds);
console.log('Rezultat batch:', batchResult);
console.log(`Anunțuri create cu succes: ${batchResult.success_count}`);
console.log(`Anunțuri cu erori: ${batchResult.error_count}`);
} catch (error) {
console.error('Eroare în utilizarea API-ului:', error);
}
}
// Utilizează funcția potrivită în funcție de situație
if (new URLSearchParams(window.location.search).get('code')) {
initializeWithAuthCode();
} else {
useExistingTokens();
}Ce include acest exemplu:
- • Clasă API Client cu toate metodele
- • Obținerea primului token din authorization code
- • Auto-refresh token când expiră
- • Error handling complet cu retry logic
- • Token management automat în localStorage/session
- • Scenarii multiple de utilizare (primul token vs. token existent)
- • CRUD operations pentru anunțuri
- • Production-ready cu best practices
Rate Limiting & Securitate
Limite de request-uri
- • 1000 request-uri per oră per aplicație
- • Token-urile de acces expiră după 1 oră
- • Refresh token-urile sunt valabile 30 de zile
- • Implementează retry logic pentru rate limiting
Sfaturi de securitate
- • Nu expune niciodată client_secret în frontend
- • Păstrează token-urile în siguranță (nu în localStorage)
- • Folosește HTTPS pentru toate request-urile
- • Implementează refresh automat pentru token-uri