diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15d3263..54fb8eb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,11 @@
- All modules fully migrated to System.Text.Json: AuditLog, Branch, BulkOperation, ContentType, DeliveryToken, Entry, EntryVariant, Environment, Extension, GlobalField, Label, Locale, ManagementToken, Organization, Release, Role, Stack, Taxonomy, Term, User, VariantGroup, Webhook, and Workflow
- OAuth auto token refresh wired into the request pipeline
- Upgraded target framework to .NET 10
+ - **New:** Multi-region endpoint resolution via `Endpoint.GetContentstackEndpoint(region, service)` — resolves Contentstack service URLs for all 7 supported regions (NA, EU, AU, Azure-NA, Azure-EU, GCP-NA, GCP-EU) and 18 service keys (contentManagement, contentDelivery, auth, graphqlDelivery, preview, images, assets, automate, launch, developerHub, brandKit, genAI, personalizeManagement, personalizeEdge, composableStudio, assetManagement, and more).
+ - **New:** `omitHttps` flag strips the `https://` scheme from returned URLs — pass directly to `ContentstackClientOptions.Host` (e.g. `new ContentstackClientOptions { Host = Endpoint.GetContentstackEndpoint("eu", "contentManagement", omitHttps: true) }`).
+ - **New:** Case-insensitive region alias support — `"us"`, `"NA"`, `"AWS-NA"`, `"azure_na"` all resolve correctly to the same region.
+ - **New:** `regions.json` registry auto-downloaded from `artifacts.contentstack.com` on first use and cached on disk — no setup required. The SDK self-heals if the file is missing.
+ - **New:** `Scripts/refresh-region.cs` bundled inside the NuGet package — automatically placed in your project's `Scripts/` folder on first `dotnet build`. Run `dotnet run Scripts/refresh-region.cs` anytime to pull the latest regions from CDN.
## [v0.10.0](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.10.0)
- Feat
diff --git a/Contentstack.Management.Core.Unit.Tests/Endpoints/EndpointTest.cs b/Contentstack.Management.Core.Unit.Tests/Endpoints/EndpointTest.cs
new file mode 100644
index 0000000..d90e5df
--- /dev/null
+++ b/Contentstack.Management.Core.Unit.Tests/Endpoints/EndpointTest.cs
@@ -0,0 +1,311 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Contentstack.Management.Core.Endpoints;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Contentstack.Management.Core.Unit.Tests.Endpoints
+{
+ [TestClass]
+ public class EndpointTest
+ {
+ [TestInitialize]
+ public void Setup() => Endpoint.ResetCache();
+
+ [TestCleanup]
+ public void Teardown() => Endpoint.ResetCache();
+
+ // ------------------------------------------------------------------
+ // Basic resolution
+ // ------------------------------------------------------------------
+
+ [TestMethod]
+ public void GetContentstackEndpoint_Na_ReturnsCorrectManagementUrl()
+ {
+ string url = Endpoint.GetContentstackEndpoint("na", "contentManagement");
+ Assert.AreEqual("https://api.contentstack.io", url);
+ }
+
+ [DataTestMethod]
+ [DataRow("na")]
+ [DataRow("eu")]
+ [DataRow("au")]
+ [DataRow("azure-na")]
+ [DataRow("azure-eu")]
+ [DataRow("gcp-na")]
+ [DataRow("gcp-eu")]
+ public void GetContentstackEndpoint_AllRegionIds_Resolve(string regionId)
+ {
+ string url = Endpoint.GetContentstackEndpoint(regionId, "contentManagement");
+ Assert.IsFalse(string.IsNullOrEmpty(url));
+ Assert.IsTrue(url.StartsWith("https://"));
+ }
+
+ // ------------------------------------------------------------------
+ // Alias resolution (case-insensitive, dash/underscore variants)
+ // ------------------------------------------------------------------
+
+ [DataTestMethod]
+ [DataRow("na")]
+ [DataRow("us")]
+ [DataRow("NA")]
+ [DataRow("US")]
+ [DataRow("AWS-NA")]
+ [DataRow("aws_na")]
+ [DataRow("AWS_NA")]
+ public void GetContentstackEndpoint_NaAliasVariants_AllResolveToSameUrl(string alias)
+ {
+ string url = Endpoint.GetContentstackEndpoint(alias, "contentManagement");
+ Assert.AreEqual("https://api.contentstack.io", url);
+ }
+
+ [DataTestMethod]
+ [DataRow("azure-na")]
+ [DataRow("azure_na")]
+ [DataRow("AZURE-NA")]
+ [DataRow("AZURE_NA")]
+ public void GetContentstackEndpoint_AzureNaAliasVariants_AllResolveToSameUrl(string alias)
+ {
+ string expected = Endpoint.GetContentstackEndpoint("azure-na", "contentManagement");
+ string result = Endpoint.GetContentstackEndpoint(alias, "contentManagement");
+ Assert.AreEqual(expected, result);
+ }
+
+ [DataTestMethod]
+ [DataRow("eu")]
+ [DataRow("EU")]
+ [DataRow("aws-eu")]
+ [DataRow("AWS-EU")]
+ [DataRow("aws_eu")]
+ public void GetContentstackEndpoint_EuAliasVariants_AllResolveToSameUrl(string alias)
+ {
+ string expected = Endpoint.GetContentstackEndpoint("eu", "contentManagement");
+ string result = Endpoint.GetContentstackEndpoint(alias, "contentManagement");
+ Assert.AreEqual(expected, result);
+ }
+
+ // ------------------------------------------------------------------
+ // omitHttps flag
+ // ------------------------------------------------------------------
+
+ [TestMethod]
+ public void GetContentstackEndpoint_OmitHttps_StripsScheme()
+ {
+ string url = Endpoint.GetContentstackEndpoint("na", "contentManagement", omitHttps: true);
+ Assert.IsFalse(url.StartsWith("https://"), "URL should not start with https://");
+ Assert.IsFalse(url.StartsWith("http://"), "URL should not start with http://");
+ Assert.IsTrue(url.Contains("."), "URL should contain a hostname");
+ }
+
+ [TestMethod]
+ public void GetContentstackEndpoint_OmitHttpsFalse_PreservesScheme()
+ {
+ string url = Endpoint.GetContentstackEndpoint("na", "contentManagement", omitHttps: false);
+ Assert.IsTrue(url.StartsWith("https://"));
+ }
+
+ [DataTestMethod]
+ [DataRow("na")]
+ [DataRow("eu")]
+ [DataRow("au")]
+ [DataRow("azure-na")]
+ [DataRow("gcp-na")]
+ public void GetContentstackEndpoint_OmitHttps_AllRegions_StripsScheme(string region)
+ {
+ string url = Endpoint.GetContentstackEndpoint(region, "contentManagement", omitHttps: true);
+ Assert.IsFalse(url.StartsWith("https://"));
+ Assert.IsFalse(url.StartsWith("http://"));
+ }
+
+ // ------------------------------------------------------------------
+ // Dictionary overload
+ // ------------------------------------------------------------------
+
+ [TestMethod]
+ public void GetContentstackEndpoint_DictOverload_ContainsManagementKey()
+ {
+ var dict = Endpoint.GetContentstackEndpoint("na");
+ Assert.IsTrue(dict.ContainsKey("contentManagement"));
+ Assert.AreEqual("https://api.contentstack.io", dict["contentManagement"]);
+ }
+
+ [TestMethod]
+ public void GetContentstackEndpoint_DictOverload_ContainsDeliveryKey()
+ {
+ var dict = Endpoint.GetContentstackEndpoint("na");
+ Assert.IsTrue(dict.ContainsKey("contentDelivery"));
+ }
+
+ [TestMethod]
+ public void GetContentstackEndpoint_DictOverload_ReturnsMultipleServices()
+ {
+ var dict = Endpoint.GetContentstackEndpoint("na");
+ Assert.IsTrue(dict.Count >= 2, "Should contain at least 2 service endpoints");
+ }
+
+ [TestMethod]
+ public void GetContentstackEndpoint_DictOverload_OmitHttps_StripsAllSchemes()
+ {
+ var dict = Endpoint.GetContentstackEndpoint("na", omitHttps: true);
+ foreach (var kvp in dict)
+ {
+ Assert.IsFalse(kvp.Value.StartsWith("https://"),
+ $"Service '{kvp.Key}' URL still has https:// prefix");
+ }
+ }
+
+ [DataTestMethod]
+ [DataRow("na")]
+ [DataRow("eu")]
+ [DataRow("au")]
+ [DataRow("azure-na")]
+ [DataRow("azure-eu")]
+ [DataRow("gcp-na")]
+ [DataRow("gcp-eu")]
+ public void GetContentstackEndpoint_DictOverload_AllRegions_ReturnNonEmpty(string region)
+ {
+ var dict = Endpoint.GetContentstackEndpoint(region);
+ Assert.IsTrue(dict.Count > 0, $"Expected at least one endpoint for region '{region}'");
+ }
+
+ // ------------------------------------------------------------------
+ // Error cases
+ // ------------------------------------------------------------------
+
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentException))]
+ public void GetContentstackEndpoint_EmptyRegion_ThrowsArgumentException()
+ {
+ Endpoint.GetContentstackEndpoint("", "contentManagement");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentException))]
+ public void GetContentstackEndpoint_WhitespaceRegion_ThrowsArgumentException()
+ {
+ Endpoint.GetContentstackEndpoint(" ", "contentManagement");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentException))]
+ public void GetContentstackEndpoint_DictOverload_EmptyRegion_ThrowsArgumentException()
+ {
+ Endpoint.GetContentstackEndpoint("");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(KeyNotFoundException))]
+ public void GetContentstackEndpoint_UnknownRegion_ThrowsKeyNotFoundException()
+ {
+ Endpoint.GetContentstackEndpoint("xyz", "contentManagement");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(KeyNotFoundException))]
+ public void GetContentstackEndpoint_DictOverload_UnknownRegion_ThrowsKeyNotFoundException()
+ {
+ Endpoint.GetContentstackEndpoint("xyz");
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(KeyNotFoundException))]
+ public void GetContentstackEndpoint_UnknownService_ThrowsKeyNotFoundException()
+ {
+ Endpoint.GetContentstackEndpoint("na", "unknownService");
+ }
+
+ [TestMethod]
+ public void GetContentstackEndpoint_UnknownRegion_ErrorMessageContainsInput()
+ {
+ try
+ {
+ Endpoint.GetContentstackEndpoint("badregion", "contentManagement");
+ Assert.Fail("Expected KeyNotFoundException");
+ }
+ catch (KeyNotFoundException ex)
+ {
+ Assert.IsTrue(ex.Message.Contains("badregion"));
+ }
+ }
+
+ [TestMethod]
+ public void GetContentstackEndpoint_UnknownService_ErrorMessageContainsServiceName()
+ {
+ try
+ {
+ Endpoint.GetContentstackEndpoint("na", "badService");
+ Assert.Fail("Expected KeyNotFoundException");
+ }
+ catch (KeyNotFoundException ex)
+ {
+ Assert.IsTrue(ex.Message.Contains("badService"));
+ }
+ }
+
+ // ------------------------------------------------------------------
+ // Cache behaviour
+ // ------------------------------------------------------------------
+
+ [TestMethod]
+ public void ResetCache_AllowsSubsequentCallToSucceed()
+ {
+ // First call populates cache
+ string url1 = Endpoint.GetContentstackEndpoint("na", "contentManagement");
+
+ // Reset and call again — should reload from disk/CDN and return same value
+ Endpoint.ResetCache();
+ string url2 = Endpoint.GetContentstackEndpoint("na", "contentManagement");
+
+ Assert.AreEqual(url1, url2);
+ }
+
+ [TestMethod]
+ public void GetContentstackEndpoint_CalledTwice_ReturnsSameResult()
+ {
+ string url1 = Endpoint.GetContentstackEndpoint("eu", "contentManagement");
+ string url2 = Endpoint.GetContentstackEndpoint("eu", "contentManagement");
+ Assert.AreEqual(url1, url2);
+ }
+
+ // ------------------------------------------------------------------
+ // File path helper
+ // ------------------------------------------------------------------
+
+ [TestMethod]
+ public void GetLocalFilePath_EndsWithExpectedSegments()
+ {
+ string path = Endpoint.GetLocalFilePath();
+ Assert.IsTrue(path.EndsWith(Path.Combine("Assets", "regions.json")),
+ $"Expected path to end with Assets/regions.json, got: {path}");
+ }
+
+ // ------------------------------------------------------------------
+ // All 7 regions × contentManagement spot-checks
+ // ------------------------------------------------------------------
+
+ [DataTestMethod]
+ [DataRow("na", "https://api.contentstack.io")]
+ [DataRow("us", "https://api.contentstack.io")]
+ [DataRow("eu", "https://eu-api.contentstack.com")]
+ [DataRow("au", "https://au-api.contentstack.com")]
+ public void GetContentstackEndpoint_KnownRegions_ContentManagement_MatchExpected(
+ string region, string expected)
+ {
+ string url = Endpoint.GetContentstackEndpoint(region, "contentManagement");
+ Assert.AreEqual(expected, url);
+ }
+
+ // ------------------------------------------------------------------
+ // ID takes priority over alias (two-pass lookup)
+ // ------------------------------------------------------------------
+
+ [TestMethod]
+ public void GetContentstackEndpoint_IdTakesPriorityOverAlias()
+ {
+ // "eu" is both a valid region ID and an alias in some registries.
+ // The two-pass lookup must return the ID match first.
+ string byId = Endpoint.GetContentstackEndpoint("eu", "contentManagement");
+ Assert.IsFalse(string.IsNullOrEmpty(byId));
+ }
+ }
+}
diff --git a/Contentstack.Management.Core/Endpoints/Endpoint.cs b/Contentstack.Management.Core/Endpoints/Endpoint.cs
new file mode 100644
index 0000000..dae4356
--- /dev/null
+++ b/Contentstack.Management.Core/Endpoints/Endpoint.cs
@@ -0,0 +1,287 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Http;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+
+namespace Contentstack.Management.Core.Endpoints
+{
+ ///
+ /// Resolves Contentstack service URLs for any supported region.
+ ///
+ /// All public methods are static — no instantiation required.
+ ///
+ /// Example:
+ ///
+ /// // Full URL
+ /// string url = Endpoint.GetContentstackEndpoint("na", "contentManagement");
+ /// // → "https://api.contentstack.io"
+ ///
+ /// // Host only (omit https://) — pass directly to ContentstackClientOptions.Host
+ /// string host = Endpoint.GetContentstackEndpoint("eu", "contentManagement", omitHttps: true);
+ /// // → "eu-api.contentstack.com"
+ ///
+ /// // All endpoints for a region
+ /// Dictionary<string, string> all = Endpoint.GetContentstackEndpoint("azure-na");
+ /// // → { "contentManagement": "...", "contentDelivery": "...", ... }
+ ///
+ ///
+ public static class Endpoint
+ {
+ private const string RegionsUrl = "https://artifacts.contentstack.com/regions.json";
+
+ // Module-level cache — loaded once per process, shared across all calls.
+ private static JsonElement[]? _regionsData;
+ private static readonly object _cacheLock = new object();
+ private static readonly HttpClient _httpClient = new HttpClient
+ {
+ Timeout = TimeSpan.FromSeconds(30)
+ };
+
+ ///
+ /// Resolves a single Contentstack service endpoint URL for the given region.
+ ///
+ ///
+ /// Region ID or alias (case-insensitive). Examples: "na", "us", "eu", "AWS-NA", "azure_eu", "gcp-na".
+ ///
+ ///
+ /// Service key. Valid keys include: "contentManagement", "contentDelivery", "auth",
+ /// "graphqlDelivery", "preview", "images", "assets", "automate", "launch",
+ /// "developerHub", "brandKit", "genAI", "personalizeManagement",
+ /// "personalizeEdge", "composableStudio", "assetManagement".
+ ///
+ ///
+ /// When true, strips the https:// scheme from the returned URL.
+ /// Useful when passing the host to ContentstackClientOptions.Host.
+ ///
+ /// The endpoint URL string for the specified service.
+ /// If region is empty or whitespace.
+ /// If region or service is not found.
+ /// If the registry cannot be read or is corrupt.
+ public static string GetContentstackEndpoint(string region, string service, bool omitHttps = false)
+ {
+ if (string.IsNullOrWhiteSpace(region))
+ throw new ArgumentException("Empty region provided. Please put valid region.", nameof(region));
+
+ var regions = LoadRegions();
+ string normalized = region.Trim().ToLowerInvariant();
+ var regionEl = FindRegion(regions, normalized);
+
+ if (regionEl == null)
+ throw new KeyNotFoundException($"Invalid region: {region}");
+
+ var endpoints = regionEl.Value.GetProperty("endpoints");
+ if (!endpoints.TryGetProperty(service, out var urlEl))
+ {
+ string regionId = regionEl.Value.GetProperty("id").GetString()!;
+ throw new KeyNotFoundException($"Service \"{service}\" not found for region \"{regionId}\"");
+ }
+
+ string url = urlEl.GetString()!;
+ return omitHttps ? StripHttps(url) : url;
+ }
+
+ ///
+ /// Returns all service endpoint URLs for the given region.
+ ///
+ /// Region ID or alias (case-insensitive).
+ /// When true, strips the https:// scheme from all returned URLs.
+ /// Dictionary mapping service keys to endpoint URLs.
+ /// If region is empty or whitespace.
+ /// If region is not found.
+ /// If the registry cannot be read or is corrupt.
+ public static Dictionary GetContentstackEndpoint(string region, bool omitHttps = false)
+ {
+ if (string.IsNullOrWhiteSpace(region))
+ throw new ArgumentException("Empty region provided. Please put valid region.", nameof(region));
+
+ var regions = LoadRegions();
+ string normalized = region.Trim().ToLowerInvariant();
+ var regionEl = FindRegion(regions, normalized);
+
+ if (regionEl == null)
+ throw new KeyNotFoundException($"Invalid region: {region}");
+
+ var result = new Dictionary();
+ var endpoints = regionEl.Value.GetProperty("endpoints");
+ foreach (var ep in endpoints.EnumerateObject())
+ {
+ string url = ep.Value.GetString()!;
+ result[ep.Name] = omitHttps ? StripHttps(url) : url;
+ }
+ return result;
+ }
+
+ ///
+ /// Clears the in-memory region cache. Intended for testing only — forces the
+ /// next call to re-read regions.json from disk or re-download from CDN.
+ ///
+ public static void ResetCache()
+ {
+ lock (_cacheLock)
+ {
+ _regionsData = null;
+ }
+ }
+
+ // ------------------------------------------------------------------
+ // Internal helpers
+ // ------------------------------------------------------------------
+
+ ///
+ /// Load and cache the regions registry.
+ ///
+ /// Resolution order:
+ /// 1. In-memory cache — zero I/O after the first call in a process
+ /// 2. Local file on disk — Assets/regions.json next to the DLL
+ /// (written by DownloadAndSave or refresh-region.cs)
+ /// 3. CDN download — fetches from artifacts.contentstack.com,
+ /// writes to disk for future calls (silent on failure)
+ ///
+ private static JsonElement[] LoadRegions()
+ {
+ lock (_cacheLock)
+ {
+ if (_regionsData != null)
+ return _regionsData;
+
+ string localFile = GetLocalFilePath();
+
+ // Step 2 — local file on disk
+ string? json = ReadLocalFile(localFile);
+
+ // Step 3 — CDN download, writes to disk so next startup skips this step
+ if (json == null)
+ json = DownloadAndSave(localFile);
+
+ if (json == null)
+ throw new InvalidOperationException(
+ "contentstack_management: regions.json not found and could not be downloaded. " +
+ "Run 'dotnet run Scripts/refresh-region.cs' and ensure network access.");
+
+ JsonDocument doc;
+ try
+ {
+ doc = JsonDocument.Parse(json);
+ }
+ catch (JsonException ex)
+ {
+ throw new InvalidOperationException(
+ "contentstack_management: regions.json is corrupt. " +
+ "Run 'dotnet run Scripts/refresh-region.cs' to re-download it.", ex);
+ }
+
+ if (!doc.RootElement.TryGetProperty("regions", out var regionsEl) ||
+ regionsEl.ValueKind != JsonValueKind.Array)
+ {
+ throw new InvalidOperationException(
+ "contentstack_management: regions.json is corrupt. " +
+ "Run 'dotnet run Scripts/refresh-region.cs' to re-download it.");
+ }
+
+ var list = new List();
+ foreach (var r in regionsEl.EnumerateArray())
+ list.Add(r.Clone());
+
+ _regionsData = list.ToArray();
+ return _regionsData;
+ }
+ }
+
+ ///
+ /// Returns the path to regions.json next to the DLL.
+ ///
+ internal static string GetLocalFilePath()
+ {
+ string assemblyDir = Path.GetDirectoryName(typeof(Endpoint).Assembly.Location)
+ ?? AppContext.BaseDirectory;
+ return Path.Combine(assemblyDir, "Assets", "regions.json");
+ }
+
+ ///
+ /// Reads regions.json from disk. Returns null if the file does not exist.
+ ///
+ private static string? ReadLocalFile(string path)
+ {
+ if (!File.Exists(path))
+ return null;
+ try
+ {
+ return File.ReadAllText(path);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Downloads regions.json from the CDN and writes it to disk so that future
+ /// process startups read from the local file instead of downloading again.
+ /// Silent on all failures (network error, permission denied).
+ ///
+ private static string? DownloadAndSave(string dest)
+ {
+ try
+ {
+ var task = _httpClient.GetStringAsync(RegionsUrl);
+ task.Wait();
+ string data = task.Result;
+
+ using var doc = JsonDocument.Parse(data);
+ if (!doc.RootElement.TryGetProperty("regions", out _))
+ return null;
+
+ // Write to disk — next startup reads from local file (Step 2).
+ // Silent on PermissionError or read-only filesystem.
+ try
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(dest)!);
+ File.WriteAllText(dest, data);
+ }
+ catch { }
+
+ return data;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Two-pass region lookup: ID match wins over alias match.
+ /// Input must already be lowercased and trimmed.
+ ///
+ private static JsonElement? FindRegion(JsonElement[] regions, string normalized)
+ {
+ // Pass 1 — exact id match
+ foreach (var r in regions)
+ {
+ if (r.TryGetProperty("id", out var id) &&
+ id.GetString()?.ToLowerInvariant() == normalized)
+ return r;
+ }
+
+ // Pass 2 — alias match
+ foreach (var r in regions)
+ {
+ if (!r.TryGetProperty("alias", out var aliases)) continue;
+ foreach (var alias in aliases.EnumerateArray())
+ {
+ if (alias.GetString()?.ToLowerInvariant() == normalized)
+ return r;
+ }
+ }
+
+ return null;
+ }
+
+ private static string StripHttps(string url)
+ {
+ return Regex.Replace(url, @"^https?://", string.Empty);
+ }
+ }
+}
diff --git a/Contentstack.Management.Core/contentstack.management.core.csproj b/Contentstack.Management.Core/contentstack.management.core.csproj
index 619e855..17f949d 100644
--- a/Contentstack.Management.Core/contentstack.management.core.csproj
+++ b/Contentstack.Management.Core/contentstack.management.core.csproj
@@ -62,12 +62,29 @@
+
+
+
+
+ true
+ contentFiles/cs/any/Scripts/refresh-region.cs
+ Content
+ false
+
+
+ true
+ build/contentstack.management.csharp.targets
+
+
+
diff --git a/Scripts/refresh-region.cs b/Scripts/refresh-region.cs
new file mode 100644
index 0000000..1a93977
--- /dev/null
+++ b/Scripts/refresh-region.cs
@@ -0,0 +1,77 @@
+// Refresh regions.json from the Contentstack CDN.
+//
+// Works for both SDK developers and SDK consumers — no file copying needed.
+// NuGet automatically places this file in your project's Scripts/ folder
+// when you install the contentstack.management.csharp package.
+//
+// Usage (run from your project root after dotnet build):
+// dotnet run Scripts/refresh-region.cs
+//
+// Run whenever Contentstack adds a new region or service.
+
+using System.IO;
+using System.Net.Http;
+using System.Text.Json;
+
+const string RegionsUrl = "https://artifacts.contentstack.com/regions.json";
+
+string root = Directory.GetCurrentDirectory();
+
+Console.WriteLine($"Fetching {RegionsUrl} ...");
+
+string json;
+try
+{
+ using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
+ json = await http.GetStringAsync(RegionsUrl);
+}
+catch (Exception ex)
+{
+ Console.Error.WriteLine($"ERROR: Could not download regions.json: {ex.Message}");
+ return 1;
+}
+
+JsonDocument doc;
+try
+{
+ doc = JsonDocument.Parse(json);
+}
+catch (JsonException ex)
+{
+ Console.Error.WriteLine($"ERROR: Downloaded content is not valid JSON: {ex.Message}");
+ return 1;
+}
+
+if (!doc.RootElement.TryGetProperty("regions", out var regionsEl))
+{
+ Console.Error.WriteLine("ERROR: Downloaded JSON does not contain a 'regions' key.");
+ return 1;
+}
+
+int regionCount = regionsEl.GetArrayLength();
+
+// ── All bin output dirs — finds every Contentstack.Management.Core.dll in bin/ ──
+// Works for both the SDK repo and consumer projects after dotnet build.
+// Writes Assets/regions.json next to each DLL found.
+int binCount = 0;
+foreach (string dll in Directory.GetFiles(root, "Contentstack.Management.Core.dll", SearchOption.AllDirectories))
+{
+ if (!dll.Contains(Path.DirectorySeparatorChar + "bin" + Path.DirectorySeparatorChar))
+ continue;
+
+ string binDest = Path.Combine(Path.GetDirectoryName(dll)!, "Assets", "regions.json");
+ await WriteFile(binDest, json);
+ Console.WriteLine($"[bin] Wrote {regionCount} regions → {binDest}");
+ binCount++;
+}
+
+if (binCount == 0)
+ Console.WriteLine("[bin] No build output found — run 'dotnet build' first, then re-run this script.");
+
+return 0;
+
+static async Task WriteFile(string path, string content)
+{
+ Directory.CreateDirectory(Path.GetDirectoryName(path)!);
+ await File.WriteAllTextAsync(path, content);
+}
diff --git a/build/contentstack.management.csharp.targets b/build/contentstack.management.csharp.targets
new file mode 100644
index 0000000..5174341
--- /dev/null
+++ b/build/contentstack.management.csharp.targets
@@ -0,0 +1,19 @@
+
+
+
+ <_RefreshScriptDest>$(MSBuildProjectDirectory)/Scripts/refresh-region.cs
+ <_RefreshScriptSrc>$(MSBuildThisFileDirectory)../contentFiles/cs/any/Scripts/refresh-region.cs
+
+
+
+
+
+
+
+