DocumentContent - Huddle/huddle-apis GitHub Wiki

Retrieving Document Content

Documents have binary content associated with them. If a document has binary content, it will advertise a link with a rel value of content. The title attribute of the link gives the filename, and the type attribute gives the MIME type.

If a document has multiple versions, the content link points to the latest active version (not deleted, and with content successfully uploaded). If no binary content exists for the document, the server returns 404 Not Found.

Operations

Method Path Purpose Details
GET, HEAD /files/documents/{documentId}/versions/latest/content Get latest document content (raw or converted) Jump
GET, HEAD /files/documents/{versionId}/content Get specific version content (raw or converted) Jump
GET, HEAD /files/documents/{versionId}/content/{fileName} Get specific version content with filename hint Jump
HEAD /files/documents/{documentId}/versions/latest/content Check whether converted content is ready Jump
GET /files/documents/{documentId}/versions/{versionId}/conversion/{mediaType}/{subType} Poll conversion progress for requested format Jump

On all routes, the desired MIME type can be specified either via the Accept request header or the mimetype query string parameter (useful in browser contexts that cannot set headers). If both are provided, the query string takes precedence.

Getting the raw content of a document

To retrieve the original binary content, issue a GET to the content link with no Accept header (or Accept: */*).

Request headers

Header Description
Accept MIME type to download. Omit (or use */*) for the original format. Set to a specific MIME type to request a converted version.
X-Download-Reason Reason for the download. See x-download-reason.
Range Request a byte range. E.g. bytes=0-1023. See Partial content.
If-None-Match Returns 304 if the ETag matches (client cache is fresh).
If-Modified-Since Returns 304 if content has not been modified since the given date.
If-Match Returns 412 if the ETag does not match.
If-Unmodified-Since Returns 412 if content has been modified since the given date.
If-Range Combined with Range: if the ETag is stale, returns full content instead of partial.

Response headers

Header Present on Description
Content-Type 200, 206 MIME type of the downloaded content.
Content-Length 200, 206 Total bytes in the response body (or in the partial range).
Content-Disposition 200, 206 Filename. E.g. attachment; filename="report.pdf".
Supported-Accept 200, 202, 406, 500 Comma-separated list of MIME types available for this document.
Vary All Accept — signals that the response varies by Accept header.
ETag 200, 206, 304 Entity tag for cache validation.
Last-Modified 200, 206, 304 Date the content was last updated (RFC 1123).
Cache-Control 200, 206, 304 no-cache, no-store
Expires 200, 206, 304 Thu, 01 Jan 1970 00:00:00 GMT
Accept-Ranges 200, 206 bytes — indicates byte-range requests are supported.
Content-Range 206, 416 Byte range returned, or total size on 416. E.g. bytes 0-1023/4096 or bytes */4096.
Location 202 URI of the conversion progress endpoint to poll.

Response codes

Code Description
200 OK Full content returned.
202 Accepted Conversion required; poll the Location URI for progress.
206 Partial Content Partial content returned (byte range request).
304 Not Modified Content unchanged since last request; no body returned.
400 Bad Request Invalid X-Download-Reason value or malformed mimetype parameter.
404 Not Found Document or version not found.
406 Not Acceptable Conversion to the requested MIME type is not supported for this document.
412 Precondition Failed Conditional request failed (If-Match/If-Unmodified-Since).
416 Range Not Satisfiable Requested range is outside the content bounds.
500 Internal Server Error A previous conversion attempt failed.

Example: Basic download (200 OK)

Request

GET /files/documents/456/versions/latest/content HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty

Response

HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 198
Content-Disposition: attachment; filename="filename.txt"
Supported-Accept: text/plain, application/pdf
Vary: Accept
ETag: "a1b2c3d4"
Last-Modified: Tue, 25 Mar 2025 10:00:00 GMT
Cache-Control: no-cache, no-store
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Accept-Ranges: bytes

Hello - this is the content of your text file. You uploaded it to Huddle to keep it safe and
secure, but now it has been used in an API example by a bad developer and so everybody can see it. Oh no!

Example: Partial content (206 Partial Content)

To download a specific byte range, include a Range header.

Notes:

  • Multipart ranges (e.g. Range: bytes=0-100, 200-300) are not supported. Use a single range.
  • Syntactically invalid Range headers are silently ignored and the full content is returned.
  • If the range start is beyond the end of the file, a 416 is returned.
  • If the range end exceeds the file size, it is silently clipped to the end of the file.

Request

GET /files/documents/456/versions/latest/content HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty
Range: bytes=0-1023

Response

HTTP/1.1 206 Partial Content
Content-Type: text/plain
Content-Range: bytes 0-1023/4096
Accept-Ranges: bytes
ETag: "a1b2c3d4"
Last-Modified: Tue, 25 Mar 2025 10:00:00 GMT
Cache-Control: no-cache, no-store
Expires: Thu, 01 Jan 1970 00:00:00 GMT

