SharePoint Integration
Keelstone can save generated documents to SharePoint and use SharePoint-hosted files as merge templates. Both features use Microsoft Graph API with an Azure AD app-only (service principal) credential — no per-user OAuth is required.
SharePoint integration is available on the Keelstone Pro plan and above. Calling any SharePoint feature on a Free or Basic org returns 403 { "error": "SharePoint integration requires the Keelstone Pro plan or higher." }.
Base URL: https://app.keelstone.dev
Setup: Register an Azure AD App
Before using SharePoint features, an admin must register an Azure AD application in the Microsoft Entra portal.
- Go to portal.azure.com → Azure Active Directory → App registrations → New registration
- Give the app a name (e.g. "Keelstone SharePoint") and click Register
- Note the Application (client) ID and Directory (tenant) ID from the Overview page
- Go to Certificates & secrets → New client secret → copy the secret value immediately
- Go to API permissions → Add a permission → Microsoft Graph → Application permissions → add
Sites.ReadWrite.All - Click Grant admin consent — this permission requires admin consent
Enter those values in the Keelstone Setup tab in Salesforce (SharePoint Integration card).
POST /api/docs/generate-from-sharepoint-template
Fetches a template from SharePoint by path, merges a data object into it, and returns the result as base64. Optionally uploads the result to Salesforce Files and/or back to a SharePoint folder.
Plan: Pro or Enterprise
Authentication: Session-based or x-org-api-key. SharePoint must be configured in the org's Keelstone Setup.
Request
POST /api/docs/generate-from-sharepoint-template
Content-Type: application/json
Authorization: Bearer <keelstoneSessionId>
{
sharePointItemPath: string; // Path to the template in the SharePoint drive
data: object; // Any JSON-serializable merge context
filename?: string; // Output filename, e.g. 'Quote.docx'
linkedEntityId?: string; // Salesforce record ID to attach the generated file to
linkedSharePointPath?: string; // SharePoint folder to upload the result into
outputFormat?: string; // 'pdf' to convert the output
}
| Field | Type | Required | Description |
|---|---|---|---|
sharePointItemPath | string | Yes | Path relative to the site's default drive root, e.g. Shared Documents/Templates/Quote.docx |
data | object | Yes | Merge context — keys become template tokens |
filename | string | No | Output filename. Defaults to the template's filename. |
linkedEntityId | string | No | Salesforce record ID — file is uploaded and linked |
linkedSharePointPath | string | No | SharePoint folder — merged result is uploaded here |
outputFormat | string | No | 'pdf' to convert the output |
Response
200 OK
{
"base64": "<base64-encoded merged file>",
"contentDocumentId": "069...",
"sharePointFileUrl": "https://contoso.sharepoint.com/..."
}
contentDocumentId is present only when linkedEntityId was supplied.
sharePointFileUrl is present only when linkedSharePointPath was supplied.
400 Bad Request
{ "error": "sharePointItemPath is required" }
{ "error": "data is required" }
{ "error": "SharePoint is not configured for this org. Visit Keelstone Setup to connect SharePoint." }
{ "error": "Unsupported template type: .pptx. Supported: .docx, .xlsx" }
403 Forbidden — org is not on Pro or Enterprise
{ "error": "SharePoint integration requires the Keelstone Pro plan or higher." }
Using from an LWC
import { KSWord } from 'kstone/api';
export default class ContractGenerator extends KSWord {
@api keelstoneSessionId;
@api recordId;
async handleGenerate() {
const sfRecord = await getContractData({ recordId: this.recordId });
const result = await this.ksGenerateFromSharePoint(
'Shared Documents/Templates/Contract.docx',
{ ...sfRecord },
{
filename: 'Contract.docx',
linkedEntityId: this.recordId,
linkedSharePointPath: 'Shared Documents/Generated Contracts',
}
);
// result.sharePointFileUrl — SharePoint link to the generated file
// result.contentDocumentId — Salesforce Files ID
}
}
linkedSharePointPath on existing generate endpoints
All three document generation endpoints accept an optional linkedSharePointPath parameter. When provided, the generated file is uploaded to that SharePoint folder after merging.
Plan requirement: Passing linkedSharePointPath (or sharePointPath in generate-bulk) on a Free or Basic org returns an error — see each endpoint below for the exact shape. Document generation itself is unaffected; only the SharePoint upload requires Pro.
SharePoint not configured: If the org is on Pro but SharePoint has not been set up in Keelstone Setup, the upload block is silently skipped and sharePointFileUrl is omitted from the response. No error is returned.
POST /api/docs/generate
Add linkedSharePointPath and filename to the request body:
{
templateBase64: string;
data: object;
doc_type?: string;
linkedSharePointPath?: string; // SharePoint folder to upload the result into (Pro+)
filename?: string; // Used as the SharePoint filename
}
When linkedSharePointPath is supplied, the response includes sharePointFileUrl:
{ "base64": "...", "sharePointFileUrl": "https://contoso.sharepoint.com/..." }
403 Forbidden — returned (instead of 200) when linkedSharePointPath is present and the org is not on Pro:
{ "error": "SharePoint integration requires the Keelstone Pro plan or higher." }
POST /api/docs/generate-from-key
{
templateKey: string;
data: object;
filename?: string;
linkedEntityId?: string;
outputFormat?: string;
linkedSharePointPath?: string; // SharePoint folder to upload the result into (Pro+)
}
When linkedSharePointPath is supplied, sharePointFileUrl is included alongside the existing base64 and contentDocumentId.
403 Forbidden — returned when linkedSharePointPath is present and the org is not on Pro:
{ "error": "SharePoint integration requires the Keelstone Pro plan or higher." }
POST /api/docs/generate-bulk
Each document in the documents array can include a sharePointPath field:
type Document = {
data: object;
filename?: string;
linkedEntityId?: string;
externalLink?: boolean;
sharePointPath?: string; // SharePoint folder for this document (Pro+)
}
Per-document result when the upload succeeds:
{
"success": true,
"contentDocumentId": "069...",
"sharePointFileUrl": "https://contoso.sharepoint.com/...",
"filename": "Ada Welcome.docx"
}
Because generate-bulk processes documents independently, plan and upload errors are reported per document rather than aborting the batch. When sharePointPath is set on a non-Pro org, or when the SP upload fails, sharePointError is added to that document's result — success and contentDocumentId are unaffected:
{
"success": true,
"contentDocumentId": "069...",
"sharePointError": "SharePoint integration requires the Keelstone Pro plan or higher.",
"filename": "Ada Welcome.docx"
}
LWC methods
ksGenerateFromData(templateKey, data, options?) — updated
The options object now accepts linkedSharePointPath:
options: {
filename?: string;
linkedEntityId?: string;
outputFormat?: string;
linkedSharePointPath?: string; // NEW
}
ksGenerateFromSharePoint(sharePointItemPath, data, options?) — new
Available on KSExcel and KSWord.
async ksGenerateFromSharePoint(
sharePointItemPath: string,
data: object,
options?: {
filename?: string;
linkedEntityId?: string;
linkedSharePointPath?: string;
outputFormat?: string;
}
) → Promise<{ base64: string, contentDocumentId?: string, sharePointFileUrl?: string }>
| Param | Description |
|---|---|
sharePointItemPath | Path to the template, relative to the drive root, e.g. Shared Documents/Templates/Quote.xlsx |
data | Merge context — same token syntax as Salesforce-hosted templates |
options.linkedSharePointPath | Folder to upload the result into |
options.linkedEntityId | Salesforce record to attach the Salesforce File to |
How SharePoint auth works
Keelstone uses app-only credentials (client credentials flow) — no per-user OAuth or delegated permissions. The Azure AD app must have Sites.ReadWrite.All as an application permission (not delegated) with admin consent granted.
Tokens are cached in the server process for 55 minutes (tokens are valid for 60 min; Keelstone refreshes 5 minutes early). The SharePoint site's drive ID is resolved once and cached in the org's configuration to avoid a Graph API lookup on every generation request.