Skip to content

DocumentTags

Chai Chee Kong edited this page Jun 9, 2026 · 1 revision

Summary

Tags are account-scoped labels that can be assigned to documents to classify and organise content. A tag belongs to an account and can be reused across every workspace in that account. Each document can carry a limited number of tags (configurable per environment, default 10).

There are three groups of operations:

  1. Tag management — create, rename, delete, and bulk-import the account's tag library. Restricted to Account Managers.
  2. Viewing tags — list, retrieve, and search the tag library for a single account, or search across many accounts in one request.
  3. Document tags — assign tags to a document, list a document's tags, and remove them. Requires write access to the document.

Tags are an account-level feature gated by a two-tier flag: DocumentTaggingEnabled (set by a system administrator) and DocumentTaggingEnabledByAccManager (set by an Account Manager). Both must be enabled before any tag operation is permitted. See Account Settings.

Note on representation: The tag endpoints support standard Huddle content negotiation — both JSON (application/vnd.huddle.data+json) and XML (application/vnd.huddle.data+xml) are accepted on requests and returned on responses, selected via the Accept and Content-Type headers. Most examples on this page use JSON for brevity; send Accept: application/vnd.huddle.data+xml to receive the equivalent XML representation. Timestamps are formatted differently per representation: JSON uses RFC 1123 (e.g. "Fri, 12 Jun 2026 03:52:21 GMT"), whereas XML uses ISO 8601 (e.g. 2026-06-22T01:42:51.7766667Z). XML responses carry the standard xmlns:xsd/xmlns:xsi declarations on the root element. The document resource also advertises a tags element and a link with rel="tags" pointing at /files/documents/{documentId}/tags.

Operations

Method Path Purpose Details
POST /files/accounts/{accountId}/tags Create a tag Jump
POST /files/accounts/{accountId}/tags/bulk Bulk upload tags from a CSV file Jump
PUT /files/accounts/{accountId}/tags/{tagId} Rename a tag Jump
DELETE /files/accounts/{accountId}/tags/{tagId} Delete a tag Jump
GET /files/accounts/{accountId}/tags List an account's tags Jump
GET /files/accounts/{accountId}/tags/{tagId} Get a single tag Jump
GET /files/accounts/{accountId}/tags/search Search/autocomplete tags in one account Jump
POST /files/tags/search Search tags across many accounts Jump
POST /files/documents/{documentId}/tags Assign tags to a document Jump
GET /files/documents/{documentId}/tags Get a document's tags Jump
DELETE /files/documents/{documentId}/tags/{tagId} Remove one tag from a document Jump
DELETE /files/documents/{documentId}/tags Remove all tags from a document Jump
GET /files/search/documents Find documents by tag Jump

Tag management

These operations maintain the tag library for an account. They all require the caller to be an Account Manager of the account (CanEditTags() policy).

Create a tag

Create a new tag in the account's library.

Request

POST /files/accounts/123/tags HTTP/1.1
Content-Type: application/vnd.huddle.data+json
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty
{
  "name": "Important"
}

The same request body in XML (root element createTagRequest):

<?xml version="1.0" encoding="utf-8"?>
<createTagRequest>
  <name>Important</name>
</createTagRequest>

Response

HTTP/1.1 201 Created
Location: /files/accounts/123/tags/1
Content-Type: application/vnd.huddle.data+json
{
  "id": 1,
  "name": "Important",
  "createdDate": "Fri, 12 Jun 2026 03:52:21 GMT",
  "createdBy": "John Smith",
  "documentCount": 0,
  "links": [
    { "rel": "self", "href": "/files/accounts/123/tags/1" },
    { "rel": "edit", "href": "/files/accounts/123/tags/1" },
    { "rel": "delete", "href": "/files/accounts/123/tags/1" }
  ]
}

The same response in XML. A freshly created tag has no modifiedDate/modifiedBy:

