REST API

TSync REST API permite codului tău să comunice cu TSync. Cu el poți extrage date într-un alt sistem, construi un dashboard sau o aplicație mobilă personalizată, menține două sisteme sincronizate sau conecta TSync la instrumente de automatizare — totul prin HTTPS simplu, cu JSON.

Dacă ai mai folosit vreun API web modern, asta îți va părea familiar: obții un token, îl trimiți la fiecare cerere și primești JSON curat înapoi. Această pagină te trece prin proces, apoi listează fiecare endpoint cu exemple de cerere și răspuns.

Base URL: https://your-domain.com/api/v1

Fiecare cale de mai jos este relativă la acel base URL. Așadar /invoices înseamnă de fapt https://your-domain.com/api/v1/invoices.


Pornire în trei pași

  1. Creează un token în panoul de administrare (vezi mai jos) și copiază-l undeva în siguranță.

  2. Trimite-l la o cerere. Cel mai rapid test rapid — listează-ți facturile:

    curl -s -H "Authorization: Bearer YOUR_TOKEN" \
      "https://your-domain.com/api/v1/invoices?per_page=5"
    
  3. Citește JSON-ul. Un răspuns reușit are "status": true și datele tale sub data. Asta este toată bucla — restul sunt doar mai multe endpoint-uri și parametri.


Autentificare

Fiecare cerere trebuie să poarte un token Bearer în header-ul Authorization.

Generarea unui token

  1. Deschide Setup → REST API tokens (/admin/tsync_api_tokens). Poți și apăsa Ctrl/⌘+K și căuta „API”.
  2. Introdu o etichetă (de ex. „Mobile App”, „Integrare Zapier”) ca să-l poți recunoaște mai târziu.
  3. Alege membrul de personal în numele căruia acționează token-ul. Token-ul moștenește permisiunile acelei persoane — poate face exact ce poate face ea, nimic mai mult.
  4. Apasă Generează token. Token-ul este afișat o singură dată — copiază-l și stochează-l în siguranță. Nu îl vei mai putea vedea (se păstrează doar un hash).

Folosirea token-ului

Include-l în fiecare cerere:

Authorization: Bearer your-api-token-here

Pentru că un token moștenește permisiunile membrului său de personal, accesul se aliniază cu interfața de administrare. Dacă acea persoană nu poate vedea facturile în TSync, nici token-ul nu poate ajunge la /invoices — primește un 403.

Revocarea unui token

Mergi la Setup → REST API tokens, găsește token-ul în listă (etichetă, proprietar, ultima utilizare, expirare, status) și apasă Revocă. Încetează să mai funcționeze imediat.


Limitarea ratei (rate limiting)

  • 100 de cereri pe minut per token.
  • Fiecare răspuns include header-e ca să te poți autoregla:
Header Semnificație
X-RateLimit-Limit Numărul maxim de cereri per fereastră (de ex. 100)
X-RateLimit-Remaining Cererile rămase în fereastra curentă
X-RateLimit-Reset Timestamp Unix când se resetează fereastra

Depășește limita și primești 429 Too Many Requests. Corpul îți spune cât să aștepți:

{
  "status": false,
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Retry after 23 seconds.",
  "retry_after": 23
}

Un client simplu și politicos urmărește X-RateLimit-Remaining și își reduce ritmul, sau reîncearcă după retry_after secunde când vede un 429.


Formatul răspunsului

Fiecare răspuns folosește aceeași formă JSON, astfel încât să le poți trata pe toate la fel.

Un singur element vine înapoi sub data:

{
  "status": true,
  "data": {
    "id": 123,
    "number": "INV-000123",
    "total": 5000.00
  }
}

O listă vine înapoi ca un array sub data, cu un bloc meta pentru paginare:

{
  "status": true,
  "data": [
    { "id": 1, "...": "..." },
    { "id": 2, "...": "..." }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 142,
    "total_pages": 6
  }
}

