Skip to main content
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
  }
}
FieldDescription
totalFilesTotal number of documents stored
totalSizeTotal storage used, in bytes
maxSizeYour organization’s storage quota, in bytes
usedPercentagePercentage of quota consumed (0–100)
availableSizeRemaining 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.