... first 1024 bytes of content ...

Example: Range not satisfiable (416)

Request

GET /files/documents/456/versions/latest/content HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty
Range: bytes=9999-10000

Response

HTTP/1.1 416 Range Not Satisfiable
Content-Range: bytes */198

Example: Conditional GET — not modified (304)

If the client already has a cached copy with a matching ETag, the server returns 304 with no body.

Request

GET /files/documents/456/versions/latest/content HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty
If-None-Match: "a1b2c3d4"

Response

HTTP/1.1 304 Not Modified
ETag: "a1b2c3d4"
Last-Modified: Tue, 25 Mar 2025 10:00:00 GMT
Cache-Control: no-cache, no-store
Expires: Thu, 01 Jan 1970 00:00:00 GMT

Example: Conditional GET — precondition failed (412)

Request

GET /files/documents/456/versions/latest/content HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty
If-Match: "outdated-etag"

Response

HTTP/1.1 412 Precondition Failed

Getting the content of a document in a different format

Huddle supports automatic conversion of documents into different formats. For example, Word documents (application/msword) can be converted on-demand to PDF (application/pdf).

To request a converted format, issue a GET to the content link with the Accept header set to the desired MIME type. The supported MIME types for a document are listed in the Supported-Accept response header.

Specifying the same MIME type as the original document is equivalent to omitting the Accept header.

In addition to using the Accept header, the MIME type can be specified via the mimetype query string parameter (useful in browser contexts that cannot set headers).

Example: Content already generated (200 OK)

If a converted copy already exists, it is returned immediately.

Sequence diagram

Client                            Server
  |           GET content              |
  |     Accept: application/pdf        |
  |               OR                   |
  |               GET                  |
  | content?mimetype=application%2Fpdf |
  | ------------------------------>    |
  |                                    |
  | <------------------------------    |
  |             200 OK                 |
  |  Content-Type: application/pdf     |
  |      ...binary content...          |

Request (via Accept header)

GET /files/documents/456/versions/latest/content HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty
Accept: application/pdf

Request (via query string)

GET /files/documents/456/versions/latest/content?mimetype=application%2Fpdf HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty

Response

HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Length: 198
Content-Disposition: attachment; filename="filename.pdf"
Supported-Accept: application/msword, text/plain, application/pdf
Vary: Accept
ETag: "a1b2c3d4"
Last-Modified: Tue, 25 Mar 2025 10:00:00 GMT
Cache-Control: no-cache, no-store
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Accept-Ranges: bytes

... generated binary PDF content ...

Example: Content not yet generated — polling flow (202 Accepted)

If no converted copy exists, the server responds with 202 Accepted and a Location header pointing to a progress endpoint. The client should poll this endpoint until the conversion is complete, then follow the content link.

Sequence diagram

Client                            Server
  |           GET content              |
  |     Accept: application/pdf        |
  |               OR                   |
  |               GET                  |
  | content?mimetype=application%2Fpdf |
  | ------------------------------>    |
  |                                    |
  | <------------------------------    |
  |          202 Accepted              |
  |   Location: .../conversion/...     |
  |                                    |
  |                                    |
  |      GET .../conversion/...        |
  | ------------------------------>    |
  |                                    |
  | <------------------------------    |
  |             200 OK                 |
  |     (conversion in progress)       |
  |                                    |
  |                                    |
  |      GET .../conversion/...        |
  | ------------------------------>    |
  |                                    |
  | <------------------------------    |
  |          200 OK                    |
  |    Link: .../content/...           |
  |      (conversion complete)         |
  |                                    |
  |                                    |
  |           GET content              |
  |     Accept: application/pdf        |
  | ------------------------------>    |
  |                                    |
  | <------------------------------    |
  |             200 OK                 |
  |  Content-Type: application/pdf     |
  |      ...binary content...          |

Request (via Accept header)

GET /files/documents/456/versions/latest/content HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty
Accept: application/pdf

Request (via query string)

GET /files/documents/456/versions/latest/content?mimetype=application%2Fpdf HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty

Response (202 Accepted — conversion queued)

HTTP/1.1 202 Accepted
Location: /files/documents/456/versions/235253235/conversion/application/pdf
Supported-Accept: application/msword, text/plain, application/pdf
Vary: Accept

Now poll the Location URI. While conversion is in progress:

Request

GET /files/documents/456/versions/235253235/conversion/application/pdf HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty
Accept: application/json

Response (still in progress)

HTTP/1.1 200 OK
Content-Type: application/json

{
    "status": "InProgress",
    "links": [
        {"rel": "self", "href": "/files/documents/456/versions/235253235/conversion/application/pdf"},
        {"rel": "content", "href": "/files/documents/versions/235253235/content?mimetype=application%2Fpdf"}
    ]
}

