The labeling and classification engine for Quinyx WFM — attach metadata to shifts, tasks, absences, and punches
The folders that organize tags. Each customer account defines their own categories.
The actual labels within a category. Each tag belongs to exactly one category.
The glue that links tags to schedule items — shifts, tasks, absences, and punches.
Think of it like this: A Tag Category is a drawer (e.g. "Cost Centers"). A Tag is a sticker in that drawer (e.g. "Marketing Dept"). A Tag Connection is the act of sticking that sticker onto a shift, task, or punch on the schedule.
Key relationships: Categories are mapped to domains via tag_categories_mapping. Tags are mapped to groups (units) via tags_mapping. Connections link tags to schedule items. The mapping tables enable multi-tenancy — one tag definition can be shared across groups in the hierarchy.
Managers create & edit tags and categories through the web application.
External APIAttaches tags to shifts & tasks during scheduling. Reads tag info for display.
Internal APICreate, move, & delete tag connections asynchronously via event commands.
Kafka EventsPayroll & ERP systems sync tags using external IDs instead of Quinyx IDs.
Integration API| Method | Endpoint | Description |
|---|---|---|
| GET | /external/groups/{groupId}/tags | List all tags for a group (paginated, filterable by name) |
| GET | /external/groups/{groupId}/tags/{tagId} | Get a specific tag |
| POST | /external/groups/{groupId}/tags | Create a new tag (unit or domain only) |
| PUT | /external/groups/{groupId}/tags/{tagId} | Update an existing tag |
| DELETE | /external/groups/{groupId}/tags/{tagId} | Delete a tag |
| GET | /external/accounts/{groupId}/categories | List all tag categories for an account |
| POST | /external/accounts/{groupId}/categories | Create a tag category (domain-level only) |
| PUT | /external/accounts/{groupId}/categories/{id} | Update a tag category |
| DELETE | /external/accounts/{groupId}/categories/{id} | Delete a tag category |
| GET | /external/groups/{groupId}/tag-info/by-id | Get tag info by IDs (max 1000) |
| GET | /external/groups/{groupId}/tag-info/search | Search tags by name (paginated) |
| Method | Endpoint | Description |
|---|---|---|
| POST | /internal/groups/{groupId}/tag-connections/types/{type}/connection-ids/{id} | Replace tag connections (upsert pattern) |
| GET | /internal/groups/{groupId}/tag-connections/types/{type}/connection-ids/{id} | Get connections for a specific item |
| POST | /internal/groups/{groupId}/tag-connections/types/{type}/lookup | Bulk lookup connections (streaming response) |
| DELETE | /internal/groups/{groupId}/tag-connections/types/{type}/connection-ids/{id} | Delete connections for an item |
| POST | /internal/groups/{groupId}/tag-connections/copy | Copy tag connections |
| POST | /internal/groups/{groupId}/tag-connections/move | Move tag connections |
| Method | Endpoint | Description |
|---|---|---|
| GET | /integration/categories | List all tag categories |
| GET | /integration/categories/{extId} | Get a category by external ID |
| GET | /integration/v2/categories/{extId}/tags | List tags in category (cursor-paginated) |
| GET | /integration/categories/{catExtId}/tags/{tagExtId} | Get a tag by external IDs |
| POST | /integration/categories/{extId}/tags | Create a tag via external IDs |
| PUT | /integration/categories/{catExtId}/tags/{tagExtId} | Update a tag via external IDs |
| DELETE | /integration/categories/{catExtId}/tags/{tagExtId} | Delete a tag via external IDs |
Events consumed by the Tags service
When event processing fails
Key design choice: Missing group or unit IDs in a command are treated as malformed and throw an exception — they don't silently fail.
Tag categories can only be created, updated, and deleted at the domain (account) level. Individual stores/units cannot define their own category types.
Tags can be created at unit or domain level — not at section level. The API returns 400 Bad Request if attempted at a section.
When items are deleted, tag connections change type to *_DELETED variants (e.g. SHIFT → SHIFT_DELETED) — preserving history.
Tags with uniqueScheduling = true prevent multiple shifts from using the same tag simultaneously — enforcing resource exclusivity.
Tags can have start and end dates. Time-limited tags are used for seasonal projects, temporary cost centers, or campaign tracking.
Querying tags for a group returns tags from the group itself AND all parents up to the domain. Tags flow downward through inheritance.
When updating tag connections for an item, the system deletes all existing connections and creates new ones — it's a full replace, not an append.
Tag categories must be unique per domain. Validation also enforces field length limits on name, code, external ID, and information fields.
| Area | Can Add Tags? | Can View Tags? | How It Works |
|---|---|---|---|
|
Shift Types
|
YES | YES | Managers pick tags when editing a shift type. This is the main way tags get onto the schedule. |
|
Shifts on Schedule
|
NO | YES | Shift detail panel shows tags, but they're inherited from the shift type — can't add per-shift tags from the UI. |
|
Time Punches
|
NO | YES | Punch detail panel shows tags when present — display only, inherited from associated shift. |
|
Absences (Timecard)
|
NO | YES | Absence rows in the timecard show tags — display only. |
|
Tasks (within shifts)
|
NO | NO | The backend supports tagging individual tasks, but no UI exists for it. |
|
Base Schedule
|
NO | NO | Backend supports base schedule tags, but no UI exists for viewing or adding them. |
|
Reports
|
— | YES | Tags Follow-Up report — compare budgeted vs. assigned vs. punched hours per tag over a date range. |
Full tag management + viewing on schedule, punches, absences. Tags Follow-Up report.
Tags shown on shifts, tasks, and stories. Tag coordinates mapped to locations. No tag editing.
Feature flag exists (mobile_time_tag) but no tag components built. Employees can't see tags.
The backend supports 12 connection types for tagging items, but the UI only lets managers assign tags through one path: Shift Types. Everything else is either view-only or invisible. These are areas where new UI could unlock value without any backend work.
Today: shifts inherit tags from their shift type. Managers can't override or add tags to a specific shift. The backend already supports it.
The backend supports tags on individual tasks within shifts, but there's no UI anywhere to add, view, or manage task tags.
Tags can be attached to base schedule shifts and tasks in the backend, but the base schedule UI doesn't show or edit tags at all.
A feature flag (mobile_time_tag) exists in the employee portal, but no UI was built. Employees currently can't see tags on their shifts.
Punches display inherited tags but managers can't assign tags directly to punches. The backend supports PUNCH connections.
Absence shifts in the timecard show tags (read-only). The backend supports both SHIFT_ABSENCE and SHIFT_ABSENCE_TASK connections — no editing UI.