<?xml version="1.0" encoding="utf-8"?>
<tag xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <id>1</id>
  <name>Important</name>
  <createdDate>2026-06-12T03:52:21Z</createdDate>
  <createdBy>John Smith</createdBy>
  <documentCount>0</documentCount>
  <links>
    <link rel="self" href="/files/accounts/123/tags/1" />
    <link rel="edit" href="/files/accounts/123/tags/1" />
    <link rel="delete" href="/files/accounts/123/tags/1" />
  </links>
</tag>

Tag name validation

Rule Detail
Required Must not be null, empty, or whitespace only.
Max length 60 characters.
Allowed characters Letters (A–Z, a–z), digits (0–9), spaces, hyphens (-), underscores (_).
Pattern ^[a-zA-Z0-9_-]+( [a-zA-Z0-9_-]+)*$ — no leading/trailing spaces, single spaces between words only.
Uniqueness Case-insensitive, per account.

Response codes

Code Description
201 Created Tag created. Location header points to the new tag.
400 Bad Request Invalid tag name (empty, too long, illegal characters, multiple spaces).
401 Unauthorized Not authenticated.
403 Forbidden Caller is not an Account Manager, or tagging is disabled for the account.
409 Conflict A tag with the same name already exists (case-insensitive).

Bulk upload tags

Import many tags at once by uploading a CSV file. This uses a partial success model: valid tags are created, duplicates are skipped, invalid names fail, and the response reports the outcome for each.

Request

POST /files/accounts/123/tags/bulk HTTP/1.1
Content-Type: multipart/form-data; boundary=----boundary
Authorization: Bearer frootymcnooty/vonbootycherooty

The body is multipart/form-data with a single file part containing a .csv file. Tag names may be comma-separated, newline-separated, or both. Leading/trailing spaces are trimmed and empty values ignored.

Project Alpha, Q4 2026, Important, Urgent
Marketing, Sales, Engineering
Bug-Fix, Feature-Request

Response

HTTP/1.1 200 OK
Content-Type: application/vnd.huddle.data+json
{
  "created": [
    {
      "id": 101,
      "name": "Project Alpha",
      "createdDate": "Fri, 12 Jun 2026 10:30:00 GMT",
      "createdBy": "John Smith",
      "documentCount": 0,
      "links": [
        { "rel": "self", "href": "/files/accounts/123/tags/101" }
      ]
    }
    // ... 6 more created tags omitted (summary.created: 7) ...
  ],
  "skipped": [
    {
      "name": "Important",
      "reason": "A tag with the name 'Important' already exists in this account."
    }
    // ... 1 more skipped tag omitted (summary.skipped: 2) ...
  ],
  "failed": [
    {
      "name": "Invalid@Tag",
      "reason": "TagName: can only contain letters (A-Z, a-z), digits (0-9), spaces, hyphens (-), and underscores (_), with single spaces between words."
    }
  ],
  "summary": {
    "total": 10,
    "created": 7,
    "skipped": 2,
    "failed": 1
  },
  "links": [
    { "rel": "self", "href": "/files/accounts/123/tags/bulk" },
    { "rel": "tags", "href": "/files/accounts/123/tags" }
  ]
}

The same response in XML (root bulkCreateTagsResponse; each created/skipped/failed entry is a <tag>):

<?xml version="1.0" encoding="utf-8"?>
<bulkCreateTagsResponse xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <created>
    <tag>
      <id>101</id>
      <name>Project Alpha</name>
      <createdDate>2026-06-12T10:30:00Z</createdDate>
      <createdBy>John Smith</createdBy>
      <documentCount>0</documentCount>
      <links>
        <link rel="self" href="/files/accounts/123/tags/101" />
      </links>
    </tag>
    <!-- ... 6 more created tags omitted (summary.created: 7) ... -->
  </created>
  <skipped>
    <tag>
      <name>Important</name>
      <reason>A tag with the name 'Important' already exists in this account.</reason>
    </tag>
    <!-- ... 1 more skipped tag omitted (summary.skipped: 2) ... -->
  </skipped>
  <failed>
    <tag>
      <name>Invalid@Tag</name>
      <reason>TagName: can only contain letters (A-Z, a-z), digits (0-9), spaces, hyphens (-), and underscores (_), with single spaces between words.</reason>
    </tag>
  </failed>
  <summary>
    <total>10</total>
    <created>7</created>
    <skipped>2</skipped>
    <failed>1</failed>
  </summary>
  <links>
    <link rel="self" href="/files/accounts/123/tags/bulk" />
    <link rel="tags" href="/files/accounts/123/tags" />
  </links>
