# Project Tags https://api-docs.lumar.io/docs/graphql/project-tags Tags are account-level labels — a name and a colour — that group the Analyze projects, Protect test suites and AI Visibility projects in an account. One pool of tags covers all three: a project can carry any number of tags, and a tag can be applied to any number of projects. Creating, changing, deleting, reordering and applying tags after project creation require the **admin** role on the account. Reading them requires no special role. ## Listing an account's tags ```graphql query GetAccountTags($accountId: ObjectID!, $after: String) { getAccount(id: $accountId) { id tags(first: 100, after: $after) { edges { node { id name color } } pageInfo { hasNextPage endCursor } } } } ``` **Variables:** ```json { "accountId": "TjAwN0FjY291bnQ3MTU", "after": null } ``` **Response:** ```json { "data": { "getAccount": { "id": "TjAwN0FjY291bnQ3MTU", "tags": { "edges": [ { "node": { "id": "TjAwOFRhZzE", "name": "Production", "color": "#4CAF50" } }, { "node": { "id": "TjAwOFRhZzI", "name": "Staging", "color": "#FF9800" } } ], "pageInfo": { "hasNextPage": false, "endCursor": "Mg" } } } } } ``` Tags come back in the order an admin arranged them (see [Display order](#display-order)) unless you pass your own `orderBy`. ## Creating a tag ```graphql mutation CreateAccountTag($input: CreateAccountTagInput!) { createAccountTag(input: $input) { accountTag { id name color position } } } ``` **Variables:** ```json { "input": { "accountId": "TjAwN0FjY291bnQ3MTU", "name": "Production", "color": "#4CAF50" } } ``` `color` must be a hex colour. A new tag is appended after the account's existing tags, so its `position` is one past the current highest. Use `updateAccountTag` to rename or recolour a tag, and `deleteAccountTag` to remove one. Deleting a tag is allowed only when it has no project or user assignments. Otherwise `ACCOUNT_TAG_IN_USE` returns `projects`, `aiVisibilityProjects` and `users` reference counts in its error extensions. Remove those assignments first. ## Applying tags to existing projects `createAccountTagProjects` links tags to projects, and `deleteAccountTagProjects` unlinks them. Both take lists, so one call can apply several tags to several projects. Every tag and every project in the call must belong to the same account; mixing accounts is rejected with `ACCOUNT_TAG_PROJECT_ACCOUNT_MISMATCH`, which is also what supplying a foreign tag to a create mutation returns. ## Applying tags when creating a project Every project- and test-suite-creating mutation accepts `accountTagIds`, which saves a second round trip: ```graphql mutation CreateTaggedProject($input: CreateProjectInput!) { createSEOProject(input: $input) { project { id name tags { id name } } } } ``` **Variables:** ```json { "input": { "accountId": "TjAwN0FjY291bnQ3MTU", "name": "example.com", "primaryDomain": "https://www.example.com/", "accountTagIds": ["TjAwOFRhZzE", "TjAwOFRhZzI"] } } ``` The same input field is available on `createBasicProject`, `createAccessibilityProject`, `createSiteSpeedProject`, `createTestSuite`, `createAccessibilityTestSuite` and `createSiteSpeedTestSuite`. Creating a project needs the editor role. A tag-restricted editor may supply only tags assigned to them in `accountTagIds`, or omit tags to create a project visible to everyone. Admins may choose any tag in the account. Other editors must omit tags. After creation, adding or removing project tags remains admin-only. This creation exception also applies to AI Visibility projects and Protect test suites. When a restricted user clones a project, only source tags assigned to that user are copied. ## Protect test suites Test suites are tagged from the same pool of account tags as projects. `TestSuite.tags` returns a test suite's tags, and `Account.testSuites` accepts the same `tagIds` filter as `Account.projects`: ```graphql query GetTaggedTestSuites($accountId: ObjectID!, $tagIds: [ObjectID!]) { getAccount(id: $accountId) { testSuites(first: 20, tagIds: $tagIds) { edges { node { id name tags { id name color } } } totalCount } } } ``` **Variables:** ```json { "accountId": "TjAwN0FjY291bnQ3MTU", "tagIds": ["TjAwOFRhZzE"] } ``` ## AI Visibility projects AI Visibility projects are tagged from the same pool of account tags, through their own pair of mutations: `createAccountTagAiVisibilityProjects` and `deleteAccountTagAiVisibilityProjects`. Both take an explicit `accountId` alongside the lists of tags and projects, because AI Visibility project ids are only unique within the account that owns them: ```graphql mutation TagAiVisibilityProjects($input: CreateAccountTagAiVisibilityProjectsInput!) { createAccountTagAiVisibilityProjects(input: $input) { aiVisibilityProjects { id name } accountTags { id name } } } ``` **Variables:** ```json { "input": { "accountId": "TjAwN0FjY291bnQ3MTU", "accountTagIds": ["TjAwOFRhZzE"], "aiVisibilityProjectIds": ["TjAwOEFpVmlzaWJpbGl0eVByb2plY3Qx"] } } ``` `AiVisibilityProject.tags` returns a project's tags, and `getAiVisibilityProjects` accepts a `tagIds` filter: ```graphql query GetTaggedAiVisibilityProjects($accountId: ObjectID!, $tagIds: [ObjectID!]) { getAiVisibilityProjects(accountId: $accountId, first: 20, tagIds: $tagIds) { nodes { id name tags { id name color } } totalCount } } ``` **Variables:** ```json { "accountId": "TjAwN0FjY291bnQ3MTU", "tagIds": ["TjAwOFRhZzE"] } ``` `createAiVisibilityProject` accepts `accountTagIds` in the same way the project-creating mutations do, with the same creation-only exception for a restricted editor’s assigned tags. ## Filtering by tag Pass `tagIds` to `Account.projects`, `Account.testSuites` or `getAiVisibilityProjects` to return only the projects carrying at least one of those tags: ```graphql query GetTaggedProjects($accountId: ObjectID!, $tagIds: [ObjectID!]) { getAccount(id: $accountId) { projects(first: 20, tagIds: $tagIds) { edges { node { id name tags { id name color } } } totalCount } } } ``` **Variables:** ```json { "accountId": "TjAwN0FjY291bnQ3MTU", "tagIds": ["TjAwOFRhZzE"] } ``` ## Display order Each tag carries a `position` that decides the order tags are listed in within its account. It is not a priority or a grouping — it only controls display. Treat the value itself as opaque: sort by it, don't compute with it. Positions ascend in display order, but they are not guaranteed to be consecutive or to start at zero, and a given tag's number can change when other tags are added, removed or reordered. Only the relative order is meaningful. `reorderAccountTags` sets the whole account's order in one call. Pass every tag the account has, exactly once, in the order you want: ```graphql mutation ReorderAccountTags($input: ReorderAccountTagsInput!) { reorderAccountTags(input: $input) { accountTags { id name position } } } ``` **Variables:** ```json { "input": { "accountId": "TjAwN0FjY291bnQ3MTU", "accountTagIds": ["TjAwOFRhZzI", "TjAwOFRhZzE"] } } ``` Listing a subset, repeating a tag, or including a tag from another account is rejected with `ACCOUNT_TAG_ORDER_INCOMPLETE` and changes nothing, so a partial list cannot silently reshuffle the tags it omits. The error's extensions name the offending ids under `missingIds`, `unknownIds` and `duplicateIds`. Read the account's tags first if you need the current set. ## Restricting a member's project visibility An account admin can replace an existing member's visibility tags with `updateAccountUserTags`: ```graphql mutation UpdateAccountUserTags($input: UpdateAccountUserTagsInput!) { updateAccountUserTags(input: $input) { account { id } } } ``` **Variables:** ```json { "input": { "accountId": "12345", "userId": "67890", "accountTagIds": ["123"] } } ``` `AccountUser.tags` returns the member's current assignments. Every supplied tag must belong to the same account; the mutation rejects foreign tags with `ACCOUNT_TAG_PROJECT_ACCOUNT_MISMATCH`. The replacement accepts up to 1,000 tag IDs and takes effect on subsequent requests. A non-admin member with one or more tags sees only projects carrying **at least one** of those tags, plus every untagged project. This applies to Analyze, Protect and AI Visibility, including project data in dashboards, searches, direct-ID lookups, downloads and connection counts. An additional `tagIds` filter narrows these results; it never grants access to other projects. A hidden direct-ID lookup behaves like a missing project. **No assigned tags means access to every project in that account.** Before clearing the last tag, your application should warn that the member will gain access to all projects. Tags are independent between accounts and do not change a member's role or the operations that role allows. Admins always see all projects and cannot receive visibility tags (`ACCOUNT_TAG_ADMIN_USER`). Promoting a member to admin clears their tags. A subsequent demotion starts with unrestricted visibility until an admin assigns tags again. Removing and re-adding a membership also starts without visibility tags. Authenticated users can create personal API keys, including Viewers. These keys use their owner's current membership, role and tags. Service accounts inherit their creator's current visibility tags and continue working if their creator is no longer an admin. While the creator is tag-restricted, the service account cannot perform account-admin operations, including changing member roles or visibility tags. If the creator is removed from the account, the service account remains enabled but loses access to all projects, including untagged projects. Legacy service accounts without a recorded creator retain their existing account scope. Creating service accounts and their keys requires account admin access. Sharelinks remain explicit access grants. Project email notifications check tagged members' current project access when notifications are sent. Configured external recipients and account integrations remain explicit delivery grants. ## Monitor dashboards and boards A dashboard is visible only when every project behind every view is visible to the caller. One hidden project hides the whole dashboard. Accessible dashboards render all their views with the same counts for everyone who can open them. A board appears when at least one of its dashboards is visible. Creating a board without dashboard inputs initializes a default Monitor dashboard for a tag-restricted member so the new board remains reachable. Deleting a board requires visibility of every dashboard it contains; a mixed board cannot be deleted by someone who cannot see all of its dashboards. Bulk sorting preserves the position slots occupied by hidden boards and dashboards. Moving a single board requires visibility of every board in the account; otherwise it returns `ACCESS_DENIED_PERMISSIONS_MISSING` without changing the order. Visibility does not add a partial editing state: existing role permissions continue to apply to the whole accessible dashboard or alert. ## Links between AI Visibility and crawl projects Linking projects with different tag sets is allowed. Linked datasource content renders in full for anyone who can open the project, including someone who cannot see the project the data came from. **This is a known and accepted gap in the Tag boundary.** Linked data is never hidden at read time. Before creating a link, call `previewProjectLinkTags` with `accountId`, `projectId` and `aiVisibilityProjectId`. Before a tag edit, supply either project ID and the proposed complete `projectTagIds` or `aiVisibilityProjectTagIds` set. Omit a tag set to read its current value; an empty array previews removing every tag. With one project ID, the API checks every existing link affected by that edit. The response names both tag sets and explains that anyone who can see the AI Visibility project can read linked crawl data whatever tags they hold. Show these warnings and allow the user to continue. This preview does not save changes or block linking. A saved link exposes the same information through `ProjectAiVisibilitySettings.tagWarningsForLink`. Warning messages and the empty-tag label follow the request locale, including Japanese; tag names remain unchanged. ## AI Visibility usage Account-wide credits, prompt limits, provider entitlements and run totals remain visible in full. `getAiVisibilityRunUsage.projectUsage` names only visible projects. It combines hidden projects into one `Other projects` row with no project ID; its `estimatedRuns` values sum to `accountEstimatedRuns`. These are planning-window estimates (including the modeled project's draft settings), not actual credit consumption. Actual account credit usage remains available in `currentCreditUsage`. Crawl-project settings do not expose a linked AI Visibility project that the caller cannot see. Link-warning previews omit inaccessible AI Visibility links. The accepted linked-data exception applies only from an accessible AI Visibility project to its crawl datasource. A warning preview without either project ID returns `PROJECT_LINK_PREVIEW_PROJECT_REQUIRED`. A crawl project outside the selected account returns `PROJECT_LINK_PREVIEW_ACCOUNT_MISMATCH`. Both errors are localized.