Skip to content

Calling eXate ProtectValues via gRPC (Multi-Language Samples)

This guide provides end-to-end examples for using the eXate ProtectValues gRPC API for processing CSV’s but the examples below can be extended to use JSON / XML with a few adjustments.
Each example shows how to authenticate using the identity endpoint, retrieve a Bearer token via client credentials, and make a ProtectValues request
to the gRPC service at apigator.:9090.


1. Overview

Each sample:

  • Requests a Bearer token from the Identity endpoint.
  • Calls ProtectValues with Bulk_IND_Dots2 configuration.
  • Demonstrates proper metadata authentication with Authorization: Bearer <token>.
  • Includes TLS/Plaintext options and a shared proto file.

2. Environment Variables

Variable Description Example
TOKEN_URL Identity URL https://apisix./apigator/identity/v1/token
X_API_KEY API key secret
CLIENT_ID Client ID secret
CLIENT_SECRET Client Secret secret
GRPC_ADDRESS gRPC endpoint apigator.:9090
USE_TLS Whether to use TLS true
CONF_NAME Manifest name Bulk_IND_Dots2
DATASET_TYPE FILE or JSON FILE
LABELS Columns to protect SHORT.NAME,NAME.1,GIVEN.NAMES,FAMILY.NAME

3. Shared Proto

syntax = "proto3";
package com.exate.apigator;

service ProtectService {
  rpc ProtectValues (ProtectValuesRequest) returns (ProtectValuesResponse);
}

message ProtectValuesRequest  {
  string request_id = 1;
  JobConfig job_config = 2;
  repeated string labels = 3;
  repeated RowData rows = 4;
}
message ProtectValuesResponse { repeated RowData rows = 1; }

message JobConfig {
  string configuration_name = 1;
  string job_type = 2;
  string dataset_type = 3;
  string data_owning_country_code = 4;
  string country_code = 5;
  int32  data_usage_id = 6;
  int32  third_party_id = 7;
  string execution_context = 8;
  bool   silent_mode = 9;
  MatchingRule matching_rule = 10;
  bool   protect_null_values = 11;
  bool   preserve_string_length = 12;
  bool   use_restricted_text = 13;
  string restricted_text = 14;
  bool   is_data_consistent_across_organization = 15;
  string snapshot_date = 16;
  string api_type = 17;
}

message RowData { repeated ColumnData columns = 1; }
message ColumnData { oneof value { string string_value = 1; } }
message MatchingRule { repeated Claim claims = 1; }
message Claim { string attribute_name = 1; string attribute_value = 2; }

4. Python Example

pip install grpcio grpcio-tools requests

import os, sys, json, subprocess, importlib, grpc
from pathlib import Path

--- Env ---

TOKEN_URL = os.environ.get("TOKEN_URL", "https://apisix.<eXateServer>/apigator/identity/v1/token")
X_API_KEY = os.environ["X_API_KEY"]
CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
GRPC_ADDRESS = os.environ.get("GRPC_ADDRESS", "apigator.<eXateServer>:9090")
USE_TLS = os.environ.get("USE_TLS","true").lower() in ("1","true","yes","on")
CONF_NAME = os.environ.get("CONF_NAME", "Bulk_IND_Dots2")
DATASET_TYPE = os.environ.get("DATASET_TYPE","FILE")
LABELS = [s.strip() for s in os.environ.get("LABELS","SHORT.NAME,NAME.1,GIVEN.NAMES,FAMILY.NAME").split(",") if s.strip()]

--- Token ---

import urllib.request, urllib.parse
data = urllib.parse.urlencode({
  "client_id": CLIENT_ID,
  "client_secret": CLIENT_SECRET,
  "grant_type": "client_credentials"
}).encode("utf-8")
req = urllib.request.Request(TOKEN_URL, data=data, headers={
  "X-Api-Key": X_API_KEY,
  "Content-Type": "application/x-www-form-urlencoded"
})
with urllib.request.urlopen(req) as r:
    body = r.read().decode("utf-8", "ignore")
j = json.loads(body)
token = j["access_token"]

--- Compile proto (local runtime) ---

