---
title: "JSON API — Tags for Laravel | Roundly"
description: "Voliteľné JSON API — typy, zoznamy a stromy, našepkávanie a popularita, správa štítkov s presunmi a radením, za vašimi oprávneniami, bez názvov tried."
url: https://roundly-consulting.com/sk/open-source/navody/tags-for-laravel/json-api
language: sk
---

[Všetky balíky](https://roundly-consulting.com/sk/open-source.md)

[Tags for Laravel](https://roundly-consulting.com/sk/open-source/navody/tags-for-laravel.md)

# JSON API

Routy, form requesty, controllery, resources, filtre zoznamu cez query-builder a limity požiadaviek — vypnuté, kým ich nezapnete:

```bash
TAGS_ROUTES=on
```

- Vypnuté, kým ho nezapnete: nenastavený prepínač — chýbajúci v publikovanej konfigurácii, ktorej pole routes prepisuje len niektoré kľúče, null alebo prázdny — ho nechá vypnuté.
- Prefix tags.routes.prefix (api), názvy rout tags.\*, middleware tags.routes.middleware (\['api', 'auth', 'throttle:tags-api'\]).
- {type} je názov typu; {tag} je kľúč vždy, keď zodpovedá typu kľúča, inak slug alebo názov. suggest, popular a reorder sa registrujú pred {tag}, takže štítok s jedným z týchto slugov sa adresuje kľúčom.
- Ak chcete routu zmeniť, publikujte routes/tags.php (--tag=tags-routes); publikovaná kópia nahradí súbor balíka a stále ju riadi tags.routes.enabled.
- Každý zápis ide cez manažéra, takže Tags::fake() zaznamená aj volania API.

## Aliasy, Sanctum a oprávnenia

API nikdy neprijme názov triedy: rozsahy a modely so štítkami sa pomenúvajú aliasmi, ktoré povolíte. Balík nedefinuje žiadny Gate a nedefinované oprávnenie zamieta — definujte tie, ktoré používate:

```php
// config/tags.php
'scopes' => ['team' => App\Models\Team::class],          // ?scope=team:12
'taggables' => ['concert' => App\Models\Concert::class], // popular?on=concert

'routes' => [
    'middleware' => ['api', 'auth:sanctum', 'throttle:tags-api'],
    // …
],

// AppServiceProvider::boot() — the package defines no gate; an undefined ability denies
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Gate;

Gate::define('tags.manage', fn (User $user, string $type, ?Model $scope = null) => $scope === null
    ? $user->is_admin                                     // global tags: admins only
    : $user->team_id === $scope->getKey() && $user->is_team_admin);
Gate::define('tags.scope', fn (User $user, Model $scope) => $user->team_id === $scope->getKey());
```

- manage (tags.manage) sa pýta pred každým zápisom s názvom typu a cieľovým rozsahom — null pri globálnom zápise — takže správcovia tenanta môžu spravovať vlastné štítky a nikdy nie globálne. Rozhoduje aj o tom, kto vidí súkromné typy a — vždy v rozsahu daného štítku, takže globálny štítok si pýta globálnu správu bez ohľadu na rozsah, ktorý požiadavka číta — usage\_count, preklady a filter\[trashed\]; kontrola beží raz na typ a rozsah v rámci požiadavky.
- scope (tags.scope) sa pýta s modelom rozsahu vždy, keď požiadavka pošle ?scope=. Zamietnutý rozsah odpovie 422 unknown\_alias presne ako rozsah, ktorý neexistuje, takže odkaz neprezradí id žiadneho tenanta.
- view (predvolene null) sa pýta pred čítaním typu a jeho štítkov s názvom typu a rozsahom; bez neho stačí na čítanie middleware routy.
- is\_admin, is\_team\_admin a team\_id vyššie zastupujú vaše vlastné stĺpce či kontroly rolí; funguje aj balík oprávnení, ktorý ich udeľuje cez Gate::before.

## Routy

| Metóda a cesta | Názov | Oprávnenie | Poznámky |
| --- | --- | --- | --- |
| `GET tag-types` | `tags.types.index` | — | Typy, ktoré volajúci smie vidieť — súkromné len pre ich správcov. Pri zozname typov sa view nepýta. |
| `GET tag-types/{type}` | `tags.types.show` | view | Jeden typ. |
| `GET tags/{type}` | `tags.index` | view | Zoznam — filtre, radenie, stránkovanie — alebo ?tree=1 pre vnorené uzly. |
| `POST tags/{type}` | `tags.store` | manage | Vytvorí v rozsahu požiadavky; 201. |
| `GET tags/{type}/suggest` | `tags.suggest` | view | ?q= (1–64 znakov) a limit; navyše throttle:tags-suggest. |
| `GET tags/{type}/popular` | `tags.popular` | view | ?limit a on=alias z tags.taggables. |
| `POST tags/{type}/reorder` | `tags.reorder` | manage | {parent, order} |
| `GET tags/{type}/{tag}` | `tags.show` | view | Jeden štítok s predkami. |
| `PATCH tags/{type}/{tag}` | `tags.update` | manage | Zmenia sa len poslané polia. |
| `DELETE tags/{type}/{tag}` | `tags.destroy` | manage | ?children=refuse (predvolené), reparent alebo delete; vráti zmazaný štítok. |
| `POST tags/{type}/{tag}/move` | `tags.move` | manage | {parent: key\|null, position} |
| `POST tags/{type}/{tag}/restore` | `tags.restore` | manage | Podľa kľúča alebo podľa slugu či názvu zmazaného štítku presne z rozsahu požiadavky. |

```http
GET    /api/tag-types
GET    /api/tags/genre?tree=1
GET    /api/tags/label?scope=team:12&filter[search]=pri&sort=-usage_count&per_page=50
GET    /api/tags/label/suggest?q=liv&limit=5
GET    /api/tags/label/popular?on=concert&limit=10
GET    /api/tags/genre/jazz
POST   /api/tags/genre                       {"name": {"en": "Bebop"}, "parent": "2", "color": "#f24e29"}
PATCH  /api/tags/genre/bebop                 {"name": {"en": "Bebop jazz"}, "description": {"sk": null}, "color": null}
POST   /api/tags/genre/bebop/move            {"parent": null, "position": 0}
POST   /api/tags/genre/reorder               {"parent": "1", "order": ["4", "2", "3"]}
DELETE /api/tags/genre/music?children=reparent
POST   /api/tags/genre/rock/restore
```

## Telá požiadaviek

| Endpoint | Telo / query |
| --- | --- |
| `POST tags/{type}` | name (mapa jazykov, aspoň jeden jazyk, každý ≤ names.max\_length), description (mapa jazykov), slug (mapa jazykov — len kurátorované typy), color, icon, parent (kľúč toho istého typu presne v rozsahu požiadavky; null žiada koreň), position (0–2147483647, číslo alebo reťazec číslic). Názov, ktorý drží zmazaný štítok, tento štítok vráti s atribútmi z tela — tam, kde bol, pokiaľ telo nemá kľúč parent. |
| `PATCH tags/{type}/{tag}` | name, description (null vymaže všetky jazyky; null alebo prázdna položka vymaže daný jazyk), slug, color (null vymaže), icon (null vymaže) — zmení sa len poslané; mapy sa zlúčia po jazykoch. |
| `POST …/{tag}/move` | parent — povinný, kľúč alebo null pre koreň; position ako pri vytvorení. |
| `POST tags/{type}/reorder` | parent (kľúč alebo null pre korene), order — povinné, pole s najviac 1000 kľúčmi. |
| `DELETE tags/{type}/{tag}` | ?children=refuse\|reparent\|delete |
| každý endpoint | ?scope=alias:kľúč (alias z tags.scopes — čítanie vidí rozsah a globálne štítky, zápisy idú do rozsahu) a ?scope\_only=1 (čítanie vidí len štítky rozsahu). |

## Zoznam štítkov

| Parameter | Význam |
| --- | --- |
| `filter[search]` | Názvy, ktoré obsahujú výraz (escapovaný), v ktoromkoľvek podporovanom jazyku. Vždy jeden výraz — čiarka zostáva jeho súčasťou; prázdny výraz nefiltruje nič. |
| `filter[parent]` | Kľúč rodiča alebo root. |
| `filter[depth]` | Číslo úrovne. |
| `filter[ids]` | Kľúče oddelené čiarkou; chybné sa preskočia. |
| `filter[slug]` | Slugy oddelené čiarkou, v ktoromkoľvek jazyku reťaze. |
| `filter[trashed]` | Len pre správcov (pre ostatných neznámy filter, 400): with alebo only — a len zmazané štítky rozsahov, ktoré volajúci spravuje, takže správca tímu, ktorý nesmie spravovať globálne štítky, zmazané globálne štítky nikdy neuvidí. |
| `sort` | position (predvolené), name (v aktuálnom jazyku), usage\_count, created\_at; - pre zostupne, oddelené čiarkou. |
| `per_page, page` | Najviac tags.routes.max\_per\_page; predvolene 20. |
| `tree=1` | Vnorené uzly hierarchického typu (pri plochom type prázdny zoznam); viac než max\_tree\_nodes odpovie 422 tree\_too\_large. |
| `include=translations` | Všetky jazyky name, description a slug — ak volajúci spravuje typ v rozsahu požiadavky, a len pri štítkoch, ktorých rozsah spravuje tiež. |

Každý zoznam — index, suggest, popular aj reorder — načíta vlastníkov rozsahov svojich štítkov jedným dotazom, takže stránka stojí rovnaký počet dotazov bez ohľadu na jej veľkosť.

## Resources

| Resource | Každý, kto smie čítať | Správcovia navyše |
| --- | --- | --- |
| `TagResource` | id, type (názov typu), name, slug a description v aktuálnom jazyku, color, icon, parent\_id, depth, position, scope (cez tags.resources.scope; pri globálnom štítku null), ancestors (ak sú načítané — endpoint show ich načíta), deleted\_at (ak je zmazaný), created\_at, updated\_at. | usage\_count — ktorý pri globálnom štítku sčíta priradenia všetkých tenantov — a translations s ?include=translations, pre správcov rozsahu daného štítku: globálny štítok si pýta globálnu správu bez ohľadu na rozsah, ktorý požiadavka číta. |
| `TagSummaryResource` | id, type, name, slug, color, icon, depth — plus ancestors, ak sú načítané. Bez počtu použití, rozsahu, rodiča a časových pečiatok, takže bez Gate kontroly, vzťahu aj dotazu. | — |
| `TagNodeResource` | Súhrn plus parent\_id, position a children. | — |
| `TagTypeResource` | name, label (preložený pri vykreslení), hierarchical, max\_depth, max, curated. | — |
| `TagScopeResource` | {id, type} — kľúč a krátky názov triedy vlastníka (Team), nikdy celý názov triedy. | — |

GET /api/tags/genre/jazz pre čitateľa:

```json
{
  "data": {
    "id": 2,
    "type": "genre",
    "name": "Jazz",
    "slug": "jazz",
    "description": null,
    "color": null,
    "icon": "sax",
    "parent_id": 1,
    "depth": 2,
    "position": 0,
    "scope": null,
    "ancestors": [
      { "id": 1, "type": "genre", "name": "Music", "slug": "music", "color": "#f24e29", "icon": null, "depth": 1 }
    ],
    "created_at": "2026-10-19T09:30:00+00:00",
    "updated_at": "2026-10-19T09:30:00+00:00"
  }
}
```

Označenie tímu v zozname pre správcu tohto tímu s ?include=translations — vlastník sa vykreslí cez TagScopeResource ako kľúč a krátky názov triedy, nikdy nie celý názov triedy, a scope\_type ani scope\_key server nikdy neopustia:

```json
{
  "id": 31,
  "type": "label",
  "name": "Printer",
  "slug": "printer",
  "description": "Office printers.",
  "color": null,
  "icon": null,
  "parent_id": null,
  "depth": 1,
  "position": 0,
  "usage_count": 14,
  "scope": { "id": 12, "type": "Team" },
  "translations": {
    "name": { "en": "Printer", "sk": "Tlačiareň" },
    "description": { "en": "Office printers.", "sk": "Kancelárske tlačiarne." },
    "slug": { "en": "printer", "sk": "tlaciaren" }
  },
  "created_at": "2026-10-19T09:30:00+00:00",
  "updated_at": "2026-10-19T09:30:00+00:00"
}
```

GET /api/tag-types:

```json
{
  "data": [
    { "name": "genre", "label": "Genre", "hierarchical": true, "max_depth": 5, "max": 1, "curated": true },
    { "name": "label", "label": "Label", "hierarchical": false, "max_depth": null, "max": 5, "curated": false }
  ]
}
```

GET /api/tags/genre?tree=1:

```json
{
  "data": [
    {
      "id": 1, "type": "genre", "name": "Music", "slug": "music", "color": "#f24e29", "icon": null, "depth": 1,
      "parent_id": null, "position": 0,
      "children": [
        {
          "id": 2, "type": "genre", "name": "Jazz", "slug": "jazz", "color": null, "icon": "sax", "depth": 2,
          "parent_id": 1, "position": 0,
          "children": []
        }
      ]
    }
  ]
}
```

## Vlastný tvar vlastníka

Nastavte tags.resources.scope na vlastný JsonResource, ak chcete vykresliť meno alebo čokoľvek iné, čo vaše API potrebuje; dostane model rozsahu:

```php
// config/tags.php
'resources' => [
    'scope' => App\Http\Resources\TagOwnerResource::class,
],

// app/Http/Resources/TagOwnerResource.php — receives the scope model
final class TagOwnerResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->resource->getKey(),
            'name' => $this->resource->name,
        ];
    }
}
```

## Súkromné typy

Typ deklarovaný cez private() je neviditeľný pre každého, kto ho nesmie spravovať: chýba v /tag-types a každý endpoint pod ním odpovie 404 unknown\_tag\_type, presne ako typ, ktorý neexistuje.

## Limity požiadaviek

Definujú sa len pri zapnutých routách: tags-api povolí tags.routes.rate\_limit (120) požiadaviek za minútu na prihláseného používateľa, inak na IP; endpoint na našepkávanie pridáva tags-suggest s tags.routes.suggest\_rate\_limit (120).

## Chyby

Chyby balíka sa mapujú v jeho vlastných controlleroch — nikdy vo vašom exception handleri — na {message, code} v jazyku požiadavky. Segment cesty, ktorý neoznačuje nič, čo volajúci smie vidieť, je 404, odkaz v tele 422 a štítok iného rozsahu odpovie presne ako chýbajúci:

```json
{ "message": "Unknown tag type [nope].", "code": "unknown_tag_type" }
{ "message": "A tag with the slug [jazz] in [en] already exists.", "code": "duplicate_tag" }
```

| Stav | code | Kedy |
| --- | --- | --- |
| 404 | `unknown_tag_type` | Segment {type} neoznačuje žiadny typ, ktorý volajúci smie vidieť — súkromný typ je pre nesprávcov neznámy. |
| 404 | `tag_not_found` | Segment {tag} neoznačuje žiadny štítok v čitateľných rozsahoch (pri zápisoch presne v rozsahu požiadavky). Štítok iného rozsahu odpovie úplne rovnako. |
| 422 | `tag_not_found` | Kľúč parent alebo order v tele, ktorý chýba alebo je iného typu či rozsahu. |
| 422 | `unknown_alias` | Alias ?scope= či on=, ktorý povolené zoznamy nepoznajú, názov triedy, chybný kľúč, kľúč bez záznamu — alebo rozsah, ktorý volajúci nesmie použiť: zamietnuté oprávnenie scope odpovie úplne rovnako. |
| 422 | `invalid_tag, invalid_hierarchy, tag_type_mismatch` | Odmietnutý atribút (farba, slug, jazyk…); cyklus, príliš hlboký strom či zmazaný rodič; štítok iného typu. |
| 422 | `tree_too_large` | ?tree=1 nad tags.routes.max\_tree\_nodes. |
| 409 | `duplicate_tag, tag_has_children` | Slug, ktorý drží iný štítok typu a rozsahu (aj zmazaný); zmazanie, ktoré by osirelo živé deti. |
| 403 | — | Gate manage alebo view zamietol — nedefinované oprávnenie zamieta. |
| 400 | — | Neznámy filter či radenie pri predvolenom mode.unknown\_filter / unknown\_sort = reject v query-builder; ignore ho namiesto toho zahodí. |
| 422 / 429 | — | Validačné chyby Laravelu (názvy polí z tags::validation.attributes); limitery požiadaviek. |

## 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 kryptomien](https://roundly-consulting.com/sk/podporte-nas.md)

Odoslaním daru súhlasíte s našimi [podmienkami prijímania darov](https://roundly-consulting.com/sk/podmienky-darov.md).

[Podporte našu open-source prácu (otvorí sa v novej karte)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Pridajte sa k nám na Patreone (otvorí sa v novej karte)](https://www.patreon.com/cw/roundly)

## 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.

[Ponuka do 48 hodín](https://roundly-consulting.com/sk/kontakt.md) [Prezrieť všetky balíky](https://roundly-consulting.com/sk/open-source/navody/tags-for-laravel.md)
