REST API

TSync REST API позволяет вашему собственному коду взаимодействовать с TSync. С его помощью вы можете загружать данные в другую систему, создавать собственную панель или мобильное приложение, синхронизировать две системы или подключать TSync к инструментам автоматизации — всё это по обычному HTTPS с JSON.

Если вы уже работали с любым современным веб-API, это покажется знакомым: вы получаете токен, отправляете его с каждым запросом и получаете чистый JSON в ответ. Эта страница проведёт вас по основам, а затем перечислит каждый эндпоинт с примерами запросов и ответов.

Базовый URL: https://your-domain.com/api/v1

Каждый путь ниже указан относительно этого базового. Так что /invoices на самом деле означает https://your-domain.com/api/v1/invoices.


Начало работы за три шага

  1. Создайте токен в админ-панели (см. ниже) и скопируйте его в надёжное место.

  2. Отправьте его в запросе. Самая быстрая проверка — список ваших счетов:

    curl -s -H "Authorization: Bearer YOUR_TOKEN" \
      "https://your-domain.com/api/v1/invoices?per_page=5"
    
  3. Прочитайте JSON. Успешный ответ содержит "status": true и ваши данные в поле data. Вот и весь цикл — всё остальное это просто другие эндпоинты и параметры.


Аутентификация

Каждый запрос должен нести Bearer-токен в заголовке Authorization.

Генерация токена

  1. Откройте Setup → REST API tokens (/admin/tsync_api_tokens). Можно также нажать Ctrl/⌘+K и найти «API».
  2. Введите метку (например, «Mobile App», «Интеграция Zapier»), чтобы потом узнать его.
  3. Выберите сотрудника, от имени которого действует токен. Токен наследует права этого человека — он может делать ровно то, что может он, не больше.
  4. Нажмите Создать токен. Токен показывается один раз — скопируйте и сохраните его надёжно. Снова увидеть его не получится (хранится только хеш).

Использование токена

Включайте его в каждый запрос:

Authorization: Bearer your-api-token-here

Поскольку токен наследует права своего сотрудника, доступ совпадает с админ-интерфейсом. Если этот человек не может просматривать счета в TSync, то и токен не может обратиться к /invoices — он получит 403.

Отзыв токена

Перейдите в Setup → REST API tokens, найдите токен в списке (метка, владелец, последнее использование, срок, статус) и нажмите Отозвать. Он перестаёт работать немедленно.


Ограничение частоты запросов (rate limiting)

  • 100 запросов в минуту на токен.
  • Каждый ответ включает заголовки, чтобы вы могли регулировать темп:
Заголовок Значение
X-RateLimit-Limit Максимум запросов за окно (например, 100)
X-RateLimit-Remaining Сколько запросов осталось в текущем окне
X-RateLimit-Reset Unix-метка времени сброса окна

Превысите лимит — и получите 429 Too Many Requests. Тело ответа сообщает, сколько ждать:

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

Простой и вежливый клиент следит за X-RateLimit-Remaining и сбавляет темп либо повторяет попытку через retry_after секунд, когда видит 429.


Формат ответа

Каждый ответ использует одну и ту же структуру JSON, так что обрабатывать их все можно одинаково.

Отдельный объект возвращается в поле data:

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

Список возвращается как массив в поле data, с блоком meta для постраничной навигации:

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

Ошибка всегда содержит "status": false, короткий код error, по которому можно ветвить логику, и понятное человеку сообщение message:

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

Совет: сначала проверяйте status. Если он true, читайте data; если false — читайте error и message.


Постраничная навигация (pagination)

Каждый эндпоинт-список поддерживает постраничную навигацию:

Параметр По умолчанию Значение
page 1 Номер страницы (с 1)
per_page 25 Элементов на странице (макс. 100)

Используйте значение total_pages в meta, чтобы понять, когда остановиться.

Пример: GET /api/v1/invoices?page=2&per_page=50


Фильтрация и сортировка

Эндпоинты-списки принимают параметры запроса для сужения и упорядочивания результатов:

Параметр Применяется к Значение
search Ко всем Текстовый поиск по ключевым полям
status Счета, Лиды Фильтр по статусу (например, paid, unpaid, overdue)
date_from Ко всему с датами Начальная дата (YYYY-MM-DD)
date_to Ко всему с датами Конечная дата (YYYY-MM-DD)
client_id Счета, Платежи Фильтр по клиенту
assigned Лиды Фильтр по ID назначенного сотрудника
sort Ко всем Поле для сортировки (например, date, total, name)
order Ко всем asc или desc

Пример: GET /api/v1/invoices?status=unpaid&date_from=2026-01-01&sort=total&order=desc


Эндпоинты

API охватывает ваши основные бизнес-записи. Ниже каждый из них с параметрами и примером ответа.

Счета (Invoices)

Список счетов

GET /api/v1/invoices

Параметры запроса: status, client_id, date_from, date_to, search, page, per_page

Ответ:

{
  "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 /api/v1/invoices/:id

Возвращает счёт целиком, включая строки и любые зафиксированные платежи.

{
  "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)

Список клиентов

GET /api/v1/clients

Параметры запроса: search, page, per_page

Ответ:

{
  "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 /api/v1/clients/:id

Возвращает полные данные клиента, включая контакты, заметки и сводную статистику.


Лиды (Leads)

Список лидов

GET /api/v1/leads

Параметры запроса: status, source, assigned, search, date_from, date_to, page, per_page

Ответ:

{
  "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 }
}

Создать лид

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

Тело запроса:

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

Ответ (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)

Список товаров на складе

GET /api/v1/stock

Параметры запроса: search, warehouse_id, below_reorder (boolean), page, per_page

Ответ:

{
  "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 }
}

Движения товаров

GET /api/v1/stock/movements

Параметры запроса: item_id, warehouse_id, type (in/out/transfer), date_from, date_to, page, per_page

Ответ:

{
  "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)

Список платежей

GET /api/v1/payments

Параметры запроса: client_id, invoice_id, date_from, date_to, page, per_page

Ответ:

{
  "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)

Список расходов

GET /api/v1/expenses

Параметры запроса: category, date_from, date_to, billable (boolean), search, page, per_page

Ответ:

{
  "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)

Список сотрудников

GET /api/v1/staff

Параметры запроса: search, role, active (boolean), page, per_page

Ответ:

{
  "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 }
}

Собственный пользователь токена

GET /api/v1/staff/me

Удобно для проверки того, что токен работает, и для просмотра того, что ему разрешено. Возвращает сотрудника, стоящего за токеном, его права и текущий статус ограничения частоты запросов.

{
  "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)

Список наборов данных

GET /api/v1/analytics/datasets

Перечисляет встроенные наборы данных аналитики, которые можно запустить. У каждого есть стабильный строковый id, читаемый label и краткое description.

Ответ:

{
  "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

Запускает один из наборов данных выше (:dataset_id — это id из списка) и возвращает вычисленные строки. Каждый набор проверяет соответствующее право (например, revenue_monthly требует право на просмотр счетов).

{
  "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

Ещё несколько эндпоинтов на чтение дополняют API. Они подчиняются тем же правилам envelope, токена и постраничной навигации, что и описанные выше.

Эндпоинт Метод Возвращает
/api/v1/status GET Состояние сервиса и личность, стоящая за токеном (также ответ для GET /api/v1).
/api/v1/items GET Позиции каталога (список). search, page, per_page.
/api/v1/items/:id GET Одна позиция каталога.
/api/v1/treasury/places GET Места хранения денег — только чтение, в пределах компаний токена.
/api/v1/bank/accounts GET Банковские счета — только чтение, в пределах компаний токена.

Некоторые ресурсы также принимают запись, когда у сотрудника токена есть нужное право: POST /api/v1/invoices, POST /api/v1/clients, а также GET /api/v1/leads/:id и PUT /api/v1/leads/:id в дополнение к эндпоинтам лидов, показанным выше.


Коды ошибок

Когда status равен false, HTTP-статус и код error сообщают, что пошло не так:

HTTP-статус Код ошибки Значение
400 bad_request Некорректный запрос или неверные параметры
401 unauthorized Отсутствует или недействителен токен
403 forbidden Токен действителен, но не имеет прав на этот ресурс
404 not_found Ресурс не существует
422 validation_error Входные данные не прошли валидацию (подробности в объекте errors)
429 rate_limit_exceeded Слишком много запросов
500 server_error На сервере произошла ошибка

Ошибка валидации точно указывает, какие поля нужно исправить:

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

Примеры кода

Одни и те же две задачи — получить список неоплаченных счетов и создать лид — на трёх языках.

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 (скоро)

Push-уведомления при изменении записей запланированы на будущий релиз. Пока же опрашивайте API с фильтром date_from, чтобы подхватывать новые или обновлённые записи с момента последней проверки.


Права доступа (Permissions)

Доступ к API использует ту же систему прав, что и админ-интерфейс. Соответствующие права:

Право Нужно, чтобы
Manage API tokens Генерировать и отзывать токены
API access Вообще пользоваться API (базовое право)
Per-record permissions Достучаться до каждого эндпоинта (например, просмотр счетов, создание лидов)

Поскольку токены наследуют роль своего сотрудника, проще всего управлять тем, что может делать токен, назначив его сотруднику с ровно нужным набором прав.


Смотрите также


Формат ответа (v5.9.17)

Каждый ответ использует единый формат:

Успех (один объект):

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

Успех (список):

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

Ошибка:

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

(ok также присутствует рядом со status для старых клиентов.) Каждый ответ содержит заголовки лимита — X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset — и каждый вызов логируется (токен, сотрудник, компания, метод, endpoint, статус, длительность, IP).

Эндпоинты

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 (только чтение) · GET /api/v1/bank/accounts (только чтение) · плюс счета, платежи, расходы, аналитика.

Каждый токен принадлежит сотруднику и соблюдает его права; данные не возвращаются между компаниями, к которым у токена нет доступа.