On the next poll, the conversion has completed:

Response (complete)

HTTP/1.1 200 OK
Content-Type: application/json

{
    "status": "Complete",
    "links": [
        {"rel": "self", "href": "/files/documents/456/versions/235253235/conversion/application/pdf"},
        {"rel": "content", "href": "/files/documents/versions/235253235/content?mimetype=application%2Fpdf"}
    ]
}

Follow the content link with the desired Accept header to download the converted file.

Request

GET /files/documents/versions/235253235/content?mimetype=application%2Fpdf HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty
Accept: application/pdf

Response

HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Length: 198
Content-Disposition: attachment; filename="filename.pdf"
Supported-Accept: application/msword, text/plain, application/pdf
Vary: Accept
ETag: "a1b2c3d4"
Last-Modified: Tue, 25 Mar 2025 10:00:00 GMT
Cache-Control: no-cache, no-store
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Accept-Ranges: bytes

... generated binary PDF content ...

Example: MIME type not supported (406 Not Acceptable)

If the requested MIME type is not listed in Supported-Accept, the server returns 406.

Request

GET /files/documents/456/versions/latest/content HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty
Accept: video/mp4

Response

HTTP/1.1 406 Not Acceptable
Supported-Accept: application/msword, text/plain, application/pdf
Vary: Accept

Example: Document conversion process failed (500 Internal Server Error)

If a previous conversion attempt failed, the server returns 500.

Request

GET /files/documents/456/versions/latest/content HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty
Accept: application/pdf

Response

HTTP/1.1 500 Internal Server Error
Supported-Accept: application/msword, text/plain, application/pdf
Vary: Accept

Retrying the conversion for this document may result in success.

Checking if content for a specific MIME type is ready (HEAD)

Use a HEAD request to determine whether converted content is available without downloading it. The response has the same headers as a GET but no body.

Example: Supported MIME types (HEAD, no Accept)

Request

HEAD /files/documents/456/versions/latest/content HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty

Response

HTTP/1.1 200 OK
Content-Type: application/msword
Content-Length: 198
Content-Disposition: attachment; filename="filename.docx"
Supported-Accept: application/msword, text/plain, application/pdf
Vary: Accept

Example: Content available (HEAD + Accept)

Request (via query string)

HEAD /files/documents/456/versions/latest/content?mimetype=application%2Fmsword HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty

Request (via Accept header)

HEAD /files/documents/456/versions/latest/content HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty
Accept: application/msword

Response

HTTP/1.1 200 OK
Content-Type: application/msword
Content-Length: 198
Content-Disposition: attachment; filename="filename.docx"
Supported-Accept: application/msword, text/plain, application/pdf
Vary: Accept

Example: Content requires conversion (HEAD + Accept)

Request

HEAD /files/documents/456/versions/latest/content HTTP/1.1
Authorization: Bearer frootymcnooty/vonbootycherooty
Accept: application/pdf

Response

HTTP/1.1 202 Accepted
Supported-Accept: application/msword, text/plain, application/pdf
Vary: Accept

x-download-reason request header

The X-Download-Reason request header allows the client to record why a file is being downloaded. This value is stored in the audit log's download activity entry.

If X-Download-Reason is not provided, the reason defaults to unspecified.

If an unsupported value is provided, the server returns 400 Bad Request.

The following values are supported:

Reason code Description
unspecified Reason not known (default if header is omitted)
file-preview Download to preview the file
open-file-other-app Download to open in another app
offline-file Automatic download for offline files
offline-file-preview Automatic download for offline files (preview)
suggested-file-preview Automatic download for suggested files
other/{freetext reason} Other reason (freetext, max 100 characters)

Note: For other/ reasons, the freetext portion is HTML-encoded server-side before storage.

Conversion progress endpoint

When the server responds with 202 Accepted to a content request, the Location header contains the URI of a conversion progress endpoint.

Route: GET /files/documents/{documentId}/versions/{versionId}/conversion/{mediaType}/{subType}

An optional ?q={quality} parameter is appended when a quality preference was included in the original request.

Response codes

Code status value Description
200 OK InProgress Conversion is still in progress. Continue polling.
200 OK Complete Conversion is complete. Follow the content link to download.
404 Not Found NotFound Conversion record not found (e.g. expired or never started).
500 Internal Server Error Failed Conversion failed. Retrying may succeed.

Response body

{
    "status": "InProgress",
    "links": [
        {"rel": "self", "href": "/files/documents/456/versions/235253235/conversion/application/pdf"},
        {"rel": "content", "href": "/files/documents/versions/235253235/content?mimetype=application%2Fpdf"}
    ]
}

The self link points back to this progress endpoint. The content link points to the version content endpoint pre-configured with the requested MIME type.