</bulkCreateTagsResponse>

Response codes

Code Description
200 OK File processed. The body is a status report (not 201 Created) because the request is a batch, not a single resource.
400 Bad Request No file uploaded, file is not a .csv, or it contains no valid tag names.
401 Unauthorized Not authenticated.
403 Forbidden Caller is not an Account Manager, or tagging is disabled for the account.

Rename a tag

Update a tag's name. Renaming affects every document the tag is assigned to.

Request

PUT /files/accounts/123/tags/1 HTTP/1.1
Content-Type: application/vnd.huddle.data+json
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty
{
  "name": "Very Important"
}

The same request body in XML (root element updateTagRequest):

<?xml version="1.0" encoding="utf-8"?>
<updateTagRequest>
  <name>Very Important</name>
</updateTagRequest>

Response

HTTP/1.1 200 OK
Content-Type: application/vnd.huddle.data+json
{
  "id": 1,
  "name": "Very Important",
  "createdDate": "Thu, 02 Apr 2026 09:39:57 GMT",
  "createdBy": "John Smith",
  "modifiedDate": "Mon, 22 Jun 2026 01:42:51 GMT",
  "modifiedBy": "Jane Smith",
  "documentCount": 10,
  "links": [
    { "rel": "self", "href": "/files/accounts/123/tags/1" },
    { "rel": "edit", "href": "/files/accounts/123/tags/1" },
    { "rel": "delete", "href": "/files/accounts/123/tags/1" }
  ]
}

The same response in XML (Accept: application/vnd.huddle.data+xml):

<?xml version="1.0" encoding="utf-8"?>
<tag xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <id>1</id>
  <name>Very Important</name>
  <createdDate>2026-04-02T09:39:57Z</createdDate>
  <createdBy>John Smith</createdBy>
  <modifiedDate>2026-06-22T01:42:51.7766667Z</modifiedDate>
  <modifiedBy>Jane Smith</modifiedBy>
  <documentCount>10</documentCount>
  <links>
    <link rel="self" href="/files/accounts/123/tags/1" />
    <link rel="edit" href="/files/accounts/123/tags/1" />
    <link rel="delete" href="/files/accounts/123/tags/1" />
  </links>
</tag>

modifiedDate and modifiedBy are populated once a tag has been modified (as after this rename). For a tag that has never been modified they are null — omitted from JSON and absent from the XML.

Response codes

Code Description
200 OK Tag renamed.
400 Bad Request Invalid tag name (see validation).
401 Unauthorized Not authenticated.
403 Forbidden Caller is not an Account Manager.
404 Not Found Tag does not exist in this account.
409 Conflict A tag with the new name already exists (case-insensitive).

Delete a tag

Permanently delete a tag from the account library.

Request

DELETE /files/accounts/123/tags/1 HTTP/1.1
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty

Response

HTTP/1.1 204 No Content

Side effect: Deleting a tag is a hard delete and cascades — the tag is removed from every document it was assigned to.

Response codes

Code Description
204 No Content Tag deleted.
401 Unauthorized Not authenticated.
403 Forbidden Caller is not an Account Manager.
404 Not Found Tag does not exist in this account.

Viewing tags

These operations read the tag library. They are governed by the CanReadTags() policy, which permits any user who can tag documents in the account (workspace manager, or write permission on any folder) or an Account Manager. The policy checks workspace/folder access first for performance, falling back to the Account Manager check only when needed.

List account tags

Return all tags defined for an account, each with its document count.

Request

GET /files/accounts/123/tags HTTP/1.1
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty

Response