WORK = Path(file).resolve().parent
PROTO_DIR = WORK.parent / "proto"
GEN_DIR = WORK / "gen"
GEN_DIR.mkdir(exist_ok=True, parents=True)

subprocess.run([sys.executable, "-m", "grpc_tools.protoc",
                f"--proto_path={PROTO_DIR}",
                f"--python_out={GEN_DIR}",
                f"--grpc_python_out={GEN_DIR}",
                str(PROTO_DIR / "protect.proto")], check=True)

sys.path.insert(0, str(GEN_DIR))
pb2 = importlib.import_module("protect_pb2")
pb2_grpc = importlib.import_module("protect_pb2_grpc")

--- gRPC call ---

creds = grpc.ssl_channel_credentials() if USE_TLS else None
channel = (grpc.secure_channel(GRPC_ADDRESS, creds) if creds else grpc.insecure_channel(GRPC_ADDRESS))
stub = pb2_grpc.ProtectServiceStub(channel)
md = (("authorization", f"Bearer {token}"),)

req = pb2.ProtectValuesRequest(
    request_id="python-sample",
    job_config=pb2.JobConfig(
        configuration_name=CONF_NAME,
        job_type="Pseudonymise",
        dataset_type=DATASET_TYPE,
        data_owning_country_code="GB",
        country_code="GB",
        data_usage_id=392,
        third_party_id=0,
        matching_rule=pb2.MatchingRule(claims=[pb2.Claim(attribute_name="USER", attribute_value="claim")]),
        protect_null_values=True,
        preserve_string_length=False,
        use_restricted_text=True,
        restricted_text="x",
        is_data_consistent_across_organization=False,
        snapshot_date="2025-01-03T13:11:50Z",
    ),
    labels=LABELS,
    rows=[
        pb2.RowData(columns=[pb2.ColumnData(string_value="Alice") for _ in LABELS]),
        pb2.RowData(columns=[pb2.ColumnData(string_value="Bob") for _ in LABELS]),
    ]
)

resp = stub.ProtectValues(req, metadata=md)
print("rows:", [[getattr(c, c.WhichOneof("value")) for c in r.columns] for r in resp.rows])

5. Node.js Example

// npm i @grpc/grpc-js @grpc/proto-loader node-fetch@2
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const fetch = require('node-fetch');
const TOKEN_URL = process.env.TOKEN_URL || 'https://apisix.<eXateServer>/apigator/identity/v1/token';
const X_API_KEY = process.env.X_API_KEY;
const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET;
const GRPC_ADDRESS = process.env.GRPC_ADDRESS || 'apigator.<eXateServer>:9090';
const USE_TLS = (process.env.USE_TLS || 'true').toLowerCase() === 'true';
const CONF_NAME = process.env.CONF_NAME || 'Bulk_IND_Dots2';
const DATASET_TYPE = process.env.DATASET_TYPE || 'FILE';
const LABELS = (process.env.LABELS || 'SHORT.NAME,NAME.1,GIVEN.NAMES,FAMILY.NAME').split(',').map(s=>s.trim()).filter(Boolean);

async function token() {
  const params = new URLSearchParams({ client_id: CLIENT_ID, client_secret: CLIENT_SECRET, grant_type:'client_credentials' });
  const res = await fetch(TOKEN_URL, { method: 'POST', headers: { 'X-Api-Key': X_API_KEY, 'Content-Type':'application/x-www-form-urlencoded' }, body: params.toString() });
  const j = await res.json();
  if (!j.access_token) throw new Error('No access_token');
  return j.access_token;
}

