// Export CDR (Call Detail Records) from a CodeB tenant to CSV for BI ingest. // European Digital Identity Wallet-enabled tenants use the same key surface. // [compliance-archive-cookbook 2026-07-23] // // Endpoint live-verified in signal.ashx: // GET signal.ashx?cdrlog=1&date=YYYY-MM-DD&limit=N using System; using System.IO; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Threading.Tasks; class ExportCdr { static readonly string[] Cols = new[]{ "recordedUtc","startUtc","endUtc","direction","trunk", "callingNumber","calledNumber","durationSec","answered","outcome","releaseReason"}; static string CsvCell(string v) { if (v == null) return ""; return (v.IndexOfAny(new[]{',','"','\n'}) >= 0) ? "\"" + v.Replace("\"","\"\"") + "\"" : v; } static async Task Main() { var tenant = Environment.GetEnvironmentVariable("CODEB_TENANT") ?? "https://"; var apiKey = Environment.GetEnvironmentVariable("CODEB_API_KEY"); var date = Environment.GetEnvironmentVariable("DATE") ?? DateTime.UtcNow.AddDays(-1).ToString("yyyy-MM-dd"); var outF = Environment.GetEnvironmentVariable("OUT_FILE") ?? $"cdr-{date}.csv"; if (string.IsNullOrEmpty(apiKey)) { Console.Error.WriteLine("CODEB_API_KEY not set"); Environment.Exit(2); } using var http = new HttpClient { BaseAddress = new Uri(tenant) }; http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); var body = await http.GetStringAsync($"/signal.ashx?cdrlog=1&date={date}&limit=5000"); using var doc = JsonDocument.Parse(body); if (!doc.RootElement.TryGetProperty("events", out var events)) return; using var sw = new StreamWriter(outF, false, new UTF8Encoding(false)); sw.WriteLine(string.Join(",", Cols)); int n = 0; foreach (var e in events.EnumerateArray()) { var sb = new StringBuilder(); for (int i = 0; i < Cols.Length; i++) { if (i > 0) sb.Append(','); sb.Append(CsvCell(e.TryGetProperty(Cols[i], out var v) ? v.ToString() : "")); } sw.WriteLine(sb.ToString()); n++; } Console.WriteLine($"[done] {n} rows -> {outF}"); } }