HTTP/1.1 200 OK
Content-Type: application/vnd.huddle.data+json
{
  "tags": [
    {
      "id": 1,
      "name": "Very Important",
      "createdDate": "Thu, 02 Apr 2026 08:28:08 GMT",
      "createdBy": "John Smith",
      "documentCount": 15,
      "links": [
        { "rel": "self", "href": "/files/accounts/123/tags/1" }
      ]
    },
    {
      "id": 2,
      "name": "Confidential",
      "createdDate": "Thu, 19 Mar 2026 07:48:19 GMT",
      "createdBy": "Jane Doe",
      "documentCount": 8,
      "links": [
        { "rel": "self", "href": "/files/accounts/123/tags/2" }
      ]
    }
  ],
  "size": 2,
  "offset": 0,
  "limit": 2,
  "links": [
    { "rel": "self", "href": "/files/accounts/123/tags" },
    { "rel": "create", "href": "/files/accounts/123/tags" }
  ]
}

The same response in XML. Note the root element and the collection wrapper are both named tags, so the tag list is doubly nested (<tags><tags><tag>…):

<?xml version="1.0" encoding="utf-8"?>
<tags xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <tags>
    <tag>
      <id>1</id>
      <name>Very Important</name>
      <createdDate>2026-04-02T08:28:08Z</createdDate>
      <createdBy>John Smith</createdBy>
      <documentCount>15</documentCount>
      <links>
        <link rel="self" href="/files/accounts/123/tags/1" />
      </links>
    </tag>
    <tag>
      <id>2</id>
      <name>Confidential</name>
      <createdDate>2026-03-19T07:48:19Z</createdDate>
      <createdBy>Jane Doe</createdBy>
      <documentCount>8</documentCount>
      <links>
        <link rel="self" href="/files/accounts/123/tags/2" />
      </links>
    </tag>
  </tags>
  <size>2</size>
  <offset>0</offset>
  <limit>2</limit>
  <links>
    <link rel="self" href="/files/accounts/123/tags" />
    <link rel="create" href="/files/accounts/123/tags" />
  </links>
</tags>

Notes:

  • Tags are returned alphabetically by name. Each tag in the collection carries only a self link.
  • documentCount counts only active (non-deleted) documents.
  • size is the total number of tags returned. offset/limit form part of the schema for pagination, but the endpoint currently returns the full set in a single response (observed limit echoes the total count, and no next link is emitted); pagination is not yet fully implemented.

Response codes

Code Description
200 OK Tags returned (empty tags array if none).
401 Unauthorized Not authenticated.
403 Forbidden CanReadTags() failed (no workspace access and not an Account Manager, or tagging disabled).
404 Not Found Account does not exist.

Get a single tag

Retrieve one tag with full metadata.

Request

GET /files/accounts/123/tags/1 HTTP/1.1
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty

Response

HTTP/1.1 200 OK
Content-Type: application/vnd.huddle.data+json
{
  "id": 1,
  "name": "Important",
  "createdDate": "Thu, 02 Apr 2026 09:39:57 GMT",
  "createdBy": "John Smith",
  "documentCount": 10,
  "links": [
    { "rel": "self", "href": "/files/accounts/123/tags/1" },
    { "rel": "edit", "href": "/files/accounts/123/tags/1" },
    { "rel": "delete", "href": "/files/accounts/123/tags/1" }
  ]
}

The same response in XML (this tag has not been modified, so modifiedDate/modifiedBy are absent):

<?xml version="1.0" encoding="utf-8"?>
<tag xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <id>1</id>
  <name>Important</name>
  <createdDate>2026-04-02T09:39:57Z</createdDate>
  <createdBy>John Smith</createdBy>
  <documentCount>10</documentCount>
  <links>
    <link rel="self" href="/files/accounts/123/tags/1" />
    <link rel="edit" href="/files/accounts/123/tags/1" />
    <link rel="delete" href="/files/accounts/123/tags/1" />
  </links>
</tag>

If the tag has been modified, the response also includes modifiedDate and modifiedBy (see Rename a tag); these are omitted when the tag has never been modified. The edit and delete links are advertised only when the caller is an Account Manager.

Response codes