(async () => {
  const t = await token();
  const pkgDef = await protoLoader.load('../proto/protect.proto', {keepCase:true, longs:String, defaults:true});
  const api = grpc.loadPackageDefinition(pkgDef).com.exate.apigator;
  const creds = USE_TLS ? grpc.credentials.createSsl() : grpc.credentials.createInsecure();
  const client = new api.ProtectService(GRPC_ADDRESS, creds);

  const req = {
    request_id: 'node-sample',
    job_config: {
      configuration_name: CONF_NAME, job_type: 'Pseudonymise', dataset_type: DATASET_TYPE,
      data_owning_country_code: 'GB', country_code: 'GB', data_usage_id:392, third_party_id:0,
      matching_rule:{ claims:[{attribute_name:'USER', attribute_value:'claim'}] },
      protect_null_values:true, use_restricted_text:true, restricted_text:'x',
      preserve_string_length:false, silent_mode:false, is_data_consistent_across_organization:false,
      snapshot_date:'2025-01-03T13:11:50Z'
    },
    labels: LABELS,
    rows: [
      {columns: LABELS.map(()=>({string_value:'Alice'}))},
      {columns: LABELS.map(()=>({string_value:'Bob'}))},
    ]
  };

  const md = new grpc.Metadata(); md.set('authorization', Bearer ${t});
  client.ProtectValues(req, md, (err, resp) => {
    if (err) return console.error(err);
    console.log('rows:', resp.rows);
  });
})().catch(e => { console.error(e); process.exit(1); });

6. Go Example

// Generate: protoc --go_out=. --go-grpc_out=. -I../proto ../proto/protect.proto
package main
import (
  "bytes"
  "context"
  "crypto/tls"
  "encoding/json"
  "fmt"
  "io"
  "net/http"
  "os"
  "strings"
  "time"

  pb "exate/grpcsample/com/exate/apigator"
  "http://google.golang.org/grpc "
  "http://google.golang.org/grpc/credentials "
  "http://google.golang.org/grpc/metadata "
)

func env(k, def string) string { v := os.Getenv(k); if v=="" { return def }; return v }
func must(k string) string { v := os.Getenv(k); if v=="" { panic("missing env "+k) }; return v }

func token() (string, error) {
  body := []byte("client_id="+must("CLIENT_ID")+"&client_secret="+must("CLIENT_SECRET")+"&grant_type=client_credentials")
  req, _ := http.NewRequest("POST", env("TOKEN_URL","https://apisix.<eXateServer>/apigator/identity/v1/token"), bytes.NewReader(body))
  req.Header.Set("X-Api-Key", must("X_API_KEY"))
  req.Header.Set("Content-Type","application/x-www-form-urlencoded")
  res, err := http.DefaultClient.Do(req); if err!=nil { return "", err }
  defer res.Body.Close()
  b, _ := io.ReadAll(res.Body)
  var j map[string]any
  if err := json.Unmarshal(b, &j); err != nil { return "", err }
  t, _ := j["access_token"].(string)
  if t=="" { return "", fmt.Errorf("no access_token") }
  return t, nil
}

func main() {
  addr := env("GRPC_ADDRESS","apigator.<eXateServer>:9090")
  useTLS := strings.EqualFold(env("USE_TLS","true"),"true")
  labels := strings.Split(env("LABELS","SHORT.NAME,NAME.1,GIVEN.NAMES,FAMILY.NAME"),",")
  for i:=range labels { labels[i] = strings.TrimSpace(labels[i]) }

  tok, err := token(); if err != nil { panic(err) }

  var opts grpc.DialOption
  if useTLS { opts = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})) } else { opts = grpc.WithInsecure() }
  conn, err := grpc.Dial(addr, opts); if err != nil { panic(err) }
  defer conn.Close()

  cli := pb.NewProtectServiceClient(conn)
  req := &pb.ProtectValuesRequest{
    RequestId: "go-sample",
    JobConfig: &pb.JobConfig{
      ConfigurationName: env("CONF_NAME","Bulk_IND_Dots2"),
      JobType: "Pseudonymise",
      DatasetType: env("DATASET_TYPE","FILE"),
      DataOwningCountryCode: "GB", CountryCode:"GB", DataUsageId:392, ThirdPartyId:0,
      MatchingRule: &pb.MatchingRule{ Claims: []*pb.Claim{{AttributeName:"USER", AttributeValue:"claim"}} },
      ProtectNullValues:true, UseRestrictedText:true, RestrictedText:"x",
      PreserveStringLength:false, SilentMode:false, IsDataConsistentAcrossOrganization:false,
      SnapshotDate:"2025-01-03T13:11:50Z",
    },
    Labels: labels,
    Rows: []*pb.RowData{
      { Columns: []*pb.ColumnData{ {Value:&pb.ColumnData_StringValue{StringValue:"Alice"}} } },
      { Columns: []*pb.ColumnData{ {Value:&pb.ColumnData_StringValue{StringValue:"Bob"}} } },
    },
  }

  md := metadata.New(map[string]string{"authorization":"Bearer "+tok})
  ctx, cancel := context.WithTimeout(metadata.NewOutgoingContext(context.Background(), md), 10*time.Second)
  defer cancel()

  resp, err := cli.ProtectValues(ctx, req)
  if err != nil { panic(err) }
  fmt.Println("rows:", len(resp.Rows))
}

