Nodea — logo

Zgłoszenia (tickets) — API v1 i MCP | Dokumentacja Nodea

Moduł Zgłoszenia (tickets) obejmuje portal wsparcia klienta: listowanie własnych zgłoszeń, podgląd konwersacji, otwieranie nowego zgłoszenia, dodawanie odpowiedzi i zamykanie. Dokument opisuje warstwę REST API v1 (/api/v1/tickets) oraz narzędzia MCP z tego modułu.

Uwierzytelnianie i zakresy (scopes)

API v1 uwierzytelnia się osobistym tokenem Passport przez nagłówek Authorization: Bearer <token>.

  • account:read — listowanie i podgląd (index, show).
  • account:write — tworzenie, odpowiedź i zamknięcie (store, reply, close).

Cały moduł jest dodatkowo bramkowany flagą Pennant features:hosting-suite — odpowiada web'owym grupom /app/account/*. Limit zapytań: throttle:api-v1 = 240/min na użytkownika tokenu.

Własność i widoczność (ownership)

Zgłoszenia nie mają modelu współdzielenia ani klasy polityki — dostęp na poziomie wiersza jest wymuszany inline (ensureOwned): tylko właściciel widzi i działa na swoim zgłoszeniu. Cudzy lub nieznany identyfikator zwraca 404 (nie 403), by API nie potwierdzało istnienia zasobu innego najemcy.

Notatki wewnętrzne — niewidoczne

Konwersacja jest zawsze ładowana przez scope public() na TicketReply, więc wewnętrzne notatki administratorów nigdy nie trafiają do odpowiedzi — to wymóg bezpieczeństwa, nie optymalizacja. Klient nigdy nie może utworzyć notatki wewnętrznej: pole is_internal_note jest twardo ustawiane na false przy każdej odpowiedzi tworzonej przez API. Załączniki nie są obsługiwane przez API (brak uploadu i URL-i pobierania) — eksponowane są wyłącznie metadane załączników odpowiedzi (id, original_name, size, mime).

API v1 — endpointy

MetodaŚcieżkaScopeOpis
GET/api/v1/ticketsaccount:readLista własnych zgłoszeń, najnowsza aktywność pierwsza (last_reply_at DESC, id DESC). Filtr status. Paginacja simple.
GET/api/v1/tickets/{ticket}account:readSzczegóły zgłoszenia z pełną konwersacją (publiczne odpowiedzi w porządku chronologicznym, bez notatek wewnętrznych).
POST/api/v1/ticketsaccount:writeOtwarcie nowego zgłoszenia (status open); treść staje się pierwszą odpowiedzią. Zwraca 201.
POST/api/v1/tickets/{ticket}/replyaccount:writeDodanie odpowiedzi klienta. Zwraca 201 (TicketReplyResource).
POST/api/v1/tickets/{ticket}/closeaccount:writeZamknięcie zgłoszenia przez klienta. Zwraca zaktualizowane zgłoszenie.

GET /tickets — parametry zapytania

  • status (enum, opcjonalny) — dokładne dopasowanie: open, pending, answered, closed, spam.
  • per_page (int 1–100, domyślnie 20), page (int ≥1).

Odpowiedź: kolekcja TicketResource — pola: id, number (T-YYYY-NNNNNN), subject, status, priority, department (id/name/slug), last_reply_at, last_reply_by_admin, is_closed, closed_at, created_at, updated_at. Klucz replies[] pojawia się tylko w szczegółach (gdy kontroler doładował relację). Każda odpowiedź: id, body, source, is_by_owner, author (id/name), attachments[] (metadane), created_at.

POST /tickets — parametry ciała

  • subject (string, wymagany, min 5, max 255).
  • body (string, wymagany, min 10) — persystowany jako pierwsza publiczna odpowiedź.
  • ticket_department_id (wymagany, musi istnieć w ticket_departments z is_active = true).
  • priority (enum, opcjonalny: low|normal|high|urgent; domyślnie normal).