Code Description
200 OK Tag returned.
401 Unauthorized Not authenticated.
403 Forbidden CanReadTags() failed.
404 Not Found Tag does not exist or is not in the caller's account.

Search tags (single account)

Autocomplete-style search within one account. Designed for tag-picker input fields.

Request

GET /files/accounts/123/tags/search?q=imp&limit=10 HTTP/1.1
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty

Query parameters

Parameter Required Default Description
q Yes Search query. Minimum 1 character.
limit No 10 Max results. Auto-corrected: > 50 → 50, < 1 → 1.

Response

HTTP/1.1 200 OK
Content-Type: application/vnd.huddle.data+json
{
  "suggestions": [
    { "id": 1, "name": "Important" },
    { "id": 5, "name": "Implementation" }
  ],
  "query": "imp",
  "links": [
    { "rel": "self", "href": "/files/accounts/123/tags/search?q=imp&limit=10" }
  ]
}

The same response in XML (root searchResults; each suggestion is a <suggestion>; note & in the href is XML-escaped as &amp;):

<?xml version="1.0" encoding="utf-8"?>
<searchResults xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <suggestions>
    <suggestion>
      <id>1</id>
      <name>Important</name>
    </suggestion>
    <suggestion>
      <id>5</id>
      <name>Implementation</name>
    </suggestion>
  </suggestions>
  <query>imp</query>
  <links>
    <link rel="self" href="/files/accounts/123/tags/search?q=imp&amp;limit=10" />
  </links>
</searchResults>

Behaviour:

  • Hybrid strategy — short queries (1–2 chars) use a prefix match (LIKE 'term%'); longer queries (3+ chars) use a contains match (LIKE '%term%').
  • Case-insensitive. Results are ordered exact-match first, then prefix, then contains, then by length/alphabetically.
  • Document counts are omitted for performance. Use List account tags if counts are needed.
  • An empty result set is still 200 OK with an empty suggestions array (never 404).

Response codes

Code Description
200 OK Search executed (possibly empty).
400 Bad Request q is missing, null, empty, or whitespace.
401 Unauthorized Not authenticated.
403 Forbidden CanReadTags() failed.
404 Not Found Account does not exist.

Search tags across accounts

Retrieve tags from many accounts in a single request. Uses POST (rather than GET) so that large lists of account IDs are sent in the body, avoiding URL-length limits — it supports thousands of account IDs.

This endpoint uses the CanSearchTagsBulk() policy: the same checks as CanReadTags() minus the Account Manager check. Users with read access (workspace manager or folder Read/Write) can view tags. Inaccessible or tagging-disabled accounts are filtered out and reported in the summary.

Request

POST /files/tags/search HTTP/1.1
Content-Type: application/vnd.huddle.data+json
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty
{
  "accountIds": [123, 456, 789],
  "options": {
    "includeEmptyAccounts": true,
    "maxTagsPerAccount": 0,
    "assignedOnly": false
  }
}

The same request body in XML (root tagsSearch; each id is an <accountId>):

<?xml version="1.0" encoding="utf-8"?>
<tagsSearch>
  <accountIds>
    <accountId>123</accountId>
    <accountId>456</accountId>
    <accountId>789</accountId>
  </accountIds>
  <options>
    <includeEmptyAccounts>true</includeEmptyAccounts>
    <maxTagsPerAccount>0</maxTagsPerAccount>
    <assignedOnly>false</assignedOnly>
  </options>
</tagsSearch>

Request fields

Field Type Required Default Description
accountIds int[] Yes Account IDs to query (max 10,000).
options.includeEmptyAccounts bool No true Include accounts that have no tags.
options.maxTagsPerAccount int No 0 Max tags per account; 0 returns all.
options.assignedOnly bool No false When true, return only tags assigned to at least one document.

Response