7. C# Example

// After generating stubs from proto into csharp namespace com.exate.apigator
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using Grpc.Net.Client;
using Grpc.Core;
using com.exate.apigator;
class Program {
  static string Env(string k, string d=null) => Environment.GetEnvironmentVariable(k) ?? d ?? throw new Exception("Missing env "+k);
  static string GetToken() {
    var url = Env("TOKEN_URL", "https://apisix.<eXateServer>/apigator/identity/v1/token");
    var data = new StringContent($"client_id={Env("CLIENT_ID")}&client_secret={Env("CLIENT_SECRET")}&grant_type=client_credentials", Encoding.UTF8, "application/x-www-form-urlencoded");
    var http = new HttpClient();
    http.DefaultRequestHeaders.Add("X-Api-Key", Env("X_API_KEY"));
    var res = http.PostAsync(url, data).Result;
    var json = JsonDocument.Parse(res.Content.ReadAsStringAsync().Result);
    return json.RootElement.GetProperty("access_token").GetString();
  }
  static void Main() {
    var token = GetToken();
    var addr = Env("GRPC_ADDRESS", "apigator.<eXateServer>:9090");
    var useTls = (Env("USE_TLS","true").ToLower()=="true");
    var channel = GrpcChannel.ForAddress((useTls?"https://":"http://") + addr);
    var client = new ProtectService.ProtectServiceClient(channel);
    var md = new Metadata { {"authorization", $"Bearer {token}"} };
    var labels = (Env("LABELS","SHORT.NAME,NAME.1,GIVEN.NAMES,FAMILY.NAME")).Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
    var req = new ProtectValuesRequest{
      RequestId="dotnet-sample",
      JobConfig = new JobConfig{
        ConfigurationName = Env("CONF_NAME","Bulk_IND_Dots2"),
        JobType = "Pseudonymise",
        DatasetType = Env("DATASET_TYPE","FILE"),
        DataOwningCountryCode="GB", CountryCode="GB", DataUsageId=392, ThirdPartyId=0,
        MatchingRule = new MatchingRule { Claims = { new Claim{ AttributeName="USER", AttributeValue="claim" } } },
        ProtectNullValues = true, UseRestrictedText = true, RestrictedText = "x",
        PreserveStringLength = false, SilentMode = false,
        IsDataConsistentAcrossOrganization = false, SnapshotDate="2025-01-03T13:11:50Z"
      }
    };
    foreach (var _ in labels) req.Rows.Add(new RowData{ Columns = { new ColumnData{ StringValue = "Alice" } } });
    var resp = client.ProtectValues(req, md);
    Console.WriteLine($"rows: {resp.Rows.Count}");
  }
}

8. Java Example

import com.exate.apigator.;
import io.grpc.;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import java.net.http.*; import java.net.URI; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.time.Duration;

public class ProtectClient {
  static String env(String k, String d) { String v=System.getenv(k); return (v==null||v.isEmpty())?d:v; }
  static String must(String k){ String v=env(k,null); if(v==null) throw new RuntimeException("Missing env "+k); return v; }

