JSON API
Routy, form requesty, kontroléry, resources podľa roly, filtre zoznamu cez query-builder, policy, limity požiadaviek a endpointy pre hostí — vypnuté, kým ich nezapnete:
TICKETS_ROUTES=on- Prefix tickets.routes.prefix (api), názvy rout tickets.*, middleware tickets.routes.middleware (['api', 'auth', 'throttle:tickets-api']).
- {ticket} je číslo tiketu ([A-Z][A-Z0-9]{0,9}-[0-9]+). Kontroléry hľadajú tikety cez miesto pre modely, takže API funguje bez SubstituteBindings aj s vymeneným modelom tiketu. Soft-deleted tiket je 404, okrem obnovenia.
- Ak chcete zmeniť routu, publikujte routes/tickets.php (--tag=tickets-routes); publikovaná kópia natrvalo nahradí routy balíka.
So Sanctum
// config/tickets.php
'routes' => [
'enabled' => env('TICKETS_ROUTES') ?: 'off',
'middleware' => ['api', 'auth:sanctum', 'throttle:tickets-api'],
// …
],Rovnako funguje akýkoľvek guard (auth:api, …). Bez middleware auth by každý endpoint odpovedal anonymným volajúcim — tickets:doctor upozorní.
Limity požiadaviek
Definujú sa len pri zapnutých routách a platia, len kým je ich middleware throttle: v zozname: tickets-api povolí tickets.routes.rate_limit (120) požiadaviek za minútu na prihláseného používateľa (inak na IP); tickets-guest povolí tickets.routes.guest.rate_limit (30) za minútu na IP a tiket.
Routy
| Metóda a cesta | Názov | Oprávnenie | Poznámky |
|---|---|---|---|
GET /tickets | index | viewAny | Obmedzené cez visibleTo; filtre, radenie a stránkovanie nižšie. |
POST /tickets | store | create | 201 |
POST /tickets/bulk | bulk | pre každý tiket | Hromadné akcie, nižšie. |
GET /tickets/{ticket} | show | view | Detailné: plus hodnoty polí a prílohy. |
PATCH /tickets/{ticket} | update | view + update (s dotknutými atribútmi) | — |
DELETE /tickets/{ticket} | destroy | view + delete | Soft; 204. |
POST /tickets/{ticket}/restore | restore | view + restore | — |
GET /tickets/{ticket}/transitions | transitions.index | view | Prechody, ktoré tento používateľ smie práve spustiť: name, label, to, to_label, requires_reason, payload. |
POST /tickets/{ticket}/transitions/{transition} | transitions.store | view + transition | {reason?, payload?} |
PUT /tickets/{ticket}/assignee | assignee.update | view + assign | {assignee: {type, id}} | {claim: true} |
DELETE /tickets/{ticket}/assignee | assignee.destroy | view + unassign | — |
GET /tickets/{ticket}/comments | comments.index | view (+ viewInternal pri ?internal=1) | — |
POST /tickets/{ticket}/replies | replies.store | reply (+ transition) | {body, channel?, attachments[]?, transition?, reason?, payload?}; 201 — aj keď odpoveď zadrží moderácia hostiteľa; vtedy sa žiadny prechod neuplatní. |
POST /tickets/{ticket}/notes | notes.store | view + note | {body, channel?, attachments[]?}; 201 |
PATCH /tickets/{ticket}/comments/{comment} | comments.update | view + updateComment | {body}; komentár musí patriť tomuto tiketu; zaznamená sa ako comment_edited. |
DELETE /tickets/{ticket}/comments/{comment} | comments.destroy | view + deleteComment | 204; zaznamená sa ako comment_deleted. |
GET /tickets/{ticket}/fields | fields.index | view | Filtrované podľa roly. |
PUT /tickets/{ticket}/fields | fields.update | view + update (fields) | {fields: {key: value}}; null hodnotu zruší. |
GET /tickets/{ticket}/watchers | watchers.index | view + manageWatchers | Len agenti. |
POST /tickets/{ticket}/watchers | watchers.store | view + manageWatchers | {watcher: {type, id}}; 201 |
DELETE /tickets/{ticket}/watchers/{alias:key} | watchers.destroy | view + manageWatchers | 204 |
POST / DELETE /tickets/{ticket}/watch | watch / unwatch | view + manageWatchers (pre seba) | 204 |
PUT /tickets/{ticket}/tags | tags.sync | view + tag | {tags: [names]} |
GET /tickets/{ticket}/links | links.index | view + link | — |
POST /tickets/{ticket}/links | links.store | view + link na oboch tiketoch | {ticket: reference, type}; 201 |
DELETE /tickets/{ticket}/links/{link} | links.destroy | view + link | 204; odstráni presne tento riadok, z ktoréhokoľvek konca. |
POST /tickets/{ticket}/merge | merge | view + merge na zdroji aj cieli | {target: reference, reason?} |
GET /tickets/{ticket}/attachments | attachments.index | view | Krátkodobé podpísané URL. |
POST /tickets/{ticket}/attachments | attachments.store | view + update (attachments) — agenti | Multipart file; 201. |
GET /tickets/{ticket}/attachments/{media} | attachments.show | view | — |
DELETE /tickets/{ticket}/attachments/{media} | attachments.destroy | view + update (attachments) | 204 |
GET /tickets/{ticket}/activity | activity | view | Filtrované podľa roly. |
GET / POST /ticket-categories | admin.categories.index / .store | viewAny / manageCategories | Strom; ?project=alias:key; neaktívne kategórie (aj s podstromami) len pre agentov. |
GET / PATCH / DELETE /ticket-categories/{id} | admin.categories.show / .update / .destroy | viewAny / manageCategories | Neaktívna kategória je pre neagentov 404 category_not_found; mazanie berie {reassign_to?}. |
POST /ticket-categories/{id}/move | admin.categories.move | manageCategories | {parent_id, position?} |
GET / POST /ticket-fields | admin.fields.index / .store | viewAny / manageFields | ?project=, ?category=; filtrované podľa roly. |
GET / PATCH / DELETE /ticket-fields/{id} | admin.fields.show / .update / .destroy | viewAny / manageFields | — |
GET / POST /ticket-tags | admin.tags.index / .store | viewTags (agenti) / manageTags | — |
PATCH / DELETE /ticket-tags/{id} | admin.tags.update / .destroy | manageTags | — |
GET /tickets/guest/{ticket} | guest.show | podpísaný odkaz | Pozrite Hostia a odkazy pre hostí. |
POST /tickets/guest/{ticket}/replies | guest.reply | podpísaný odkaz | — |
POST /tickets/guest/{ticket}/transitions/{transition} | guest.transition | podpísaný odkaz | Len prechody žiadateľa. |
GET /tickets/guest/{ticket}/attachments/{media} | guest.attachment | podpísaný odkaz | Súbory tiketu a jeho verejného vlákna. |
Endpoint na obnovenie kategórie neexistuje — použite Tickets::categories()->restore().
Založenie tiketu
POST /api/tickets
{
"subject": "Printer on floor 3 is jammed",
"description": "Paper jam error E-102.",
"category_id": 4,
"priority": "high",
"channel": "web",
"fields": {"asset_tag": "PRN-0042"},
"project": {"type": "project", "id": 1}
}Samoobsluha zakladá tiket pre prihláseného používateľa. subject (≤ 255), description, category_id, priority, channel, project a fields (pre neagenta len polia, ktoré smie upravovať) sú otvorené každému, kto smie zakladať. requester, guest (polia namiesto žiadateľa), assignee, due_at (po teraz), tags a watchers sú vstupy len pre agentov; riešiteľ iný ako vy vyžaduje oprávnenie assign. Polymorfné vstupy sú {type: alias, id} s aliasom z povolených zoznamov — názov triedy, neznámy alias alebo kľúč bez záznamu vráti 422. Prílohy sú multipart attachments[].
Hromadné akcie
POST /api/tickets/bulk
{"action": "transition", "tickets": ["TCK-1", "TCK-2"], "transition": "resolve", "payload": {"resolution": "Fixed"}}action je assign (assignee alebo claim: true), unassign, transition (transition, reason?, payload?), priority (priority) alebo tags (tags.attach / tags.detach, každé najviac 50 názvov) — najviac tickets.routes.bulk.max (100) rôznych čísel. Každý tiket ide vlastnou kontrolou policy aj transakciou a zlyhanie jedného nikdy nezastaví ostatné:
{"data": [
{"ticket": "TCK-1", "ok": true, "status": 200},
{"ticket": "TCK-2", "ok": false, "status": 422, "code": "ticket_transition_denied", "message": "…"}
]}Zlyhanie validácie pri jednom tikete hlási code validation_failed; každé iné zlyhanie nesie kód z tabuľky chýb (ticket_not_found pre neznáme číslo alebo tiket, ktorý používateľ vidieť nesmie). assign bez assignee a bez claim: true je 422 pre celú požiadavku.
Telá požiadaviek
| Endpoint | Telo |
|---|---|
PATCH /tickets/{ticket} | Ktorékoľvek z subject (≤ 255), description (≤ 65535, nullable), priority, category_id (nullable), due_at (po teraz, nullable), requester ({type, id}) — zmení sa len to, čo pošlete; meta hostiteľa sa cez API zapisovať nedá. |
POST …/transitions/{transition} | {reason?: string ≤ 5000, payload?: object}; {transition} zodpovedá [a-z][a-z0-9_]{0,63}. |
POST …/replies | {body: string ≤ 65535, channel?, attachments[]?, transition?, reason?, payload?} |
POST …/notes, POST /tickets/guest/{ticket}/replies | {body, channel?, attachments[]?} |
PATCH …/comments/{comment} | {body} |
GET …/comments | ?internal=1 pre interné vlákno (agenti). |
PUT …/fields | {fields: {key: value, …}} (present; null hodnotu zruší). |
POST …/watchers | {watcher: {type, id}}; DELETE …/watchers/{alias}:{key}. |
PUT …/tags | {tags: [name, …]} (present, ≤ 50, každý ≤ 64) — nahradí celú sadu. |
POST …/links | {ticket: reference, type: relates_to | duplicates | blocks | parent_of} |
POST …/merge | {target: reference, reason?: string ≤ 5000} |
POST …/attachments | Multipart file. |
GET /tickets | per_page (1 – max_per_page), page, filter[…], sort |
Uploady (attachments[], file) sa cez media-library riadia mime typmi a limitom veľkosti svojej skupiny — tickets.attachments.* pre súbory tiketu, comments.media.* z balíka comments pre súbory správ — najviac 10 súborov na požiadavku.
Admin endpointy
Preložiteľný text (name, description, label, help, popis možnosti) je reťazec v aktuálnom jazyku alebo mapa jazykov (objekt s kľúčmi en, sk, …). Polymorfné vstupy sú {type: alias, id}; filtre zoznamov berú alias:key.
| Endpoint | Telo / query |
|---|---|
GET /ticket-categories | ?project=alias:key — globálny strom plus kategórie projektu, najprv korene, potomkovia vnorení. |
POST /ticket-categories | {name, description?, parent_id?, project?: {type, id}, position?, is_active?, default_priority?, default_assignee?: {type, id}, default_due_in?: ISO-8601 (PT4H)} |
PATCH /ticket-categories/{id} | Ktorékoľvek z name, description, position, is_active, default_priority, default_assignee, default_due_in (null zruší popis alebo predvolenú hodnotu); rodič sa mení cez /move, projekt sa nemení nikdy. |
POST /ticket-categories/{id}/move | {parent_id: id | null, position?} (parent_id musí byť prítomné; null = do koreňa). |
DELETE /ticket-categories/{id} | {reassign_to?: id} — potrebné, kým kategóriu používajú živé tikety. |
GET /ticket-fields | ?project=alias:key, ?category=id (sada, ktorú by dostal tiket v tejto kategórii). Neagenti vidia len aktívne polia viditeľné pre žiadateľa. |
POST /ticket-fields | {key, type, label, help?, options?: {value: label}, rules?: [string], required?, encrypted?, default?, requester_visible?, requester_editable?, position?, is_active?, project?: {type, id}, category_id?} — najviac 200 možností a 20 pravidiel. |
PATCH /ticket-fields/{id} | Ktorékoľvek z type, label, help, options, rules, required, encrypted, default, requester_visible, requester_editable, position, is_active (key, projekt a kategória sú pevné). |
GET /ticket-tags | ?project=alias:key — globálne štítky plus štítky projektu, podľa názvu (agenti). |
POST /ticket-tags | {name: string ≤ 64, color?: #rrggbb, project?: {type, id}} — opätovné vytvorenie zmazaného názvu ho obnoví, bez tiketov. |
PATCH /ticket-tags/{id} | Ktorékoľvek z name, color (null ju zruší). |
Resources
Podľa roly: divák je agent (oprávnenie agenta a rozsah agentov mu tiket povoľujú), žiadateľ, účastník (sledujúci) alebo hosť (cez podpísaný odkaz).
| Resource | Každý, kto smie vidieť | Agenti navyše |
|---|---|---|
TicketResource | reference, number, subject, description, status, priority, category ({id, name, slug}), project ({type, id}), requester, assignee, channel, due_at, resolved_at, closed_at, merged_into (číslo tiketu), messages_count (verejné správy), created_at, updated_at; detailné odpovede pridajú fields (viditeľné pre žiadateľa) a attachments. | assigned_at, first_responded_at, last_activity_at, tags ({name, slug, color}), úplný žiadateľ, všetky správy v počte, všetky polia. |
TicketCommentResource | id, body, role, author, channel, attachments, created_at, updated_at | internal, status (stav moderácie z comments). |
TicketActivityResource | id, type, label, system, actor, properties (typy morph ako aliasy), occurred_at — len verejné záznamy. | visibility, interné záznamy, surové properties. |
TicketLinkResource | id, type, label, direction (outgoing / incoming), ticket {reference, subject, status} — len ticket {reference}, ak divák druhý tiket vidieť nesmie. | — |
TicketAttachmentResource | id (uuid), name, mime_type, size, url, created_at | — |
TicketGuestResource | {type: 'guest', name} — meno len pre samotného hosťa. | {type: 'guest', id, name, fields} — všetky polia hosťa; osobné údaje zostávajú agentom. |
TicketCategoryResource | id, name, description, slug, parent_id, project, position, is_active, children | translations {name, description} (mapy jazykov), defaults {priority, assignee, due_in}. |
TicketFieldResource | id, key, type, label, help, options ([{value, label}], aktuálny jazyk), required, category_id, project, position. | translations {label, help}, rules, default, encrypted, requester_visible, requester_editable, is_active |
TicketTagResource | id, name, slug, color, project (dostanú sa k nemu len agenti). | — |
Osoby (žiadateľ, riešiteľ, autor, aktér, sledujúci) sa zobrazujú svojím aliasom z povolených zoznamov, nikdy názvom triedy: agenti dostanú {type, id, name}; ostatní {type, name}, pričom name je null pre hosťa, ktorý nie je samotným divákom. Žiadatelia nikdy neuvidia interné poznámky, zadržané odpovede, interné aktivity, last_activity_at, štítky ani zoznamy sledujúcich a meta hostiteľa sa neobjaví v žiadnom resource. Hodnoty enumov prichádzajú s preloženým popisom a farbou, takže klient vykreslí odznak bez vlastných tabuliek:
"status": {"value": "pending", "label": "Waiting on requester", "category": "waiting", "color": "amber"},
"priority": {"value": "high", "label": "High", "weight": 30, "color": "orange"},
"channel": {"value": "web", "label": "Web", "color": "blue"}Telá odpovedí
| Endpoint | Telo |
|---|---|
GET /tickets | Stránkovaná kolekcia TicketResource (data, links, meta). |
POST /tickets (201), GET / PATCH /tickets/{ticket}, POST …/restore, POST …/transitions/{transition}, PUT / DELETE …/assignee | Detailný TicketResource. |
GET …/transitions | {data: [{name, label, to, to_label, requires_reason, payload}]} |
GET …/comments | Kolekcia TicketCommentResource, od najstaršej. |
POST …/replies, POST …/notes (201), PATCH …/comments/{comment} | TicketCommentResource. |
GET / PUT …/fields | {data: [{key, label, type, value}]} — platné polia v poradí (pre neagentov len viditeľné pre žiadateľa); value je null, ak nie je nastavená. |
GET / POST …/watchers (201) | {data: [person, …]} — všetci sledujúci od najstaršieho; neagent, ktorý pridá seba, dostane len vlastný záznam. |
PUT …/tags | Štítky tiketu, kolekcia TicketTagResource. |
GET / POST …/links (201) | {data: [TicketLinkResource, …]} — všetky prepojenia z pohľadu tohto tiketu. |
POST …/merge | {source: TicketResource, target: TicketResource, moved: {comments, attachments}} |
GET …/attachments, GET …/attachments/{media}, POST …/attachments (201) | TicketAttachmentResource (kolekcia). |
GET …/activity | Kolekcia TicketActivityResource od najstaršieho ([] pri vypnutom tickets.activity.enabled). |
POST /tickets/bulk | {data: [{ticket, ok, status, code?, message?}]} |
| admin endpointy | TicketCategoryResource (strom pre index), TicketFieldResource, TicketTagResource; store vráti 201. |
| každé iné DELETE, POST / DELETE …/watch | 204, bez tela. |
Zoznam tiketov — filtre, radenie, stránkovanie
GET /tickets je query-builder-for-laravel nad visibleTo($user); každý filter je jeden zo scopes modelu Ticket:
| Filter | Hodnota | Scope |
|---|---|---|
filter[status] | zoznam hodnôt stavov | withStatus |
filter[status_category] | zoznam: open, active, waiting, resolved, closed | withStatusCategory |
filter[priority] | zoznam | withPriority |
filter[category] | zoznam id; filter[category_descendants]=1 pridá podkategórie | inCategory |
filter[assignee] | zoznam alias:key, me, none | assignedTo / unassigned |
filter[requester] | zoznam alias:key (agenti; ktokoľvek iný nájde len seba), me | requestedBy |
filter[project] | alias:key, none | inProject |
filter[tag] | zoznam slugov — len agenti (pre ostatných 400) | taggedWith |
filter[watching] | me | watchedBy |
filter[overdue] | 1 (0 nepridá nič) | overdue |
filter[due_from], filter[due_to], filter[created_from], filter[created_to] | dátum alebo dátum a čas (inkluzívne; *_to len s dátumom, napríklad 2026-10-17, platí do konca dňa) | dueFrom / dueTo / createdFrom / createdTo |
filter[channel] | zoznam | viaChannel |
filter[merged] | boolean | merged |
filter[search] | text — presne číslo tiketu, alebo predmet / popis (% a _ doslova) | search |
filter[fields.<key>] | zoznam | whereField |
filter[fields.<key>.from], filter[fields.<key>.to] | polia number / decimal / date / datetime (inkluzívne) | whereFieldBetween |
filter[trashed] | with, only — len držitelia oprávnenia manage | — |
Filtre len pre agentov — tag, requester=alias:key a pole, ktoré nie je viditeľné pre žiadateľa — nájdu len tikety, na ktorých diváka nechá pracovať rozsah agentov. Neznámy filter alebo radenie vráti 400 pri query-builder.mode.unknown_filter = reject (predvolené); ignore ich zahodí.
Radenie: created_at, updated_at, due_at, number, priority (váhy enumu) a pre agentov last_activity_at. Predvolene -last_activity_at pre agentov a -created_at pre ostatných. Stránkovanie: per_page (predvolene 20, najviac tickets.routes.max_per_page) a page; zoznam načíta kategóriu, riešiteľa, žiadateľa, štítky a počty správ vopred, v pevnom počte dopytov.
Chyby
Mapujú sa v kontroléroch balíka — nikdy vo vašom exception handleri — na {message, code}. Chyby konfigurácie zostávajú 500: opraviť ich musí hostiteľ, nie klient.
| Stav | code | Kedy |
|---|---|---|
| 400 | (query-builder) | Neznámy filter alebo radenie. |
| 403 | forbidden | Zamietnutie policy; UnauthorizedCommentActionException z comments (pri zapnutom comments.authorization). |
| 403 | access_denied | Nie je účastníkom tiketu. |
| 403 | guest_link_invalid | Akýkoľvek nefunkčný odkaz pre hosťa — jedno telo pre všetky. |
| 404 | ticket_not_found | Neznáme číslo, alebo tiket, ktorý používateľ vidieť nesmie (skrytý = neexistujúci); na každom endpointe tiketu sa najprv overí view. |
| 404 | comment_not_found, attachment_not_found, link_not_found, category_not_found, field_not_found, tag_not_found | Záznam, ktorý nepatrí tomuto tiketu, alebo neznámy. |
| 409 | ticket_closed | Správa na uzavretom tikete (alebo zámok z comments). |
| 409 | ticket_trashed | Zmena zmazaného tiketu. |
| 409 | field_type_locked, duplicate_reference | Uzamknutý typ poľa; duplicitný prefix projektu. |
| 422 / 429 | ticket_transition_denied | Lifecycle prechod odmietol (s denials); 429 s Retry-After, keď zamietnutie uvádza, kedy to skúsiť znova. |
| 422 | unknown_transition, unknown_field, field_not_editable, unknown_*_type | — |
| 422 | invalid_assignee, invalid_link, invalid_merge, invalid_category, invalid_field, invalid_tag, invalid_watcher, invalid_project | — |
| 422 | comment_rejected, invalid_comment_body | comments text odmietol. |
| 422 | validačné telo Laravelu | Validácia form requestu alebo hodnôt polí — vstupy pomenované v jazyku požiadavky; polia hosťa pod guest.<name>. |
Prejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.