Documentation Index
Fetch the complete documentation index at: https://docs.gradial.com/llms.txt
Use this file to discover all available pages before exploring further.
Gradial connects to Sitecore XP by running a lightweight Service Gateway module inside your CM’s IIS process. The gateway exposes a secured REST API at /_api/gradial/* that Gradial agents call to read items, apply edits, and trigger publishing — all without VPN tunnels, additional servers, or schema changes to your Sitecore databases.
Authentication uses short-lived JWT bearer tokens issued by your existing OIDC provider (Sitecore Identity Server or a third-party IdP like Azure AD, Okta, or Keycloak). Gradial exchanges a client secret for a token on every request, so no long-lived credentials are stored on the CM server.
This page covers the on-premises / managed Sitecore XP integration. For Sitecore XM Cloud or Content Hub, see the Sitecore overview.
What you’ll need
- A Sitecore XP 10.0–10.4 CM instance reachable over HTTPS
- Administrator access to the CM to install files and create a service user
- Access to your OIDC provider (Sitecore Identity Server or equivalent) to register a client
- A Gradial account with permission to create integrations under Settings → Integrations
The setup takes around 30 minutes for a straightforward deployment. Allow extra time if your environment uses split-horizon networking or a non-standard identity provider.
Steps in this guide
| Section | What you’ll do |
|---|
| 1. Install the Service Gateway | Copy two DLLs and a config patch into the CM webroot |
| 2. Register the OIDC client | Create an API resource, scope, and client-credentials client in your IdP |
| 3. Service user permissions | Create a Sitecore user and grant item-level rights on the content tree |
| 4. Configuration reference | Full reference for every gateway setting |
| 5. Create the integration in Gradial | Enter the connection details and credentials in Gradial to go live |
When you are done, Gradial talks to your CM over HTTPS at /_api/gradial/*, authenticated by short-lived JWT bearer tokens issued by your OIDC provider via the client-credentials grant.
1. Install the Service Gateway module
The gateway ships as two DLLs and a config patch. It runs in-process inside the Sitecore CM IIS app domain — no separate service, no database changes, no schema updates.
Supported versions: Sitecore XP 10.0, 10.1, 10.2, 10.3, 10.4 (CM role).
Stop the CM application pool
Stop the Sitecore CM application pool before copying files.
Unzip the release archive
Unzip Gradial.SitecoreXP.Gateway-<version>-sc10.zip into the Sitecore CM webroot (e.g. C:\inetpub\wwwroot\<instance>\). The archive mirrors the webroot layout:
bin\Gradial.SitecoreXP.Gateway.Core.dll
bin\Gradial.SitecoreXP.Gateway.Sitecore10.dll
bin\Swashbuckle.Core.dll
App_Config\Include\Gradial\Gradial.CmGateway.config
Edit the config patch
Open App_Config\Include\Gradial\Gradial.CmGateway.config and set at minimum:
Gradial.Identity.Authority — your OIDC issuer URL
Gradial.Identity.ExpectedAudience — the JWT aud claim Gradial’s token will carry
Gradial.Identity.MetadataAddress — base URL the gateway uses to fetch the OIDC discovery document (the gateway appends /.well-known/openid-configuration itself). Leave empty unless your CM cannot reach the same URL as Authority — see Split-horizon networks
Gradial.Security.AllowedDatabases — usually master
Create the service user
Create the Sitecore service user referenced by Gradial.Sitecore.ServiceUser (default: sitecore\gradial-service) and grant the rights described in Section 3. Start the CM application pool
Start the CM application pool and verify the gateway is running:GET https://<cm-host>/_api/gradial/health → 200 OK
If you get 404, the gateway did not initialize — check the Sitecore log for [Gradial.Gateway] entries during app pool startup.
Uninstall
Remove bin\Gradial.SitecoreXP.Gateway.*.dll, bin\Swashbuckle.Core.dll, and the entire App_Config\Include\Gradial\ folder, then restart the CM application pool.
2. Register Gradial as an OIDC client
The gateway is an OAuth 2.0 resource server — it validates JWTs but never issues them. Token issuance is the job of your OIDC provider. You need to register three things there:
| Concept | Purpose |
|---|
| API resource (audience) | Identifies the gateway as a protected resource. The token’s aud claim must match. |
| API scope | Permission name the caller must request to get a usable token. |
| Client | Gradial’s identity. Has a client_id + client_secret and is authorized for the scope above. |
Gradial requests tokens with the client credentials grant and sends them on the Authorization: Bearer <token> header of every request.
How the values map across the three sides
OIDC Provider Gateway config (Gradial.CmGateway.config)
───────────── ──────────────────────────────────────────
API resource "gradial.gateway" ───► Gradial.Identity.ExpectedAudience
API scope "gradial.cm.gateway" ───► Gradial.Identity.RequiredScopes
Client id "gradial" ───► Gradial.Identity.AllowedClientIds
Issuer URL https://id.example ───► Gradial.Identity.Authority
Discovery base URL ───► Gradial.Identity.MetadataAddress
(gateway appends /.well-known/openid-configuration;
leave empty to reuse Authority)
All four values must agree on both sides, or the gateway rejects tokens with 401.
Reference: Sitecore Identity Server
Sitecore Identity Server is based on IdentityServer4 and accepts XML patch files mounted into its container. A complete Gradial registration:
<?xml version="1.0" encoding="utf-8"?>
<Settings>
<Sitecore>
<IdentityServer>
<!-- The audience. Gateway's ExpectedAudience must equal Name. -->
<ApiResources>
<GradialGatewayResource>
<Name>gradial.gateway</Name>
<DisplayName>Gradial CM Gateway</DisplayName>
<Scopes>
<Scope1>gradial.cm.gateway</Scope1>
</Scopes>
</GradialGatewayResource>
</ApiResources>
<!-- The scope. Gateway's RequiredScopes must contain Name. -->
<ApiScopes>
<GradialCmGatewayScope>
<Name>gradial.cm.gateway</Name>
<DisplayName>Gradial CM Gateway Access</DisplayName>
</GradialCmGatewayScope>
</ApiScopes>
<!-- One client per calling tool. Gateway's AllowedClientIds must contain ClientId. -->
<Clients>
<GradialClient>
<ClientId>gradial</ClientId>
<ClientName>Gradial Gateway Client</ClientName>
<AccessTokenType>0</AccessTokenType>
<AccessTokenLifetimeInSeconds>3600</AccessTokenLifetimeInSeconds>
<RequireClientSecret>true</RequireClientSecret>
<AllowOfflineAccess>false</AllowOfflineAccess>
<AllowedGrantTypes>
<AllowedGrantType1>client_credentials</AllowedGrantType1>
</AllowedGrantTypes>
<ClientSecrets>
<ClientSecret1>$(env:GRADIAL_CLIENT_SECRET)</ClientSecret1>
</ClientSecrets>
<AllowedScopes>
<AllowedScope1>gradial.cm.gateway</AllowedScope1>
</AllowedScopes>
</GradialClient>
</Clients>
</IdentityServer>
</Sitecore>
</Settings>
Place this in the Identity container’s config directory (typically Config/production/ or via a mounted volume) and restart the container.
If you use a different OIDC provider (Azure AD, Okta, Auth0, Keycloak, etc.), the concepts are identical — register an API resource, an API scope, and a client-credentials client, then map the four values into the gateway’s config.
Split-horizon networks (internal vs. public issuer)
MetadataAddress exists for the case where the ID server has a different internal origin than the JWT’s public issuer URL. In simple deployments where the CM can reach the same URL clients use, leave MetadataAddress empty and the gateway will derive everything from Authority.
This split is common in containerized deployments. For example:
- The Sitecore Identity container is reachable from the CM container at
http://id (an internal Docker DNS name, plain HTTP).
- The same Identity service is reachable from the public internet at
https://id.example.com (TLS, public DNS).
- Tokens issued to Gradial carry
iss: https://id.example.com because that’s the issuer the public clients trust.
In this case:
| Setting | Value | Why |
|---|
Gradial.Identity.Authority | https://id.example.com | Must match the JWT’s iss claim. The gateway compares this string to the token’s issuer during validation. |
Gradial.Identity.MetadataAddress | http://id | Base URL the gateway uses to fetch the discovery document and signing keys. Use the internal hostname the CM container can actually reach. The gateway appends /.well-known/openid-configuration automatically — do not include that suffix here. |
Gradial.Identity.DiscoveryRequireHttps | false | Required when MetadataAddress is plain HTTP. Leave true when both URLs are HTTPS. |
Sitecore patch form for MetadataAddress:
<setting name="Gradial.Identity.MetadataAddress">
<patch:attribute name="value">http://id</patch:attribute>
</setting>
Never set Authority to the internal URL. The gateway validates the token’s iss claim against Authority, and the issuer baked into the token is the public URL — using the internal URL there will fail every token with a 401.
Generating the client secret
Use 32 bytes (256 bits) of cryptographic randomness, base64-encoded.
PowerShell:
[Convert]::ToBase64String([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32))
OpenSSL:
Do not use GUIDs, Get-Random, or human-friendly password generators — they have insufficient entropy.
Store the plaintext secret in a secret manager. Inject it into the Identity container as an environment variable (the sample above reads $(env:GRADIAL_CLIENT_SECRET)). Provide the same plaintext value to Gradial when you create the integration.
Rotating the secret
- Generate a new secret.
- Add it as
<ClientSecret2> alongside the existing one — IdentityServer4 accepts any configured secret, so old and new both work during rollover.
- Update the integration in Gradial to use the new secret.
- Once traffic has migrated, remove the old
<ClientSecret1> and restart the Identity container.
End-to-end smoke test
$body = @{
client_id = "gradial"
client_secret = $env:GRADIAL_CLIENT_SECRET
grant_type = "client_credentials"
scope = "gradial.cm.gateway"
}
$token = (Invoke-RestMethod `
-Uri "https://<your-id-host>/connect/token" `
-Method Post `
-Body $body).access_token
Invoke-RestMethod `
-Uri "https://<your-cm-host>/_api/gradial/whoami" `
-Headers @{ Authorization = "Bearer $token" }
A successful /whoami response returns the client identity extracted from the token.
3. Service user permissions
The gateway executes every Sitecore operation as the configured service user (Gradial.Sitecore.ServiceUser, default sitecore\gradial-service). The OIDC client identity authorizes Gradial to call the gateway; the Sitecore user is what actually reads and writes content in Sitecore. Sitecore’s audit trail records this user, not the JWT caller — the gateway logs the JWT caller separately, with a correlation ID joining the two streams.
Recommended rights
Grant the minimum item-level rights for the content roots the integration will operate against. Replace [TENANT] and [SITE] with your actual values; repeat for every tenant/site you want to expose.
| Path | Rights | Notes |
|---|
/sitecore/content/[TENANT]/[SITE] | Read, Write, Create, Delete, Rename | Content tree the integration will manage. |
/sitecore/media library/Project/[TENANT]/[SITE] | Read, Write, Create, Delete, Rename | Media items associated with the same site. |
/sitecore/templates | Read | Required so the gateway can resolve field definitions when reading or creating items. Do not grant Write — content operations should never mutate templates. |
Apply rights with inheritance (“Descendants”) so child items inherit them. Use the Sitecore Security Editor (Desktop → Security Tools → Security Editor), or assign the user to a role that carries these rights.
Path-level access control
Path-level access control is enforced by:
- The Sitecore ACLs above on the service user — anything outside the granted scope returns
403 from the underlying SDK.
- The consuming application’s allowed-paths configuration (set in Gradial).
Database-level access is enforced separately by the Gradial.Security.AllowedDatabases setting. Requests targeting a database outside the allowlist are rejected before any Sitecore call is made. The setting fails closed — an empty allowlist denies all requests.
4. Module configuration reference
All settings live in App_Config\Include\Gradial\Gradial.CmGateway.config. Most defaults are correct out of the box; only the identity values are required.
Module
| Setting | Default | Description |
|---|
Gradial.Enabled | true | Master switch. When false, the initializer skips route registration and the gateway is dormant. |
Identity (OIDC / JWT validation)
| Setting | Default | Description |
|---|
Gradial.Identity.Authority | (empty — required) | OIDC issuer URL. The token’s iss claim must match. |
Gradial.Identity.ExpectedAudience | (empty — required) | Value the token’s aud claim must equal. |
Gradial.Identity.ValidateAudience | true | Disable only for diagnostics. Keep true in production. |
Gradial.Identity.AllowedClientIds | gradial | Pipe-separated list of client IDs allowed to call the gateway (e.g. gradial|tool-b). The token’s client_id claim must appear here. |
Gradial.Identity.RequiredScopes | gradial.cm.gateway | Pipe-separated list of scopes the token must contain. |
Gradial.Identity.ClockSkewMinutes | 2 | Allowance for clock drift between the OIDC issuer and CM server. |
Gradial.Identity.ClientIdClaimType | client_id | Claim name used to read the client ID from the token. Override only if your provider emits a non-standard claim. |
Gradial.Identity.MetadataAddress | (empty) | Base URL for OIDC discovery and JWKS fetches. The gateway appends /.well-known/openid-configuration itself, so the value is just an origin (e.g. http://id) — not a full discovery URL. Leave empty to reuse Authority. Set this only when the CM cannot reach Authority directly; see Split-horizon networks. |
Gradial.Identity.DiscoveryRequireHttps | true | Enforces HTTPS for the discovery endpoint. Only set false for local development against a non-TLS Identity host. |
Sitecore execution context
| Setting | Default | Description |
|---|
Gradial.Sitecore.ServiceUser | sitecore\gradial-service | The Sitecore user every gateway operation runs as. Must exist before the app pool starts. See Section 3. |
Gradial.Sitecore.DefaultDatabase | master | Database used when a request omits ?db=. |
Security
| Setting | Default | Description |
|---|
Gradial.Security.AllowedDatabases | master | Pipe-separated allowlist of databases the gateway may touch (e.g. master|web). Fails closed — empty value denies all requests. |
Gradial.Security.DefaultFields | (empty) | Pipe-separated list of fields always returned in item responses. Empty means “all fields except those denied”. |
Gradial.Security.DeniedFields | __Security|__Owner|__Workflow state|__Lock|__Created by|__Updated by | Pipe-separated list of fields stripped from every response. |
Feature flags
| Setting | Default | Description |
|---|
Gradial.Features.Preview.Enabled | true | Enables the page preview rendering endpoints. Set false to disable preview entirely. |
Preview rendering (HTTP loopback)
The preview endpoint renders a page by issuing an HTTP request back to the CM. These settings control that loopback.
| Setting | Default | Description |
|---|
Gradial.Preview.CmBaseUrl | http://localhost | Base URL preview requests target. If empty, preview derives the base URL from the incoming gateway request’s scheme + host. |
Gradial.Preview.IgnoreCertificateErrors | false | When true, the loopback HTTP client accepts self-signed and invalid TLS certs. Only enable in non-production. |
Gradial.Preview.RenderTimeoutSeconds | 60 | Maximum time to wait for a loopback render before giving up. |
5. Create the integration in Gradial
With the gateway installed, the OIDC client registered, and the service user permissioned, you can now create the integration in Gradial.
Connection
| Field | Value |
|---|
| Integration name | A name for this connection (e.g. Acme Production CM). |
| Description (optional) | Free-text notes — environment, owner, ticket reference, etc. |
| Sitecore CM URL | Base URL of the CM instance, e.g. https://cm.example.com. Do not include /_api/gradial; Gradial appends that automatically. |
| Mark as production | Tick if this is a production CM. Gradial uses this flag to apply additional confirmation steps and audit emphasis. |
Credential
These are the values you produced in Section 2.
| Field | Value | Maps to |
|---|
| Client ID | The client registered in your OIDC provider (e.g. gradial). | <ClientId> and Gradial.Identity.AllowedClientIds. |
| Client Secret | Plaintext secret you generated (32 bytes, base64). Store securely — Gradial will not display it again after save. | <ClientSecret1>. |
| Token URL | Token endpoint of your OIDC provider, e.g. https://id.example.com/connect/token. | The token_endpoint published by the discovery document. |
| Scope | The scope the gateway requires. | Gradial.Identity.RequiredScopes (default gradial.cm.gateway). |
| Audience | The audience the gateway expects. | Gradial.Identity.ExpectedAudience (e.g. gradial.gateway). |
After saving, Gradial will fetch a token via the client-credentials grant and call /_api/gradial/health and /_api/gradial/whoami to verify the connection end-to-end.
Failure modes
| HTTP status | Likely cause |
|---|
401 Unauthorized (every request) | Authority mismatch, signing key not loadable, or ExpectedAudience doesn’t match the token’s aud claim. |
403 Forbidden with insufficient_scope | Token doesn’t include a scope listed in RequiredScopes. |
403 Forbidden with client not allowed | Client ID isn’t in AllowedClientIds. |
403 Forbidden on item operations | Service user lacks the required rights on the requested path. Review Section 3. |
503 Service Unavailable on authenticated endpoints | Authority is empty or the gateway can’t reach the discovery document at MetadataAddress. |
404 Not Found on /_api/gradial/* | Gateway didn’t initialize. Check the Sitecore log for [Gradial.Gateway] errors during app pool startup. |
All gateway log entries are tagged [Gradial.Gateway] and include a correlation ID. When opening a support case, include the correlation ID from the failed request’s X-Correlation-Id response header.