  static String token() throws Exception {
    String body = "client_id="+ URLEncoder.encode(must("CLIENT_ID"), StandardCharsets.UTF_8) +
                  "&client_secret="+ URLEncoder.encode(must("CLIENT_SECRET"), StandardCharsets.UTF_8) +
                  "&grant_type=client_credentials";
    HttpClient http = HttpClient.newHttpClient();
    HttpRequest req = HttpRequest.newBuilder(URI.create(env("TOKEN_URL","https://apisix.<eXateServer>/apigator/identity/v1/token")))
      .header("X-Api-Key", must("X_API_KEY"))
      .header("Content-Type","application/x-www-form-urlencoded")
      .timeout(Duration.ofSeconds(10)).POST(HttpRequest.BodyPublishers.ofString(body)).build();
    String resp = http.send(req, HttpResponse.BodyHandlers.ofString()).body();
    int i = resp.indexOf("\"access_token\""); if(i<0) throw new RuntimeException("No access_token");
    int s = resp.indexOf('"', i+15)+1; int e = resp.indexOf('"', s); return resp.substring(s,e);
  }

  public static void main(String[] args) throws Exception {
    String tok = token();
    boolean tls = env("USE_TLS","true").equalsIgnoreCase("true");
    ManagedChannel ch = (tls ? NettyChannelBuilder.forTarget(env("GRPC_ADDRESS","apigator.<eXateServer>:9090")).sslContext(io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts.forClient().build()).build()
                             : NettyChannelBuilder.forTarget(env("GRPC_ADDRESS","apigator.<eXateServer>:9090")).usePlaintext().build());
    ProtectServiceGrpc.ProtectServiceBlockingStub stub = ProtectServiceGrpc.newBlockingStub(ch).withCallCredentials(
      new CallCredentials(){ public void thisUsesUnstableApi(){} public void applyRequestMetadata(RequestInfo ri, java.util.concurrent.Executor ex, MetadataApplier ap){ Metadata md=new Metadata(); md.put(Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER), "Bearer "+tok); ap.apply(md);} }
    );
    String conf = env("CONF_NAME","Bulk_IND_Dots2"); String dtype = env("DATASET_TYPE","FILE");
    ProtectValuesRequest req = ProtectValuesRequest.newBuilder()
      .setRequestId("java-sample")
      .setJobConfig(JobConfig.newBuilder().setConfigurationName(conf).setJobType("Pseudonymise").setDatasetType(dtype)
        .setDataOwningCountryCode("GB").setCountryCode("GB").setDataUsageId(392).setThirdPartyId(0)
        .setMatchingRule(MatchingRule.newBuilder().addClaims(Claim.newBuilder().setAttributeName("USER").setAttributeValue("claim")))
        .setProtectNullValues(true).setUseRestrictedText(true).setRestrictedText("x")
        .setPreserveStringLength(false).setSilentMode(false).setIsDataConsistentAcrossOrganization(false)
        .setSnapshotDate("2025-01-03T13:11:50Z")).addLabels("SHORT.NAME").addLabels("NAME.1").build();
    ProtectValuesResponse resp = stub.protectValues(req);
    System.out.println("rows: "+resp.getRowsCount()); ch.shutdownNow();
  }
}

9. Azure DevOps Pipeline

trigger: none
pool: { name: AWS-eXate-Agent }

steps:

task: Bash@3
displayName: Setup Python
inputs: { targetType: inline, script: |
    PY=$(command -v python3 || command -v python); echo "##vso[task.setvariable variable=PY]$PY"
    $PY -m ensurepip --upgrade || true
    $PY -m pip install --upgrade grpcio grpcio-tools requests
  }

task: PythonScript@0
displayName: Python ProtectValues sample
inputs:
  pythonInterpreter: $(PY)
  scriptSource: filePath
  scriptPath: python/protect_values.py
env:
  TOKEN_URL: https://apisix.<eXateServer>/apigator/identity/v1/token
  X_API_KEY: $(X_API_KEY)
  CLIENT_ID: $(CLIENT_ID)
  CLIENT_SECRET: $(CLIENT_SECRET)
  GRPC_ADDRESS: apigator.<eXateServer>:9090
  USE_TLS: "true"
  CONF_NAME: Bulk_IND_Dots2
  DATASET_TYPE: FILE
  LABELS: SHORT.NAME,NAME.1,GIVEN.NAMES,FAMILY.NAME