Nymble Commerce’s Company Docs feature gives your organization a secure document repository — files are organized in folders, stored in encrypted cloud storage, and served via signed CDN URLs that expire after 1 hour. All data is isolated to your organization; no other tenant can access your documents.
Base URL
All Company Docs endpoints are available under:
https://api.achievemomentum.com/api/companydocs
Every request must include a valid JWT in the Authorization header:
Authorization: Bearer {token}
The JWT must contain an OrganizationId claim. Nymble Commerce uses this claim to enforce organization-level isolation — you only ever see your organization’s folders and documents.
Folder management
Folders let you organize documents into a hierarchy. You can nest folders to any depth by setting parentId when you create a child folder.
Create a folder
POST https://api.achievemomentum.com/api/companydocs/folders
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "HR Documents",
"parentId": null
}
Response
{
"folder": {
"folderId": "folder_a1b2c3",
"organizationId": "org_4e9b7f22",
"name": "HR Documents",
"parentId": null,
"path": "/HR Documents",
"createdAt": "2024-06-01T09:00:00Z",
"updatedAt": "2024-06-01T09:00:00Z"
}
}
Name rules: maximum 255 characters; the / and \ characters are not allowed.
Build a nested folder structure
Use the folderId returned from a parent folder creation as the parentId for child folders:
const token = localStorage.getItem("token");
const headers = {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
};
// 1. Create root folder
const hrResponse = await fetch(
"https://api.achievemomentum.com/api/companydocs/folders",
{
method: "POST",
headers,
body: JSON.stringify({ name: "HR Documents", parentId: null }),
}
);
const { folder: hrFolder } = await hrResponse.json();
// 2. Create subfolder under HR Documents
const policiesResponse = await fetch(
"https://api.achievemomentum.com/api/companydocs/folders",
{
method: "POST",
headers,
body: JSON.stringify({ name: "Policies", parentId: hrFolder.folderId }),
}
);
const { folder: policiesFolder } = await policiesResponse.json();
// policiesFolder.path → "/HR Documents/Policies"
List all folders
GET https://api.achievemomentum.com/api/companydocs/folders
Authorization: Bearer {token}
Response
{
"folders": [
{
"folderId": "folder_a1b2c3",
"name": "HR Documents",
"parentId": null,
"path": "/HR Documents",
"createdAt": "2024-06-01T09:00:00Z",
"updatedAt": "2024-06-01T09:00:00Z"
},
{
"folderId": "folder_d4e5f6",
"name": "Policies",
"parentId": "folder_a1b2c3",
"path": "/HR Documents/Policies",
"createdAt": "2024-06-01T09:05:00Z",
"updatedAt": "2024-06-01T09:05:00Z"
}
]
}
Delete a folder
DELETE https://api.achievemomentum.com/api/companydocs/folders/{folderId}
Authorization: Bearer {token}
Returns 204 No Content on success. You must remove all documents and subfolders from a folder before you can delete it — attempting to delete a non-empty folder returns a 400 error.
Uploading documents
Upload files using a multipart POST request. Include the file in the file field and optionally specify a target folderId.
POST https://api.achievemomentum.com/api/companydocs/documents/upload
Authorization: Bearer {token}
Content-Type: multipart/form-data
const formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("folderId", "folder_d4e5f6"); // optional
const response = await fetch(
"https://api.achievemomentum.com/api/companydocs/documents/upload",
{
method: "POST",
headers: { Authorization: `Bearer ${token}` },
body: formData,
}
);
const { document } = await response.json();
Response
{
"document": {
"documentId": "doc_9f1e2d3c",
"organizationId": "org_4e9b7f22",
"name": "employee-handbook-2024.pdf",
"fileExtension": ".pdf",
"mimeType": "application/pdf",
"size": 2097152,
"folderId": "folder_d4e5f6",
"cdnUrl": "https://cdn.achievemomentum.com/docs/org_4e9b7f22/doc_9f1e2d3c?sig=...",
"path": "/HR Documents/Policies/employee-handbook-2024.pdf",
"createdAt": "2024-06-01T09:10:00Z",
"updatedAt": "2024-06-01T09:10:00Z"
}
}
Constraints:
- Maximum file size: 50 MB
- Files are scanned before storage; uploads that fail the virus scan are rejected
- Omit
folderId (or send null) to place the file at the root level
Listing and retrieving documents
List all documents in your organization, or filter to a specific folder:
GET https://api.achievemomentum.com/api/companydocs/documents
GET https://api.achievemomentum.com/api/companydocs/documents?folderId=folder_d4e5f6
Authorization: Bearer {token}
Response shape
{
"documents": [
{
"documentId": "doc_9f1e2d3c",
"name": "employee-handbook-2024.pdf",
"fileExtension": ".pdf",
"mimeType": "application/pdf",
"size": 2097152,
"folderId": "folder_d4e5f6",
"cdnUrl": "https://cdn.achievemomentum.com/docs/...",
"path": "/HR Documents/Policies/employee-handbook-2024.pdf",
"createdAt": "2024-06-01T09:10:00Z",
"updatedAt": "2024-06-01T09:10:00Z"
}
]
}
Retrieve metadata for a single document:
GET https://api.achievemomentum.com/api/companydocs/documents/{documentId}
Authorization: Bearer {token}
Downloading documents
To stream a file’s binary content directly (for example, to trigger a browser download), use the download endpoint:
GET https://api.achievemomentum.com/api/companydocs/documents/{documentId}/download
Authorization: Bearer {token}
The response includes the file’s binary content with the appropriate Content-Type and a Content-Disposition: attachment; filename="..." header.
const response = await fetch(
`https://api.achievemomentum.com/api/companydocs/documents/${documentId}/download`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "employee-handbook-2024.pdf";
a.click();
window.URL.revokeObjectURL(url);
Moving documents
Move a single document to a different folder:
PUT https://api.achievemomentum.com/api/companydocs/documents/{documentId}/move
Authorization: Bearer {token}
Content-Type: application/json
{
"targetFolderId": "folder_a1b2c3"
}
Set targetFolderId to null to move the document back to the root level. Returns 204 No Content on success.
Move multiple documents at once using the bulk endpoint:
POST https://api.achievemomentum.com/api/companydocs/documents/bulk-move
Authorization: Bearer {token}
Content-Type: application/json
{
"documentIds": ["doc_9f1e2d3c", "doc_4a5b6c7d", "doc_8e9f0a1b"],
"targetFolderId": "folder_a1b2c3"
}
CDN URL expiry and refresh
The cdnUrl on every document is a signed URL that expires after 1 hour. If you cache document metadata and display the URL later, you must refresh it first.
POST https://api.achievemomentum.com/api/companydocs/documents/{documentId}/refresh-url
Authorization: Bearer {token}
Response
{
"cdnUrl": "https://cdn.achievemomentum.com/docs/org_4e9b7f22/doc_9f1e2d3c?sig=newtoken&expires=..."
}
// Example: refresh URL before rendering a download link
async function getFreshUrl(documentId) {
const response = await fetch(
`https://api.achievemomentum.com/api/companydocs/documents/${documentId}/refresh-url`,
{
method: "POST",
headers: { Authorization: `Bearer ${token}` },
}
);
const { cdnUrl } = await response.json();
return cdnUrl;
}
Do not cache cdnUrl values for longer than 1 hour. Presenting an expired URL to a user results in an access-denied error from the CDN. Always call refresh-url if there is any chance the URL has aged past its expiry window.
Storage statistics
Check your organization’s storage usage at any time:
GET https://api.achievemomentum.com/api/companydocs/stats/storage
Authorization: Bearer {token}
Response
{
"stats": {
"totalFiles": 1247,
"totalSize": 5368709120,
"maxSize": 10737418240,
"usedPercentage": 50.0,
"availableSize": 5368709120
}
}
| Field | Description |
|---|
totalFiles | Total number of documents stored |
totalSize | Total storage used, in bytes |
maxSize | Your organization’s storage quota, in bytes |
usedPercentage | Percentage of quota consumed (0–100) |
availableSize | Remaining quota, in bytes |
Bulk operations
Use bulk endpoints to perform batch actions in a single API call, reducing round-trips and improving performance.
Bulk delete
Delete up to 100 documents at once:
POST https://api.achievemomentum.com/api/companydocs/documents/bulk-delete
Authorization: Bearer {token}
Content-Type: application/json
{
"documentIds": ["doc_9f1e2d3c", "doc_4a5b6c7d", "doc_8e9f0a1b"]
}
const response = await fetch(
"https://api.achievemomentum.com/api/companydocs/documents/bulk-delete",
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
documentIds: ["doc_9f1e2d3c", "doc_4a5b6c7d", "doc_8e9f0a1b"],
}),
}
);
// 204 No Content on success
Both bulk-delete and bulk-move accept a maximum of 100 document IDs per request. Split larger batches into multiple calls.