> ## Documentation Index
> Fetch the complete documentation index at: https://developers.lighton.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Organizing documents with tags

> Group documents into collections with flat, reusable labels, then scope search and ask to a collection.

Tags are flat, reusable labels you attach to documents to group them into collections: a project, a team, a topic, a customer. Unlike [Facets](/tutorials/facets/overview), there's no schema and no hierarchy to design first, and unlike [Workspaces](/tutorials/workspaces), a tag isn't tied to one container: the same tag can group files that live in **different workspaces**, so you can form cross-workspace collections. You create a tag, assign it to files, and immediately scope search, ask, and file listings to that collection.

> *"Search only the Project Alpha documents."*
>
> *"Answer this question using just our compliance files."*

That's what tags give you: a lightweight way to carve your corpus into named groups your app can filter on.

<Tip>
  This tutorial covers the tag lifecycle: [`POST /api/v3/tags`](/api-reference/tags/create-a-new-tag-for-the-company), [`GET /api/v3/tags`](/api-reference/tags/list-all-tags-for-the-authenticated-users-company), [`POST /api/v3/files/{id}/tags`](/api-reference/files/add-tags-to-a-file), and [`DELETE /api/v3/files/{id}/tags/{tag_id}`](/api-reference/files/remove-a-tag-from-a-file). Assigning tags at upload time is covered in [Uploading & managing files](/tutorials/files#organising-documents-with-tags-and-titles).
</Tip>

## Workspaces, tags, or facets?

All three organise documents, but at different layers. They compose: a file lives in one [workspace](/tutorials/workspaces), can carry several tags, and can be classified with [facets](/tutorials/facets/overview).

|                        | Workspaces                                                     | Tags                                       | Facets                                     |
| ---------------------- | -------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------ |
| **What it is**         | A container; every file lives in exactly one                   | Flat, reusable labels                      | Typed, hierarchical metadata with a schema |
| **A file belongs to**  | exactly one workspace                                          | many tags, **across workspaces**           | many content types, **across workspaces**  |
| **Best for**           | Isolating teams, customers, tenants                            | Cross-cutting collections (project, topic) | Precise structured queries                 |
| **Setup cost**         | Create a workspace                                             | Create a tag                               | Design a content-type tree                 |
| **Scope a query with** | `workspace_id`                                                 | `tag_id`                                   | `content_type` / `attribute`               |
| **Access control**     | Yes: API keys can be scoped to a workspace with a per-key role | No: not a permission boundary              | No: not a permission boundary              |

Only [workspaces](/tutorials/workspaces) are a permission boundary: API keys can be scoped to specific workspaces, so reach for workspaces (not tags) when different data needs different permission levels. Tags are the right tool when a collection spans workspaces, for example a `Project Alpha` tag on files that live in both the Engineering and Design workspaces. You can combine `tag_id` with `workspace_id` to scope to a tagged collection within a single workspace. The rest of this tutorial covers tags.

## Step 1: Create a tag

A tag has a `name` and a `description`. Create it once at the company level, then reuse it across as many files as you like.

<CodeGroup>
  ```python Python SDK theme={null}
  from lighton import LightOn, Tag

  with LightOn() as client:  # reads LIGHTON_API_KEY from the environment
      tag = Tag(
          name="Project Alpha",
          description="Documents related to the development and release of Project Alpha",
      ).create(client)
      print(tag.id, tag.name)
  ```

  ```python Plain Python theme={null}
  import os
  import requests

  headers = {"Authorization": f"Bearer {os.environ['LIGHTON_API_KEY']}"}

  response = requests.post(
      "https://api.lighton.ai/api/v3/tags",
      headers=headers,
      json={
          "name": "Project Alpha",
          "description": "Documents related to the development and release of Project Alpha",
      },
  )
  print(response.json())
  ```
</CodeGroup>

A `201` returns the new tag with its `id`. Note it down: you'll use the ID everywhere else.

```json theme={null}
{
  "id": 7,
  "name": "Project Alpha",
  "description": "Documents related to the development and release of Project Alpha",
  "auto_assign": true,
  "document_count": 0
}
```

**`auto_assign`** controls who can attach the tag. It defaults to `true`, meaning LightOn's AI can assign the tag to matching documents automatically (your `description` is the signal it matches on, so write it well). Set it to `false` for a manual-only tag that the system never touches.

## Step 2: Find a tag's ID

Tags are company-scoped, so a teammate may already have created the one you need. List them, optionally filtering by name, to grab the ID.

<CodeGroup>
  ```python Python SDK theme={null}
  from lighton import LightOn, Tag

  with LightOn() as client:
      # Find a tag by name (case-insensitive partial match) to get its ID.
      # Tag.list follows pagination to the end; omit `name` to list them all.
      for tag in Tag.list(client, name="alpha"):
          print(tag.id, tag.name, tag.document_count)
  ```

  ```python Plain Python theme={null}
  import os
  import requests

  headers = {"Authorization": f"Bearer {os.environ['LIGHTON_API_KEY']}"}

  # Find a tag by name (case-insensitive partial match) to get its ID
  response = requests.get(
      "https://api.lighton.ai/api/v3/tags",
      headers=headers,
      params={"name": "alpha"},
  )
  print(response.json())
  ```
</CodeGroup>

The response is paginated. Each tag carries a `document_count` so you can see how populated a collection is.

```json theme={null}
{
  "count": 1,
  "results": [
    {
      "id": 7,
      "name": "Project Alpha",
      "auto_assign": true,
      "document_count": 12
    }
  ]
}
```

## Step 3: Assign tags to files

You have three ways to attach tags, depending on timing:

* **At upload time:** pass `tags` in the upload payload. See [Uploading & managing files](/tutorials/files#organising-documents-with-tags-and-titles).
* **Add to an existing file:** `POST /api/v3/files/{id}/tags`. Adds tags without disturbing the ones already there.
* **Replace every tag:** [`PATCH /api/v3/files/{id}`](/api-reference/files/update-file-metadata) with a new `tags` array. This overwrites *all* existing tags, manual and auto-assigned alike.

For incremental tagging, adding is what you want.

<CodeGroup>
  ```python Python SDK theme={null}
  from lighton import File, LightOn

  file_id = 1234  # replace with your file ID

  with LightOn() as client:
      doc = File.get(client, file_id)
      # Adds these tags without touching tags already on the file.
      # tag() also takes Tag objects or names: doc.tag(["Project Alpha"])
      doc.tag([3, 7])  # tag IDs from Tag.list(client)
      print(f"tagged file {doc.id}")
  ```

  ```python Plain Python theme={null}
  import os
  import requests

  headers = {"Authorization": f"Bearer {os.environ['LIGHTON_API_KEY']}"}

  file_id = 1234  # replace with your file ID

  # Adds these tags without touching tags already on the file
  response = requests.post(
      f"https://api.lighton.ai/api/v3/files/{file_id}/tags",
      headers=headers,
      json={"tags": [3, 7]},  # tag IDs from GET /api/v3/tags
  )
  print(response.json())
  ```
</CodeGroup>

This returns the full file object with its updated `tags` array. Duplicate tags are ignored (no error), and newly added tags are marked `auto_assigned: false`.

## Step 4: List files in a collection

Once files are tagged, filter the file listing by `tag_id` to retrieve a collection.

<CodeGroup>
  ```python Python SDK theme={null}
  from lighton import File, LightOn

  with LightOn() as client:
      # All files carrying tag 7, most recent first
      for doc in File.list(client, tag_id=7, ordering="-created_at"):
          print(doc.id, doc.title, doc.status)
  ```

  ```python Plain Python theme={null}
  import os
  import requests

  headers = {"Authorization": f"Bearer {os.environ['LIGHTON_API_KEY']}"}

  # All files carrying tag 7, most recent first
  response = requests.get(
      "https://api.lighton.ai/api/v3/files",
      headers=headers,
      params={"tag_id": "7", "ordering": "-created_at"},
  )
  print(response.json())
  ```
</CodeGroup>

You get back a paginated list of files carrying that tag. Combine `tag_id` with other filters (`extension`, `workspace_id`, `total_pages_min`) to narrow further.

## Step 5: Scope search and ask to a collection

This is where tags earn their keep. Both [`POST /api/v3/search`](/tutorials/search) and [`POST /api/v3/ask`](/tutorials/ask) accept a `tag_id` array that restricts the query to documents in those collections. Pass several IDs to widen the scope to a union of collections.

Search across a collection:

<CodeGroup>
  ```python Python SDK theme={null}
  from lighton import LightOn

  with LightOn() as client:
      # Search only within the collection tagged 7 (pass several IDs to widen it).
      # tags= also takes Tag objects or names: tags=["Project Alpha"]
      response = client.search("rollout timeline and milestones", tags=[7])
      for result in response.results:
          print(result.score, result.source.filename, result.content)
  ```

  ```python Plain Python theme={null}
  import os
  import requests

  headers = {"Authorization": f"Bearer {os.environ['LIGHTON_API_KEY']}"}

  # Search only within the collection tagged 7 (combine several IDs to widen it)
  response = requests.post(
      "https://api.lighton.ai/api/v3/search",
      headers=headers,
      json={
          "query": "rollout timeline and milestones",
          "tag_id": [7],
      },
  )
  print(response.json())
  ```
</CodeGroup>

Ask a grounded question over a collection:

<CodeGroup>
  ```python Python SDK theme={null}
  from lighton import LightOn

  with LightOn() as client:
      # Grounded answer over only the documents tagged 7
      response = client.ask("What is the launch date for Project Alpha?", tags=[7])
      print(response.answer)
  ```

  ```python Plain Python theme={null}
  import os
  import requests

  headers = {"Authorization": f"Bearer {os.environ['LIGHTON_API_KEY']}"}

  # Grounded answer over only the documents tagged 7
  response = requests.post(
      "https://api.lighton.ai/api/v3/ask",
      headers=headers,
      json={
          "query": "What is the launch date for Project Alpha?",
          "tag_id": [7],
      },
  )
  print(response.json())
  ```
</CodeGroup>

`tag_id` combines with `workspace_id` (both narrow the corpus together), but is mutually exclusive with `file_id`.

## Step 6: Remove a tag

To take a single file out of a collection, delete that one association. The file and the tag both survive; only the link is removed.

<CodeGroup>
  ```python Python SDK theme={null}
  from lighton import File, LightOn

  file_id = 1234  # replace with your file ID
  tag_id = 7  # replace with the tag to remove

  with LightOn() as client:
      doc = File.get(client, file_id)
      # Idempotent: no error if the tag was not on the file
      doc.untag([tag_id])
      print(f"removed tag {tag_id} from file {doc.id}")
  ```

  ```python Plain Python theme={null}
  import os
  import requests

  headers = {"Authorization": f"Bearer {os.environ['LIGHTON_API_KEY']}"}

  file_id = 1234  # replace with your file ID
  tag_id = 7      # replace with the tag to remove

  # Idempotent: returns 204 even if the tag was not on the file
  response = requests.delete(
      f"https://api.lighton.ai/api/v3/files/{file_id}/tags/{tag_id}",
      headers=headers,
  )
  print(response.status_code)
  ```
</CodeGroup>

A `204` confirms success. The call is idempotent: you get `204` even if the tag wasn't on the file.

To retire a tag everywhere, [`DELETE /api/v3/tags/{id}`](/api-reference/tags/delete-a-company-tag). That removes the tag from every document it was attached to, so use it deliberately.

## Next steps

<CardGroup cols={2}>
  <Card title="Uploading & managing files" href="/tutorials/files">
    Assign tags at upload time and manage file metadata
  </Card>

  <Card title="Searching documents" href="/tutorials/search">
    Rank passages, then scope the search by tag
  </Card>

  <Card title="Asking questions" href="/tutorials/ask">
    Get grounded answers over a tagged collection
  </Card>

  <Card title="Organizing documents with metadata" href="/tutorials/facets/overview">
    Step up to typed, structured metadata with facets
  </Card>
</CardGroup>