HTTP/1.1 200 OK
Content-Type: application/vnd.huddle.data+json
{
  "accounts": [
    {
      "accountId": 123,
      "tags": [
        { "id": 1, "name": "Important" },
        { "id": 2, "name": "Urgent" }
        // ... remaining tags omitted (returnedTags: 50000) ...
      ],
      "links": [
        { "rel": "self", "href": "/files/accounts/123/tags" }
      ]
    }
  ],
  "summary": {
    "requestedAccounts": 3,
    "accessibleAccounts": 1,
    "returnedAccounts": 1,
    "returnedTags": 50000,
    "skippedAccounts": 0,
    "deniedAccounts": 2
  },
  "links": [
    { "rel": "self", "href": "/files/tags/search" }
  ]
}

The same response in XML (root tagsSearchResult; each account is an <account>):

<?xml version="1.0" encoding="utf-8"?>
<tagsSearchResult xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <accounts>
    <account>
      <accountId>123</accountId>
      <tags>
        <tag>
          <id>1</id>
          <name>Important</name>
        </tag>
        <tag>
          <id>2</id>
          <name>Urgent</name>
        </tag>
        <!-- ... remaining tags omitted (returnedTags: 50000) ... -->
      </tags>
      <links>
        <link rel="self" href="/files/accounts/123/tags" />
      </links>
    </account>
  </accounts>
  <summary>
    <requestedAccounts>3</requestedAccounts>
    <accessibleAccounts>1</accessibleAccounts>
    <returnedAccounts>1</returnedAccounts>
    <returnedTags>50000</returnedTags>
    <skippedAccounts>0</skippedAccounts>
    <deniedAccounts>2</deniedAccounts>
  </summary>
  <links>
    <link rel="self" href="/files/tags/search" />
  </links>
</tagsSearchResult>

Tags are returned with id and name only. For full tag details, use Get a single tag.

Summary fields

Field Description
requestedAccounts Total account IDs in the request.
accessibleAccounts Accounts the caller has permission to view.
returnedAccounts Accounts included in the response.
returnedTags Total tags returned.
skippedAccounts Accounts excluded because they are inactive or have tagging disabled.
deniedAccounts Accounts the caller has no access to.

Response codes

Code Description
200 OK Search executed; see summary for what was returned vs. filtered.
400 Bad Request accountIds missing/empty, or exceeds the maximum.
401 Unauthorized Not authenticated.

Document tags

These operations attach tags to, and read tags from, a specific document. Assigning and removing tags requires write access to the document (CanTagDocument() policy); reading a document's tags requires read access plus tagging enabled (CanReadDocumentTags() policy).

Assign tags to a document

POST to the document's tags URI to add one or more tags. The body may identify tags by ID or by name (use one form per request).

A document may hold at most MaximumTagsPerDocument tags (configurable, default 10). Tags beyond the limit are reported in failed rather than failing the whole request.

By tag ID

POST /files/documents/789/tags HTTP/1.1
Content-Type: application/vnd.huddle.data+json
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty
{
  "tagIds": [1, 2, 3, 99]
}

The same request body in XML (root assignTags; each id is a <tagId>):

<?xml version="1.0" encoding="utf-8"?>
<assignTags>
  <tagId>1</tagId>
  <tagId>2</tagId>
  <tagId>3</tagId>
  <tagId>99</tagId>
</assignTags>

Response

HTTP/1.1 200 OK
Content-Type: application/vnd.huddle.data+json
{
  "added": [1, 2],
  "existing": [3],
  "failed": [
    { "tagId": 99, "reason": "Tag with ID 99 not found or does not belong to this account" }
  ],
  "links": [
    { "rel": "self", "href": "/files/documents/789/tags" }
  ]
}

The same response in XML. Note the shape: added/existing are repeated bare elements, each failed puts tagId/tagName in attributes with the reason as the element text, and link is not wrapped in a <links> container (root tagAssignmentResult):

<?xml version="1.0" encoding="utf-8"?>
<tagAssignmentResult xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <added>1</added>
  <added>2</added>
  <existing>3</existing>
  <failed tagId="99">Tag with ID 99 not found or does not belong to this account</failed>
  <link rel="self" href="/files/documents/789/tags" />
