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
Creează un token în panoul de administrare (vezi mai jos) și copiază-l undeva în siguranță.
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"Citește JSON-ul. Un răspuns reușit are
"status": trueși datele tale subdata. 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
- Deschide Setup → REST API tokens (
/admin/tsync_api_tokens). Poți și apăsa Ctrl/⌘+K și căuta „API”. - Introdu o etichetă (de ex. „Mobile App”, „Integrare Zapier”) ca să-l poți recunoaște mai târziu.
- 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.
- 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
- Automatizări — reacționează la modificările de date fără să scrii cod
- Primii pași cu TSync — prezentarea generală a produsului
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.