Getting Started with Sitecore Authoring and Management GraphQL APIs
A practical guide to OAuth 2.0 authentication for Sitecore’s Authoring and Management GraphQL APIs with production-tested patterns.
Start typing to search...
Building applications with Sitecore XM Cloud requires a solid understanding of authentication and API integration. While Sitecore provides powerful GraphQL APIs for content management and workflow automation, the authentication layer can be confusing for developers new to the platform. The key challenge lies in understanding that Sitecore offers two distinct APIs—Authoring and Management—that share the same GraphQL endpoint but require different OAuth credentials with different permission scopes.
This guide provides a practical, production-tested approach to implementing OAuth 2.0 authentication for both Sitecore APIs. You'll learn how to build reusable authentication services with intelligent token caching to avoid rate limits, create a base client architecture that handles both content operations and workflow commands, and implement proper error handling patterns. Whether you're building a content approval system, automating workflow processes, or integrating Sitecore with external applications, this guide will give you the foundation you need to connect reliably and securely to Sitecore XM Cloud.
Critical Finding: Both APIs use the same GraphQL endpoint. The difference is in the OAuth credentials and their associated scopes.
// Both use the same endpoint URL
const endpoint = "https://xmc-[your-instance].sitecorecloud.io/sitecore/api/authoring/graphql/v1";
| Feature | Authoring API | Management API |
|---|---|---|
| OAuth Scope | Content authoring | xmcloud.cm:admin |
| Use Cases | Create/update content items | Workflow commands, system operations |
| Authentication | sc_apikey OR OAuth Bearer |
OAuth Bearer (required) |
| Typical Operations | createItem, updateItem |
executeWorkflowCommand |
When to Use:
# Authoring API (Content Operations)SITECORE_CLIENT_ID=your_authoring_client_id
SITECORE_CLIENT_SECRET=your_authoring_client_secret
SITECORE_AUTH_ENDPOINT=https://auth.sitecorecloud.io/oauth/token
SITECORE_AUDIENCE=https://api.sitecorecloud.io
# Management API (Workflow Operations)SITECORE_MANAGEMENT_CLIENT_ID=your_management_client_id
SITECORE_MANAGEMENT_CLIENT_SECRET=your_management_client_secret
SITECORE_MANAGEMENT_AUTH_ENDPOINT=https://auth.sitecorecloud.io/oauth/token
SITECORE_MANAGEMENT_AUDIENCE=https://api.sitecorecloud.io
# GraphQL Endpoints (Same for Both!)SITECORE_AUTHORING_ENDPOINT=https://xmc-[instance].sitecorecloud.io/sitecore/api/authoring/graphql/v1
SITECORE_MANAGEMENT_ENDPOINT=https://xmc-[instance].sitecorecloud.io/sitecore/api/authoring/graphql/v1
# API Key (For Read-Only Queries)SITECORE_API_KEY=your_api_key
File: src/lib/sitecore-auth.ts
Purpose: Handles OAuth token fetching and caching for Sitecore Authoring API. This service manages content operation authentication (create, update, delete items) and automatically refreshes tokens before they expire.
interface SitecoreOAuthToken {
access_token: string;
scope: string;
expires_in: number;
token_type: string;
}
interface CachedToken {
token: string;
expiresAt: number;
}
class SitecoreAuthService {
private cachedToken: CachedToken | null = null;
private clientId: string;
private clientSecret: string;
private authEndpoint: string;
private audience: string;
constructor() {
this.clientId = process.env.SITECORE_CLIENT_ID || "";
this.clientSecret = process.env.SITECORE_CLIENT_SECRET || "";
this.authEndpoint = process.env.SITECORE_AUTH_ENDPOINT ||
"https://auth.sitecorecloud.io/oauth/token";
this.audience = process.env.SITECORE_AUDIENCE ||
"https://api.sitecorecloud.io";
if (!this.clientId || !this.clientSecret) {
throw new Error("Sitecore OAuth credentials must be configured");
}
}
async getAccessToken(): Promise<string> {
// Return cached token if still valid
if (this.cachedToken && this.cachedToken.expiresAt > Date.now()) {
return this.cachedToken.token;
}
// Fetch new token
const response = await fetch(this.authEndpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: this.clientId,
client_secret: this.clientSecret,
audience: this.audience,
grant_type: "client_credentials",
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`OAuth request failed: ${response.status} - ${errorText}`);
}
const tokenData: SitecoreOAuthToken = await response.json();
// Cache with 5-minute buffer before expiry
this.cachedToken = {
token: tokenData.access_token,
expiresAt: Date.now() + (tokenData.expires_in - 300) * 1000,
};
return tokenData.access_token;
}
clearCache(): void {
this.cachedToken = null;
}
}
export const sitecoreAuthService = new SitecoreAuthService();
Key Features:
File: src/lib/sitecore-management-auth.ts
Purpose: Handles OAuth token fetching and caching for Sitecore Management API. This service manages workflow operation authentication (execute workflow commands, advance workflow states) with admin-level permissions.
class SitecoreManagementAuthService {
private cachedToken: CachedToken | null = null;
private clientId: string;
private clientSecret: string;
private authEndpoint: string;
private audience: string;
constructor() {
this.clientId = process.env.SITECORE_MANAGEMENT_CLIENT_ID || "";
this.clientSecret = process.env.SITECORE_MANAGEMENT_CLIENT_SECRET || "";
this.authEndpoint = process.env.SITECORE_MANAGEMENT_AUTH_ENDPOINT ||
"https://auth.sitecorecloud.io/oauth/token";
this.audience = process.env.SITECORE_MANAGEMENT_AUDIENCE ||
"https://api.sitecorecloud.io";
if (!this.clientId || !this.clientSecret) {
throw new Error("Management API OAuth credentials must be configured");
}
}
async getAccessToken(): Promise<string> {
if (this.cachedToken && this.cachedToken.expiresAt > Date.now()) {
return this.cachedToken.token;
}
const response = await fetch(this.authEndpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: this.clientId,
client_secret: this.clientSecret,
audience: this.audience,
grant_type: "client_credentials",
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Management API OAuth failed: ${response.status} - ${errorText}`);
}
const tokenData: SitecoreOAuthToken = await response.json();
this.cachedToken = {
token: tokenData.access_token,
expiresAt: Date.now() + (tokenData.expires_in - 300) * 1000,
};
return tokenData.access_token;
}
clearCache(): void {
this.cachedToken = null;
}
}
export const sitecoreManagementAuthService = new SitecoreManagementAuthService();
File: src/lib/sitecore/base-client.ts
Purpose: Provides a reusable foundation for all Sitecore operations. This abstract class handles GraphQL queries (with API key) and mutations (with OAuth), automatically selecting the correct authentication method and endpoint based on operation type.
import { sitecoreAuthService } from "../sitecore-auth";
import { sitecoreManagementAuthService } from "../sitecore-management-auth";
export abstract class BaseSitecoreClient {
protected endpoint?: string;
protected apiKey?: string;
protected authoringEndpoint?: string;
protected managementEndpoint?: string;
protected initialized = false;
protected initialize() {
if (this.initialized) return;
this.endpoint = process.env.SITECORE_GRAPHQL_ENDPOINT || "";
this.apiKey = process.env.SITECORE_API_KEY || "";
this.authoringEndpoint = process.env.SITECORE_AUTHORING_ENDPOINT || this.endpoint;
this.managementEndpoint = process.env.SITECORE_MANAGEMENT_ENDPOINT || this.endpoint;
this.initialized = true;
if (!this.endpoint || !this.apiKey) {
throw new Error("Sitecore GraphQL endpoint and API key must be configured");
}
}
/**
* Execute GraphQL query (read-only with API key)
*/
async query<T = unknown>(
query: string,
variables?: Record<string, unknown>
): Promise<T> {
this.initialize();
const response = await fetch(this.endpoint!, {
method: "POST",
headers: {
"Content-Type": "application/json",
sc_apikey: this.apiKey!,
},
body: JSON.stringify({ query, variables }),
});
if (!response.ok) {
throw new Error(`GraphQL request failed: ${response.status}`);
}
const result = await response.json();
if (result.errors) {
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
}
return result;
}
/**
* Execute GraphQL mutation with OAuth authentication
* @param useManagementAPI - true for workflow operations
*/
protected async mutateWithAuth(
mutation: string,
variables: Record<string, unknown>,
operationName?: string,
useManagementAPI = false
): Promise<unknown> {
this.initialize();
// Get appropriate token
const accessToken = useManagementAPI
? await sitecoreManagementAuthService.getAccessToken()
: await sitecoreAuthService.getAccessToken();
const endpoint = useManagementAPI
? this.managementEndpoint
: this.authoringEndpoint;
const response = await fetch(endpoint!, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ query: mutation, variables, operationName }),
});
if (!response.ok) {
throw new Error(`Mutation failed: ${response.status}`);
}
const result = await response.json();
if (result.errors) {
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
}
return result;
}
}
File: src/lib/sitecore/content-operations.ts
Purpose: Demonstrates practical implementation of content and workflow operations. This example shows how to extend the base client to perform real operations: updating item fields (Authoring API) and executing workflow commands (Management API).
import { BaseSitecoreClient } from "./base-client";
export class ContentOperations extends BaseSitecoreClient {
/**
* Update an existing content item
*/
async updateItem(itemId: string, fields: Array<{ name: string; value: string }>) {
const mutation = `
mutation UpdateItem($itemId: ID!, $fields: [ItemFieldInput!]!) {
updateItem(input: { itemId: $itemId, fields: $fields }) {
item {
itemId
name
}
}
}
`;
const variables = {
itemId: this.formatGuid(itemId),
fields,
};
// Uses Authoring API (useManagementAPI = false)
const result = await this.mutateWithAuth(
mutation,
variables,
"UpdateItem",
false
);
return result;
}
/**
* Execute workflow command
*/
async executeWorkflowCommand(itemId: string, commandId: string) {
const mutation = `
mutation ExecuteWorkflowCommand($item: ItemQueryInput!, $commandId: String!) {
executeWorkflowCommand(input: { item: $item, commandId: $commandId }) {
successful
message
}
}
`;
const variables = {
item: { itemId: this.formatGuid(itemId) },
commandId,
};
// Uses Management API (useManagementAPI = true)
const result = await this.mutateWithAuth(
mutation,
variables,
"ExecuteWorkflowCommand",
true // ⚠️ Critical: Use Management API for workflow
);
return result;
}
private formatGuid(guid: string): string {
const clean = guid.replace(/[-{}\s]/g, "").toUpperCase();
const formatted = clean.replace(
/^(.{8})(.{4})(.{4})(.{4})(.{12})$/,
"$1-$2-$3-$4-$5"
);
return `{${formatted}}`;
}
}
Usage:
const contentOps = new ContentOperations();
// Update item fields (Authoring API)
await contentOps.updateItem(
"110EC58A-A0F2-4AC4-8393-C866D813B8D1",
[
{ name: "Title", value: "Updated Title" },
{ name: "Content", value: "Updated content..." },
]
);
// Advance workflow (Management API)
await contentOps.executeWorkflowCommand(
"110EC58A-A0F2-4AC4-8393-C866D813B8D1",
"{WORKFLOW-COMMAND-GUID}"
);
Always cache tokens to avoid rate limits:
// ❌ WRONG - Fetches token every time
async function doOperation() {
const token = await fetchNewToken();
// ... use token
}
// ✅ CORRECT - Uses cached token
async function doOperation() {
const token = await authService.getAccessToken(); // Returns cached if valid
// ... use token
}
Sitecore requires GUIDs with braces and hyphens:
// ❌ WRONG
const itemId = "110EC58AA0F24AC48393C866D813B8D1";
// ✅ CORRECT
const itemId = "{110EC58A-A0F2-4AC4-8393-C866D813B8D1}";
// ❌ WRONG - Won't work for workflow
await this.mutateWithAuth(workflowMutation, variables, "AdvanceWorkflow", false);
// ✅ CORRECT - Use Management API
await this.mutateWithAuth(workflowMutation, variables, "AdvanceWorkflow", true);
try {
const result = await contentOps.updateItem(itemId, fields);
return { success: true, data: result };
} catch (error) {
console.error(`Failed to update item ${itemId}:`, error);
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error"
};
}
Use pipe separator for multi-reference fields:
// ✅ CORRECT - Pipe-separated GUIDs with braces
const approvers = [
"{110EC58A-A0F2-4AC4-8393-C866D813B8D1}",
"{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
].join("|");
// Result: "{GUID1}|{GUID2}"
Problem: Using expired tokens causes 401 errors.
Solution: Implement token caching with automatic refresh.
Problem: Using Authoring API for workflow operations fails.
Solution: Use useManagementAPI = true for workflow commands.
Problem: Sitecore rejects improperly formatted GUIDs.
Solution: Always format as {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}.
Problem: Too many OAuth requests trigger rate limits.
Solution: Cache tokens with 5-minute buffer before expiry.
Problem: Generic errors make debugging difficult.
Solution: Include operation context, itemId, and timestamp in error logs.
File: src/pages/api/debug/test-auth.ts
Purpose: Validates that both OAuth services are configured correctly and can fetch tokens. This test endpoint verifies your Authoring and Management API credentials without making actual GraphQL requests to Sitecore.
import { NextApiRequest, NextApiResponse } from "next";
import { sitecoreAuthService } from "@/lib/sitecore-auth";
import { sitecoreManagementAuthService } from "@/lib/sitecore-management-auth";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const results = {
authoring: { success: false, error: "" },
management: { success: false, error: "" },
};
// Test Authoring API
try {
const token = await sitecoreAuthService.getAccessToken();
results.authoring = {
success: true,
tokenLength: token.length
};
} catch (error) {
results.authoring = {
success: false,
error: error.message
};
}
// Test Management API
try {
const token = await sitecoreManagementAuthService.getAccessToken();
results.management = {
success: true,
tokenLength: token.length
};
} catch (error) {
results.management = {
success: false,
error: error.message
};
}
const allPassed = results.authoring.success && results.management.success;
res.status(allPassed ? 200 : 500).json({
success: allPassed,
results,
message: allPassed ? "✅ All tests passed!" : "❌ Some tests failed",
});
}
Test it:
curl http://localhost:3001/api/debug/test-auth
Successfully connecting to Sitecore XM Cloud's APIs requires understanding a few critical concepts. The most important discovery is that both the Authoring and Management APIs use the same GraphQL endpoint—the difference lies entirely in the OAuth credentials and their permission scopes. This means you can reuse your connection infrastructure while simply swapping authentication tokens based on the operation type.
Three things are essential for production: token caching with a 5-minute buffer to avoid rate limits, precise GUID formatting with braces and hyphens {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}, and choosing the right API (Authoring for content operations, Management for workflow commands). The architecture presented here—with separate authentication services and a reusable base client—has been tested in production environments handling thousands of requests daily. Follow these patterns, avoid the common pitfalls, and you'll build a robust, maintainable integration.