O eroare are întotdeauna "status": false, un cod scurt error pe care poți ramifica logica și un message lizibil de oameni:

{
  "status": false,
  "error": "not_found",
  "message": "Invoice #999 not found."
}

Sfat: verifică status mai întâi. Dacă este true, citește data; dacă este false, citește error și message.


Paginare

Fiecare endpoint de tip listă este paginat:

Parametru Implicit Semnificație
page 1 Numărul paginii (începe de la 1)
per_page 25 Elemente pe pagină (maxim 100)

Folosește valoarea total_pages din meta ca să știi când să te oprești.

Exemplu: GET /api/v1/invoices?page=2&per_page=50


Filtrare și sortare

Endpoint-urile de tip listă acceptă parametri de query pentru a restrânge și ordona rezultatele:

Parametru Se aplică la Semnificație
search Toate Căutare text în câmpurile cheie
status Invoices, Leads Filtrare după status (de ex. paid, unpaid, overdue)
date_from Orice are dată Data de început (YYYY-MM-DD)
date_to Orice are dată Data de sfârșit (YYYY-MM-DD)
client_id Invoices, Payments Filtrare după client
assigned Leads Filtrare după ID-ul membrului de personal asignat
sort Toate Câmpul după care se sortează (de ex. date, total, name)
order Toate asc sau desc

Exemplu: GET /api/v1/invoices?status=unpaid&date_from=2026-01-01&sort=total&order=desc


Endpoint-uri

API-ul acoperă evidențele tale de afaceri de bază. Iată-le pe fiecare cu parametrii și un răspuns exemplu.

Invoices

List invoices

GET /api/v1/invoices

Parametri de query: status, client_id, date_from, date_to, search, page, per_page

Răspuns:

