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/callback

Pentru 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

Trimite cererea la:

contact@zup.ro

Te vom contacta în 1-2 zile lucrătoare cu credențialele.

Procesul de autentificare OAuth2

1

Obținerea codului de autorizare

Redirectează utilizatorul la pagina de autorizare ZUP:

https://zup.ro/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code
2

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.

3

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).

YOUR_REDIRECT_URI?code=AUTHORIZATION_CODE&state=YOUR_STATE
4

Aplicația ta face schimbul codului pe token

În backend, fă un request POST la https://zup.ro/zapi/v1/oauth/token:

URL: https://zup.ro/zapi/v1/oauth/token
Method: POST
Content-Type: application/x-www-form-urlencoded
Grant Type: authorization_code
5

Utilizarea token-ului

Adaugă token-ul în header-ul Authorization pentru toate request-urile API:

Authorization: Bearer YOUR_ACCESS_TOKEN
6

Actualizarea token-ului expirat

Dacă token-ul expiră, poți folosi refresh token-ul pentru a-l actualiza:

URL: https://zup.ro/zapi/v1/oauth/token
Method: POST
Content-Type: application/x-www-form-urlencoded
Grant Type: refresh_token

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

Suport și Contact

Email Support

contact@zup.ro

Obține credențiale API

Solicită acces la contact@zup.ro