</tagAssignmentResult>
  • added — tag IDs newly assigned.
  • existing — tag IDs already on the document (no duplicate created).
  • failed — objects describing tags that could not be assigned. For the by-ID form each entry carries tagId and reason. (When a tag is found but rejected for another reason — e.g. exceeding MaximumTagsPerDocument — the entry also includes the resolved tagName.)

By tag name

POST /files/documents/789/tags HTTP/1.1
Content-Type: application/vnd.huddle.data+json
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty
{
  "tagNames": ["Important", "Urgent", "New Tag"]
}

The same request body in XML (root assignTags; each name is a <tagName>):

<?xml version="1.0" encoding="utf-8"?>
<assignTags>
  <tagName>Important</tagName>
  <tagName>Urgent</tagName>
  <tagName>New Tag</tagName>
</assignTags>

Response

HTTP/1.1 200 OK
Content-Type: application/vnd.huddle.data+json
{
  "added": [1],
  "existing": [2],
  "failed": [
    { "tagName": "New Tag", "reason": "Tag 'New Tag' does not exist in this account" }
  ],
  "links": [
    { "rel": "self", "href": "/files/documents/789/tags" }
  ]
}

The same response in XML (a by-name failure carries tagName instead of tagId):

<?xml version="1.0" encoding="utf-8"?>
<tagAssignmentResult xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <added>1</added>
  <existing>2</existing>
  <failed tagName="New Tag">Tag 'New Tag' does not exist in this account</failed>
  <link rel="self" href="/files/documents/789/tags" />
</tagAssignmentResult>

In this example the three submitted names resolve as: Important (id 1) is newly assigned, Urgent (id 2) is already on the document, and New Tag does not exist in the account library so it fails.

Assigning by name only assigns existing tags; names not present in the account library are returned in failed — each entry carries tagName and reason. To add new tags to the library first, an Account Manager must create them.

Response codes

Code Description
200 OK Request processed; inspect added/existing/failed for per-tag outcome.
400 Bad Request Neither tagIds nor tagNames supplied, or validation failed.
401 Unauthorized Not authenticated.
403 Forbidden CanTagDocument() failed — e.g. no write permission, document deleted/locked/moving, folder deleted, workspace/account inactive, or tagging disabled.
404 Not Found Document not found.

Get a document's tags

List the tags currently assigned to a document, including who assigned each tag and when.

Request

GET /files/documents/789/tags HTTP/1.1
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty

Response

HTTP/1.1 200 OK
Content-Type: application/vnd.huddle.data+json
{
  "tags": [
    {
      "id": 1,
      "name": "Important",
      "assignedDate": "Thu, 28 May 2026 10:30:00 GMT",
      "assignedBy": {
        "id": 456,
        "displayName": "John Doe"
      },
      "links": [
        { "rel": "self", "href": "/files/accounts/123/tags/1" },
        { "rel": "remove", "href": "/files/documents/789/tags/1" }
      ]
    }
  ],
  "links": [
    { "rel": "self", "href": "/files/documents/789/tags" },
    { "rel": "add", "href": "/files/documents/789/tags" }
  ]
}

The same response in XML. As with the account tag list, the root and the collection wrapper are both tags (doubly nested):

<?xml version="1.0" encoding="utf-8"?>
<tags xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <tags>
    <tag>
      <id>1</id>
      <name>Important</name>
      <assignedDate>2026-05-28T10:30:00Z</assignedDate>
      <assignedBy>
        <id>456</id>
        <displayName>John Doe</displayName>
      </assignedBy>
      <links>
        <link rel="self" href="/files/accounts/123/tags/1" />
        <link rel="remove" href="/files/documents/789/tags/1" />
      </links>
    </tag>
  </tags>
  <links>
    <link rel="self" href="/files/documents/789/tags" />
    <link rel="add" href="/files/documents/789/tags" />
  </links>
</tags>

When the document has no tags, the response is still 200 OK with an empty tags array. Tags are ordered by assignedDate descending, then by name ascending. The add and remove links appear only when the caller may modify the document's tags.

Response codes