{
  "status": true,
  "data": [
    {
      "id": 123,
      "number": "INV-000123",
      "client_id": 45,
      "client_name": "Acme SRL",
      "date": "2026-05-01",
      "duedate": "2026-05-31",
      "subtotal": 4201.68,
      "total_tax": 798.32,
      "total": 5000.00,
      "currency": "RON",
      "status": "unpaid",
      "status_label": "Unpaid"
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 142, "total_pages": 6 }
}

Get a single invoice

GET /api/v1/invoices/:id

Returnează factura completă, inclusiv liniile ei și orice plăți înregistrate.

{
  "status": true,
  "data": {
    "id": 123,
    "number": "INV-000123",
    "client_id": 45,
    "client_name": "Acme SRL",
    "date": "2026-05-01",
    "duedate": "2026-05-31",
    "subtotal": 4201.68,
    "total_tax": 798.32,
    "total": 5000.00,
    "currency": "RON",
    "status": "unpaid",
    "items": [
      {
        "description": "Consulting services — May 2026",
        "qty": 40,
        "rate": 100.00,
        "tax_name": "TVA 19%",
        "tax_rate": 19.00,
        "amount": 4000.00
      }
    ],
    "payments": [],
    "created_at": "2026-05-01T10:30:00+03:00"
  }
}

Clients

List clients

GET /api/v1/clients

Parametri de query: search, page, per_page

Răspuns:

{
  "status": true,
  "data": [
    {
      "id": 45,
      "company": "Acme SRL",
      "vat": "RO12345678",
      "phonenumber": "+40721000000",
      "city": "Bucharest",
      "country": "Romania",
      "active": true,
      "total_invoiced": 125000.00,
      "health_score": 82
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 310, "total_pages": 13 }
}

Get a single client

GET /api/v1/clients/:id

Returnează detaliile complete ale clientului, inclusiv contacte, note și statistici de sumar.


Leads

List leads

GET /api/v1/leads

Parametri de query: status, source, assigned, search, date_from, date_to, page, per_page

Răspuns:

{
  "status": true,
  "data": [
    {
      "id": 789,
      "name": "John Smith",
      "company": "Widget Corp",
      "email": "john@widgetcorp.com",
      "phonenumber": "+40722000000",
      "value": 15000.00,
      "status": "new",
      "source": "Website",
      "assigned": 3,
      "assigned_name": "Maria Popescu",
      "created_at": "2026-05-15T14:20:00+03:00"
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 58, "total_pages": 3 }
}

Create a lead

POST /api/v1/leads
Content-Type: application/json

Corpul cererii:

{
  "name": "Jane Doe",
  "company": "NewCo SRL",
  "email": "jane@newco.ro",
  "phonenumber": "+40723000000",
  "value": 8000.00,
  "source": "API",
  "assigned": 3,
  "description": "Interested in warehouse module"
}

Răspuns (201 Created):

{
  "status": true,
  "data": {
    "id": 790,
    "name": "Jane Doe",
    "company": "NewCo SRL",
    "status": "new",
    "created_at": "2026-05-16T09:00:00+03:00"
  }
}

Stock

List stock items

GET /api/v1/stock

Parametri de query: search, warehouse_id, below_reorder (boolean), page, per_page

Răspuns:

{
  "status": true,
  "data": [
    {
      "id": 201,
      "code": "MAT-001",
      "name": "Steel Rod 10mm",
      "warehouse_id": 1,
      "warehouse_name": "Main Warehouse",
      "qty_on_hand": 450,
      "qty_reserved": 30,
      "qty_available": 420,
      "reorder_level": 100,
      "unit": "kg",
      "avg_cost": 12.50,
      "total_value": 5625.00
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 1200, "total_pages": 48 }
}

Stock movements

GET /api/v1/stock/movements

Parametri de query: item_id, warehouse_id, type (in/out/transfer), date_from, date_to, page, per_page

Răspuns:

{
  "status": true,
  "data": [
    {
      "id": 5001,
      "item_id": 201,
      "item_name": "Steel Rod 10mm",
      "type": "in",
      "qty": 100,
      "reference_type": "grn",
      "reference_id": 88,
      "warehouse_id": 1,
      "date": "2026-05-14T08:00:00+03:00",
      "staff_id": 2,
      "staff_name": "Ion Popescu"
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 8400, "total_pages": 336 }
}

Payments

List payments

GET /api/v1/payments

Parametri de query: client_id, invoice_id, date_from, date_to, page, per_page

Răspuns:

{
  "status": true,
  "data": [
    {
      "id": 567,
      "invoice_id": 123,
      "invoice_number": "INV-000123",
      "client_id": 45,
      "amount": 5000.00,
      "payment_mode": "Bank Transfer",
      "date": "2026-05-10",
      "note": "Wire transfer ref: TRF-2026-0510"
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 890, "total_pages": 36 }
}

Expenses

List expenses

GET /api/v1/expenses

Parametri de query: category, date_from, date_to, billable (boolean), search, page, per_page

Răspuns:

{
  "status": true,
  "data": [
    {
      "id": 340,
      "category": "Office Supplies",
      "amount": 250.00,
      "currency": "RON",
      "tax": 47.50,
      "date": "2026-05-12",
      "client_id": null,
      "billable": false,
      "reference_no": "RCP-2026-0512",
      "note": "Printer cartridges"
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 234, "total_pages": 10 }
}

Staff

List staff

GET /api/v1/staff

Parametri de query: search, role, active (boolean), page, per_page

Răspuns:

{
  "status": true,
  "data": [
    {
      "id": 3,
      "firstname": "Maria",
      "lastname": "Popescu",
      "email": "maria@company.ro",
      "role": "Senior Sales",
      "active": true,
      "last_login": "2026-05-16T08:45:00+03:00"
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 18, "total_pages": 1 }
}

The token's own user

GET /api/v1/staff/me

Util pentru a confirma că un token funcționează și a vedea ce îi este permis să facă. Returnează membrul de personal din spatele token-ului, permisiunile lui și starea curentă a limitei de rată.

{
  "status": true,
  "data": {
    "id": 3,
    "firstname": "Maria",
    "lastname": "Popescu",
    "email": "maria@company.ro",
    "role": "Senior Sales",
    "permissions": ["invoices.view", "clients.view", "leads.manage", "stock.view"],
    "token_description": "Mobile App",
    "rate_limit": 100,
    "rate_limit_remaining": 87
  }
}

Analytics datasets

List datasets

GET /api/v1/analytics/datasets

Listează seturile de date de analiză predefinite pe care le poți rula. Fiecare are un id de tip text stabil, un label lizibil și o scurtă description.

Răspuns:

{
  "status": true,
  "data": [
    { "id": "revenue_monthly",      "label": "Monthly Revenue",       "description": "Total invoiced revenue grouped by month." },
    { "id": "leads_by_status",      "label": "Leads by Status",       "description": "Count of leads grouped by their current status." },
    { "id": "expenses_by_category", "label": "Expenses by Category",  "description": "Total expenses grouped by category." },
    { "id": "invoices_aging",       "label": "Invoices Aging",        "description": "Unpaid invoices grouped by aging buckets." },
    { "id": "stock_valuation",      "label": "Stock Valuation",       "description": "Current stock quantities and values by warehouse." }
  ]
}

Run a dataset

GET /api/v1/analytics/run/:dataset_id

Rulează unul dintre seturile de date de mai sus (:dataset_id este id-ul din listă) și returnează rândurile calculate. Fiecare set verifică permisiunea corespunzătoare (de ex. revenue_monthly necesită drept de vizualizare a facturilor).

{
  "status": true,
  "data": {
    "dataset": "revenue_monthly",
    "results": [
      { "month": "2026-05", "revenue": 184200.00, "invoice_count": 37 }
    ],
    "generated_at": "2026-05-16 02:00:00"
  }
}

More endpoints

Câteva endpoint-uri de citire suplimentare completează API-ul. Respectă aceleași reguli de envelope, token și paginare ca cele de mai sus.

Endpoint Verb Returnează
/api/v1/status GET Starea serviciului și identitatea din spatele token-ului (și răspunsul pentru GET /api/v1).
/api/v1/items GET Articole din catalog (listă). search, page, per_page.
/api/v1/items/:id GET Un singur articol din catalog.
/api/v1/treasury/places GET Locuri de păstrare a banilor — doar citire, limitate la companiile token-ului.
/api/v1/bank/accounts GET Conturi bancare — doar citire, limitate la companiile token-ului.

Unele resurse acceptă și scrieri când membrul de personal al token-ului are permisiunea necesară: POST /api/v1/invoices, POST /api/v1/clients, plus GET /api/v1/leads/:id și PUT /api/v1/leads/:id pe lângă endpoint-urile de leaduri prezentate mai sus.


Coduri de eroare

Când status este false, statusul HTTP și codul error îți spun ce a mers prost:

Status HTTP Cod eroare Semnificație
400 bad_request Cerere malformată sau parametri invalizi
401 unauthorized Token lipsă sau invalid
403 forbidden Token-ul este valid, dar nu are permisiune pentru această resursă
404 not_found Resursa nu există
422 validation_error Datele de intrare au eșuat validarea (detalii în obiectul errors)
429 rate_limit_exceeded Prea multe cereri
500 server_error Ceva a mers prost pe server

O eroare de validare îți spune exact ce câmpuri să corectezi:

{
  "status": false,
  "error": "validation_error",
  "message": "Validation failed.",
  "errors": {
    "email": ["The email field is required."],
    "name": ["The name must be at least 2 characters."]
  }
}

Exemple de cod

Aceleași două sarcini — listarea facturilor neplătite și crearea unui lead — în trei limbaje.

cURL

# List unpaid invoices
curl -s -H "Authorization: Bearer YOUR_TOKEN" \
  "https://erp.example.com/api/v1/invoices?status=unpaid&per_page=10"

# Get a single client
curl -s -H "Authorization: Bearer YOUR_TOKEN" \
  "https://erp.example.com/api/v1/clients/45"

# Create a lead
curl -s -X POST \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Jane Doe","email":"jane@example.com","value":5000,"source":"API"}' \
  "https://erp.example.com/api/v1/leads"

PHP

<?php
$token = 'YOUR_TOKEN';
$base  = 'https://erp.example.com/api/v1';

// List invoices
$ch = curl_init("{$base}/invoices?status=unpaid&per_page=50");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ["Authorization: Bearer {$token}"],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

foreach ($response['data'] as $invoice) {
    echo "#{$invoice['number']} — {$invoice['total']} {$invoice['currency']}\n";
}

// Create a lead
$ch = curl_init("{$base}/leads");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        "Authorization: Bearer {$token}",
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'name'    => 'Jane Doe',
        'email'   => 'jane@example.com',
        'value'   => 5000,
        'source'  => 'API',
    ]),
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);

echo "Created lead #{$result['data']['id']}\n";

JavaScript (fetch)

const TOKEN = 'YOUR_TOKEN';
const BASE  = 'https://erp.example.com/api/v1';

// List invoices
const invoices = await fetch(`${BASE}/invoices?status=unpaid`, {
  headers: { 'Authorization': `Bearer ${TOKEN}` }
}).then(r => r.json());

console.log(`Found ${invoices.meta.total} unpaid invoices`);
invoices.data.forEach(inv => {
  console.log(`#${inv.number} — ${inv.total} ${inv.currency}`);
});

// Create a lead
const newLead = await fetch(`${BASE}/leads`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${TOKEN}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Jane Doe',
    email: 'jane@example.com',
    value: 5000,
    source: 'API'
  })
}).then(r => r.json());

console.log(`Created lead #${newLead.data.id}`);

Webhooks (în curând)

Notificările push când se modifică înregistrările sunt planificate pentru o versiune viitoare. Deocamdată, interoghează cu filtrul date_from ca să prinzi înregistrările noi sau actualizate de la ultima ta verificare.


Permisiuni

Accesul la API folosește același sistem de permisiuni ca interfața de administrare. Cele relevante:

Permisiune Necesară pentru
Manage API tokens Generarea și revocarea token-urilor
API access Folosirea API-ului în general (baza)
Permisiuni per înregistrare Accesarea fiecărui endpoint (de ex. vizualizarea facturilor, crearea lead-urilor)

Pentru că token-urile moștenesc rolul membrului lor de personal, cea mai simplă cale de a controla ce poate face un token este să-l îndrepți către un membru de personal cu exact permisiunile potrivite.


Vezi și


Format răspuns (v5.9.17)

Fiecare răspuns folosește un singur format:

Succes (un obiect):

{ "status": true, "data": { } }

Succes (listă):

{ "status": true, "data": [ ], "meta": { "page": 1, "per_page": 25, "total": 100, "total_pages": 4 } }

Eroare:

{ "status": false, "error": "not_found", "message": "…" }

(ok apare și el lângă status pentru clienții mai vechi.) Fiecare răspuns are headere de rate-limit — X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset — iar fiecare apel este logat (token, staff, companie, metodă, endpoint, status, durată, IP).

Endpointuri

GET /api/v1/status · GET /api/v1/staff/me · GET|POST /api/v1/clients · GET|POST /api/v1/leads, PUT /api/v1/leads/{id} · GET /api/v1/items · GET /api/v1/stock · GET /api/v1/treasury/places (doar citire) · GET /api/v1/bank/accounts (doar citire) · plus facturi, plăți, cheltuieli, analytics.

Fiecare token aparține unui membru al echipei și respectă permisiunile acestuia; datele nu sunt returnate între companii la care token-ul nu are acces.