Tworzenie jest atomowe (transakcja): tymczasowy numer spełnia unikalny indeks, następnie generateNumber() wylicza realny T-YYYY-NNNNNN, a treść zapisywana jest jako pierwsza odpowiedź (source = api). Skrzynka działu jest powiadamiana jako anonimowy notifiable. Załączniki nie są przyjmowane przez API.

POST /reply oraz /close — reguły biznesowe

  • reply: ciało body (string, wymagany, min 1). Odpowiedź klienta przełącza status answeredopen (pozostałe stany zachowane).
  • Zgłoszenie closed odrzuca odpowiedź: 422, kod ticket_closed.
  • Zgłoszenie spam odrzuca odpowiedź: 422, kod ticket_spam.
  • close: zamknięcie już zamkniętego to 422, kod already_closed; zgłoszenia spam klient nie może zamknąć: 422, kod ticket_spam. Sukces stempluje closed_at i closed_by_id.

Kody błędów

  • 401 — brak/niepoprawny token.
  • 403 — brak wymaganego scope lub nieaktywna flaga hosting-suite.
  • 404 — nieznane lub cudze zgłoszenie (ownership inline).
  • 422 — walidacja lub reguła biznesowa (kody: ticket_closed, ticket_spam, already_closed).

Przykłady — API

Lista zgłoszeń z filtrem statusu

curl -s "https://app.nodea.io/api/v1/tickets?status=open&per_page=20" \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Accept: application/json"

Otwarcie nowego zgłoszenia

curl -s -X POST https://app.nodea.io/api/v1/tickets \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ticket_department_id": 1,
    "subject": "Problem z DNS",
    "body": "Rekord A nie propaguje się od godziny.",
    "priority": "high"
  }'

Odpowiedź na zgłoszenie

curl -s -X POST https://app.nodea.io/api/v1/tickets/42/reply \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "body": "Dziękuję, już działa." }'

Zamknięcie zgłoszenia

curl -s -X POST https://app.nodea.io/api/v1/tickets/42/close \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Accept: application/json"

MCP — narzędzia

Narzędzia MCP dzielą model uwierzytelniania i zakresów z API v1 (Bearer + scope sprawdzany w ensureScope) oraz flagę features:hosting-suite — narzędzie na nieaktywnej fladze znika z tools/list i każde wywołanie kończy się błędem „Tool [...] not found.”. Identyfikatory zgłoszeń to liczby całkowite (id). Brakujący i cudzy identyfikator zwracają ten sam błąd (AuthorizationException: „Ticket not found or not accessible with your permissions.”) — brak enumeracji między najemcami.

NarzędzieScopeTrybOpis
tickets-listaccount:readread-onlyLista własnych zgłoszeń; filtr status, page, per_page (max 50, domyślnie 15). reply_count liczy tylko publiczne odpowiedzi.
ticket-getaccount:readread-onlyJedno zgłoszenie z pełną konwersacją (publiczne odpowiedzi chronologicznie, bez notatek wewnętrznych). Param: id.
ticket-createaccount:writezapisOtwarcie zgłoszenia; ticket_department_id, subject (max 255), body, opcjonalny priority. Status open, source = mcp.
ticket-replyaccount:writezapisOdpowiedź klienta; id, body. answeredopen. Zamknięte/spam odrzucają.
ticket-closeaccount:writezapisZamknięcie zgłoszenia; id. Już zamknięte lub spam zwracają błąd narzędzia.

Uwagi: tickets-list, ticket-get mają adnotacje IsReadOnly/IsIdempotent. ticket-create, ticket-reply, ticket-close są zapisujące (bez adnotacji read-only). Reguły biznesowe (zamknięte/spam, reopen z answered, twarde is_internal_note = false, powiadomienie skrzynki działu) są identyczne jak w API; przy błędzie zwracany jest Response::error zamiast HTTP 422. ticket-create i ticket-close ładują reply_count tylko z publicznych odpowiedzi.

Przykład — MCP (JSON-RPC)

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "ticket-create",
    "arguments": {
      "ticket_department_id": 1,
      "subject": "Problem z DNS",
      "body": "Rekord A nie propaguje się od godziny.",
      "priority": "high"
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "ticket-reply",
    "arguments": { "id": 42, "body": "Dziękuję, już działa." }
  }
}