DocumentTags - Huddle/huddle-apis GitHub Wiki
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:
- Tag management — create, rename, delete, and bulk-import the account's tag library. Restricted to Account Managers.
- Viewing tags — list, retrieve, and search the tag library for a single account, or search across many accounts in one request.
- 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 theAcceptandContent-Typeheaders. Most examples on this page use JSON for brevity; sendAccept: application/vnd.huddle.data+xmlto 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 standardxmlns:xsd/xmlns:xsideclarations on the root element. The document resource also advertises atagselement and a link withrel="tags"pointing at/files/documents/{documentId}/tags.
| 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 |
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 new tag in the account's library.
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>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>| 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. |
| 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). |
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.
POST /files/accounts/123/tags/bulk HTTP/1.1
Content-Type: multipart/form-data; boundary=----boundary
Authorization: Bearer frootymcnooty/vonbootycherootyThe 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
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>| 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. |
Update a tag's name. Renaming affects every document the tag is assigned to.
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>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>
modifiedDateandmodifiedByare populated once a tag has been modified (as after this rename). For a tag that has never been modified they arenull— omitted from JSON and absent from the XML.
| 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). |
Permanently delete a tag from the account library.
DELETE /files/accounts/123/tags/1 HTTP/1.1
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherootyHTTP/1.1 204 No ContentSide effect: Deleting a tag is a hard delete and cascades — the tag is removed from every document it was assigned to.
| 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. |
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.
Return all tags defined for an account, each with its document count.
GET /files/accounts/123/tags HTTP/1.1
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherootyHTTP/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 aselflink. -
documentCountcounts only active (non-deleted) documents. -
sizeis the total number of tags returned.offset/limitform part of the schema for pagination, but the endpoint currently returns the full set in a single response (observedlimitechoes the total count, and nonextlink is emitted); pagination is not yet fully implemented.
| 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. |
Retrieve one tag with full metadata.
GET /files/accounts/123/tags/1 HTTP/1.1
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherootyHTTP/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.
| 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. |
Autocomplete-style search within one account. Designed for tag-picker input fields.
GET /files/accounts/123/tags/search?q=imp&limit=10 HTTP/1.1
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty| Parameter | Required | Default | Description |
|---|---|---|---|
q |
Yes | – | Search query. Minimum 1 character. |
limit |
No | 10 | Max results. Auto-corrected: > 50 → 50, < 1 → 1. |
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 &):
<?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&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 OKwith an emptysuggestionsarray (never404).
| 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. |
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.
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>| 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. |
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.
| 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. |
| 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. |
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).
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.
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>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 carriestagIdandreason. (When a tag is found but rejected for another reason — e.g. exceedingMaximumTagsPerDocument— the entry also includes the resolvedtagName.)
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>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 carriestagNameandreason. To add new tags to the library first, an Account Manager must create them.
| 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. |
List the tags currently assigned to a document, including who assigned each tag and when.
GET /files/documents/789/tags HTTP/1.1
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherootyHTTP/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.
| 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 single tag from a document.
DELETE /files/documents/789/tags/1 HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherootyHTTP/1.1 204 No Content| 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 every tag from a document in one request.
DELETE /files/documents/789/tags HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherootyHTTP/1.1 204 No ContentSide effects:
- A single audit entry is written (
"All {count} tags removed from document.", or"Tag {tagName} removed from document."for a single tag). - A
FolderModifiedEventis published for the document's folder. - A
DocumentTagDeletedEventis 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.
| 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. |
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.
GET /files/search/documents?tags=Important&includetitle=true HTTP/1.1
Accept: application/vnd.huddle.data+json
Authorization: Bearer frootymcnooty/vonbootycherooty| 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
tagsparameter filters on the assigned tag, whereasquerycombined withincludecontent=trueperforms a free-text match that also covers tag values. Usetagsfor an exact tag filter andqueryfor free-text discovery.
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.
| 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.
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.
-
Document — the document resource and its
tagselement /rel="tags"link. -
Account Settings — the
DocumentTaggingEnabled/DocumentTaggingEnabledByAccManagerfeature flags. -
Search files or folders — document search supports filtering by
tags(see Finding documents by tag). - Permissions — folder-level access control that governs tag assignment.