Code Description
200 OK Tags returned (empty array if none).
401 Unauthorized Not authenticated.
403 Forbidden CanReadDocumentTags() failed — caller cannot read the document, or tagging is disabled.
404 Not Found Document does not exist.
500 Internal Server Error System/database error.

Remove a tag from a document

Remove a single tag from a document.

Request

DELETE /files/documents/789/tags/1 HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty

Response

HTTP/1.1 204 No Content

Response codes

Code Description
204 No Content Tag removed.
401 Unauthorized Not authenticated.
403 Forbidden CanTagDocument() failed (see Assign tags).
404 Not Found Document or tag not found, or the tag is not assigned to the document.

Example 404 body when the tag is not assigned:

{
  "statusCode": 404,
  "errorMessages": [
    "Tag 'Important' (ID: 1) is not assigned to document 789"
  ],
  "errorCode": "ObjectNotFound"
}

Remove all tags from a document

Remove every tag from a document in one request.

Request

DELETE /files/documents/789/tags HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty

Response

HTTP/1.1 204 No Content

Side effects:

  • A single audit entry is written ("All {count} tags removed from document.", or "Tag {tagName} removed from document." for a single tag).
  • A FolderModifiedEvent is published for the document's folder.
  • A DocumentTagDeletedEvent is published for each removed tag (for downstream search indexing and other consumers).

If the document has no tags, the operation still succeeds (204) with no audit entry or events.

Response codes

Code Description
204 No Content All tags removed (or document already had none).
401 Unauthorized Not authenticated.
403 Forbidden CanTagDocument() failed (see Assign tags).
404 Not Found Document does not exist.

Finding documents by tag

To discover which documents carry a given tag, use the document search endpoint with the tags filter. This is the inverse of Get a document's tags: instead of listing one document's tags, it returns the documents that match one or more tags.

The full search endpoint — including content/title search, workspace and date filtering, paging, and the response shape — is documented in Search files or folders. This section covers only the tag-specific usage.

Request

GET /files/search/documents?tags=Important&includetitle=true HTTP/1.1
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty

Tag-related parameters

Parameter Required Description
query One of query or tags Free-text search term. Tags are also matched when includecontent=true.
tags One of query or tags Tag name to filter by. Must follow the tag name rules: letters, digits, hyphens, underscores, and single spaces between words.
includetitle One of includetitle or includecontent Include the document title in the search scope.
includecontent One of includetitle or includecontent Include document content, description, and tags in the search scope.

The tags parameter filters on the assigned tag, whereas query combined with includecontent=true performs a free-text match that also covers tag values. Use tags for an exact tag filter and query for free-text discovery.

Response

Returns the standard document search result set (relevance score plus a document for each hit; each document carries its own tags array). See Search files or folders → Document Search for the full response example, remaining parameters, and error codes.


Permissions

Operation Policy Who is permitted
Create / Rename / Delete / Bulk upload tags CanEditTags() Account Manager
List / Get / Search tags (single account) CanReadTags() User who can tag documents in the account (workspace manager or folder write) or Account Manager
Search tags across accounts CanSearchTagsBulk() User with read access (workspace manager or folder Read/Write); Account Manager status is not considered
Assign / Remove document tags CanTagDocument() User with write permission on the document's folder, or workspace manager
Get a document's tags CanReadDocumentTags() User who can read the document, with tagging enabled

All policies additionally require: the account is active (or migrating), the user is active, the API client is not restricted, and the two-tier tagging flags (DocumentTaggingEnabled and DocumentTaggingEnabledByAccManager) are both enabled.

Auditing

Tag operations are recorded in the Files audit trail. Tag-library operations (TagCreated, TagUpdated, TagDeleted) are stored against the tag; document tag operations (DocumentTagAdded, DocumentTagRemoved) are stored against the document and include the containing folder. See Audit Trail.

See also

Overview & HTTP platform


Authentication & sessions


People, companies & membership


Files, folders & library


Search


Tasks, approvals & actions


Notifications & real-time


Integrations & clients


Administration


Classic — legacy API docs

Older endpoints kept for reference. Prefer the sections above for new work.

Clone this wiki locally