Notes: File Uploads
Section titled “Notes: File Uploads”Use these endpoints to upload, download, and manage files and avatars attached to notebooks. The notebook service supports both direct multipart uploads for avatars and the TUS protocol for resumable file uploads.
All endpoints run on the notes container inside a project. Replace {projectId}, {containerId}, and {server} with your real identifiers, or substitute the house example 67e89abc123def456789abcd-890abcdef12345678901cdef and server node-us.
Avatars
Section titled “Avatars”Avatars are images (JPEG, PNG, or WebP) that identify users within the notebook service. Uploaded avatars are normalized to a 500x500 JPEG before storage.
POST /api/v1/notes/avatars
Section titled “POST /api/v1/notes/avatars”Uploads a raw image (JPEG, PNG, or WebP) as an avatar. The image is resized to 500x500 and converted to JPEG.
This endpoint takes no parameters.
Send the image bytes as the raw request body (no JSON envelope).
curl -X POST "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/avatars" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: image/png" \ --data-binary @./avatar.pngimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.notes.avatars.upload();{ "success": true, "id": "8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d"}{ "message": "No avatar file was uploaded.", "code": "avatar_file_not_uploaded", "details": [ { "path": "body", "message": "Multipart form must contain a JPEG or PNG image file" } ]}| Error Code | Title | Description | Resolution |
|---|---|---|---|
avatar_file_not_uploaded | Invalid upload | No file was uploaded or the content type is not a valid image | Send a multipart form with a JPEG or PNG image file |
{ "message": "Failed to upload avatar.", "code": "avatar_upload_failed", "details": [ { "path": "server", "message": "Unexpected error while persisting the avatar" } ]}| Error Code | Title | Description | Resolution |
|---|---|---|---|
avatar_upload_failed | Upload failed | Avatar upload failed due to a server error | Retry the upload |
GET /api/v1/notes/avatars/{avatarId}
Section titled “GET /api/v1/notes/avatars/{avatarId}”Returns the avatar image as JPEG binary data.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
avatarId | path | string | Yes | The unique identifier of the avatar to download |
curl -X GET "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/avatars/8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d" \ -H "Authorization: Bearer $HOODY_TOKEN" \ --output avatar.jpgimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.notes.avatars.download('8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d');{ "contentType": "image/jpeg", "body": "<binary JPEG data>"}Files are user-uploaded attachments that live inside a notebook. Each file belongs to exactly one notebook and may be referenced by zero or more documents inside that notebook.
GET /api/v1/notes/notebooks/{notebookId}/files
Section titled “GET /api/v1/notes/notebooks/{notebookId}/files”Returns a paginated list of files uploaded to a notebook. Supports limit/offset pagination.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
notebookId | path | string | Yes | The unique identifier of the notebook to list files from |
limit | query | integer | No | Maximum number of files to return (default: 50) |
offset | query | integer | No | Number of files to skip before returning results (default: 0) |
curl -X GET "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/67e89abc123def456789abcd/files?limit=20&offset=0" \ -H "Authorization: Bearer $HOODY_TOKEN"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.notes.files.listIterator('67e89abc123def456789abcd', { limit: 20 });{ "files": [ { "id": "8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d", "name": "quarterly-report.pdf", "mimeType": "application/pdf", "size": 184320, "createdAt": "2026-03-14T18:22:09.512Z", "createdBy": "user_a1b2c3d4e5f6a7b8c9d0e1f2", "documentId": "doc_77f8e9abc123def45678901a", "documentName": "Q1 Planning" }, { "id": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d", "name": "architecture-diagram.png", "mimeType": "image/png", "size": 41256, "createdAt": "2026-03-12T09:05:41.000Z", "createdBy": "user_b2c3d4e5f6a7b8c9d0e1f2a3", "documentId": "doc_77f8e9abc123def45678901a", "documentName": "Q1 Planning" } ], "total": 2}{ "message": "Notebook not found.", "code": "notebook_not_found", "details": [ { "path": "notebookId", "message": "No notebook with the supplied identifier" } ]}| Error Code | Title | Description | Resolution |
|---|---|---|---|
notebook_not_found | Notebook not found | No notebook exists with the provided ID | Verify notebook ID using listNotebooks |
notebook_no_access | Access denied | User does not have permission to access files in this notebook | Check collaborator list or request access from the notebook owner |
GET /api/v1/notes/notebooks/{notebookId}/files/{fileId}
Section titled “GET /api/v1/notes/notebooks/{notebookId}/files/{fileId}”Downloads the content of an uploaded file. Returns the binary data with the original content type.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
notebookId | path | string | Yes | The unique identifier of the notebook that owns the file |
fileId | path | string | Yes | The unique identifier of the file to download |
curl -X GET "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/67e89abc123def456789abcd/files/8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d" \ -H "Authorization: Bearer $HOODY_TOKEN" \ --output quarterly-report.pdfimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.notes.files.download('8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d', '67e89abc123def456789abcd');{ "contentType": "application/pdf", "body": "<binary file data>"}TUS Resumable Uploads
Section titled “TUS Resumable Uploads”Large file uploads use the TUS protocol so that transfers can be paused, resumed, and aborted. Each of the four HTTP verbs below is dispatched to the same TUS endpoint based on the request method.
The lifecycle is:
POSTcreates the upload session and returns the initial offset (typically 0).PATCHsends one or more chunks, advancing the offset reported back to the client.HEADqueries the current offset, allowing the client to resume after a disconnect.DELETEaborts the upload and discards any partial bytes.
POST /api/v1/notes/notebooks/{notebookId}/files/{fileId}/tus
Section titled “POST /api/v1/notes/notebooks/{notebookId}/files/{fileId}/tus”Creates a new resumable upload session for the given file.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
notebookId | path | string | Yes | The unique identifier of the notebook that will own the file |
fileId | path | string | Yes | The unique identifier of the file slot reserved for this upload |
curl -X POST "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/67e89abc123def456789abcd/files/8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d/tus" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Tus-Resumable: 1.0.0" \ -H "Upload-Length: 184320"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.notes.files.tusCreateUpload('67e89abc123def456789abcd', '8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d');{ "status": "created"}PATCH /api/v1/notes/notebooks/{notebookId}/files/{fileId}/tus
Section titled “PATCH /api/v1/notes/notebooks/{notebookId}/files/{fileId}/tus”Uploads a chunk of bytes to an existing TUS session. The Upload-Offset header marks where the chunk begins; the response carries the new offset on success.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
notebookId | path | string | Yes | The unique identifier of the notebook that owns the upload |
fileId | path | string | Yes | The unique identifier of the file being uploaded |
curl -X PATCH "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/67e89abc123def456789abcd/files/8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d/tus" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Tus-Resumable: 1.0.0" \ -H "Upload-Offset: 0" \ -H "Content-Type: application/offset+octet-stream" \ --data-binary @./chunk-0.binimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.notes.files.tusUploadChunk('67e89abc123def456789abcd', '8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d');{ "status": "chunk accepted"}HEAD /api/v1/notes/notebooks/{notebookId}/files/{fileId}/tus
Section titled “HEAD /api/v1/notes/notebooks/{notebookId}/files/{fileId}/tus”Queries the current offset of an in-progress TUS upload so a client can resume after a disconnect.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
notebookId | path | string | Yes | notebookId path parameter |
fileId | path | string | Yes | fileId path parameter |
curl -X HEAD "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/67e89abc123def456789abcd/files/8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d/tus" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Tus-Resumable: 1.0.0"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.notes.files.tusCheckUpload('67e89abc123def456789abcd', '8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d');{ "status": "offset reported"}DELETE /api/v1/notes/notebooks/{notebookId}/files/{fileId}/tus
Section titled “DELETE /api/v1/notes/notebooks/{notebookId}/files/{fileId}/tus”Aborts an in-progress TUS upload and discards any bytes received so far.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
notebookId | path | string | Yes | The unique identifier of the notebook that owns the upload |
fileId | path | string | Yes | The unique identifier of the file being uploaded |
curl -X DELETE "https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com/api/v1/notes/notebooks/67e89abc123def456789abcd/files/8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d/tus" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Tus-Resumable: 1.0.0"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-notes-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.notes.files.tusAbortUpload('67e89abc123def456789abcd', '8f3b2c1d4e5f6a7b8c9d0e1f2a3b4c5d');{ "status": "aborted"}