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.
1. Overview
Each sample:
- Requests a Bearer token from the Identity endpoint.
- Calls ProtectValues with
Bulk_IND_Dots2configuration. - 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. |
| X_API_KEY | API key | secret |
| CLIENT_ID | Client ID | secret |
| CLIENT_SECRET | Client Secret | secret |
| GRPC_ADDRESS | gRPC endpoint | apigator. |
| 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